Skip to content

Commit

Permalink
repl: fix disruptive autocomplete without inspector
Browse files Browse the repository at this point in the history
Fix an issue where the autocomplete wrongly autocompletes
a value from a correct value to an undefined value when `node`
is built without an inspector by disabling the preview view.

fixes: nodejs#40635
PR-URL: nodejs#40661
Fixes: nodejs#40635
Reviewed-By: Rich Trott <rtrott@gmail.com>
Reviewed-By: Ruben Bridgewater <ruben@bridgewater.de>
Reviewed-By: James M Snell <jasnell@gmail.com>
  • Loading branch information
Linkgoron authored and bmeck committed Jun 22, 2024
1 parent e0cafcf commit c632b7b
Show file tree
Hide file tree
Showing 3 changed files with 164 additions and 3 deletions.
2 changes: 1 addition & 1 deletion lib/internal/repl/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -365,7 +365,7 @@ function setupPreview(repl, contextSymbol, bufferSymbol, active) {

const showPreview = (showCompletion = true) => {
// Prevent duplicated previews after a refresh.
if (inputPreview !== null || !repl.isCompletionEnabled) {
if (inputPreview !== null || !repl.isCompletionEnabled || !process.features.inspector) {
return;
}

Expand Down
4 changes: 2 additions & 2 deletions test/parallel/test-repl-history-navigation.js
Original file line number Diff line number Diff line change
Expand Up @@ -585,7 +585,7 @@ const tests = [
prompt, ...'const util = {}',
'undefined\n',
prompt, ...'ut', ...(prev ? [' // il', '\n// {}',
'il', '\n// {}'] : [' // il', 'il']),
'il', '\n// {}'] : ['il']),
'{}\n',
prompt,
],
Expand All @@ -605,7 +605,7 @@ const tests = [
'undefined\n',
prompt, ...'globalThis.util = {}',
'{}\n',
prompt, ...'ut', ' // il', 'il',
prompt, ...'ut', ...(prev ? [' // il', 'il' ] : ['il']),
'{}\n',
prompt, ...'Reflect.defineProperty(globalThis, "util", utilDesc)',
'true\n',
Expand Down
161 changes: 161 additions & 0 deletions test/parallel/test-repl-preview-without-inspector.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
'use strict';

const common = require('../common');
const assert = require('assert');
const { REPLServer } = require('repl');
const { Stream } = require('stream');

if (process.features.inspector)
common.skip('test is for node compiled with --without-inspector only');

// Ignore terminal settings. This is so the test can be run intact if TERM=dumb.
process.env.TERM = '';
const PROMPT = 'repl > ';

class REPLStream extends Stream {
readable = true;
writable = true;

constructor() {
super();
this.lines = [''];
}
run(data) {
for (const entry of data) {
this.emit('data', entry);
}
this.emit('data', '\n');
}
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);
});
}
pause() { }
resume() { }
}

function runAndWait(cmds, repl) {
const promise = repl.inputStream.wait();
for (const cmd of cmds) {
repl.inputStream.run(cmd);
}
return promise;
}

const repl = REPLServer({
prompt: PROMPT,
stream: new REPLStream(),
ignoreUndefined: true,
useColors: true,
terminal: true,
});

repl.inputStream.run([
'function foo(x) { return x; }',
'function koo() { console.log("abc"); }',
'a = undefined;',
'const r = 5;',
]);

const testCases = [{
input: 'foo',
preview: [
'foo\r',
'\x1B[36m[Function: foo]\x1B[39m',
]
}, {
input: 'r',
preview: [
'r\r',
'\x1B[33m5\x1B[39m',
]
}, {
input: 'koo',
preview: [
'koo\r',
'\x1B[36m[Function: koo]\x1B[39m',
]
}, {
input: 'a',
preview: ['a\r'] // No "undefined" preview.
}, {
input: " { b: 1 }['b'] === 1",
preview: [
" { b: 1 }['b'] === 1\r",
'\x1B[33mtrue\x1B[39m',
]
}, {
input: "{ b: 1 }['b'] === 1;",
preview: [
"{ b: 1 }['b'] === 1;\r",
'\x1B[33mfalse\x1B[39m',
]
}, {
input: '{ a: true }',
preview: [
'{ a: true }\r',
'{ a: \x1B[33mtrue\x1B[39m }',
]
}, {
input: '{ a: true };',
preview: [
'{ a: true };\r',
'\x1B[33mtrue\x1B[39m',
]
}, {
input: ' \t { a: true};',
preview: [
' { a: true};\r',
'\x1B[33mtrue\x1B[39m',
]
}, {
input: '1n + 2n',
preview: [
'1n + 2n\r',
'\x1B[33m3n\x1B[39m',
]
}, {
input: '{};1',
preview: [
'{};1\r',
'\x1B[33m1\x1B[39m',
],
}];

async function runTest() {
for (const { input, preview } of testCases) {
const toBeRun = input.split('\n');
let lines = await runAndWait(toBeRun, repl);
// 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);
}
}

runTest().then(common.mustCall());

0 comments on commit c632b7b

Please sign in to comment.