From a946a05aaea9b5c10f15b4f38ec39e8bd1626e22 Mon Sep 17 00:00:00 2001 From: Ruben Bridgewater Date: Mon, 24 Feb 2020 18:57:19 +0100 Subject: [PATCH 1/2] repl: align preview with the actual executed code This adds preview output for input that may not be wrapped. --- lib/internal/repl/utils.js | 25 ++++++++++++++++++++----- test/parallel/test-repl-preview.js | 8 +++++++- test/parallel/test-repl.js | 22 ++++++++++++++++++++++ 3 files changed, 49 insertions(+), 6 deletions(-) diff --git a/lib/internal/repl/utils.js b/lib/internal/repl/utils.js index 49a40e3030a3e2..476760a08d5934 100644 --- a/lib/internal/repl/utils.js +++ b/lib/internal/repl/utils.js @@ -136,6 +136,8 @@ function setupPreview(repl, contextSymbol, bufferSymbol, active) { let previewCompletionCounter = 0; let completionPreview = null; + let wrapped = false; + function getPreviewPos() { const displayPos = repl._getDisplayPos(`${repl._prompt}${repl.line}`); const cursorPos = repl.line.length !== repl.cursor ? @@ -244,8 +246,9 @@ function setupPreview(repl, contextSymbol, bufferSymbol, active) { function getInputPreview(input, callback) { // For similar reasons as `defaultEval`, wrap expressions starting with a // curly brace with parenthesis. - if (input.startsWith('{') && !input.endsWith(';')) { + if (input.startsWith('{') && !input.endsWith(';') && !wrapped) { input = `(${input})`; + wrapped = true; } sendInspectorCommand((session) => { session.post('Runtime.evaluate', { @@ -329,13 +332,19 @@ function setupPreview(repl, contextSymbol, bufferSymbol, active) { return; } - getInputPreview(line, (error, inspected) => { + const inputPreviewCallback = (error, inspected) => { + if (inspected === null) { + return; + } + + wrapped = false; + // 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 && lastInputPreview !== inspected) || - inspected === null) { + if (line === inspected && lastInputPreview !== inspected) { return; } + if (error) { debug('Error while generating preview', error); return; @@ -386,7 +395,13 @@ function setupPreview(repl, contextSymbol, bufferSymbol, active) { repl.output.write(`\n${result}`); cursorTo(repl.output, cursorPos.cols); moveCursor(repl.output, 0, -rows - 1); - }); + }; + + getInputPreview(line, inputPreviewCallback); + if (wrapped) { + getInputPreview(line, inputPreviewCallback); + } + wrapped = false; }; // -------------------------------------------------------------------------// diff --git a/test/parallel/test-repl-preview.js b/test/parallel/test-repl-preview.js index b36b99cca7c40e..185052b9b7ce45 100644 --- a/test/parallel/test-repl-preview.js +++ b/test/parallel/test-repl-preview.js @@ -125,7 +125,13 @@ async function tests(options) { '\x1B[90m{ a: true }\x1B[39m\x1B[20G\x1B[1A\x1B[1B\x1B[2K\x1B[1A;', '\x1B[90mtrue\x1B[39m\x1B[21G\x1B[1A\x1B[1B\x1B[2K\x1B[1A\r', '\x1B[33mtrue\x1B[39m', - '\x1B[1G\x1B[0Jrepl > \x1B[8G'] + '\x1B[1G\x1B[0Jrepl > \x1B[8G'], + ['{};1', [2, 4], '\x1B[33m1\x1B[39m', + '{};1', + '\x1B[90m1\x1B[39m\x1B[12G\x1B[1A\x1B[1B\x1B[2K\x1B[1A\r', + '\x1B[33m1\x1B[39m', + '\x1B[1G\x1B[0Jrepl > \x1B[8G' + ] ]; const hasPreview = repl.terminal && diff --git a/test/parallel/test-repl.js b/test/parallel/test-repl.js index b0dad397ccc06b..dcb3fa40630423 100644 --- a/test/parallel/test-repl.js +++ b/test/parallel/test-repl.js @@ -457,6 +457,28 @@ const errorTests = [ /'thefourtheye'/ ] }, + // Check for wrapped objects. + { + send: '{ a: 1 }.a', // ({ a: 1 }.a); + expect: '1' + }, + { + send: '{ a: 1 }.a;', // { a: 1 }.a; + expect: [ + kSource, + kArrow, + '', + /^Uncaught SyntaxError: / + ] + }, + { + send: '{ a: 1 }["a"] === 1', // ({ a: 1 }['a'] === 1); + expect: 'true' + }, + { + send: '{ a: 1 }["a"] === 1;', // { a: 1 }; ['a'] === 1; + expect: 'false' + }, // Empty lines in the REPL should be allowed { send: '\n\r\n\r\n', From 1fe1612449e69885580194e8f559c053ea6be093 Mon Sep 17 00:00:00 2001 From: Ruben Bridgewater Date: Mon, 9 Mar 2020 13:08:52 +0100 Subject: [PATCH 2/2] test: refactor and simplify test-repl-preview Use an object to indicate which part belongs to the input and which to the output. On top of that this also simplifies the expected output by automatically inserting the default repl line for previews and by automatically checking for the correct output line length. --- test/parallel/test-repl-preview.js | 152 +++++++++++++++++------------ 1 file changed, 90 insertions(+), 62 deletions(-) diff --git a/test/parallel/test-repl-preview.js b/test/parallel/test-repl-preview.js index 185052b9b7ce45..b8dbbf3f9a993d 100644 --- a/test/parallel/test-repl-preview.js +++ b/test/parallel/test-repl-preview.js @@ -4,6 +4,7 @@ const common = require('../common'); const assert = require('assert'); const { REPLServer } = require('repl'); const { Stream } = require('stream'); +const { inspect } = require('util'); common.skipIfInspectorDisabled(); @@ -76,84 +77,111 @@ async function tests(options) { 'function koo() { console.log("abc"); }', 'a = undefined;' ]); - const testCases = [ - ['foo', [2, 4], '[Function: foo]', - 'foo', - '\x1B[90m[Function: foo]\x1B[39m\x1B[11G\x1B[1A\x1B[1B\x1B[2K\x1B[1A\r', - '\x1B[36m[Function: foo]\x1B[39m', - '\x1B[1G\x1B[0Jrepl > \x1B[8G'], - ['koo', [2, 4], '[Function: koo]', - 'k\x1B[90moo\x1B[39m\x1B[9G\x1B[0Ko\x1B[90mo\x1B[39m\x1B[10G\x1B[0Ko', - '\x1B[90m[Function: koo]\x1B[39m\x1B[11G\x1B[1A\x1B[1B\x1B[2K\x1B[1A\r', - '\x1B[36m[Function: koo]\x1B[39m', - '\x1B[1G\x1B[0Jrepl > \x1B[8G'], - ['a', [1, 2], undefined], - [" { b: 1 }['b'] === 1", [2, 6], '\x1B[33mtrue\x1B[39m', - " { b: 1 }['b']", - '\x1B[90m1\x1B[39m\x1B[22G\x1B[1A\x1B[1B\x1B[2K\x1B[1A ', - '\x1B[90m1\x1B[39m\x1B[23G\x1B[1A\x1B[1B\x1B[2K\x1B[1A=== 1', - '\x1B[90mtrue\x1B[39m\x1B[28G\x1B[1A\x1B[1B\x1B[2K\x1B[1A\r', - '\x1B[33mtrue\x1B[39m', - '\x1B[1G\x1B[0Jrepl > \x1B[8G' - ], - ["{ b: 1 }['b'] === 1;", [2, 7], '\x1B[33mfalse\x1B[39m', - "{ b: 1 }['b']", - '\x1B[90m1\x1B[39m\x1B[21G\x1B[1A\x1B[1B\x1B[2K\x1B[1A ', - '\x1B[90m1\x1B[39m\x1B[22G\x1B[1A\x1B[1B\x1B[2K\x1B[1A=== 1', - '\x1B[90mtrue\x1B[39m\x1B[27G\x1B[1A\x1B[1B\x1B[2K\x1B[1A;', - '\x1B[90mfalse\x1B[39m\x1B[28G\x1B[1A\x1B[1B\x1B[2K\x1B[1A\r', - '\x1B[33mfalse\x1B[39m', - '\x1B[1G\x1B[0Jrepl > \x1B[8G' - ], - ['{ a: true }', [2, 3], '{ a: \x1B[33mtrue\x1B[39m }', - '{ a: tru\x1B[90me\x1B[39m\x1B[16G\x1B[0Ke }\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[14G\x1B[1A\x1B[1B\x1B[2K\x1B[1An', - '\x1B[90m3n\x1B[39m\x1B[15G\x1B[1A\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: tru\x1B[90me\x1B[39m\x1B[16G\x1B[0Ke };', - '\x1B[90mtrue\x1B[39m\x1B[20G\x1B[1A\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', - ' { a: tru\x1B[90me\x1B[39m\x1B[18G\x1B[0Ke}', - '\x1B[90m{ a: true }\x1B[39m\x1B[20G\x1B[1A\x1B[1B\x1B[2K\x1B[1A;', - '\x1B[90mtrue\x1B[39m\x1B[21G\x1B[1A\x1B[1B\x1B[2K\x1B[1A\r', - '\x1B[33mtrue\x1B[39m', - '\x1B[1G\x1B[0Jrepl > \x1B[8G'], - ['{};1', [2, 4], '\x1B[33m1\x1B[39m', - '{};1', - '\x1B[90m1\x1B[39m\x1B[12G\x1B[1A\x1B[1B\x1B[2K\x1B[1A\r', - '\x1B[33m1\x1B[39m', - '\x1B[1G\x1B[0Jrepl > \x1B[8G' + + const testCases = [{ + input: 'foo', + noPreview: '[Function: foo]', + preview: [ + 'foo', + '\x1B[90m[Function: foo]\x1B[39m\x1B[11G\x1B[1A\x1B[1B\x1B[2K\x1B[1A\r', + '\x1B[36m[Function: foo]\x1B[39m' + ] + }, { + input: 'koo', + noPreview: '[Function: koo]', + preview: [ + 'k\x1B[90moo\x1B[39m\x1B[9G\x1B[0Ko\x1B[90mo\x1B[39m\x1B[10G\x1B[0Ko', + '\x1B[90m[Function: koo]\x1B[39m\x1B[11G\x1B[1A\x1B[1B\x1B[2K\x1B[1A\r', + '\x1B[36m[Function: koo]\x1B[39m' + ] + }, { + input: 'a', + noPreview: 'repl > ', // No "undefined" output. + preview: ['a\r'] // No "undefined" preview. + }, { + input: " { b: 1 }['b'] === 1", + noPreview: '\x1B[33mtrue\x1B[39m', + preview: [ + " { b: 1 }['b']", + '\x1B[90m1\x1B[39m\x1B[22G\x1B[1A\x1B[1B\x1B[2K\x1B[1A ', + '\x1B[90m1\x1B[39m\x1B[23G\x1B[1A\x1B[1B\x1B[2K\x1B[1A=== 1', + '\x1B[90mtrue\x1B[39m\x1B[28G\x1B[1A\x1B[1B\x1B[2K\x1B[1A\r', + '\x1B[33mtrue\x1B[39m' + ] + }, { + input: "{ b: 1 }['b'] === 1;", + noPreview: '\x1B[33mfalse\x1B[39m', + preview: [ + "{ b: 1 }['b']", + '\x1B[90m1\x1B[39m\x1B[21G\x1B[1A\x1B[1B\x1B[2K\x1B[1A ', + '\x1B[90m1\x1B[39m\x1B[22G\x1B[1A\x1B[1B\x1B[2K\x1B[1A=== 1', + '\x1B[90mtrue\x1B[39m\x1B[27G\x1B[1A\x1B[1B\x1B[2K\x1B[1A;', + '\x1B[90mfalse\x1B[39m\x1B[28G\x1B[1A\x1B[1B\x1B[2K\x1B[1A\r', + '\x1B[33mfalse\x1B[39m' + ] + }, { + input: '{ a: true }', + noPreview: '{ a: \x1B[33mtrue\x1B[39m }', + preview: [ + '{ a: tru\x1B[90me\x1B[39m\x1B[16G\x1B[0Ke }\r', + '{ a: \x1B[33mtrue\x1B[39m }' + ] + }, { + input: '{ a: true };', + noPreview: '\x1B[33mtrue\x1B[39m', + preview: [ + '{ a: tru\x1B[90me\x1B[39m\x1B[16G\x1B[0Ke };', + '\x1B[90mtrue\x1B[39m\x1B[20G\x1B[1A\x1B[1B\x1B[2K\x1B[1A\r', + '\x1B[33mtrue\x1B[39m' + ] + }, { + input: ' \t { a: true};', + noPreview: '\x1B[33mtrue\x1B[39m', + preview: [ + ' { a: tru\x1B[90me\x1B[39m\x1B[18G\x1B[0Ke}', + '\x1B[90m{ a: true }\x1B[39m\x1B[20G\x1B[1A\x1B[1B\x1B[2K\x1B[1A;', + '\x1B[90mtrue\x1B[39m\x1B[21G\x1B[1A\x1B[1B\x1B[2K\x1B[1A\r', + '\x1B[33mtrue\x1B[39m' + ] + }, { + input: '1n + 2n', + noPreview: '\x1B[33m3n\x1B[39m', + preview: [ + '1n + 2', + '\x1B[90mType[39m\x1B[14G\x1B[1A\x1B[1B\x1B[2K\x1B[1An', + '\x1B[90m3n\x1B[39m\x1B[15G\x1B[1A\x1B[1B\x1B[2K\x1B[1A\r', + '\x1B[33m3n\x1B[39m' + ] + }, { + input: '{};1', + noPreview: '\x1B[33m1\x1B[39m', + preview: [ + '{};1', + '\x1B[90m1\x1B[39m\x1B[12G\x1B[1A\x1B[1B\x1B[2K\x1B[1A\r', + '\x1B[33m1\x1B[39m' ] - ]; + }]; const hasPreview = repl.terminal && (options.preview !== undefined ? !!options.preview : true); - for (const [input, length, expected, ...preview] of testCases) { + for (const { input, noPreview, 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) { + 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.strictEqual(lines.pop(), '\x1B[1G\x1B[0Jrepl > \x1B[8G'); assert.deepStrictEqual(lines, preview); } else { - assert.ok(lines[0].includes(expected), lines); + assert.ok(lines[0].includes(noPreview), lines.map(inspect)); + if (preview.length !== 1 || preview[0] !== `${input}\r`) + assert.strictEqual(lines.length, 2); } } }