Skip to content

Commit

Permalink
repl: use better uncaught exceptions indicator
Browse files Browse the repository at this point in the history
This switches "Thrown:" with "Uncaught" to outline clearer that the
thrown error is not caught.

PR-URL: #29676
Reviewed-By: Gus Caplan <me@gus.host>
Reviewed-By: James M Snell <jasnell@gmail.com>
  • Loading branch information
BridgeAR authored and targos committed Jan 14, 2020
1 parent e5e60f5 commit d4b41f6
Show file tree
Hide file tree
Showing 10 changed files with 90 additions and 87 deletions.
25 changes: 21 additions & 4 deletions lib/repl.js
Expand Up @@ -567,11 +567,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 = [];
Expand Down
2 changes: 1 addition & 1 deletion test/parallel/test-repl-harmony.js
Expand Up @@ -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) => {
Expand Down
2 changes: 1 addition & 1 deletion test/parallel/test-repl-null-thrown.js
Expand Up @@ -20,5 +20,5 @@ replserver.emit('line', '.exit');

setTimeout(() => {
console.log(text);
assert(text.includes('Thrown: null'));
assert(text.includes('Uncaught null'));
}, 0);
12 changes: 6 additions & 6 deletions test/parallel/test-repl-pretty-custom-stack.js
Expand Up @@ -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'
}
];

Expand Down
19 changes: 10 additions & 9 deletions test/parallel/test-repl-pretty-stack.js
Expand Up @@ -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();
Expand All @@ -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*:*')
Expand All @@ -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'
}
];

Expand Down
10 changes: 5 additions & 5 deletions test/parallel/test-repl-top-level-await.js
Expand Up @@ -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' ],
Expand Down Expand Up @@ -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'",
Expand Down
3 changes: 1 addition & 2 deletions test/parallel/test-repl-uncaught-exception-standalone.js
Expand Up @@ -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',
Expand Down
25 changes: 15 additions & 10 deletions test/parallel/test-repl-uncaught-exception.js
Expand Up @@ -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();
Expand All @@ -17,7 +17,7 @@ function run({ command, expected }) {
input: new ArrayStream(),
output,
terminal: false,
useColors: false
useColors
});

r.write(`${command}\n`);
Expand All @@ -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/
}
];

Expand Down
12 changes: 4 additions & 8 deletions test/parallel/test-repl-underscore.js
Expand Up @@ -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',",
Expand All @@ -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();
Expand All @@ -218,8 +215,7 @@ function testError() {
"'baz'",
'Expression assignment to _error now disabled.',
'0',
'Thrown:',
'Error: quux',
'Uncaught Error: quux',
'0'
]);
});
Expand Down

0 comments on commit d4b41f6

Please sign in to comment.