Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: privatenumber/tsx
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: v4.8.0
Choose a base ref
...
head repository: privatenumber/tsx
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: 084dec008149b29c0f56124d4a1400f2bb98f101
Choose a head ref
  • 1 commit
  • 4 files changed
  • 1 contributor

Commits on May 1, 2024

  1. fix: catchable transform errors (pvtnbr/tsx#7)

    privatenumber authored May 1, 2024

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature.
    Copy the full SHA
    084dec0 View commit details
Showing with 80 additions and 61 deletions.
  1. +17 −33 src/utils/transform/index.ts
  2. +1 −0 tests/index.ts
  3. +47 −27 tests/specs/api.ts
  4. +15 −1 tests/specs/smoke.ts
50 changes: 17 additions & 33 deletions src/utils/transform/index.ts
Original file line number Diff line number Diff line change
@@ -22,21 +22,15 @@ import {
patchOptions,
} from './get-esbuild-options.js';

const handleEsbuildError = (
const formatEsbuildError = (
error: TransformFailure,
) => {
const [firstError] = error.errors;
let errorMessage = `[esbuild Error]: ${firstError.text}`;

if (firstError.location) {
const { file, line, column } = firstError.location;
errorMessage += `\n at ${file}:${line}:${column}`;
}

console.error(errorMessage);

// eslint-disable-next-line n/no-process-exit
process.exit(1);
error.name = 'TransformError';
// @ts-expect-error deleting non-option property
delete error.errors;
// @ts-expect-error deleting non-option property
delete error.warnings;
throw error;
};

// Used by cjs-loader
@@ -76,19 +70,14 @@ export const transformSync = (
[
// eslint-disable-next-line @typescript-eslint/no-shadow
(_filePath, code) => {
const patchResults = patchOptions(esbuildOptions);
const patchResult = patchOptions(esbuildOptions);
let result;
try {
return patchResults(
esbuildTransformSync(code, esbuildOptions),
);
result = esbuildTransformSync(code, esbuildOptions);
} catch (error) {
handleEsbuildError(error as TransformFailure);

/**
* esbuild warnings are ignored because they're usually caught
* at runtime by Node.js with better errors + stack traces
*/
throw formatEsbuildError(error as TransformFailure);
}
return patchResult(result);
},
transformDynamicImport,
],
@@ -128,19 +117,14 @@ export const transform = async (
[
// eslint-disable-next-line @typescript-eslint/no-shadow
async (_filePath, code) => {
const patchResults = patchOptions(esbuildOptions);
const patchResult = patchOptions(esbuildOptions);
let result;
try {
return patchResults(
await esbuildTransform(code, esbuildOptions),
);
result = await esbuildTransform(code, esbuildOptions);
} catch (error) {
handleEsbuildError(error as TransformFailure);

/**
* esbuild warnings are ignored because they're usually caught
* at runtime by Node.js with better errors + stack traces
*/
throw formatEsbuildError(error as TransformFailure);
}
return patchResult(result);
},
transformDynamicImport,
],
1 change: 1 addition & 0 deletions tests/index.ts
Original file line number Diff line number Diff line change
@@ -10,6 +10,7 @@ import { nodeVersions } from './utils/node-versions';
for (const nodeVersion of nodeVersions) {
const node = await createNode(nodeVersion);
await describe(`Node ${node.version}`, async ({ runTestSuite }) => {
await runTestSuite(import('./specs/api'), node);
await runTestSuite(import('./specs/cli'), node);
await runTestSuite(import('./specs/api'), node);
await runTestSuite(import('./specs/watch'), node);
74 changes: 47 additions & 27 deletions tests/specs/api.ts
Original file line number Diff line number Diff line change
@@ -58,39 +58,59 @@ export default testSuite(({ describe }, node: NodeApis) => {
expect(stdout).toBe('Fails as expected\nfoo bar\nUnregistered');
});

test('tsx.require()', async ({ onTestFinish }) => {
const fixture = await createFixture({
'require.cjs': `
const tsx = require(${JSON.stringify(cjsApiPath)});
try {
require('./file');
} catch {
console.log('Fails as expected');
}
describe('tsx.require()', ({ test }) => {
test('loads', async ({ onTestFinish }) => {
const fixture = await createFixture({
'require.cjs': `
const tsx = require(${JSON.stringify(cjsApiPath)});
try {
require('./file');
} catch {
console.log('Fails as expected');
}
const loaded = tsx.require('./file', __filename);
console.log(loaded.message);
const loaded = tsx.require('./file', __filename);
console.log(loaded.message);
// Remove from cache
const loadedPath = tsx.require.resolve('./file', __filename);
delete require.cache[loadedPath];
// Remove from cache
const loadedPath = tsx.require.resolve('./file', __filename);
delete require.cache[loadedPath];
try {
require('./file');
} catch {
console.log('Unpolluted global require');
}
`,
...tsFiles,
});
onTestFinish(async () => await fixture.rm());
try {
require('./file');
} catch {
console.log('Unpolluted global require');
}
`,
...tsFiles,
});
onTestFinish(async () => await fixture.rm());

const { stdout } = await execaNode(path.join(fixture.path, 'require.cjs'), [], {
nodePath: node.path,
nodeOptions: [],
const { stdout } = await execaNode(path.join(fixture.path, 'require.cjs'), [], {
nodePath: node.path,
nodeOptions: [],
});

expect(stdout).toBe('Fails as expected\nfoo bar\nUnpolluted global require');
});

expect(stdout).toBe('Fails as expected\nfoo bar\nUnpolluted global require');
test('catchable', async ({ onTestFinish }) => {
const fixture = await createFixture({
'require.cjs': `
const tsx = require(${JSON.stringify(cjsApiPath)});
try { tsx.require('./file', __filename); } catch {}
`,
'file.ts': 'if',
});
onTestFinish(async () => await fixture.rm());

const { all } = await execaNode(path.join(fixture.path, 'require.cjs'), [], {
nodePath: node.path,
nodeOptions: [],
all: true,
});
expect(all).toBe('');
});
});
});

16 changes: 15 additions & 1 deletion tests/specs/smoke.ts
Original file line number Diff line number Diff line change
@@ -202,7 +202,7 @@ const files = {
if (!thrown) {
return new Error('No error thrown');
} else if (!thrown.message.includes(expectedError)) {
return new Error(\`Message \${JSON.stringify(expectedError)} not found in \${JSON.stringify(thrown.message)}\`);
return new Error(\`Message \${JSON.stringify(expectedError)} not found in \${JSON.stringify(thrown.message)}\n\${thrown.stack}\`);
}
}),
);
@@ -226,6 +226,8 @@ const files = {
console.log('imported');
`,

'broken-syntax.ts': 'if',

node_modules: {
'pkg-commonjs': {
'package.json': JSON.stringify({
@@ -425,6 +427,12 @@ export default testSuite(async ({ describe }, { tsx }: NodeApis) => {
`
: ''
}
${
isCommonJs
? '[() => require(\'./broken-syntax\'), \'Transform failed\'],'
: ''
}
[() => import('./broken-syntax'), 'Transform failed'],
);
console.log(JSON.stringify({
@@ -608,6 +616,12 @@ export default testSuite(async ({ describe }, { tsx }: NodeApis) => {
`
: ''
}
${
isCommonJs
? '[() => require(\'./broken-syntax\'), \'Transform failed\'],'
: ''
}
[() => import('./broken-syntax'), 'Transform failed'],
);
console.log(JSON.stringify({