Skip to content
Closed
Prev Previous commit
Next Next commit
module: improve typescript error message format
PR-URL: #57687
Fixes: #56830
Reviewed-By: Chengzhong Wu <legendecas@gmail.com>
Reviewed-By: Antoine du Hamel <duhamelantoine1995@gmail.com>
Reviewed-By: Rafael Gonzaga <rafael.nunu@hotmail.com>
  • Loading branch information
marco-ippolito committed Jul 3, 2025
commit 8c22513f94e757a149ad82bac83be9ca91ff0854
24 changes: 20 additions & 4 deletions lib/internal/modules/typescript.js
Original file line number Diff line number Diff line change
Expand Up @@ -59,10 +59,14 @@ function parseTypeScript(source, options) {
* 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);
case 'UnsupportedSyntax': {
const unsupportedSyntaxError = new ERR_UNSUPPORTED_TYPESCRIPT_SYNTAX(error.message);
throw decorateErrorWithSnippet(unsupportedSyntaxError, error); /* node-do-not-add-exception-line */
}
case 'InvalidSyntax': {
const invalidSyntaxError = new ERR_INVALID_TYPESCRIPT_SYNTAX(error.message);
throw decorateErrorWithSnippet(invalidSyntaxError, error); /* node-do-not-add-exception-line */
}
default:
// SWC may throw strings when something goes wrong.
if (typeof error === 'string') { assert.fail(error); }
Expand All @@ -72,6 +76,18 @@ function parseTypeScript(source, options) {
}
}

/**
*
* @param {Error} error the error to decorate: ERR_INVALID_TYPESCRIPT_SYNTAX, ERR_UNSUPPORTED_TYPESCRIPT_SYNTAX
* @param {object} amaroError the error object from amaro
* @returns {Error} the decorated error
*/
function decorateErrorWithSnippet(error, amaroError) {
const errorHints = `${amaroError.filename}:${amaroError.startLine}${amaroError.snippet}`;
error.stack = `${errorHints}${error.stack}`;
return error;
}

/**
* Performs type-stripping to TypeScript source code.
* @param {string} code TypeScript code to parse.
Expand Down
6 changes: 4 additions & 2 deletions lib/internal/process/execution.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ const {
// communication with JS.
const { shouldAbortOnUncaughtToggle } = internalBinding('util');

const kEvalTag = '[eval]';

function tryGetCwd() {
try {
return process.cwd();
Expand Down Expand Up @@ -346,7 +348,7 @@ function evalTypeScriptModuleEntryPoint(source, print) {
*/
function parseAndEvalModuleTypeScript(source, print) {
// We know its a TypeScript module, we can safely emit the experimental warning.
const strippedSource = stripTypeScriptModuleTypes(source, getEvalModuleUrl());
const strippedSource = stripTypeScriptModuleTypes(source, kEvalTag);
evalModuleEntryPoint(strippedSource, print);
}

Expand All @@ -361,7 +363,7 @@ function parseAndEvalModuleTypeScript(source, print) {
*/
function parseAndEvalCommonjsTypeScript(name, source, breakFirstLine, print, shouldLoadESM = false) {
// We know its a TypeScript module, we can safely emit the experimental warning.
const strippedSource = stripTypeScriptModuleTypes(source, getEvalModuleUrl());
const strippedSource = stripTypeScriptModuleTypes(source, kEvalTag);
evalScript(name, strippedSource, breakFirstLine, print, shouldLoadESM);
}

Expand Down
20 changes: 20 additions & 0 deletions test/es-module/test-typescript-eval.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -264,3 +264,23 @@ test('should not allow declare module keyword', async () => {
match(result.stderr, /ERR_UNSUPPORTED_TYPESCRIPT_SYNTAX/);
strictEqual(result.code, 1);
});

// TODO (marco-ippolito) Remove the extra padding from the error message
// The padding comes from swc it will be removed in a future amaro release
test('the error message should not contain extra padding', async () => {
const result = await spawnPromisified(process.execPath, [
'--input-type=module-typescript',
'--eval',
'declare module F { export type x = number }']);
strictEqual(result.stdout, '');
// Windows uses \r\n as line endings
const lines = result.stderr.replace(/\r\n/g, '\n').split('\n');
// The extra padding at the end should not be present
strictEqual(lines[0], '[eval]:1 ');
// The extra padding at the beginning should not be present
strictEqual(lines[2], ' declare module F { export type x = number }');
strictEqual(lines[3], ' ^^^^^^^^');
strictEqual(lines[5], 'SyntaxError [ERR_UNSUPPORTED_TYPESCRIPT_SYNTAX]:' +
' `module` keyword is not supported. Use `namespace` instead.');
strictEqual(result.code, 1);
});
6 changes: 1 addition & 5 deletions test/fixtures/eval/eval_messages.snapshot
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,7 @@
[eval]:1
with(this){__filename}
^^^^
x The 'with' statement is not supported. All symbols in a 'with' block will have type 'any'.
,----
1 | with(this){__filename}
: ^^^^
`----
The 'with' statement is not supported. All symbols in a 'with' block will have type 'any'.

SyntaxError: Strict mode code may not include a with statement

Expand Down
24 changes: 4 additions & 20 deletions test/fixtures/eval/eval_typescript.snapshot
Original file line number Diff line number Diff line change
@@ -1,11 +1,7 @@
[eval]:1
enum Foo{};
^^^^
x TypeScript enum is not supported in strip-only mode
,----
1 | enum Foo{};
: ^^^^^^^^^^
`----
TypeScript enum is not supported in strip-only mode

SyntaxError: Unexpected reserved word

Expand All @@ -20,11 +16,7 @@ Node.js *
[eval]:1
const foo;
^^^
x 'const' declarations must be initialized
,----
1 | const foo;
: ^^^
`----
'const' declarations must be initialized

SyntaxError: Missing initializer in const declaration

Expand All @@ -35,23 +27,15 @@ false
[eval]:1
interface Foo{};const foo;
^^^
x 'const' declarations must be initialized
,----
1 | interface Foo{};const foo;
: ^^^
`----
'const' declarations must be initialized

SyntaxError: Unexpected identifier 'Foo'

Node.js *
[eval]:1
function foo(){ await Promise.resolve(1)};
^^^^^
x await isn't allowed in non-async function
,----
1 | function foo(){ await Promise.resolve(1)};
: ^^^^^^^
`----
await isn't allowed in non-async function

SyntaxError: await is only valid in async functions and the top level bodies of modules

Expand Down
6 changes: 1 addition & 5 deletions test/fixtures/eval/stdin_messages.snapshot
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,7 @@
[stdin]:1
with(this){__filename}
^^^^
x The 'with' statement is not supported. All symbols in a 'with' block will have type 'any'.
,----
1 | with(this){__filename}
: ^^^^
`----
The 'with' statement is not supported. All symbols in a 'with' block will have type 'any'.

SyntaxError: Strict mode code may not include a with statement

Expand Down
48 changes: 8 additions & 40 deletions test/fixtures/eval/stdin_typescript.snapshot
Original file line number Diff line number Diff line change
@@ -1,23 +1,15 @@
[stdin]:1
enum Foo{};
^^^^
x TypeScript enum is not supported in strip-only mode
,----
1 | enum Foo{};
: ^^^^^^^^^^
`----
TypeScript enum is not supported in strip-only mode

SyntaxError: Unexpected reserved word

Node.js *
[stdin]:1
enum Foo{};
^^^^
x TypeScript enum is not supported in strip-only mode
,----
1 | enum Foo{};
: ^^^^^^^^^^
`----
TypeScript enum is not supported in strip-only mode

SyntaxError: Unexpected reserved word

Expand All @@ -39,23 +31,15 @@ Node.js *
[stdin]:1
const foo;
^^^
x 'const' declarations must be initialized
,----
1 | const foo;
: ^^^
`----
'const' declarations must be initialized

SyntaxError: Missing initializer in const declaration

Node.js *
[stdin]:1
const foo;
^^^
x 'const' declarations must be initialized
,----
1 | const foo;
: ^^^
`----
'const' declarations must be initialized

SyntaxError: Missing initializer in const declaration

Expand All @@ -69,47 +53,31 @@ false
[stdin]:1
interface Foo{};const foo;
^^^
x 'const' declarations must be initialized
,----
1 | interface Foo{};const foo;
: ^^^
`----
'const' declarations must be initialized

SyntaxError: Unexpected identifier 'Foo'

Node.js *
[stdin]:1
interface Foo{};const foo;
^^^^^^^^^
x 'const' declarations must be initialized
,----
1 | interface Foo{};const foo;
: ^^^
`----
'const' declarations must be initialized

SyntaxError: Unexpected strict mode reserved word

Node.js *
[stdin]:1
function foo(){ await Promise.resolve(1)};
^^^^^
x await isn't allowed in non-async function
,----
1 | function foo(){ await Promise.resolve(1)};
: ^^^^^^^
`----
await isn't allowed in non-async function

SyntaxError: await is only valid in async functions and the top level bodies of modules

Node.js *
[stdin]:1
function foo(){ await Promise.resolve(1)};
^^^^^
x await isn't allowed in non-async function
,----
1 | function foo(){ await Promise.resolve(1)};
: ^^^^^^^
`----
await isn't allowed in non-async function

SyntaxError: await is only valid in async functions and the top level bodies of modules

Expand Down