From 6c40cb2aca89df4c7c0e3923d93024734dd49f2d Mon Sep 17 00:00:00 2001 From: Ruben Bridgewater Date: Mon, 23 Sep 2019 19:38:12 +0200 Subject: repl: use better uncaught exceptions indicator This switches "Thrown:" with "Uncaught" to outline clearer that the thrown error is not caught. PR-URL: https://github.com/nodejs/node/pull/29676 Reviewed-By: Gus Caplan Reviewed-By: James M Snell --- lib/repl.js | 25 ++++++-- test/parallel/test-repl-harmony.js | 2 +- test/parallel/test-repl-null-thrown.js | 2 +- test/parallel/test-repl-pretty-custom-stack.js | 12 ++-- test/parallel/test-repl-pretty-stack.js | 19 +++--- test/parallel/test-repl-top-level-await.js | 10 ++-- .../test-repl-uncaught-exception-standalone.js | 3 +- test/parallel/test-repl-uncaught-exception.js | 25 ++++---- test/parallel/test-repl-underscore.js | 12 ++-- test/parallel/test-repl.js | 67 +++++++++------------- 10 files changed, 90 insertions(+), 87 deletions(-) diff --git a/lib/repl.js b/lib/repl.js index c1473588d3..55daf2f47f 100644 --- a/lib/repl.js +++ b/lib/repl.js @@ -552,11 +552,28 @@ function REPLServer(prompt, }); } else { if (errStack === '') { - errStack = `Thrown: ${self.writer(e)}\n`; - } else { - const ln = errStack.endsWith('\n') ? '' : '\n'; - errStack = `Thrown:\n${errStack}${ln}`; + errStack = self.writer(e); + } + const lines = errStack.split(/(?<=\n)/); + let matched = false; + + errStack = ''; + for (const line of lines) { + if (!matched && /^\[?([A-Z][a-z0-9_]*)*Error/.test(line)) { + errStack += writer.options.breakLength >= line.length ? + `Uncaught ${line}` : + `Uncaught:\n${line}`; + matched = true; + } else { + errStack += line; + } + } + if (!matched) { + const ln = lines.length === 1 ? ' ' : ':\n'; + errStack = `Uncaught${ln}${errStack}`; } + // Normalize line endings. + errStack += errStack.endsWith('\n') ? '' : '\n'; top.outputStream.write(errStack); top.clearBufferedCommand(); top.lines.level = []; diff --git a/test/parallel/test-repl-harmony.js b/test/parallel/test-repl-harmony.js index 0aa8e56bdd..88b2c9deca 100644 --- a/test/parallel/test-repl-harmony.js +++ b/test/parallel/test-repl-harmony.js @@ -30,7 +30,7 @@ const child = spawn(process.execPath, args); const input = '(function(){"use strict"; const y=1;y=2})()\n'; // This message will vary based on JavaScript engine, so don't check the message // contents beyond confirming that the `Error` is a `TypeError`. -const expectOut = /> Thrown:\nTypeError: /; +const expectOut = /> Uncaught TypeError: /; child.stderr.setEncoding('utf8'); child.stderr.on('data', (d) => { diff --git a/test/parallel/test-repl-null-thrown.js b/test/parallel/test-repl-null-thrown.js index 1fe5d30396..0ed4a05fd5 100644 --- a/test/parallel/test-repl-null-thrown.js +++ b/test/parallel/test-repl-null-thrown.js @@ -20,5 +20,5 @@ replserver.emit('line', '.exit'); setTimeout(() => { console.log(text); - assert(text.includes('Thrown: null')); + assert(text.includes('Uncaught null')); }, 0); diff --git a/test/parallel/test-repl-pretty-custom-stack.js b/test/parallel/test-repl-pretty-custom-stack.js index 0d2ada134f..8f633a4d48 100644 --- a/test/parallel/test-repl-pretty-custom-stack.js +++ b/test/parallel/test-repl-pretty-custom-stack.js @@ -48,26 +48,26 @@ const tests = [ { // test .load for a file that throws command: `.load ${fixtures.path('repl-pretty-stack.js')}`, - expected: 'Thrown:\nError: Whoops!--->\nrepl:*:*--->\nd (repl:*:*)' + + expected: 'Uncaught Error: Whoops!--->\nrepl:*:*--->\nd (repl:*:*)' + '--->\nc (repl:*:*)--->\nb (repl:*:*)--->\na (repl:*:*)\n' }, { command: 'let x y;', - expected: 'Thrown:\n' + - 'let x y;\n ^\n\nSyntaxError: Unexpected identifier\n' + expected: 'let x y;\n ^\n\n' + + 'Uncaught SyntaxError: Unexpected identifier\n' }, { command: 'throw new Error(\'Whoops!\')', - expected: 'Thrown:\nError: Whoops!\n' + expected: 'Uncaught Error: Whoops!\n' }, { command: 'foo = bar;', - expected: 'Thrown:\nReferenceError: bar is not defined\n' + expected: 'Uncaught ReferenceError: bar is not defined\n' }, // test anonymous IIFE { command: '(function() { throw new Error(\'Whoops!\'); })()', - expected: 'Thrown:\nError: Whoops!--->\nrepl:*:*\n' + expected: 'Uncaught Error: Whoops!--->\nrepl:*:*\n' } ]; diff --git a/test/parallel/test-repl-pretty-stack.js b/test/parallel/test-repl-pretty-stack.js index 9f307c4f43..456e866b7b 100644 --- a/test/parallel/test-repl-pretty-stack.js +++ b/test/parallel/test-repl-pretty-stack.js @@ -7,7 +7,7 @@ const repl = require('repl'); const stackRegExp = /(at .*repl:)[0-9]+:[0-9]+/g; -function run({ command, expected, ...extraREPLOptions }) { +function run({ command, expected, ...extraREPLOptions }, i) { let accum = ''; const inputStream = new ArrayStream(); @@ -25,6 +25,7 @@ function run({ command, expected, ...extraREPLOptions }) { }); r.write(`${command}\n`); + console.log(i); assert.strictEqual( accum.replace(stackRegExp, '$1*:*'), expected.replace(stackRegExp, '$1*:*') @@ -36,39 +37,39 @@ const tests = [ { // Test .load for a file that throws. command: `.load ${fixtures.path('repl-pretty-stack.js')}`, - expected: 'Thrown:\nError: Whoops!\n at repl:*:*\n' + + expected: 'Uncaught Error: Whoops!\n at repl:*:*\n' + ' at d (repl:*:*)\n at c (repl:*:*)\n' + ' at b (repl:*:*)\n at a (repl:*:*)\n' }, { command: 'let x y;', - expected: 'Thrown:\n' + - 'let x y;\n ^\n\nSyntaxError: Unexpected identifier\n' + expected: 'let x y;\n ^\n\n' + + 'Uncaught SyntaxError: Unexpected identifier\n' }, { command: 'throw new Error(\'Whoops!\')', - expected: 'Thrown:\nError: Whoops!\n' + expected: 'Uncaught Error: Whoops!\n' }, { command: '(() => { const err = Error(\'Whoops!\'); ' + 'err.foo = \'bar\'; throw err; })()', - expected: "Thrown:\nError: Whoops!\n at repl:*:* {\n foo: 'bar'\n}\n", + expected: "Uncaught Error: Whoops!\n at repl:*:* {\n foo: 'bar'\n}\n", }, { command: '(() => { const err = Error(\'Whoops!\'); ' + 'err.foo = \'bar\'; throw err; })()', - expected: 'Thrown:\nError: Whoops!\n at repl:*:* {\n foo: ' + + expected: 'Uncaught Error: Whoops!\n at repl:*:* {\n foo: ' + "\u001b[32m'bar'\u001b[39m\n}\n", useColors: true }, { command: 'foo = bar;', - expected: 'Thrown:\nReferenceError: bar is not defined\n' + expected: 'Uncaught ReferenceError: bar is not defined\n' }, // Test anonymous IIFE. { command: '(function() { throw new Error(\'Whoops!\'); })()', - expected: 'Thrown:\nError: Whoops!\n at repl:*:*\n' + expected: 'Uncaught Error: Whoops!\n at repl:*:*\n' } ]; diff --git a/test/parallel/test-repl-top-level-await.js b/test/parallel/test-repl-top-level-await.js index f629d3f1d0..5c8aa95d40 100644 --- a/test/parallel/test-repl-top-level-await.js +++ b/test/parallel/test-repl-top-level-await.js @@ -118,15 +118,15 @@ async function ordinaryTests() { [ 'if (await true) { function bar() {}; }', 'undefined' ], [ 'bar', '[Function: bar]' ], [ 'if (await true) { class Bar {}; }', 'undefined' ], - [ 'Bar', 'ReferenceError: Bar is not defined', { line: 1 } ], + [ 'Bar', 'Uncaught ReferenceError: Bar is not defined' ], [ 'await 0; function* gen(){}', 'undefined' ], [ 'for (var i = 0; i < 10; ++i) { await i; }', 'undefined' ], [ 'i', '10' ], [ 'for (let j = 0; j < 5; ++j) { await j; }', 'undefined' ], - [ 'j', 'ReferenceError: j is not defined', { line: 1 } ], + [ 'j', 'Uncaught ReferenceError: j is not defined' ], [ 'gen', '[GeneratorFunction: gen]' ], - [ 'return 42; await 5;', 'SyntaxError: Illegal return statement', - { line: 4 } ], + [ 'return 42; await 5;', 'Uncaught SyntaxError: Illegal return statement', + { line: 3 } ], [ 'let o = await 1, p', 'undefined' ], [ 'p', 'undefined' ], [ 'let q = 1, s = await 2', 'undefined' ], @@ -160,7 +160,7 @@ async function ctrlCTest() { { ctrl: true, name: 'c' } ]), [ 'await timeout(100000)\r', - 'Thrown:', + 'Uncaught:', '[Error [ERR_SCRIPT_EXECUTION_INTERRUPTED]: ' + 'Script execution was interrupted by `SIGINT`] {', " code: 'ERR_SCRIPT_EXECUTION_INTERRUPTED'", diff --git a/test/parallel/test-repl-uncaught-exception-standalone.js b/test/parallel/test-repl-uncaught-exception-standalone.js index ae7b214fef..e7371f9f7c 100644 --- a/test/parallel/test-repl-uncaught-exception-standalone.js +++ b/test/parallel/test-repl-uncaught-exception-standalone.js @@ -18,8 +18,7 @@ child.on('exit', common.mustCall(() => { [ 'Type ".help" for more information.', // x\n - '> Thrown:', - 'ReferenceError: x is not defined', + '> Uncaught ReferenceError: x is not defined', // Added `uncaughtException` listener. '> short', 'undefined', diff --git a/test/parallel/test-repl-uncaught-exception.js b/test/parallel/test-repl-uncaught-exception.js index 447b69b1d4..28e62dff22 100644 --- a/test/parallel/test-repl-uncaught-exception.js +++ b/test/parallel/test-repl-uncaught-exception.js @@ -6,7 +6,7 @@ const repl = require('repl'); let count = 0; -function run({ command, expected }) { +function run({ command, expected, useColors = false }) { let accum = ''; const output = new ArrayStream(); @@ -17,7 +17,7 @@ function run({ command, expected }) { input: new ArrayStream(), output, terminal: false, - useColors: false + useColors }); r.write(`${command}\n`); @@ -30,35 +30,40 @@ function run({ command, expected }) { // Verify that the repl is still working as expected. accum = ''; r.write('1 + 1\n'); - assert.strictEqual(accum, '2\n'); + // eslint-disable-next-line no-control-regex + assert.strictEqual(accum.replace(/\u001b\[[0-9]+m/g, ''), '2\n'); r.close(); count++; } const tests = [ { + useColors: true, command: 'x', - expected: 'Thrown:\n' + - 'ReferenceError: x is not defined\n' + expected: 'Uncaught ReferenceError: x is not defined\n' + }, + { + useColors: true, + command: 'throw { foo: "test" }', + expected: "Uncaught { foo: \x1B[32m'test'\x1B[39m }\n" }, { command: 'process.on("uncaughtException", () => console.log("Foobar"));\n', - expected: /^Thrown:\nTypeError \[ERR_INVALID_REPL_INPUT]: Listeners for `/ + expected: /^Uncaught:\nTypeError \[ERR_INVALID_REPL_INPUT]: Listeners for `/ }, { command: 'x;\n', - expected: 'Thrown:\n' + - 'ReferenceError: x is not defined\n' + expected: 'Uncaught ReferenceError: x is not defined\n' }, { command: 'process.on("uncaughtException", () => console.log("Foobar"));' + 'console.log("Baz");\n', - expected: /^Thrown:\nTypeError \[ERR_INVALID_REPL_INPUT]: Listeners for `/ + expected: /^Uncaught:\nTypeError \[ERR_INVALID_REPL_INPUT]: Listeners for `/ }, { command: 'console.log("Baz");' + 'process.on("uncaughtException", () => console.log("Foobar"));\n', - expected: /^Baz\nThrown:\nTypeError \[ERR_INVALID_REPL_INPUT]:.*uncaughtException/ + expected: /^Baz\nUncaught:\nTypeError \[ERR_INVALID_REPL_INPUT]:.*uncaughtException/ } ]; diff --git a/test/parallel/test-repl-underscore.js b/test/parallel/test-repl-underscore.js index 2e0865f5a7..c05c387471 100644 --- a/test/parallel/test-repl-underscore.js +++ b/test/parallel/test-repl-underscore.js @@ -173,13 +173,11 @@ function testError() { 'undefined', // The error, both from the original throw and the `_error` echo. - 'Thrown:', - 'Error: foo', + 'Uncaught Error: foo', '[Error: foo]', // The sync error, with individual property echoes - 'Thrown:', - /^Error: ENOENT: no such file or directory, scandir '.*nonexistent\?'/, + /^Uncaught Error: ENOENT: no such file or directory, scandir '.*nonexistent\?'/, /Object\.readdirSync/, /^ errno: -(2|4058),$/, " syscall: 'scandir',", @@ -194,8 +192,7 @@ function testError() { 'undefined', // The message from the original throw - 'Thrown:', - 'Error: baz', + 'Uncaught Error: baz', ]; for (const line of lines) { const expected = expectedLines.shift(); @@ -218,8 +215,7 @@ function testError() { "'baz'", 'Expression assignment to _error now disabled.', '0', - 'Thrown:', - 'Error: quux', + 'Uncaught Error: quux', '0' ]); }); diff --git a/test/parallel/test-repl.js b/test/parallel/test-repl.js index 05608bd5ee..b0dad397cc 100644 --- a/test/parallel/test-repl.js +++ b/test/parallel/test-repl.js @@ -126,7 +126,7 @@ const unixTests = [ const strictModeTests = [ { send: 'ref = 1', - expect: ['Thrown:', /^ReferenceError:\s/] + expect: [/^Uncaught ReferenceError:\s/] } ]; @@ -134,11 +134,11 @@ const errorTests = [ // Uncaught error throws and prints out { send: 'throw new Error(\'test error\');', - expect: ['Thrown:', 'Error: test error'] + expect: ['Uncaught Error: test error'] }, { send: "throw { foo: 'bar' };", - expect: "Thrown: { foo: 'bar' }" + expect: "Uncaught { foo: 'bar' }" }, // Common syntax error is treated as multiline command { @@ -153,7 +153,7 @@ const errorTests = [ // But passing the same string to eval() should throw { send: 'eval("function test_func() {")', - expect: ['Thrown:', /^SyntaxError: /] + expect: [/^Uncaught SyntaxError: /] }, // Can handle multiline template literals { @@ -210,89 +210,84 @@ const errorTests = [ // should throw { send: 'JSON.parse(\'{invalid: \\\'json\\\'}\');', - expect: ['Thrown:', /^SyntaxError: /] + expect: [/^Uncaught SyntaxError: /] }, // End of input to JSON.parse error is special case of syntax error, // should throw { send: 'JSON.parse(\'066\');', - expect: ['Thrown:', /^SyntaxError: /] + expect: [/^Uncaught SyntaxError: /] }, // should throw { send: 'JSON.parse(\'{\');', - expect: ['Thrown:', /^SyntaxError: /] + expect: [/^Uncaught SyntaxError: /] }, // invalid RegExps are a special case of syntax error, // should throw { send: '/(/;', - expect: ['Thrown:', /^SyntaxError: /] + expect: [/^Uncaught SyntaxError: /] }, // invalid RegExp modifiers are a special case of syntax error, // should throw (GH-4012) { send: 'new RegExp("foo", "wrong modifier");', - expect: ['Thrown:', /^SyntaxError: /] + expect: [/^Uncaught SyntaxError: /] }, // Strict mode syntax errors should be caught (GH-5178) { send: '(function() { "use strict"; return 0755; })()', expect: [ - 'Thrown:', kSource, kArrow, '', - /^SyntaxError: / + /^Uncaught SyntaxError: / ] }, { send: '(function(a, a, b) { "use strict"; return a + b + c; })()', expect: [ - 'Thrown:', kSource, kArrow, '', - /^SyntaxError: / + /^Uncaught SyntaxError: / ] }, { send: '(function() { "use strict"; with (this) {} })()', expect: [ - 'Thrown:', kSource, kArrow, '', - /^SyntaxError: / + /^Uncaught SyntaxError: / ] }, { send: '(function() { "use strict"; var x; delete x; })()', expect: [ - 'Thrown:', kSource, kArrow, '', - /^SyntaxError: / + /^Uncaught SyntaxError: / ] }, { send: '(function() { "use strict"; eval = 17; })()', expect: [ - 'Thrown:', kSource, kArrow, '', - /^SyntaxError: / + /^Uncaught SyntaxError: / ] }, { send: '(function() { "use strict"; if (true) function f() { } })()', expect: [ - 'Thrown:', kSource, kArrow, '', + 'Uncaught:', /^SyntaxError: / ] }, @@ -413,11 +408,10 @@ const errorTests = [ { send: '[] \\', expect: [ - 'Thrown:', kSource, kArrow, '', - /^SyntaxError: / + /^Uncaught SyntaxError: / ] }, // Do not fail when a String is created with line continuation @@ -548,8 +542,7 @@ const errorTests = [ { send: 'require("internal/repl")', expect: [ - 'Thrown:', - /^Error: Cannot find module 'internal\/repl'/, + /^Uncaught Error: Cannot find module 'internal\/repl'/, /^Require stack:/, /^- /, /^ at .*/, @@ -587,11 +580,10 @@ const errorTests = [ { send: 'a = 3.5e', expect: [ - 'Thrown:', kSource, kArrow, '', - /^SyntaxError: / + /^Uncaught SyntaxError: / ] }, // Mitigate https://github.com/nodejs/node/issues/548 @@ -607,22 +599,20 @@ const errorTests = [ { send: 'a = 3.5e', expect: [ - 'Thrown:', kSource, kArrow, '', - /^SyntaxError: / + /^Uncaught SyntaxError: / ] }, // Avoid emitting stack trace { send: 'a = 3.5e', expect: [ - 'Thrown:', kSource, kArrow, '', - /^SyntaxError: / + /^Uncaught SyntaxError: / ] }, @@ -687,11 +677,10 @@ const errorTests = [ { send: '...[]', expect: [ - 'Thrown:', kSource, kArrow, '', - /^SyntaxError: / + /^Uncaught SyntaxError: / ] }, // Bring back the repl to prompt @@ -702,31 +691,28 @@ const errorTests = [ { send: 'console.log("Missing comma in arg list" process.version)', expect: [ - 'Thrown:', kSource, kArrow, '', - /^SyntaxError: / + /^Uncaught SyntaxError: / ] }, { send: 'x = {\nfield\n{', expect: [ - '... ... Thrown:', - '{', + '... ... {', kArrow, '', - /^SyntaxError: / + /^Uncaught SyntaxError: / ] }, { send: '(2 + 3))', expect: [ - 'Thrown:', kSource, kArrow, '', - /^SyntaxError: / + /^Uncaught SyntaxError: / ] }, { @@ -740,11 +726,10 @@ const errorTests = [ { send: '} else {', expect: [ - 'Thrown:', kSource, kArrow, '', - /^SyntaxError: / + /^Uncaught SyntaxError: / ] }, ]; -- cgit v1.2.3