Skip to content

Commit fe7994e

Browse files
marco-ippolitoRafaelGSS
authored andcommitted
module: improve typescript error message format
PR-URL: #57687 Backport-PR-URL: #57298 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> PR-URL: #56350 Fixes: nodejs/typescript#17
1 parent c898491 commit fe7994e

File tree

7 files changed

+58
-76
lines changed

7 files changed

+58
-76
lines changed

lib/internal/modules/typescript.js

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -59,10 +59,14 @@ function parseTypeScript(source, options) {
5959
* It allows us to distinguish between invalid syntax and unsupported syntax.
6060
*/
6161
switch (error?.code) {
62-
case 'UnsupportedSyntax':
63-
throw new ERR_UNSUPPORTED_TYPESCRIPT_SYNTAX(error.message);
64-
case 'InvalidSyntax':
65-
throw new ERR_INVALID_TYPESCRIPT_SYNTAX(error.message);
62+
case 'UnsupportedSyntax': {
63+
const unsupportedSyntaxError = new ERR_UNSUPPORTED_TYPESCRIPT_SYNTAX(error.message);
64+
throw decorateErrorWithSnippet(unsupportedSyntaxError, error); /* node-do-not-add-exception-line */
65+
}
66+
case 'InvalidSyntax': {
67+
const invalidSyntaxError = new ERR_INVALID_TYPESCRIPT_SYNTAX(error.message);
68+
throw decorateErrorWithSnippet(invalidSyntaxError, error); /* node-do-not-add-exception-line */
69+
}
6670
default:
6771
// SWC may throw strings when something goes wrong.
6872
if (typeof error === 'string') { assert.fail(error); }
@@ -72,6 +76,18 @@ function parseTypeScript(source, options) {
7276
}
7377
}
7478

79+
/**
80+
*
81+
* @param {Error} error the error to decorate: ERR_INVALID_TYPESCRIPT_SYNTAX, ERR_UNSUPPORTED_TYPESCRIPT_SYNTAX
82+
* @param {object} amaroError the error object from amaro
83+
* @returns {Error} the decorated error
84+
*/
85+
function decorateErrorWithSnippet(error, amaroError) {
86+
const errorHints = `${amaroError.filename}:${amaroError.startLine}${amaroError.snippet}`;
87+
error.stack = `${errorHints}${error.stack}`;
88+
return error;
89+
}
90+
7591
/**
7692
* Performs type-stripping to TypeScript source code.
7793
* @param {string} code TypeScript code to parse.

lib/internal/process/execution.js

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@ const {
3939
// communication with JS.
4040
const { shouldAbortOnUncaughtToggle } = internalBinding('util');
4141

42+
const kEvalTag = '[eval]';
43+
4244
function tryGetCwd() {
4345
try {
4446
return process.cwd();
@@ -346,7 +348,7 @@ function evalTypeScriptModuleEntryPoint(source, print) {
346348
*/
347349
function parseAndEvalModuleTypeScript(source, print) {
348350
// We know its a TypeScript module, we can safely emit the experimental warning.
349-
const strippedSource = stripTypeScriptModuleTypes(source, getEvalModuleUrl());
351+
const strippedSource = stripTypeScriptModuleTypes(source, kEvalTag);
350352
evalModuleEntryPoint(strippedSource, print);
351353
}
352354

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

test/es-module/test-typescript-eval.mjs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -264,3 +264,23 @@ test('should not allow declare module keyword', async () => {
264264
match(result.stderr, /ERR_UNSUPPORTED_TYPESCRIPT_SYNTAX/);
265265
strictEqual(result.code, 1);
266266
});
267+
268+
// TODO (marco-ippolito) Remove the extra padding from the error message
269+
// The padding comes from swc it will be removed in a future amaro release
270+
test('the error message should not contain extra padding', async () => {
271+
const result = await spawnPromisified(process.execPath, [
272+
'--input-type=module-typescript',
273+
'--eval',
274+
'declare module F { export type x = number }']);
275+
strictEqual(result.stdout, '');
276+
// Windows uses \r\n as line endings
277+
const lines = result.stderr.replace(/\r\n/g, '\n').split('\n');
278+
// The extra padding at the end should not be present
279+
strictEqual(lines[0], '[eval]:1 ');
280+
// The extra padding at the beginning should not be present
281+
strictEqual(lines[2], ' declare module F { export type x = number }');
282+
strictEqual(lines[3], ' ^^^^^^^^');
283+
strictEqual(lines[5], 'SyntaxError [ERR_UNSUPPORTED_TYPESCRIPT_SYNTAX]:' +
284+
' `module` keyword is not supported. Use `namespace` instead.');
285+
strictEqual(result.code, 1);
286+
});

test/fixtures/eval/eval_messages.snapshot

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,7 @@
22
[eval]:1
33
with(this){__filename}
44
^^^^
5-
x The 'with' statement is not supported. All symbols in a 'with' block will have type 'any'.
6-
,----
7-
1 | with(this){__filename}
8-
: ^^^^
9-
`----
5+
The 'with' statement is not supported. All symbols in a 'with' block will have type 'any'.
106

117
SyntaxError: Strict mode code may not include a with statement
128

test/fixtures/eval/eval_typescript.snapshot

Lines changed: 4 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,7 @@
11
[eval]:1
22
enum Foo{};
33
^^^^
4-
x TypeScript enum is not supported in strip-only mode
5-
,----
6-
1 | enum Foo{};
7-
: ^^^^^^^^^^
8-
`----
4+
TypeScript enum is not supported in strip-only mode
95

106
SyntaxError: Unexpected reserved word
117

@@ -20,11 +16,7 @@ Node.js *
2016
[eval]:1
2117
const foo;
2218
^^^
23-
x 'const' declarations must be initialized
24-
,----
25-
1 | const foo;
26-
: ^^^
27-
`----
19+
'const' declarations must be initialized
2820

2921
SyntaxError: Missing initializer in const declaration
3022

@@ -35,23 +27,15 @@ false
3527
[eval]:1
3628
interface Foo{};const foo;
3729
^^^
38-
x 'const' declarations must be initialized
39-
,----
40-
1 | interface Foo{};const foo;
41-
: ^^^
42-
`----
30+
'const' declarations must be initialized
4331

4432
SyntaxError: Unexpected identifier 'Foo'
4533

4634
Node.js *
4735
[eval]:1
4836
function foo(){ await Promise.resolve(1)};
4937
^^^^^
50-
x await isn't allowed in non-async function
51-
,----
52-
1 | function foo(){ await Promise.resolve(1)};
53-
: ^^^^^^^
54-
`----
38+
await isn't allowed in non-async function
5539

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

test/fixtures/eval/stdin_messages.snapshot

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,7 @@
22
[stdin]:1
33
with(this){__filename}
44
^^^^
5-
x The 'with' statement is not supported. All symbols in a 'with' block will have type 'any'.
6-
,----
7-
1 | with(this){__filename}
8-
: ^^^^
9-
`----
5+
The 'with' statement is not supported. All symbols in a 'with' block will have type 'any'.
106

117
SyntaxError: Strict mode code may not include a with statement
128

test/fixtures/eval/stdin_typescript.snapshot

Lines changed: 8 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,15 @@
11
[stdin]:1
22
enum Foo{};
33
^^^^
4-
x TypeScript enum is not supported in strip-only mode
5-
,----
6-
1 | enum Foo{};
7-
: ^^^^^^^^^^
8-
`----
4+
TypeScript enum is not supported in strip-only mode
95

106
SyntaxError: Unexpected reserved word
117

128
Node.js *
139
[stdin]:1
1410
enum Foo{};
1511
^^^^
16-
x TypeScript enum is not supported in strip-only mode
17-
,----
18-
1 | enum Foo{};
19-
: ^^^^^^^^^^
20-
`----
12+
TypeScript enum is not supported in strip-only mode
2113

2214
SyntaxError: Unexpected reserved word
2315

@@ -39,23 +31,15 @@ Node.js *
3931
[stdin]:1
4032
const foo;
4133
^^^
42-
x 'const' declarations must be initialized
43-
,----
44-
1 | const foo;
45-
: ^^^
46-
`----
34+
'const' declarations must be initialized
4735

4836
SyntaxError: Missing initializer in const declaration
4937

5038
Node.js *
5139
[stdin]:1
5240
const foo;
5341
^^^
54-
x 'const' declarations must be initialized
55-
,----
56-
1 | const foo;
57-
: ^^^
58-
`----
42+
'const' declarations must be initialized
5943

6044
SyntaxError: Missing initializer in const declaration
6145

@@ -69,47 +53,31 @@ false
6953
[stdin]:1
7054
interface Foo{};const foo;
7155
^^^
72-
x 'const' declarations must be initialized
73-
,----
74-
1 | interface Foo{};const foo;
75-
: ^^^
76-
`----
56+
'const' declarations must be initialized
7757

7858
SyntaxError: Unexpected identifier 'Foo'
7959

8060
Node.js *
8161
[stdin]:1
8262
interface Foo{};const foo;
8363
^^^^^^^^^
84-
x 'const' declarations must be initialized
85-
,----
86-
1 | interface Foo{};const foo;
87-
: ^^^
88-
`----
64+
'const' declarations must be initialized
8965

9066
SyntaxError: Unexpected strict mode reserved word
9167

9268
Node.js *
9369
[stdin]:1
9470
function foo(){ await Promise.resolve(1)};
9571
^^^^^
96-
x await isn't allowed in non-async function
97-
,----
98-
1 | function foo(){ await Promise.resolve(1)};
99-
: ^^^^^^^
100-
`----
72+
await isn't allowed in non-async function
10173

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

10476
Node.js *
10577
[stdin]:1
10678
function foo(){ await Promise.resolve(1)};
10779
^^^^^
108-
x await isn't allowed in non-async function
109-
,----
110-
1 | function foo(){ await Promise.resolve(1)};
111-
: ^^^^^^^
112-
`----
80+
await isn't allowed in non-async function
11381

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

0 commit comments

Comments
 (0)