summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRuben Bridgewater <ruben@bridgewater.de>2019-12-05 14:41:49 +0100
committerRuben Bridgewater <ruben@bridgewater.de>2019-12-10 00:23:23 +0100
commit6bdf8d106009e4ed0f3c323d0f0874a51acc8e08 (patch)
tree57153227fef5cac26ae147e44bf571eee646feef
parent02a0c74861c3107e6a9a1752e91540f8d4c49a76 (diff)
downloadandroid-node-v8-6bdf8d106009e4ed0f3c323d0f0874a51acc8e08.tar.gz
android-node-v8-6bdf8d106009e4ed0f3c323d0f0874a51acc8e08.tar.bz2
android-node-v8-6bdf8d106009e4ed0f3c323d0f0874a51acc8e08.zip
repl: support previews by eager evaluating input
This adds input previews by using the inspectors eager evaluation functionality. It is implemented as additional line that is not counted towards the actual input. In case no colors are supported, it will be visible as comment. Otherwise it's grey. It will be triggered on any line change. It is heavily tested against edge cases and adheres to "dumb" terminals (previews are deactived in that case). PR-URL: https://github.com/nodejs/node/pull/30811 Fixes: https://github.com/nodejs/node/issues/20977 Reviewed-By: Yongsheng Zhang <zyszys98@gmail.com> Reviewed-By: Anto Aravinth <anto.aravinth.cse@gmail.com> Reviewed-By: Michaƫl Zasso <targos@protonmail.com>
-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`);