'use strict'; const common = require('../common'); const ArrayStream = require('../common/arraystream'); const assert = require('assert'); const events = require('events'); const { stripVTControlCharacters } = require('internal/util/inspect'); const repl = require('repl'); common.skipIfInspectorDisabled(); // Flags: --expose-internals 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', this.lines[this.lines.length - 1]); if (callback) callback(); return true; } async wait() { if (this.waitingForResponse) { throw new Error('Currently waiting for response to another command'); } this.lines = ['']; for await (const [line] of events.on(this, 'line')) { if (line.includes(PROMPT)) { return this.lines; } } } } const putIn = new REPLStream(); const testMe = repl.start({ prompt: PROMPT, stream: putIn, terminal: true, useColors: true, breakEvalOnSigint: true }); function runAndWait(cmds) { const promise = putIn.wait(); 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 { aa, bb } = await Promise.resolve({ aa: 1, bb: 2 }), f = 5;'], ['aa', '1'], ['bb', '2'], ['f', '5'], ['let cc = await Promise.resolve(2)'], ['cc', '2'], ['let dd;'], ['dd'], ['let [ii, { abc: { kk } }] = [0, { abc: { kk: 1 } }];'], ['ii', '0'], ['kk', '1'], ['var ll = await Promise.resolve(2);'], ['ll', '2'], ['foo(await koo())', '4'], ['_', '4'], ['const m = foo(await koo());'], ['m', '4'], ['const n = foo(await\nkoo());', ['const n = foo(await\r', '... koo());\r', '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'], ['for (let i = 0; i < 2; ++i) { await i }'], ['await 0', '0'], ['await 0; function foo() {}'], ['foo', '[Function: foo]'], ['class Foo {}; await 1;', '1'], ['Foo', '[class Foo]'], ['if (await true) { function bar() {}; }'], ['bar', '[Function: bar]'], ['if (await true) { class Bar {}; }'], ['Bar', 'Uncaught ReferenceError: Bar is not defined'], ['await 0; function* gen(){}'], ['for (var i = 0; i < 10; ++i) { await i; }'], ['i', '10'], ['for (let j = 0; j < 5; ++j) { await j; }'], ['j', 'Uncaught ReferenceError: j is not defined', { line: 0 }], ['gen', '[GeneratorFunction: gen]'], ['return 42; await 5;', 'Uncaught SyntaxError: Illegal return statement', { line: 3 }], ['let o = await 1, p'], ['p'], ['let q = 1, s = await 2'], ['s', '2'], ['for await (let i of [1,2,3]) console.log(i)', [ 'for await (let i of [1,2,3]) console.log(i)\r', '1', '2', '3', 'undefined', ], ], ['await Promise..resolve()', [ 'await Promise..resolve()\r', 'Uncaught SyntaxError: ', 'await Promise..resolve()', ' ^', '', 'Unexpected token \'.\'', ], ], ['for (const x of [1,2,3]) {\nawait x\n}', [ 'for (const x of [1,2,3]) {\r', '... await x\r', '... }\r', 'undefined', ]], ['for (const x of [1,2,3]) {\nawait x;\n}', [ 'for (const x of [1,2,3]) {\r', '... await x;\r', '... }\r', 'undefined', ]], ['for await (const x of [1,2,3]) {\nconsole.log(x)\n}', [ 'for await (const x of [1,2,3]) {\r', '... console.log(x)\r', '... }\r', '1', '2', '3', 'undefined', ]], ['for await (const x of [1,2,3]) {\nconsole.log(x);\n}', [ 'for await (const x of [1,2,3]) {\r', '... console.log(x);\r', '... }\r', '1', '2', '3', 'undefined', ]], // Regression test for https://github.com/nodejs/node/issues/43777. ['await Promise.resolve(123), Promise.resolve(456)', 'Promise {', { line: 0 }], ['await Promise.resolve(123), await Promise.resolve(456)', '456'], ['await (Promise.resolve(123), Promise.resolve(456))', '456'], ]; for (const [input, expected = [`${input}\r`], options = {}] of testCases) { console.log(`Testing ${input}`); const toBeRun = input.split('\n'); const lines = await runAndWait(toBeRun); if (Array.isArray(expected)) { if (expected.length === 1) expected.push('undefined'); if (lines[0] === input) lines.shift(); assert.deepStrictEqual(lines, [...expected, PROMPT]); } else 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() { console.log('Testing Ctrl+C'); const output = await runAndWait([ 'await new Promise(() => {})', { ctrl: true, name: 'c' }, ]); assert.deepStrictEqual(output.slice(0, 3), [ 'await new Promise(() => {})\r', 'Uncaught:', 'Error [ERR_SCRIPT_EXECUTION_INTERRUPTED]: ' + 'Script execution was interrupted by `SIGINT`', ]); assert.deepStrictEqual(output.slice(-2), [ '}', PROMPT, ]); } async function main() { await ordinaryTests(); await ctrlCTest(); } main().then(common.mustCall());