-
Notifications
You must be signed in to change notification settings - Fork 29k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
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 <rtrott@gmail.com> Reviewed-By: Ruben Bridgewater <ruben@bridgewater.de> Reviewed-By: James M Snell <jasnell@gmail.com>
- Loading branch information
1 parent
c05de49
commit 7a812dd
Showing
3 changed files
with
164 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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()); |