This improves Node.js errors by always showing the attached properties when inspecting such an error. This applies especially to SystemError. It did often not show any properties but now all properties will be visible. This is done in a mainly backwards compatible way. Instead of using prototype getters and setters, the property is now set directly on the error. PR-URL: https://github.com/nodejs/node/pull/29677 Reviewed-By: Matteo Collina <matteo.collina@gmail.com> Reviewed-By: Benjamin Gruenbaum <benjamingr@gmail.com>
178 lines
5.3 KiB
JavaScript
178 lines
5.3 KiB
JavaScript
'use strict';
|
|
|
|
require('../common');
|
|
const ArrayStream = require('../common/arraystream');
|
|
const assert = require('assert');
|
|
const { stripVTControlCharacters } = require('internal/readline/utils');
|
|
const repl = require('repl');
|
|
|
|
// Flags: --expose-internals --experimental-repl-await
|
|
|
|
const PROMPT = 'await repl > ';
|
|
|
|
class REPLStream extends ArrayStream {
|
|
constructor() {
|
|
super();
|
|
this.waitingForResponse = false;
|
|
this.lines = [''];
|
|
}
|
|
write(chunk, encoding, callback) {
|
|
if (Buffer.isBuffer(chunk)) {
|
|
chunk = chunk.toString(encoding);
|
|
}
|
|
const chunkLines = stripVTControlCharacters(chunk).split('\n');
|
|
this.lines[this.lines.length - 1] += chunkLines[0];
|
|
if (chunkLines.length > 1) {
|
|
this.lines.push(...chunkLines.slice(1));
|
|
}
|
|
this.emit('line');
|
|
if (callback) callback();
|
|
return true;
|
|
}
|
|
|
|
wait(lookFor = PROMPT) {
|
|
if (this.waitingForResponse) {
|
|
throw new Error('Currently waiting for response to another command');
|
|
}
|
|
this.lines = [''];
|
|
return new Promise((resolve, reject) => {
|
|
const onError = (err) => {
|
|
this.removeListener('line', onLine);
|
|
reject(err);
|
|
};
|
|
const onLine = () => {
|
|
if (this.lines[this.lines.length - 1].includes(lookFor)) {
|
|
this.removeListener('error', onError);
|
|
this.removeListener('line', onLine);
|
|
resolve(this.lines);
|
|
}
|
|
};
|
|
this.once('error', onError);
|
|
this.on('line', onLine);
|
|
});
|
|
}
|
|
}
|
|
|
|
const putIn = new REPLStream();
|
|
const testMe = repl.start({
|
|
prompt: PROMPT,
|
|
stream: putIn,
|
|
terminal: true,
|
|
useColors: false,
|
|
breakEvalOnSigint: true
|
|
});
|
|
|
|
function runAndWait(cmds, lookFor) {
|
|
const promise = putIn.wait(lookFor);
|
|
for (const cmd of cmds) {
|
|
if (typeof cmd === 'string') {
|
|
putIn.run([cmd]);
|
|
} else {
|
|
testMe.write('', cmd);
|
|
}
|
|
}
|
|
return promise;
|
|
}
|
|
|
|
async function ordinaryTests() {
|
|
// These tests were created based on
|
|
// https://cs.chromium.org/chromium/src/third_party/WebKit/LayoutTests/http/tests/devtools/console/console-top-level-await.js?rcl=5d0ea979f0ba87655b7ef0e03b58fa3c04986ba6
|
|
putIn.run([
|
|
'function foo(x) { return x; }',
|
|
'function koo() { return Promise.resolve(4); }'
|
|
]);
|
|
const testCases = [
|
|
[ 'await Promise.resolve(0)', '0' ],
|
|
[ '{ a: await Promise.resolve(1) }', '{ a: 1 }' ],
|
|
[ '_', '{ a: 1 }' ],
|
|
[ 'let { a, b } = await Promise.resolve({ a: 1, b: 2 }), f = 5;',
|
|
'undefined' ],
|
|
[ 'a', '1' ],
|
|
[ 'b', '2' ],
|
|
[ 'f', '5' ],
|
|
[ 'let c = await Promise.resolve(2)', 'undefined' ],
|
|
[ 'c', '2' ],
|
|
[ 'let d;', 'undefined' ],
|
|
[ 'd', 'undefined' ],
|
|
[ 'let [i, { abc: { k } }] = [0, { abc: { k: 1 } }];', 'undefined' ],
|
|
[ 'i', '0' ],
|
|
[ 'k', '1' ],
|
|
[ 'var l = await Promise.resolve(2);', 'undefined' ],
|
|
[ 'l', '2' ],
|
|
[ 'foo(await koo())', '4' ],
|
|
[ '_', '4' ],
|
|
[ 'const m = foo(await koo());', 'undefined' ],
|
|
[ 'm', '4' ],
|
|
[ 'const n = foo(await\nkoo());', 'undefined' ],
|
|
[ 'n', '4' ],
|
|
// eslint-disable-next-line no-template-curly-in-string
|
|
[ '`status: ${(await Promise.resolve({ status: 200 })).status}`',
|
|
"'status: 200'"],
|
|
[ 'for (let i = 0; i < 2; ++i) await i', 'undefined' ],
|
|
[ 'for (let i = 0; i < 2; ++i) { await i }', 'undefined' ],
|
|
[ 'await 0', '0' ],
|
|
[ 'await 0; function foo() {}', 'undefined' ],
|
|
[ 'foo', '[Function: foo]' ],
|
|
[ 'class Foo {}; await 1;', '1' ],
|
|
[ 'Foo', '[Function: Foo]' ],
|
|
[ 'if (await true) { function bar() {}; }', 'undefined' ],
|
|
[ 'bar', '[Function: bar]' ],
|
|
[ 'if (await true) { class Bar {}; }', 'undefined' ],
|
|
[ 'Bar', 'ReferenceError: Bar is not defined', { line: 1 } ],
|
|
[ '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 } ],
|
|
[ 'gen', '[GeneratorFunction: gen]' ],
|
|
[ 'return 42; await 5;', 'SyntaxError: Illegal return statement',
|
|
{ line: 4 } ],
|
|
[ 'let o = await 1, p', 'undefined' ],
|
|
[ 'p', 'undefined' ],
|
|
[ 'let q = 1, s = await 2', 'undefined' ],
|
|
[ 's', '2' ],
|
|
[ 'for await (let i of [1,2,3]) console.log(i)', 'undefined', { line: 3 } ]
|
|
];
|
|
|
|
for (const [input, expected, options = {}] of testCases) {
|
|
console.log(`Testing ${input}`);
|
|
const toBeRun = input.split('\n');
|
|
const lines = await runAndWait(toBeRun);
|
|
if ('line' in options) {
|
|
assert.strictEqual(lines[toBeRun.length + options.line], expected);
|
|
} else {
|
|
const echoed = toBeRun.map((a, i) => `${i > 0 ? '... ' : ''}${a}\r`);
|
|
assert.deepStrictEqual(lines, [...echoed, expected, PROMPT]);
|
|
}
|
|
}
|
|
}
|
|
|
|
async function ctrlCTest() {
|
|
putIn.run([
|
|
`const timeout = (msecs) => new Promise((resolve) => {
|
|
setTimeout(resolve, msecs).unref();
|
|
});`
|
|
]);
|
|
|
|
console.log('Testing Ctrl+C');
|
|
assert.deepStrictEqual(await runAndWait([
|
|
'await timeout(100000)',
|
|
{ ctrl: true, name: 'c' }
|
|
]), [
|
|
'await timeout(100000)\r',
|
|
'Thrown:',
|
|
'[Error [ERR_SCRIPT_EXECUTION_INTERRUPTED]: ' +
|
|
'Script execution was interrupted by `SIGINT`] {',
|
|
" code: 'ERR_SCRIPT_EXECUTION_INTERRUPTED'",
|
|
'}',
|
|
PROMPT
|
|
]);
|
|
}
|
|
|
|
async function main() {
|
|
await ordinaryTests();
|
|
await ctrlCTest();
|
|
}
|
|
|
|
main();
|