diff --git a/doc/api/cli.md b/doc/api/cli.md
index da2506326fc5bd..dd058306aa26f7 100644
--- a/doc/api/cli.md
+++ b/doc/api/cli.md
@@ -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.
@@ -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
diff --git a/doc/api/errors.md b/doc/api/errors.md
index 24e2290e472b6b..fcd351b6993cb5 100644
--- a/doc/api/errors.md
+++ b/doc/api/errors.md
@@ -2095,11 +2095,13 @@ does not consist of exactly two elements.
added:
- v23.0.0
- v22.10.0
+changes:
+ - version: REPLACEME
+ pr-url: https://github.com/nodejs/node/pull/56610
+ description: This error is no longer thrown on valid yet unsupported syntax.
-->
-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.
@@ -3116,6 +3118,18 @@ try {
}
```
+
+
+### `ERR_UNSUPPORTED_TYPESCRIPT_SYNTAX`
+
+
+
+The provided TypeScript syntax is unsupported.
+This could happen when using TypeScript syntax that requires
+transformation with [type-stripping][].
+
### `ERR_USE_AFTER_CLOSE`
diff --git a/lib/internal/errors.js b/lib/internal/errors.js
index bda50797124758..d6b2ceb5962351 100644
--- a/lib/internal/errors.js
+++ b/lib/internal/errors.js
@@ -1838,6 +1838,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`.
diff --git a/lib/internal/modules/typescript.js b/lib/internal/modules/typescript.js
index 993fd3ff72d74d..689788b09853c4 100644
--- a/lib/internal/modules/typescript.js
+++ b/lib/internal/modules/typescript.js
@@ -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');
@@ -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);
+ }
}
}
diff --git a/lib/internal/process/execution.js b/lib/internal/process/execution.js
index f5b19d5a7e8c9c..d4d7a604851ef1 100644
--- a/lib/internal/process/execution.js
+++ b/lib/internal/process/execution.js
@@ -35,7 +35,7 @@ const { getOptionValue } = require('internal/options');
const {
makeContextifyScript, runScriptInThisContext,
} = require('internal/vm');
-const { emitExperimentalWarning, isError } = require('internal/util');
+const { emitExperimentalWarning } = require('internal/util');
// shouldAbortOnUncaughtToggle is a typed array for faster
// communication with JS.
const { shouldAbortOnUncaughtToggle } = internalBinding('util');
@@ -254,10 +254,6 @@ function evalTypeScript(name, source, breakFirstLine, print, shouldLoadESM = fal
try {
compiledScript = compileScript(name, source, baseUrl);
} catch (originalError) {
- // If it's not a SyntaxError, rethrow it.
- if (!isError(originalError) || originalError.name !== 'SyntaxError') {
- throw originalError;
- }
try {
sourceToRun = stripTypeScriptModuleTypes(source, name, false);
// Retry the CJS/ESM syntax detection after stripping the types.
@@ -270,15 +266,14 @@ 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.
+ if (tsError.code === 'ERR_INVALID_TYPESCRIPT_SYNTAX' || tsError.code === 'ERR_UNSUPPORTED_TYPESCRIPT_SYNTAX') {
+ originalError.stack = decorateCJSErrorWithTSMessage(originalError.stack, tsError.message);
+ throw originalError;
}
- try {
- originalError.stack = decorateCJSErrorWithTSMessage(originalError.stack, tsError.message);
- } catch { /* Ignore potential errors coming from `stack` getter/setter */ }
- throw originalError;
+ throw tsError;
}
}
@@ -322,28 +317,23 @@ function evalTypeScriptModuleEntryPoint(source, print) {
// Compile the module to check for syntax errors.
moduleWrap = loader.createModuleWrap(source, url);
} catch (originalError) {
- // If it's not a SyntaxError, rethrow it.
- if (!isError(originalError) || originalError.name !== 'SyntaxError') {
- throw originalError;
- }
- let strippedSource;
try {
- strippedSource = stripTypeScriptModuleTypes(source, url, false);
+ const strippedSource = stripTypeScriptModuleTypes(source, url, false);
// If the moduleWrap was successfully created, execute the module job.
// outside the try-catch block to avoid catching runtime errors.
moduleWrap = loader.createModuleWrap(strippedSource, url);
// 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;
- }
- try {
+ // If it's invalid or unsupported TypeScript syntax, rethrow the original error
+ // with the TypeScript error message added to the stack.
+ if (tsError.code === 'ERR_INVALID_TYPESCRIPT_SYNTAX' ||
+ tsError.code === 'ERR_UNSUPPORTED_TYPESCRIPT_SYNTAX') {
originalError.stack = `${tsError.message}\n\n${originalError.stack}`;
- } catch { /* Ignore potential errors coming from `stack` getter/setter */ }
+ throw originalError;
+ }
- throw originalError;
+ throw tsError;
}
}
// If the moduleWrap was successfully created either with by just compiling
diff --git a/test/es-module/test-typescript-eval.mjs b/test/es-module/test-typescript-eval.mjs
index 5c6f25bec4df7d..bbbed8863de25a 100644
--- a/test/es-module/test-typescript-eval.mjs
+++ b/test/es-module/test-typescript-eval.mjs
@@ -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);
});
@@ -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);
});
@@ -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);
});
@@ -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);
+});
diff --git a/test/es-module/test-typescript.mjs b/test/es-module/test-typescript.mjs
index 81aed880bdcf51..74c4a0f120b758 100644
--- a/test/es-module/test-typescript.mjs
+++ b/test/es-module/test-typescript.mjs
@@ -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);
+});
diff --git a/test/fixtures/typescript/ts/test-invalid-syntax.ts b/test/fixtures/typescript/ts/test-invalid-syntax.ts
new file mode 100644
index 00000000000000..031bce938d27dc
--- /dev/null
+++ b/test/fixtures/typescript/ts/test-invalid-syntax.ts
@@ -0,0 +1,3 @@
+function foo(): string {
+ await Promise.resolve(1);
+}