Skip to content

Commit 1e490aa

Browse files
marco-ippolitoRafaelGSS
authored andcommitted
module: improve typescript error message format
PR-URL: #57687 Fixes: #56830 Reviewed-By: Chengzhong Wu <[email protected]> Reviewed-By: Antoine du Hamel <[email protected]> Reviewed-By: Rafael Gonzaga <[email protected]>
1 parent a561755 commit 1e490aa

File tree

7 files changed

+60
-78
lines changed

7 files changed

+60
-78
lines changed

lib/internal/modules/typescript.js

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -63,10 +63,14 @@ function parseTypeScript(source, options) {
6363
* It allows us to distinguish between invalid syntax and unsupported syntax.
6464
*/
6565
switch (error?.code) {
66-
case 'UnsupportedSyntax':
67-
throw new ERR_UNSUPPORTED_TYPESCRIPT_SYNTAX(error.message);
68-
case 'InvalidSyntax':
69-
throw new ERR_INVALID_TYPESCRIPT_SYNTAX(error.message);
66+
case 'UnsupportedSyntax': {
67+
const unsupportedSyntaxError = new ERR_UNSUPPORTED_TYPESCRIPT_SYNTAX(error.message);
68+
throw decorateErrorWithSnippet(unsupportedSyntaxError, error); /* node-do-not-add-exception-line */
69+
}
70+
case 'InvalidSyntax': {
71+
const invalidSyntaxError = new ERR_INVALID_TYPESCRIPT_SYNTAX(error.message);
72+
throw decorateErrorWithSnippet(invalidSyntaxError, error); /* node-do-not-add-exception-line */
73+
}
7074
default:
7175
// SWC may throw strings when something goes wrong.
7276
if (typeof error === 'string') { assert.fail(error); }
@@ -76,6 +80,18 @@ function parseTypeScript(source, options) {
7680
}
7781
}
7882

83+
/**
84+
*
85+
* @param {Error} error the error to decorate: ERR_INVALID_TYPESCRIPT_SYNTAX, ERR_UNSUPPORTED_TYPESCRIPT_SYNTAX
86+
* @param {object} amaroError the error object from amaro
87+
* @returns {Error} the decorated error
88+
*/
89+
function decorateErrorWithSnippet(error, amaroError) {
90+
const errorHints = `${amaroError.filename}:${amaroError.startLine}${amaroError.snippet}`;
91+
error.stack = `${errorHints}${error.stack}`;
92+
return error;
93+
}
94+
7995
/**
8096
* Performs type-stripping to TypeScript source code.
8197
* @param {string} code TypeScript code to parse.

lib/internal/process/execution.js

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,8 @@ const { emitExperimentalWarning } = require('internal/util');
4444
// communication with JS.
4545
const { shouldAbortOnUncaughtToggle } = internalBinding('util');
4646

47+
const kEvalTag = '[eval]';
48+
4749
function tryGetCwd() {
4850
try {
4951
return process.cwd();
@@ -259,7 +261,7 @@ function evalTypeScript(name, source, breakFirstLine, print, shouldLoadESM = fal
259261
compiledScript = compileScript(name, source, baseUrl);
260262
} catch (originalError) {
261263
try {
262-
sourceToRun = stripTypeScriptModuleTypes(source, name, false);
264+
sourceToRun = stripTypeScriptModuleTypes(source, kEvalTag, false);
263265
// Retry the CJS/ESM syntax detection after stripping the types.
264266
if (shouldUseModuleEntryPoint(name, sourceToRun)) {
265267
return evalTypeScriptModuleEntryPoint(source, print);
@@ -322,7 +324,7 @@ function evalTypeScriptModuleEntryPoint(source, print) {
322324
moduleWrap = loader.createModuleWrap(source, url);
323325
} catch (originalError) {
324326
try {
325-
const strippedSource = stripTypeScriptModuleTypes(source, url, false);
327+
const strippedSource = stripTypeScriptModuleTypes(source, kEvalTag, false);
326328
// If the moduleWrap was successfully created, execute the module job.
327329
// outside the try-catch block to avoid catching runtime errors.
328330
moduleWrap = loader.createModuleWrap(strippedSource, url);
@@ -355,7 +357,7 @@ function evalTypeScriptModuleEntryPoint(source, print) {
355357
*/
356358
function parseAndEvalModuleTypeScript(source, print) {
357359
// We know its a TypeScript module, we can safely emit the experimental warning.
358-
const strippedSource = stripTypeScriptModuleTypes(source, getEvalModuleUrl());
360+
const strippedSource = stripTypeScriptModuleTypes(source, kEvalTag);
359361
evalModuleEntryPoint(strippedSource, print);
360362
}
361363

@@ -370,7 +372,7 @@ function parseAndEvalModuleTypeScript(source, print) {
370372
*/
371373
function parseAndEvalCommonjsTypeScript(name, source, breakFirstLine, print, shouldLoadESM = false) {
372374
// We know its a TypeScript module, we can safely emit the experimental warning.
373-
const strippedSource = stripTypeScriptModuleTypes(source, getEvalModuleUrl());
375+
const strippedSource = stripTypeScriptModuleTypes(source, kEvalTag);
374376
evalScript(name, strippedSource, breakFirstLine, print, shouldLoadESM);
375377
}
376378

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

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

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)