GeezerVison is showing github.com — module: improve typescript error message format · nodejs/node@1e490aa
File treeExpand file treeCollapse file tree Open diff view settings Expand file treeCollapse file tree Open diff view settings Collapse file Copy file name to clipboardExpand all lines: lib/internal/modules/typescript.js +20-4Lines changed: 20 additions & 4 deletions | Original file line number | Diff line number | Diff line change |
|---|
@@ -63,10 +63,14 @@ function parseTypeScript(source, options) { | 63 | 63 | * It allows us to distinguish between invalid syntax and unsupported syntax. | 64 | 64 | */ | 65 | 65 | 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 | + } | 70 | 74 | default: | 71 | 75 | // SWC may throw strings when something goes wrong. | 72 | 76 | if (typeof error === 'string') { assert.fail(error); } | @@ -76,6 +80,18 @@ function parseTypeScript(source, options) { | 76 | 80 | } | 77 | 81 | } | 78 | 82 |
| | 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 | + | 79 | 95 | /** | 80 | 96 | * Performs type-stripping to TypeScript source code. | 81 | 97 | * @param {string} code TypeScript code to parse. | |
Collapse file Copy file name to clipboardExpand all lines: lib/internal/process/execution.js +6-4Lines changed: 6 additions & 4 deletions | Original file line number | Diff line number | Diff line change |
|---|
@@ -44,6 +44,8 @@ const { emitExperimentalWarning } = require('internal/util'); | 44 | 44 | // communication with JS. | 45 | 45 | const { shouldAbortOnUncaughtToggle } = internalBinding('util'); | 46 | 46 |
| | 47 | +const kEvalTag = '[eval]'; | | 48 | + | 47 | 49 | function tryGetCwd() { | 48 | 50 | try { | 49 | 51 | return process.cwd(); | @@ -259,7 +261,7 @@ function evalTypeScript(name, source, breakFirstLine, print, shouldLoadESM = fal | 259 | 261 | compiledScript = compileScript(name, source, baseUrl); | 260 | 262 | } catch (originalError) { | 261 | 263 | try { | 262 | | - sourceToRun = stripTypeScriptModuleTypes(source, name, false); | | 264 | + sourceToRun = stripTypeScriptModuleTypes(source, kEvalTag, false); | 263 | 265 | // Retry the CJS/ESM syntax detection after stripping the types. | 264 | 266 | if (shouldUseModuleEntryPoint(name, sourceToRun)) { | 265 | 267 | return evalTypeScriptModuleEntryPoint(source, print); | @@ -322,7 +324,7 @@ function evalTypeScriptModuleEntryPoint(source, print) { | 322 | 324 | moduleWrap = loader.createModuleWrap(source, url); | 323 | 325 | } catch (originalError) { | 324 | 326 | try { | 325 | | - const strippedSource = stripTypeScriptModuleTypes(source, url, false); | | 327 | + const strippedSource = stripTypeScriptModuleTypes(source, kEvalTag, false); | 326 | 328 | // If the moduleWrap was successfully created, execute the module job. | 327 | 329 | // outside the try-catch block to avoid catching runtime errors. | 328 | 330 | moduleWrap = loader.createModuleWrap(strippedSource, url); | @@ -355,7 +357,7 @@ function evalTypeScriptModuleEntryPoint(source, print) { | 355 | 357 | */ | 356 | 358 | function parseAndEvalModuleTypeScript(source, print) { | 357 | 359 | // 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); | 359 | 361 | evalModuleEntryPoint(strippedSource, print); | 360 | 362 | } | 361 | 363 |
| @@ -370,7 +372,7 @@ function parseAndEvalModuleTypeScript(source, print) { | 370 | 372 | */ | 371 | 373 | function parseAndEvalCommonjsTypeScript(name, source, breakFirstLine, print, shouldLoadESM = false) { | 372 | 374 | // 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); | 374 | 376 | evalScript(name, strippedSource, breakFirstLine, print, shouldLoadESM); | 375 | 377 | } | 376 | 378 |
| |
Collapse file Copy file name to clipboardExpand all lines: test/es-module/test-typescript-eval.mjs +20Lines changed: 20 additions & 0 deletions | Original file line number | Diff line number | Diff line change |
|---|
@@ -262,3 +262,23 @@ test('should not allow declare module keyword', async () => { | 262 | 262 | match(result.stderr, /ERR_UNSUPPORTED_TYPESCRIPT_SYNTAX/); | 263 | 263 | strictEqual(result.code, 1); | 264 | 264 | }); | | 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 | +}); |
Collapse file Copy file name to clipboardExpand all lines: test/fixtures/eval/eval_messages.snapshot +1-5Lines changed: 1 addition & 5 deletions | Original file line number | Diff line number | Diff line change |
|---|
| 2 | 2 | [eval]:1 | 3 | 3 | with(this){__filename} | 4 | 4 | ^^^^ | 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'. | 10 | 6 |
| 11 | 7 | SyntaxError: Strict mode code may not include a with statement | 12 | 8 |
| |
Collapse file Copy file name to clipboardExpand all lines: test/fixtures/eval/eval_typescript.snapshot +4-20Lines changed: 4 additions & 20 deletions | Original file line number | Diff line number | Diff line change |
|---|
| 1 | 1 | [eval]:1 | 2 | 2 | enum Foo{}; | 3 | 3 | ^^^^ | 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 | 9 | 5 |
| 10 | 6 | SyntaxError: Unexpected reserved word | 11 | 7 |
| @@ -20,11 +16,7 @@ Node.js * | 20 | 16 | [eval]:1 | 21 | 17 | const foo; | 22 | 18 | ^^^ | 23 | | - x 'const' declarations must be initialized | 24 | | - ,---- | 25 | | - 1 | const foo; | 26 | | - : ^^^ | 27 | | - `---- | | 19 | +'const' declarations must be initialized | 28 | 20 |
| 29 | 21 | SyntaxError: Missing initializer in const declaration | 30 | 22 |
| @@ -35,23 +27,15 @@ false | 35 | 27 | [eval]:1 | 36 | 28 | interface Foo{};const foo; | 37 | 29 | ^^^ | 38 | | - x 'const' declarations must be initialized | 39 | | - ,---- | 40 | | - 1 | interface Foo{};const foo; | 41 | | - : ^^^ | 42 | | - `---- | | 30 | +'const' declarations must be initialized | 43 | 31 |
| 44 | 32 | SyntaxError: Unexpected identifier 'Foo' | 45 | 33 |
| 46 | 34 | Node.js * | 47 | 35 | [eval]:1 | 48 | 36 | function foo(){ await Promise.resolve(1)}; | 49 | 37 | ^^^^^ | 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 | 55 | 39 |
| 56 | 40 | SyntaxError: await is only valid in async functions and the top level bodies of modules | 57 | 41 |
| |
Collapse file Copy file name to clipboardExpand all lines: test/fixtures/eval/stdin_messages.snapshot +1-5Lines changed: 1 addition & 5 deletions | Original file line number | Diff line number | Diff line change |
|---|
| 2 | 2 | [stdin]:1 | 3 | 3 | with(this){__filename} | 4 | 4 | ^^^^ | 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'. | 10 | 6 |
| 11 | 7 | SyntaxError: Strict mode code may not include a with statement | 12 | 8 |
| |
Collapse file Copy file name to clipboardExpand all lines: test/fixtures/eval/stdin_typescript.snapshot +8-40Lines changed: 8 additions & 40 deletions | Original file line number | Diff line number | Diff line change |
|---|
| 1 | 1 | [stdin]:1 | 2 | 2 | enum Foo{}; | 3 | 3 | ^^^^ | 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 | 9 | 5 |
| 10 | 6 | SyntaxError: Unexpected reserved word | 11 | 7 |
| 12 | 8 | Node.js * | 13 | 9 | [stdin]:1 | 14 | 10 | enum Foo{}; | 15 | 11 | ^^^^ | 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 | 21 | 13 |
| 22 | 14 | SyntaxError: Unexpected reserved word | 23 | 15 |
| @@ -39,23 +31,15 @@ Node.js * | 39 | 31 | [stdin]:1 | 40 | 32 | const foo; | 41 | 33 | ^^^ | 42 | | - x 'const' declarations must be initialized | 43 | | - ,---- | 44 | | - 1 | const foo; | 45 | | - : ^^^ | 46 | | - `---- | | 34 | +'const' declarations must be initialized | 47 | 35 |
| 48 | 36 | SyntaxError: Missing initializer in const declaration | 49 | 37 |
| 50 | 38 | Node.js * | 51 | 39 | [stdin]:1 | 52 | 40 | const foo; | 53 | 41 | ^^^ | 54 | | - x 'const' declarations must be initialized | 55 | | - ,---- | 56 | | - 1 | const foo; | 57 | | - : ^^^ | 58 | | - `---- | | 42 | +'const' declarations must be initialized | 59 | 43 |
| 60 | 44 | SyntaxError: Missing initializer in const declaration | 61 | 45 |
| @@ -69,47 +53,31 @@ false | 69 | 53 | [stdin]:1 | 70 | 54 | interface Foo{};const foo; | 71 | 55 | ^^^ | 72 | | - x 'const' declarations must be initialized | 73 | | - ,---- | 74 | | - 1 | interface Foo{};const foo; | 75 | | - : ^^^ | 76 | | - `---- | | 56 | +'const' declarations must be initialized | 77 | 57 |
| 78 | 58 | SyntaxError: Unexpected identifier 'Foo' | 79 | 59 |
| 80 | 60 | Node.js * | 81 | 61 | [stdin]:1 | 82 | 62 | interface Foo{};const foo; | 83 | 63 | ^^^^^^^^^ | 84 | | - x 'const' declarations must be initialized | 85 | | - ,---- | 86 | | - 1 | interface Foo{};const foo; | 87 | | - : ^^^ | 88 | | - `---- | | 64 | +'const' declarations must be initialized | 89 | 65 |
| 90 | 66 | SyntaxError: Unexpected strict mode reserved word | 91 | 67 |
| 92 | 68 | Node.js * | 93 | 69 | [stdin]:1 | 94 | 70 | function foo(){ await Promise.resolve(1)}; | 95 | 71 | ^^^^^ | 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 | 101 | 73 |
| 102 | 74 | SyntaxError: await is only valid in async functions and the top level bodies of modules | 103 | 75 |
| 104 | 76 | Node.js * | 105 | 77 | [stdin]:1 | 106 | 78 | function foo(){ await Promise.resolve(1)}; | 107 | 79 | ^^^^^ | 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 | 113 | 81 |
| 114 | 82 | SyntaxError: await is only valid in async functions and the top level bodies of modules | 115 | 83 |
| |
|