Skip to content

Commit d33dcf1

Browse files
BridgeARcodebytere
authored andcommitted
repl: show reference errors during preview
This aligns the behavior with the one in the Firefox console. It will visualize ReferenceErrors in case the input has no possible completion and no buffered input. That way typos can already be highlighted before being evaluated. Signed-off-by: Ruben Bridgewater <[email protected]> PR-URL: #33282 Reviewed-By: James M Snell <[email protected]> Reviewed-By: Michaël Zasso <[email protected]>
1 parent 1a9771a commit d33dcf1

File tree

4 files changed

+89
-9
lines changed

4 files changed

+89
-9
lines changed

lib/internal/repl/utils.js

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,8 @@ const previewOptions = {
4848
showHidden: false
4949
};
5050

51+
const REPL_MODE_STRICT = Symbol('repl-strict');
52+
5153
// If the error is that we've unexpectedly ended the input,
5254
// then let the user try to recover by adding more input.
5355
// Note: `e` (the original exception) is not used by the current implementation,
@@ -136,6 +138,8 @@ function setupPreview(repl, contextSymbol, bufferSymbol, active) {
136138
let previewCompletionCounter = 0;
137139
let completionPreview = null;
138140

141+
let hasCompletions = false;
142+
139143
let wrapped = false;
140144

141145
let escaped = null;
@@ -229,6 +233,8 @@ function setupPreview(repl, contextSymbol, bufferSymbol, active) {
229233
return;
230234
}
231235

236+
hasCompletions = true;
237+
232238
// If there is a common prefix to all matches, then apply that portion.
233239
const completions = rawCompletions.filter((e) => e);
234240
const prefix = commonPrefix(completions);
@@ -265,6 +271,12 @@ function setupPreview(repl, contextSymbol, bufferSymbol, active) {
265271
});
266272
}
267273

274+
function isInStrictMode(repl) {
275+
return repl.replMode === REPL_MODE_STRICT || process.execArgv
276+
.map((e) => e.toLowerCase().replace(/_/g, '-'))
277+
.includes('--use-strict');
278+
}
279+
268280
// This returns a code preview for arbitrary input code.
269281
function getInputPreview(input, callback) {
270282
// For similar reasons as `defaultEval`, wrap expressions starting with a
@@ -292,8 +304,11 @@ function setupPreview(repl, contextSymbol, bufferSymbol, active) {
292304
// may be inspected.
293305
} else if (preview.exceptionDetails &&
294306
(result.className === 'EvalError' ||
295-
result.className === 'SyntaxError' ||
296-
result.className === 'ReferenceError')) {
307+
result.className === 'SyntaxError' ||
308+
// Report ReferenceError in case the strict mode is active
309+
// for input that has no completions.
310+
(result.className === 'ReferenceError' &&
311+
(hasCompletions || !isInStrictMode(repl))))) {
297312
callback(null, null);
298313
} else if (result.objectId) {
299314
// The writer options might change and have influence on the inspect
@@ -339,6 +354,8 @@ function setupPreview(repl, contextSymbol, bufferSymbol, active) {
339354
return;
340355
}
341356

357+
hasCompletions = false;
358+
342359
// Add the autocompletion preview.
343360
const insertPreview = false;
344361
showCompletionPreview(repl.line, insertPreview);
@@ -703,6 +720,8 @@ function setupReverseSearch(repl) {
703720
}
704721

705722
module.exports = {
723+
REPL_MODE_SLOPPY: Symbol('repl-sloppy'),
724+
REPL_MODE_STRICT,
706725
isRecoverableError,
707726
kStandaloneREPL: Symbol('kStandaloneREPL'),
708727
setupPreview,

lib/repl.js

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,8 @@ const experimentalREPLAwait = require('internal/options').getOptionValue(
103103
'--experimental-repl-await'
104104
);
105105
const {
106+
REPL_MODE_SLOPPY,
107+
REPL_MODE_STRICT,
106108
isRecoverableError,
107109
kStandaloneREPL,
108110
setupPreview,
@@ -363,8 +365,7 @@ function REPLServer(prompt,
363365
}
364366
while (true) {
365367
try {
366-
if (!/^\s*$/.test(code) &&
367-
self.replMode === exports.REPL_MODE_STRICT) {
368+
if (self.replMode === exports.REPL_MODE_STRICT && !/^\s*$/.test(code)) {
368369
// "void 0" keeps the repl from returning "use strict" as the result
369370
// value for statements and declarations that don't return a value.
370371
code = `'use strict'; void 0;\n${code}`;
@@ -896,8 +897,8 @@ ObjectSetPrototypeOf(REPLServer, Interface);
896897

897898
exports.REPLServer = REPLServer;
898899

899-
exports.REPL_MODE_SLOPPY = Symbol('repl-sloppy');
900-
exports.REPL_MODE_STRICT = Symbol('repl-strict');
900+
exports.REPL_MODE_SLOPPY = REPL_MODE_SLOPPY;
901+
exports.REPL_MODE_STRICT = REPL_MODE_STRICT;
901902

902903
// Prompt is a string to print on each line for the prompt,
903904
// source is a stream to use for I/O, defaulting to stdin/stdout.

test/parallel/test-repl-mode.js

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@ const repl = require('repl');
77
const tests = [
88
testSloppyMode,
99
testStrictMode,
10-
testAutoMode
10+
testAutoMode,
11+
testStrictModeTerminal,
1112
];
1213

1314
tests.forEach(function(test) {
@@ -37,6 +38,18 @@ function testStrictMode() {
3738
assert.strictEqual(cli.output.accumulator.join(''), 'undefined\n> ');
3839
}
3940

41+
function testStrictModeTerminal() {
42+
// Verify that ReferenceErrors are reported in strict mode previews.
43+
const cli = initRepl(repl.REPL_MODE_STRICT, {
44+
terminal: true
45+
});
46+
47+
cli.input.emit('data', 'xyz ');
48+
assert.ok(
49+
cli.output.accumulator.includes('\n// ReferenceError: xyz is not defined')
50+
);
51+
}
52+
4053
function testAutoMode() {
4154
const cli = initRepl(repl.REPL_MODE_MAGIC);
4255

@@ -48,7 +61,7 @@ function testAutoMode() {
4861
assert.strictEqual(cli.output.accumulator.join(''), 'undefined\n> ');
4962
}
5063

51-
function initRepl(mode) {
64+
function initRepl(mode, options) {
5265
const input = new Stream();
5366
input.write = input.pause = input.resume = () => {};
5467
input.readable = true;
@@ -65,6 +78,7 @@ function initRepl(mode) {
6578
output: output,
6679
useColors: false,
6780
terminal: false,
68-
replMode: mode
81+
replMode: mode,
82+
...options
6983
});
7084
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
// Previews in strict mode should indicate ReferenceErrors.
2+
3+
'use strict';
4+
5+
const common = require('../common');
6+
7+
common.skipIfInspectorDisabled();
8+
9+
if (process.argv[2] === 'child') {
10+
const stream = require('stream');
11+
const repl = require('repl');
12+
class ActionStream extends stream.Stream {
13+
readable = true;
14+
run(data) {
15+
this.emit('data', `${data}`);
16+
this.emit('keypress', '', { ctrl: true, name: 'd' });
17+
}
18+
resume() {}
19+
pause() {}
20+
}
21+
22+
repl.start({
23+
input: new ActionStream(),
24+
output: new stream.Writable({
25+
write(chunk, _, next) {
26+
console.log(chunk.toString());
27+
next();
28+
}
29+
}),
30+
useColors: false,
31+
terminal: true
32+
}).inputStream.run('xyz');
33+
} else {
34+
const assert = require('assert');
35+
const { spawnSync } = require('child_process');
36+
37+
const result = spawnSync(
38+
process.execPath,
39+
['--use-strict', `${__filename}`, 'child']
40+
);
41+
42+
assert.match(
43+
result.stdout.toString(),
44+
/\/\/ ReferenceError: xyz is not defined/
45+
);
46+
}

0 commit comments

Comments
 (0)