Skip to content

Commit

Permalink
module: add ERR_UNSUPPORTED_TYPESCRIPT_SYNTAX
Browse files Browse the repository at this point in the history
  • Loading branch information
marco-ippolito committed Jan 15, 2025
1 parent c8df98d commit 526ece0
Show file tree
Hide file tree
Showing 8 changed files with 94 additions and 27 deletions.
4 changes: 3 additions & 1 deletion doc/api/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -1390,7 +1390,8 @@ Node.js will try to detect the syntax with the following steps:
1. Run the input as CommonJS.
2. If step 1 fails, run the input as an ES module.
3. If step 2 fails with a SyntaxError, strip the types.
4. If step 3 fails with an error code [`ERR_INVALID_TYPESCRIPT_SYNTAX`][],
4. If step 3 fails with an error code [`ERR_UNSUPPORTED_TYPESCRIPT_SYNTAX`][]
or [`ERR_INVALID_TYPESCRIPT_SYNTAX`][],
throw the error from step 2, including the TypeScript error in the message,
else run as CommonJS.
5. If step 4 fails, run the input as an ES module.
Expand Down Expand Up @@ -3708,6 +3709,7 @@ node --stack-trace-limit=12 -p -e "Error.stackTraceLimit" # prints 12
[`Buffer`]: buffer.md#class-buffer
[`CRYPTO_secure_malloc_init`]: https://www.openssl.org/docs/man3.0/man3/CRYPTO_secure_malloc_init.html
[`ERR_INVALID_TYPESCRIPT_SYNTAX`]: errors.md#err_invalid_typescript_syntax
[`ERR_UNSUPPORTED_TYPESCRIPT_SYNTAX`]: errors.md#err_unsupported_typescript_syntax
[`NODE_OPTIONS`]: #node_optionsoptions
[`NO_COLOR`]: https://no-color.org
[`SlowBuffer`]: buffer.md#class-slowbuffer
Expand Down
16 changes: 13 additions & 3 deletions doc/api/errors.md
Original file line number Diff line number Diff line change
Expand Up @@ -2097,9 +2097,7 @@ added:
- v22.10.0
-->

The provided TypeScript syntax is not valid or unsupported.
This could happen when using TypeScript syntax that requires
transformation with [type-stripping][].
The provided TypeScript syntax is not valid.

<a id="ERR_INVALID_URI"></a>

Expand Down Expand Up @@ -3135,6 +3133,18 @@ try {
}
```

<a id="ERR_UNSUPPORTED_TYPESCRIPT_SYNTAX"></a>

### `ERR_UNSUPPORTED_TYPESCRIPT_SYNTAX`

<!-- YAML
added: REPLACEME
-->

The provided TypeScript syntax is unsupported.
This could happen when using TypeScript syntax that requires
transformation with [type-stripping][].

<a id="ERR_USE_AFTER_CLOSE"></a>

### `ERR_USE_AFTER_CLOSE`
Expand Down
1 change: 1 addition & 0 deletions lib/internal/errors.js
Original file line number Diff line number Diff line change
Expand Up @@ -1853,6 +1853,7 @@ E('ERR_UNSUPPORTED_NODE_MODULES_TYPE_STRIPPING',
E('ERR_UNSUPPORTED_RESOLVE_REQUEST',
'Failed to resolve module specifier "%s" from "%s": Invalid relative URL or base scheme is not hierarchical.',
TypeError);
E('ERR_UNSUPPORTED_TYPESCRIPT_SYNTAX', '%s', SyntaxError);
E('ERR_USE_AFTER_CLOSE', '%s was closed', Error);

// This should probably be a `TypeError`.
Expand Down
17 changes: 16 additions & 1 deletion lib/internal/modules/typescript.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,10 @@ const { assertTypeScript,
isUnderNodeModules,
kEmptyObject } = require('internal/util');
const {
ERR_INTERNAL_ASSERTION,
ERR_INVALID_TYPESCRIPT_SYNTAX,
ERR_UNSUPPORTED_NODE_MODULES_TYPE_STRIPPING,
ERR_UNSUPPORTED_TYPESCRIPT_SYNTAX,
} = require('internal/errors').codes;
const { getOptionValue } = require('internal/options');
const assert = require('internal/assert');
Expand Down Expand Up @@ -49,7 +51,20 @@ function parseTypeScript(source, options) {
try {
return parse(source, options);
} catch (error) {
throw new ERR_INVALID_TYPESCRIPT_SYNTAX(error.message);
/**
* Amaro v0.3.0 (from SWC v1.10.7) throws an object with `message` and `code` properties.
* It allows us to distinguish between invalid syntax and unsupported syntax.
*/
switch (error.code) {
case 'UnsupportedSyntax':
throw new ERR_UNSUPPORTED_TYPESCRIPT_SYNTAX(error.message);
case 'InvalidSyntax':
throw new ERR_INVALID_TYPESCRIPT_SYNTAX(error.message);
default:
// SWC will throw strings when something goes wrong.
// Check if has the `message` property or treat it as a string.
throw new ERR_INTERNAL_ASSERTION(error.message ?? error);
}
}
}

Expand Down
34 changes: 20 additions & 14 deletions lib/internal/process/execution.js
Original file line number Diff line number Diff line change
Expand Up @@ -270,15 +270,18 @@ function evalTypeScript(name, source, breakFirstLine, print, shouldLoadESM = fal
// Emit the experimental warning after the code was successfully evaluated.
emitExperimentalWarning('Type Stripping');
} catch (tsError) {
// If its not an error, or it's not an invalid typescript syntax error, rethrow it.
if (!isError(tsError) || tsError?.code !== 'ERR_INVALID_TYPESCRIPT_SYNTAX') {
throw tsError;
// If it's invalid or unsupported TypeScript syntax, rethrow the original error
// with the TypeScript error message added to the stack.
const isInvalidTS = tsError?.code === 'ERR_INVALID_TYPESCRIPT_SYNTAX';
const isUnsupportedTS = tsError?.code === 'ERR_UNSUPPORTED_TYPESCRIPT_SYNTAX';
if ((isInvalidTS || isUnsupportedTS) && isError(tsError)) {
try {
originalError.stack = decorateCJSErrorWithTSMessage(originalError.stack, tsError.message);
} catch { /* Ignore potential errors coming from `stack` getter/setter */ }
throw originalError;
}

try {
originalError.stack = decorateCJSErrorWithTSMessage(originalError.stack, tsError.message);
} catch { /* Ignore potential errors coming from `stack` getter/setter */ }
throw originalError;
throw tsError;
}
}

Expand Down Expand Up @@ -335,15 +338,18 @@ function evalTypeScriptModuleEntryPoint(source, print) {
// Emit the experimental warning after the code was successfully compiled.
emitExperimentalWarning('Type Stripping');
} catch (tsError) {
// If its not an error, or it's not an invalid typescript syntax error, rethrow it.
if (!isError(tsError) || tsError?.code !== 'ERR_INVALID_TYPESCRIPT_SYNTAX') {
throw tsError;
// If it's invalid or unsupported TypeScript syntax, rethrow the original error
// with the TypeScript error message added to the stack.
const isInvalidTS = tsError?.code === 'ERR_INVALID_TYPESCRIPT_SYNTAX';
const isUnsupportedTS = tsError?.code === 'ERR_UNSUPPORTED_TYPESCRIPT_SYNTAX';
if ((isInvalidTS || isUnsupportedTS) && isError(tsError)) {
try {
originalError.stack = `${tsError.message}\n\n${originalError.stack}`;
} catch { /* Ignore potential errors coming from `stack` getter/setter */ }
throw originalError;
}
try {
originalError.stack = `${tsError.message}\n\n${originalError.stack}`;
} catch { /* Ignore potential errors coming from `stack` getter/setter */ }

throw originalError;
throw tsError;
}
}
// If the moduleWrap was successfully created either with by just compiling
Expand Down
36 changes: 28 additions & 8 deletions test/es-module/test-typescript-eval.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -102,33 +102,33 @@ test('expect fail eval TypeScript ESM syntax with input-type commonjs-typescript
strictEqual(result.code, 1);
});

test('check syntax error is thrown when passing invalid syntax', async () => {
test('check syntax error is thrown when passing unsupported syntax', async () => {
const result = await spawnPromisified(process.execPath, [
'--eval',
'enum Foo { A, B, C }']);
strictEqual(result.stdout, '');
match(result.stderr, /SyntaxError/);
doesNotMatch(result.stderr, /ERR_INVALID_TYPESCRIPT_SYNTAX/);
doesNotMatch(result.stderr, /ERR_UNSUPPORTED_TYPESCRIPT_SYNTAX/);
strictEqual(result.code, 1);
});

test('check syntax error is thrown when passing invalid syntax with --input-type=module-typescript', async () => {
test('check syntax error is thrown when passing unsupported syntax with --input-type=module-typescript', async () => {
const result = await spawnPromisified(process.execPath, [
'--input-type=module-typescript',
'--eval',
'enum Foo { A, B, C }']);
strictEqual(result.stdout, '');
match(result.stderr, /ERR_INVALID_TYPESCRIPT_SYNTAX/);
match(result.stderr, /ERR_UNSUPPORTED_TYPESCRIPT_SYNTAX/);
strictEqual(result.code, 1);
});

test('check syntax error is thrown when passing invalid syntax with --input-type=commonjs-typescript', async () => {
test('check syntax error is thrown when passing unsupported syntax with --input-type=commonjs-typescript', async () => {
const result = await spawnPromisified(process.execPath, [
'--input-type=commonjs-typescript',
'--eval',
'enum Foo { A, B, C }']);
strictEqual(result.stdout, '');
match(result.stderr, /ERR_INVALID_TYPESCRIPT_SYNTAX/);
match(result.stderr, /ERR_UNSUPPORTED_TYPESCRIPT_SYNTAX/);
strictEqual(result.code, 1);
});

Expand All @@ -140,7 +140,7 @@ test('should not parse TypeScript with --type-module=commonjs', async () => {

strictEqual(result.stdout, '');
match(result.stderr, /SyntaxError/);
doesNotMatch(result.stderr, /ERR_INVALID_TYPESCRIPT_SYNTAX/);
doesNotMatch(result.stderr, /ERR_UNSUPPORTED_TYPESCRIPT_SYNTAX/);
strictEqual(result.code, 1);
});

Expand All @@ -152,7 +152,7 @@ test('should not parse TypeScript with --type-module=module', async () => {

strictEqual(result.stdout, '');
match(result.stderr, /SyntaxError/);
doesNotMatch(result.stderr, /ERR_INVALID_TYPESCRIPT_SYNTAX/);
doesNotMatch(result.stderr, /ERR_UNSUPPORTED_TYPESCRIPT_SYNTAX/);
strictEqual(result.code, 1);
});

Expand Down Expand Up @@ -222,3 +222,23 @@ test('typescript CJS code is throwing a syntax error at runtime', async () => {
strictEqual(result.stdout, '');
strictEqual(result.code, 1);
});

test('check syntax error is thrown when passing invalid syntax with --input-type=commonjs-typescript', async () => {
const result = await spawnPromisified(process.execPath, [
'--input-type=commonjs-typescript',
'--eval',
'function foo(){ await Promise.resolve(1); }']);
strictEqual(result.stdout, '');
match(result.stderr, /ERR_INVALID_TYPESCRIPT_SYNTAX/);
strictEqual(result.code, 1);
});

test('check syntax error is thrown when passing invalid syntax with --input-type=module-typescript', async () => {
const result = await spawnPromisified(process.execPath, [
'--input-type=module-typescript',
'--eval',
'function foo(){ await Promise.resolve(1); }']);
strictEqual(result.stdout, '');
match(result.stderr, /ERR_INVALID_TYPESCRIPT_SYNTAX/);
strictEqual(result.code, 1);
});
10 changes: 10 additions & 0 deletions test/es-module/test-typescript.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -321,3 +321,13 @@ test('execute a TypeScript loader and a .js file', async () => {
match(result.stdout, /Hello, TypeScript!/);
strictEqual(result.code, 0);
});

test('execute invalid TypeScript syntax', async () => {
const result = await spawnPromisified(process.execPath, [
fixtures.path('typescript/ts/test-invalid-syntax.ts'),
]);

match(result.stderr, /ERR_INVALID_TYPESCRIPT_SYNTAX/);
strictEqual(result.stdout, '');
strictEqual(result.code, 1);
});
3 changes: 3 additions & 0 deletions test/fixtures/typescript/ts/test-invalid-syntax.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
function foo(): string {
await Promise.resolve(1);
}

0 comments on commit 526ece0

Please sign in to comment.