Skip to content

Commit 084dec0

Browse files
fix: catchable transform errors (pvtnbr/tsx#7)
1 parent 99ba136 commit 084dec0

File tree

4 files changed

+80
-61
lines changed

4 files changed

+80
-61
lines changed

src/utils/transform/index.ts

+17-33
Original file line numberDiff line numberDiff line change
@@ -22,21 +22,15 @@ import {
2222
patchOptions,
2323
} from './get-esbuild-options.js';
2424

25-
const handleEsbuildError = (
25+
const formatEsbuildError = (
2626
error: TransformFailure,
2727
) => {
28-
const [firstError] = error.errors;
29-
let errorMessage = `[esbuild Error]: ${firstError.text}`;
30-
31-
if (firstError.location) {
32-
const { file, line, column } = firstError.location;
33-
errorMessage += `\n at ${file}:${line}:${column}`;
34-
}
35-
36-
console.error(errorMessage);
37-
38-
// eslint-disable-next-line n/no-process-exit
39-
process.exit(1);
28+
error.name = 'TransformError';
29+
// @ts-expect-error deleting non-option property
30+
delete error.errors;
31+
// @ts-expect-error deleting non-option property
32+
delete error.warnings;
33+
throw error;
4034
};
4135

4236
// Used by cjs-loader
@@ -76,19 +70,14 @@ export const transformSync = (
7670
[
7771
// eslint-disable-next-line @typescript-eslint/no-shadow
7872
(_filePath, code) => {
79-
const patchResults = patchOptions(esbuildOptions);
73+
const patchResult = patchOptions(esbuildOptions);
74+
let result;
8075
try {
81-
return patchResults(
82-
esbuildTransformSync(code, esbuildOptions),
83-
);
76+
result = esbuildTransformSync(code, esbuildOptions);
8477
} catch (error) {
85-
handleEsbuildError(error as TransformFailure);
86-
87-
/**
88-
* esbuild warnings are ignored because they're usually caught
89-
* at runtime by Node.js with better errors + stack traces
90-
*/
78+
throw formatEsbuildError(error as TransformFailure);
9179
}
80+
return patchResult(result);
9281
},
9382
transformDynamicImport,
9483
],
@@ -128,19 +117,14 @@ export const transform = async (
128117
[
129118
// eslint-disable-next-line @typescript-eslint/no-shadow
130119
async (_filePath, code) => {
131-
const patchResults = patchOptions(esbuildOptions);
120+
const patchResult = patchOptions(esbuildOptions);
121+
let result;
132122
try {
133-
return patchResults(
134-
await esbuildTransform(code, esbuildOptions),
135-
);
123+
result = await esbuildTransform(code, esbuildOptions);
136124
} catch (error) {
137-
handleEsbuildError(error as TransformFailure);
138-
139-
/**
140-
* esbuild warnings are ignored because they're usually caught
141-
* at runtime by Node.js with better errors + stack traces
142-
*/
125+
throw formatEsbuildError(error as TransformFailure);
143126
}
127+
return patchResult(result);
144128
},
145129
transformDynamicImport,
146130
],

tests/index.ts

+1
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import { nodeVersions } from './utils/node-versions';
1010
for (const nodeVersion of nodeVersions) {
1111
const node = await createNode(nodeVersion);
1212
await describe(`Node ${node.version}`, async ({ runTestSuite }) => {
13+
await runTestSuite(import('./specs/api'), node);
1314
await runTestSuite(import('./specs/cli'), node);
1415
await runTestSuite(import('./specs/api'), node);
1516
await runTestSuite(import('./specs/watch'), node);

tests/specs/api.ts

+47-27
Original file line numberDiff line numberDiff line change
@@ -58,39 +58,59 @@ export default testSuite(({ describe }, node: NodeApis) => {
5858
expect(stdout).toBe('Fails as expected\nfoo bar\nUnregistered');
5959
});
6060

61-
test('tsx.require()', async ({ onTestFinish }) => {
62-
const fixture = await createFixture({
63-
'require.cjs': `
64-
const tsx = require(${JSON.stringify(cjsApiPath)});
65-
try {
66-
require('./file');
67-
} catch {
68-
console.log('Fails as expected');
69-
}
61+
describe('tsx.require()', ({ test }) => {
62+
test('loads', async ({ onTestFinish }) => {
63+
const fixture = await createFixture({
64+
'require.cjs': `
65+
const tsx = require(${JSON.stringify(cjsApiPath)});
66+
try {
67+
require('./file');
68+
} catch {
69+
console.log('Fails as expected');
70+
}
7071
71-
const loaded = tsx.require('./file', __filename);
72-
console.log(loaded.message);
72+
const loaded = tsx.require('./file', __filename);
73+
console.log(loaded.message);
7374
74-
// Remove from cache
75-
const loadedPath = tsx.require.resolve('./file', __filename);
76-
delete require.cache[loadedPath];
75+
// Remove from cache
76+
const loadedPath = tsx.require.resolve('./file', __filename);
77+
delete require.cache[loadedPath];
7778
78-
try {
79-
require('./file');
80-
} catch {
81-
console.log('Unpolluted global require');
82-
}
83-
`,
84-
...tsFiles,
85-
});
86-
onTestFinish(async () => await fixture.rm());
79+
try {
80+
require('./file');
81+
} catch {
82+
console.log('Unpolluted global require');
83+
}
84+
`,
85+
...tsFiles,
86+
});
87+
onTestFinish(async () => await fixture.rm());
8788

88-
const { stdout } = await execaNode(path.join(fixture.path, 'require.cjs'), [], {
89-
nodePath: node.path,
90-
nodeOptions: [],
89+
const { stdout } = await execaNode(path.join(fixture.path, 'require.cjs'), [], {
90+
nodePath: node.path,
91+
nodeOptions: [],
92+
});
93+
94+
expect(stdout).toBe('Fails as expected\nfoo bar\nUnpolluted global require');
9195
});
9296

93-
expect(stdout).toBe('Fails as expected\nfoo bar\nUnpolluted global require');
97+
test('catchable', async ({ onTestFinish }) => {
98+
const fixture = await createFixture({
99+
'require.cjs': `
100+
const tsx = require(${JSON.stringify(cjsApiPath)});
101+
try { tsx.require('./file', __filename); } catch {}
102+
`,
103+
'file.ts': 'if',
104+
});
105+
onTestFinish(async () => await fixture.rm());
106+
107+
const { all } = await execaNode(path.join(fixture.path, 'require.cjs'), [], {
108+
nodePath: node.path,
109+
nodeOptions: [],
110+
all: true,
111+
});
112+
expect(all).toBe('');
113+
});
94114
});
95115
});
96116

tests/specs/smoke.ts

+15-1
Original file line numberDiff line numberDiff line change
@@ -202,7 +202,7 @@ const files = {
202202
if (!thrown) {
203203
return new Error('No error thrown');
204204
} else if (!thrown.message.includes(expectedError)) {
205-
return new Error(\`Message \${JSON.stringify(expectedError)} not found in \${JSON.stringify(thrown.message)}\`);
205+
return new Error(\`Message \${JSON.stringify(expectedError)} not found in \${JSON.stringify(thrown.message)}\n\${thrown.stack}\`);
206206
}
207207
}),
208208
);
@@ -226,6 +226,8 @@ const files = {
226226
console.log('imported');
227227
`,
228228

229+
'broken-syntax.ts': 'if',
230+
229231
node_modules: {
230232
'pkg-commonjs': {
231233
'package.json': JSON.stringify({
@@ -425,6 +427,12 @@ export default testSuite(async ({ describe }, { tsx }: NodeApis) => {
425427
`
426428
: ''
427429
}
430+
${
431+
isCommonJs
432+
? '[() => require(\'./broken-syntax\'), \'Transform failed\'],'
433+
: ''
434+
}
435+
[() => import('./broken-syntax'), 'Transform failed'],
428436
);
429437
430438
console.log(JSON.stringify({
@@ -608,6 +616,12 @@ export default testSuite(async ({ describe }, { tsx }: NodeApis) => {
608616
`
609617
: ''
610618
}
619+
${
620+
isCommonJs
621+
? '[() => require(\'./broken-syntax\'), \'Transform failed\'],'
622+
: ''
623+
}
624+
[() => import('./broken-syntax'), 'Transform failed'],
611625
);
612626
613627
console.log(JSON.stringify({

0 commit comments

Comments
 (0)