summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--doc/api/repl.md5
-rw-r--r--lib/internal/repl/utils.js168
-rw-r--r--lib/repl.js18
-rw-r--r--test/parallel/test-repl-history-navigation.js28
-rw-r--r--test/parallel/test-repl-multiline.js62
-rw-r--r--test/parallel/test-repl-preview.js131
-rw-r--r--test/parallel/test-repl-top-level-await.js101
7 files changed, 442 insertions, 71 deletions
diff --git a/doc/api/repl.md b/doc/api/repl.md
index 967336710c..21f5193c12 100644
--- a/doc/api/repl.md
+++ b/doc/api/repl.md
@@ -510,6 +510,9 @@ with REPL instances programmatically.
<!-- YAML
added: v0.1.91
changes:
+ - version: REPLACEME
+ pr-url: https://github.com/nodejs/node/pull/30811
+ description: The `preview` option is now available.
- version: v12.0.0
pr-url: https://github.com/nodejs/node/pull/26518
description: The `terminal` option now follows the default description in
@@ -562,6 +565,8 @@ changes:
* `breakEvalOnSigint` {boolean} Stop evaluating the current piece of code when
`SIGINT` is received, such as when `Ctrl+C` is pressed. This cannot be used
together with a custom `eval` function. **Default:** `false`.
+ * `preview` {boolean} Defines if the repl prints output previews or not.
+ **Default:** `true`. Always `false` in case `terminal` is falsy.
* Returns: {repl.REPLServer}
The `repl.start()` method creates and starts a [`repl.REPLServer`][] instance.
diff --git a/lib/internal/repl/utils.js b/lib/internal/repl/utils.js
index 6c011e8533..c4280c1d1f 100644
--- a/lib/internal/repl/utils.js
+++ b/lib/internal/repl/utils.js
@@ -1,10 +1,12 @@
'use strict';
const {
+ MathMin,
Symbol,
} = primordials;
-const acorn = require('internal/deps/acorn/acorn/dist/acorn');
+const { tokTypes: tt, Parser: AcornParser } =
+ require('internal/deps/acorn/acorn/dist/acorn');
const privateMethods =
require('internal/deps/acorn-plugins/acorn-private-methods/index');
const classFields =
@@ -13,7 +15,30 @@ const numericSeparator =
require('internal/deps/acorn-plugins/acorn-numeric-separator/index');
const staticClassFeatures =
require('internal/deps/acorn-plugins/acorn-static-class-features/index');
-const { tokTypes: tt, Parser: AcornParser } = acorn;
+
+const { sendInspectorCommand } = require('internal/util/inspector');
+
+const {
+ ERR_INSPECTOR_NOT_AVAILABLE
+} = require('internal/errors').codes;
+
+const {
+ clearLine,
+ cursorTo,
+ moveCursor,
+} = require('readline');
+
+const { inspect } = require('util');
+
+const debug = require('internal/util/debuglog').debuglog('repl');
+
+const inspectOptions = {
+ depth: 1,
+ colors: false,
+ compact: true,
+ breakLength: Infinity
+};
+const inspectedOptions = inspect(inspectOptions, { colors: false });
// If the error is that we've unexpectedly ended the input,
// then let the user try to recover by adding more input.
@@ -91,7 +116,144 @@ function isRecoverableError(e, code) {
}
}
+function setupPreview(repl, contextSymbol, bufferSymbol, active) {
+ // Simple terminals can't handle previews.
+ if (process.env.TERM === 'dumb' || !active) {
+ return { showInputPreview() {}, clearPreview() {} };
+ }
+
+ let preview = null;
+ let lastPreview = '';
+
+ const clearPreview = () => {
+ if (preview !== null) {
+ moveCursor(repl.output, 0, 1);
+ clearLine(repl.output);
+ moveCursor(repl.output, 0, -1);
+ lastPreview = preview;
+ preview = null;
+ }
+ };
+
+ // This returns a code preview for arbitrary input code.
+ function getPreviewInput(input, callback) {
+ // For similar reasons as `defaultEval`, wrap expressions starting with a
+ // curly brace with parenthesis.
+ if (input.startsWith('{') && !input.endsWith(';')) {
+ input = `(${input})`;
+ }
+ sendInspectorCommand((session) => {
+ session.post('Runtime.evaluate', {
+ expression: input,
+ throwOnSideEffect: true,
+ timeout: 333,
+ contextId: repl[contextSymbol],
+ }, (error, preview) => {
+ if (error) {
+ callback(error);
+ return;
+ }
+ const { result } = preview;
+ if (result.value !== undefined) {
+ callback(null, inspect(result.value, inspectOptions));
+ // Ignore EvalErrors, SyntaxErrors and ReferenceErrors. It is not clear
+ // where they came from and if they are recoverable or not. Other errors
+ // may be inspected.
+ } else if (preview.exceptionDetails &&
+ (result.className === 'EvalError' ||
+ result.className === 'SyntaxError' ||
+ result.className === 'ReferenceError')) {
+ callback(null, null);
+ } else if (result.objectId) {
+ session.post('Runtime.callFunctionOn', {
+ functionDeclaration: `(v) => util.inspect(v, ${inspectedOptions})`,
+ objectId: result.objectId,
+ arguments: [result]
+ }, (error, preview) => {
+ if (error) {
+ callback(error);
+ } else {
+ callback(null, preview.result.value);
+ }
+ });
+ } else {
+ // Either not serializable or undefined.
+ callback(null, result.unserializableValue || result.type);
+ }
+ });
+ }, () => callback(new ERR_INSPECTOR_NOT_AVAILABLE()));
+ }
+
+ const showInputPreview = () => {
+ // Prevent duplicated previews after a refresh.
+ if (preview !== null) {
+ return;
+ }
+
+ const line = repl.line.trim();
+
+ // Do not preview if the command is buffered or if the line is empty.
+ if (repl[bufferSymbol] || line === '') {
+ return;
+ }
+
+ getPreviewInput(line, (error, inspected) => {
+ // Ignore the output if the value is identical to the current line and the
+ // former preview is not identical to this preview.
+ if ((line === inspected && lastPreview !== inspected) ||
+ inspected === null) {
+ return;
+ }
+ if (error) {
+ debug('Error while generating preview', error);
+ return;
+ }
+ // Do not preview `undefined` if colors are deactivated or explicitly
+ // requested.
+ if (inspected === 'undefined' &&
+ (!repl.useColors || repl.ignoreUndefined)) {
+ return;
+ }
+
+ preview = inspected;
+
+ // Limit the output to maximum 250 characters. Otherwise it becomes a)
+ // difficult to read and b) non terminal REPLs would visualize the whole
+ // output.
+ const maxColumns = MathMin(repl.columns, 250);
+
+ if (inspected.length > maxColumns) {
+ inspected = `${inspected.slice(0, maxColumns - 6)}...`;
+ }
+ const lineBreakPos = inspected.indexOf('\n');
+ if (lineBreakPos !== -1) {
+ inspected = `${inspected.slice(0, lineBreakPos)}`;
+ }
+ const result = repl.useColors ?
+ `\u001b[90m${inspected}\u001b[39m` :
+ `// ${inspected}`;
+
+ repl.output.write(`\n${result}`);
+ moveCursor(repl.output, 0, -1);
+ cursorTo(repl.output, repl.cursor + repl._prompt.length);
+ });
+ };
+
+ // Refresh prints the whole screen again and the preview will be removed
+ // during that procedure. Print the preview again. This also makes sure
+ // the preview is always correct after resizing the terminal window.
+ const tmpRefresh = repl._refreshLine.bind(repl);
+ repl._refreshLine = () => {
+ preview = null;
+ tmpRefresh();
+ showInputPreview();
+ };
+
+ return { showInputPreview, clearPreview };
+}
+
module.exports = {
isRecoverableError,
- kStandaloneREPL: Symbol('kStandaloneREPL')
+ kStandaloneREPL: Symbol('kStandaloneREPL'),
+ setupPreview
};
diff --git a/lib/repl.js b/lib/repl.js
index b6876bb8c6..98b0d2415d 100644
--- a/lib/repl.js
+++ b/lib/repl.js
@@ -98,7 +98,8 @@ const experimentalREPLAwait = require('internal/options').getOptionValue(
);
const {
isRecoverableError,
- kStandaloneREPL
+ kStandaloneREPL,
+ setupPreview,
} = require('internal/repl/utils');
const {
getOwnNonIndexProperties,
@@ -204,6 +205,9 @@ function REPLServer(prompt,
}
}
+ const preview = options.terminal &&
+ (options.preview !== undefined ? !!options.preview : true);
+
this.inputStream = options.input;
this.outputStream = options.output;
this.useColors = !!options.useColors;
@@ -804,9 +808,20 @@ function REPLServer(prompt,
}
});
+ const {
+ clearPreview,
+ showInputPreview
+ } = setupPreview(
+ this,
+ kContextId,
+ kBufferedCommandSymbol,
+ preview
+ );
+
// Wrap readline tty to enable editor mode and pausing.
const ttyWrite = self._ttyWrite.bind(self);
self._ttyWrite = (d, key) => {
+ clearPreview();
key = key || {};
if (paused && !(self.breakEvalOnSigint && key.ctrl && key.name === 'c')) {
pausedBuffer.push(['key', [d, key]]);
@@ -819,6 +834,7 @@ function REPLServer(prompt,
self.clearLine();
}
ttyWrite(d, key);
+ showInputPreview();
return;
}
diff --git a/test/parallel/test-repl-history-navigation.js b/test/parallel/test-repl-history-navigation.js
index b00f932aa9..3bd198880f 100644
--- a/test/parallel/test-repl-history-navigation.js
+++ b/test/parallel/test-repl-history-navigation.js
@@ -46,28 +46,50 @@ ActionStream.prototype.readable = true;
const ENTER = { name: 'enter' };
const UP = { name: 'up' };
const DOWN = { name: 'down' };
+const LEFT = { name: 'left' };
+const DELETE = { name: 'delete' };
const prompt = '> ';
+const prev = process.features.inspector;
+
const tests = [
{ // Creates few history to navigate for
env: { NODE_REPL_HISTORY: defaultHistoryPath },
test: [ 'let ab = 45', ENTER,
'555 + 909', ENTER,
- '{key : {key2 :[] }}', ENTER],
+ '{key : {key2 :[] }}', ENTER,
+ 'Array(100).fill(1).map((e, i) => i ** i)', LEFT, LEFT, DELETE,
+ '2', ENTER],
expected: [],
clean: false
},
{
env: { NODE_REPL_HISTORY: defaultHistoryPath },
- test: [UP, UP, UP, UP, DOWN, DOWN, DOWN],
+ test: [UP, UP, UP, UP, UP, DOWN, DOWN, DOWN, DOWN],
expected: [prompt,
+ `${prompt}Array(100).fill(1).map((e, i) => i ** 2)`,
+ prev && '\n// [ 0, 1, 4, 9, 16, 25, 36, 49, 64, 81, 100, 121, ' +
+ '144, 169, 196, 225, 256, 289, 324, 361, 400, 441, 484, 529,' +
+ ' 576, 625, 676, 729, 784, 841, 900, 961, 1024, 1089, 1156, ' +
+ '1225, 1296, 1369, 1444, 1521, 1600, 1681, 1764, 1849, 1936,' +
+ ' 2025, 2116, 2209, ...',
`${prompt}{key : {key2 :[] }}`,
+ prev && '\n// { key: { key2: [] } }',
`${prompt}555 + 909`,
+ prev && '\n// 1464',
`${prompt}let ab = 45`,
`${prompt}555 + 909`,
+ prev && '\n// 1464',
`${prompt}{key : {key2 :[] }}`,
- prompt],
+ prev && '\n// { key: { key2: [] } }',
+ `${prompt}Array(100).fill(1).map((e, i) => i ** 2)`,
+ prev && '\n// [ 0, 1, 4, 9, 16, 25, 36, 49, 64, 81, 100, 121, ' +
+ '144, 169, 196, 225, 256, 289, 324, 361, 400, 441, 484, 529,' +
+ ' 576, 625, 676, 729, 784, 841, 900, 961, 1024, 1089, 1156, ' +
+ '1225, 1296, 1369, 1444, 1521, 1600, 1681, 1764, 1849, 1936,' +
+ ' 2025, 2116, 2209, ...',
+ prompt].filter((e) => typeof e === 'string'),
clean: true
}
];
diff --git a/test/parallel/test-repl-multiline.js b/test/parallel/test-repl-multiline.js
index 454d5b1019..6498923b62 100644
--- a/test/parallel/test-repl-multiline.js
+++ b/test/parallel/test-repl-multiline.js
@@ -3,34 +3,44 @@ const common = require('../common');
const ArrayStream = require('../common/arraystream');
const assert = require('assert');
const repl = require('repl');
-const inputStream = new ArrayStream();
-const outputStream = new ArrayStream();
-const input = ['const foo = {', '};', 'foo;'];
-let output = '';
+const input = ['const foo = {', '};', 'foo'];
-outputStream.write = (data) => { output += data.replace('\r', ''); };
+function run({ useColors }) {
+ const inputStream = new ArrayStream();
+ const outputStream = new ArrayStream();
+ let output = '';
-const r = repl.start({
- prompt: '',
- input: inputStream,
- output: outputStream,
- terminal: true,
- useColors: false
-});
+ outputStream.write = (data) => { output += data.replace('\r', ''); };
-r.on('exit', common.mustCall(() => {
- const actual = output.split('\n');
+ const r = repl.start({
+ prompt: '',
+ input: inputStream,
+ output: outputStream,
+ terminal: true,
+ useColors
+ });
- // Validate the output, which contains terminal escape codes.
- assert.strictEqual(actual.length, 6);
- assert.ok(actual[0].endsWith(input[0]));
- assert.ok(actual[1].includes('... '));
- assert.ok(actual[1].endsWith(input[1]));
- assert.strictEqual(actual[2], 'undefined');
- assert.ok(actual[3].endsWith(input[2]));
- assert.strictEqual(actual[4], '{}');
- // Ignore the last line, which is nothing but escape codes.
-}));
+ r.on('exit', common.mustCall(() => {
+ const actual = output.split('\n');
-inputStream.run(input);
-r.close();
+ // Validate the output, which contains terminal escape codes.
+ assert.strictEqual(actual.length, 6 + process.features.inspector);
+ assert.ok(actual[0].endsWith(input[0]));
+ assert.ok(actual[1].includes('... '));
+ assert.ok(actual[1].endsWith(input[1]));
+ assert.ok(actual[2].includes('undefined'));
+ assert.ok(actual[3].endsWith(input[2]));
+ if (process.features.inspector) {
+ assert.ok(actual[4].includes(actual[5]));
+ assert.strictEqual(actual[4].includes('//'), !useColors);
+ }
+ assert.strictEqual(actual[4 + process.features.inspector], '{}');
+ // Ignore the last line, which is nothing but escape codes.
+ }));
+
+ inputStream.run(input);
+ r.close();
+}
+
+run({ useColors: true });
+run({ useColors: false });
diff --git a/test/parallel/test-repl-preview.js b/test/parallel/test-repl-preview.js
new file mode 100644
index 0000000000..92e73dd245
--- /dev/null
+++ b/test/parallel/test-repl-preview.js
@@ -0,0 +1,131 @@
+'use strict';
+
+const common = require('../common');
+const ArrayStream = require('../common/arraystream');
+const assert = require('assert');
+const Repl = require('repl');
+
+common.skipIfInspectorDisabled();
+
+const PROMPT = 'repl > ';
+
+class REPLStream extends ArrayStream {
+ constructor() {
+ super();
+ this.lines = [''];
+ }
+ write(chunk) {
+ const chunkLines = chunk.toString('utf8').split('\n');
+ this.lines[this.lines.length - 1] += chunkLines[0];
+ if (chunkLines.length > 1) {
+ this.lines.push(...chunkLines.slice(1));
+ }
+ this.emit('line');
+ return true;
+ }
+ wait() {
+ 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(PROMPT)) {
+ this.removeListener('error', onError);
+ this.removeListener('line', onLine);
+ resolve(this.lines);
+ }
+ };
+ this.once('error', onError);
+ this.on('line', onLine);
+ });
+ }
+}
+
+function runAndWait(cmds, repl) {
+ const promise = repl.inputStream.wait();
+ for (const cmd of cmds) {
+ repl.inputStream.run([cmd]);
+ }
+ return promise;
+}
+
+async function tests(options) {
+ const repl = Repl.start({
+ prompt: PROMPT,
+ stream: new REPLStream(),
+ ignoreUndefined: true,
+ useColors: true,
+ ...options
+ });
+
+ repl.inputStream.run([
+ 'function foo(x) { return x; }',
+ 'function koo() { console.log("abc"); }',
+ 'a = undefined;'
+ ]);
+ const testCases = [
+ ['foo', [2, 4], '[Function: foo]',
+ 'foo',
+ '\x1B[90m[Function: foo]\x1B[39m\x1B[1A\x1B[11G\x1B[1B\x1B[2K\x1B[1A\r',
+ '\x1B[36m[Function: foo]\x1B[39m',
+ '\x1B[1G\x1B[0Jrepl > \x1B[8G'],
+ ['koo', [2, 4], '[Function: koo]',
+ 'koo',
+ '\x1B[90m[Function: koo]\x1B[39m\x1B[1A\x1B[11G\x1B[1B\x1B[2K\x1B[1A\r',
+ '\x1B[36m[Function: koo]\x1B[39m',
+ '\x1B[1G\x1B[0Jrepl > \x1B[8G'],
+ ['a', [1, 2], undefined],
+ ['{ a: true }', [2, 3], '{ a: \x1B[33mtrue\x1B[39m }',
+ '{ a: true }\r',
+ '{ a: \x1B[33mtrue\x1B[39m }',
+ '\x1B[1G\x1B[0Jrepl > \x1B[8G'],
+ ['1n + 2n', [2, 5], '\x1B[33m3n\x1B[39m',
+ '1n + 2',
+ '\x1B[90mType[39m\x1B[1A\x1B[14G\x1B[1B\x1B[2K\x1B[1An',
+ '\x1B[90m3n\x1B[39m\x1B[1A\x1B[15G\x1B[1B\x1B[2K\x1B[1A\r',
+ '\x1B[33m3n\x1B[39m',
+ '\x1B[1G\x1B[0Jrepl > \x1B[8G'],
+ ['{ a: true };', [2, 4], '\x1B[33mtrue\x1B[39m',
+ '{ a: true };',
+ '\x1B[90mtrue\x1B[39m\x1B[1A\x1B[20G\x1B[1B\x1B[2K\x1B[1A\r',
+ '\x1B[33mtrue\x1B[39m',
+ '\x1B[1G\x1B[0Jrepl > \x1B[8G'],
+ [' \t { a: true};', [2, 5], '\x1B[33mtrue\x1B[39m',
+ ' \t { a: true}',
+ '\x1B[90m{ a: true }\x1B[39m\x1B[1A\x1B[21G\x1B[1B\x1B[2K\x1B[1A;',
+ '\x1B[90mtrue\x1B[39m\x1B[1A\x1B[22G\x1B[1B\x1B[2K\x1B[1A\r',
+ '\x1B[33mtrue\x1B[39m',
+ '\x1B[1G\x1B[0Jrepl > \x1B[8G']
+ ];
+
+ const hasPreview = repl.terminal &&
+ (options.preview !== undefined ? !!options.preview : true);
+
+ for (const [input, length, expected, ...preview] of testCases) {
+ console.log(`Testing ${input}`);
+
+ const toBeRun = input.split('\n');
+ let lines = await runAndWait(toBeRun, repl);
+
+ assert.strictEqual(lines.length, length[+hasPreview]);
+ if (expected === undefined) {
+ assert(!lines.some((e) => e.includes('undefined')));
+ } else if (hasPreview) {
+ // Remove error messages. That allows the code to run in different
+ // engines.
+ // eslint-disable-next-line no-control-regex
+ lines = lines.map((line) => line.replace(/Error: .+?\x1B/, ''));
+ assert.deepStrictEqual(lines, preview);
+ } else {
+ assert.ok(lines[0].includes(expected), lines);
+ }
+ }
+}
+
+tests({ terminal: false }); // No preview
+tests({ terminal: true }); // Preview
+tests({ terminal: false, preview: false }); // No preview
+tests({ terminal: false, preview: true }); // No preview
+tests({ terminal: true, preview: true }); // Preview
diff --git a/test/parallel/test-repl-top-level-await.js b/test/parallel/test-repl-top-level-await.js
index 5c8aa95d40..cecbd3ab45 100644
--- a/test/parallel/test-repl-top-level-await.js
+++ b/test/parallel/test-repl-top-level-await.js
@@ -1,11 +1,13 @@
'use strict';
-require('../common');
+const common = require('../common');
const ArrayStream = require('../common/arraystream');
const assert = require('assert');
const { stripVTControlCharacters } = require('internal/readline/utils');
const repl = require('repl');
+common.skipIfInspectorDisabled();
+
// Flags: --expose-internals --experimental-repl-await
const PROMPT = 'await repl > ';
@@ -84,61 +86,84 @@ async function ordinaryTests() {
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;',
+ [ '_', '// { a: 1 }\r', { line: 0 } ],
+ [ 'let { aa, bb } = await Promise.resolve({ aa: 1, bb: 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' ],
+ [ 'aa', ['// 1\r', '1'] ],
+ [ 'bb', ['// 2\r', '2'] ],
+ [ 'f', ['// 5\r', '5'] ],
+ [ 'let cc = await Promise.resolve(2)', 'undefined' ],
+ [ 'cc', ['// 2\r', '2'] ],
+ [ 'let dd;', 'undefined' ],
+ [ 'dd', 'undefined' ],
+ [ 'let [ii, { abc: { kk } }] = [0, { abc: { kk: 1 } }];', 'undefined' ],
+ [ 'ii', ['// 0\r', '0'] ],
+ [ 'kk', ['// 1\r', '1'] ],
+ [ 'var ll = await Promise.resolve(2);', 'undefined' ],
+ [ 'll', ['// 2\r', '2'] ],
+ [ 'foo(await koo())',
+ [ 'f', '// 5oo', '// [Function: foo](await koo())\r', '4' ] ],
+ [ '_', ['// 4\r', '4'] ],
+ [ 'const m = foo(await koo());',
+ [ 'const m = foo(await koo());\r', 'undefined' ] ],
+ [ 'm', ['// 4\r', '4' ] ],
+ [ 'const n = foo(await\nkoo());',
+ [ 'const n = foo(await\r', '... koo());\r', 'undefined' ] ],
+ [ 'n', ['// 4\r', '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]' ],
+ [ 'for (let i = 0; i < 2; ++i) await i',
+ ['f', '// 5or (let i = 0; i < 2; ++i) await i\r', 'undefined'] ],
+ [ 'for (let i = 0; i < 2; ++i) { await i }',
+ [ 'f', '// 5or (let i = 0; i < 2; ++i) { await i }\r', 'undefined' ] ],
+ [ 'await 0', ['await 0\r', '0'] ],
+ [ 'await 0; function foo() {}',
+ ['await 0; function foo() {}\r', 'undefined'] ],
+ [ 'foo',
+ ['f', '// 5oo', '// [Function: foo]\r', '[Function: foo]'] ],
+ [ 'class Foo {}; await 1;', ['class Foo {}; await 1;\r', '1'] ],
+ [ 'Foo', ['// [Function: Foo]\r', '[Function: Foo]'] ],
+ [ 'if (await true) { function bar() {}; }',
+ ['if (await true) { function bar() {}; }\r', 'undefined'] ],
+ [ 'bar', ['// [Function: bar]\r', '[Function: bar]'] ],
[ 'if (await true) { class Bar {}; }', 'undefined' ],
[ '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', 'Uncaught ReferenceError: j is not defined' ],
- [ 'gen', '[GeneratorFunction: gen]' ],
+ [ 'for (var i = 0; i < 10; ++i) { await i; }',
+ ['f', '// 5or (var i = 0; i < 10; ++i) { await i; }\r', 'undefined'] ],
+ [ 'i', ['// 10\r', '10'] ],
+ [ 'for (let j = 0; j < 5; ++j) { await j; }',
+ ['f', '// 5or (let j = 0; j < 5; ++j) { await j; }\r', 'undefined'] ],
+ [ 'j', 'Uncaught ReferenceError: j is not defined', { line: 0 } ],
+ [ 'gen', ['// [GeneratorFunction: gen]\r', '[GeneratorFunction: gen]'] ],
[ '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' ],
- [ 's', '2' ],
- [ 'for await (let i of [1,2,3]) console.log(i)', 'undefined', { line: 3 } ]
+ [ 's', ['// 2\r', '2'] ],
+ [ 'for await (let i of [1,2,3]) console.log(i)',
+ [
+ 'f',
+ '// 5or await (let i of [1,2,3]) console.log(i)\r',
+ '1',
+ '2',
+ '3',
+ 'undefined'
+ ]
+ ]
];
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) {
+ if (Array.isArray(expected)) {
+ 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`);