Skip to content

Commit 2bf8452

Browse files
Linkgoronmarco-ippolito
authored andcommitted
repl: fix disruptive autocomplete without inspector
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: #40635 PR-URL: #40661 Fixes: #40635 Reviewed-By: Rich Trott <[email protected]> Reviewed-By: Ruben Bridgewater <[email protected]> Reviewed-By: James M Snell <[email protected]>
1 parent c5e798a commit 2bf8452

File tree

3 files changed

+164
-3
lines changed

3 files changed

+164
-3
lines changed

lib/internal/repl/utils.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -365,7 +365,7 @@ function setupPreview(repl, contextSymbol, bufferSymbol, active) {
365365

366366
const showPreview = (showCompletion = true) => {
367367
// Prevent duplicated previews after a refresh.
368-
if (inputPreview !== null || !repl.isCompletionEnabled) {
368+
if (inputPreview !== null || !repl.isCompletionEnabled || !process.features.inspector) {
369369
return;
370370
}
371371

test/parallel/test-repl-history-navigation.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -585,7 +585,7 @@ const tests = [
585585
prompt, ...'const util = {}',
586586
'undefined\n',
587587
prompt, ...'ut', ...(prev ? [' // il', '\n// {}',
588-
'il', '\n// {}'] : [' // il', 'il']),
588+
'il', '\n// {}'] : ['il']),
589589
'{}\n',
590590
prompt,
591591
],
@@ -605,7 +605,7 @@ const tests = [
605605
'undefined\n',
606606
prompt, ...'globalThis.util = {}',
607607
'{}\n',
608-
prompt, ...'ut', ' // il', 'il',
608+
prompt, ...'ut', ...(prev ? [' // il', 'il' ] : ['il']),
609609
'{}\n',
610610
prompt, ...'Reflect.defineProperty(globalThis, "util", utilDesc)',
611611
'true\n',
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
'use strict';
2+
3+
const common = require('../common');
4+
const assert = require('assert');
5+
const { REPLServer } = require('repl');
6+
const { Stream } = require('stream');
7+
8+
if (process.features.inspector)
9+
common.skip('test is for node compiled with --without-inspector only');
10+
11+
// Ignore terminal settings. This is so the test can be run intact if TERM=dumb.
12+
process.env.TERM = '';
13+
const PROMPT = 'repl > ';
14+
15+
class REPLStream extends Stream {
16+
readable = true;
17+
writable = true;
18+
19+
constructor() {
20+
super();
21+
this.lines = [''];
22+
}
23+
run(data) {
24+
for (const entry of data) {
25+
this.emit('data', entry);
26+
}
27+
this.emit('data', '\n');
28+
}
29+
write(chunk) {
30+
const chunkLines = chunk.toString('utf8').split('\n');
31+
this.lines[this.lines.length - 1] += chunkLines[0];
32+
if (chunkLines.length > 1) {
33+
this.lines.push(...chunkLines.slice(1));
34+
}
35+
this.emit('line');
36+
return true;
37+
}
38+
wait() {
39+
this.lines = [''];
40+
return new Promise((resolve, reject) => {
41+
const onError = (err) => {
42+
this.removeListener('line', onLine);
43+
reject(err);
44+
};
45+
const onLine = () => {
46+
if (this.lines[this.lines.length - 1].includes(PROMPT)) {
47+
this.removeListener('error', onError);
48+
this.removeListener('line', onLine);
49+
resolve(this.lines);
50+
}
51+
};
52+
this.once('error', onError);
53+
this.on('line', onLine);
54+
});
55+
}
56+
pause() { }
57+
resume() { }
58+
}
59+
60+
function runAndWait(cmds, repl) {
61+
const promise = repl.inputStream.wait();
62+
for (const cmd of cmds) {
63+
repl.inputStream.run(cmd);
64+
}
65+
return promise;
66+
}
67+
68+
const repl = REPLServer({
69+
prompt: PROMPT,
70+
stream: new REPLStream(),
71+
ignoreUndefined: true,
72+
useColors: true,
73+
terminal: true,
74+
});
75+
76+
repl.inputStream.run([
77+
'function foo(x) { return x; }',
78+
'function koo() { console.log("abc"); }',
79+
'a = undefined;',
80+
'const r = 5;',
81+
]);
82+
83+
const testCases = [{
84+
input: 'foo',
85+
preview: [
86+
'foo\r',
87+
'\x1B[36m[Function: foo]\x1B[39m',
88+
]
89+
}, {
90+
input: 'r',
91+
preview: [
92+
'r\r',
93+
'\x1B[33m5\x1B[39m',
94+
]
95+
}, {
96+
input: 'koo',
97+
preview: [
98+
'koo\r',
99+
'\x1B[36m[Function: koo]\x1B[39m',
100+
]
101+
}, {
102+
input: 'a',
103+
preview: ['a\r'] // No "undefined" preview.
104+
}, {
105+
input: " { b: 1 }['b'] === 1",
106+
preview: [
107+
" { b: 1 }['b'] === 1\r",
108+
'\x1B[33mtrue\x1B[39m',
109+
]
110+
}, {
111+
input: "{ b: 1 }['b'] === 1;",
112+
preview: [
113+
"{ b: 1 }['b'] === 1;\r",
114+
'\x1B[33mfalse\x1B[39m',
115+
]
116+
}, {
117+
input: '{ a: true }',
118+
preview: [
119+
'{ a: true }\r',
120+
'{ a: \x1B[33mtrue\x1B[39m }',
121+
]
122+
}, {
123+
input: '{ a: true };',
124+
preview: [
125+
'{ a: true };\r',
126+
'\x1B[33mtrue\x1B[39m',
127+
]
128+
}, {
129+
input: ' \t { a: true};',
130+
preview: [
131+
' { a: true};\r',
132+
'\x1B[33mtrue\x1B[39m',
133+
]
134+
}, {
135+
input: '1n + 2n',
136+
preview: [
137+
'1n + 2n\r',
138+
'\x1B[33m3n\x1B[39m',
139+
]
140+
}, {
141+
input: '{};1',
142+
preview: [
143+
'{};1\r',
144+
'\x1B[33m1\x1B[39m',
145+
],
146+
}];
147+
148+
async function runTest() {
149+
for (const { input, preview } of testCases) {
150+
const toBeRun = input.split('\n');
151+
let lines = await runAndWait(toBeRun, repl);
152+
// Remove error messages. That allows the code to run in different
153+
// engines.
154+
// eslint-disable-next-line no-control-regex
155+
lines = lines.map((line) => line.replace(/Error: .+?\x1B/, ''));
156+
assert.strictEqual(lines.pop(), '\x1B[1G\x1B[0Jrepl > \x1B[8G');
157+
assert.deepStrictEqual(lines, preview);
158+
}
159+
}
160+
161+
runTest().then(common.mustCall());

0 commit comments

Comments
 (0)