Skip to content

Commit 1a8f828

Browse files
BridgeARMylesBorins
authored andcommitted
repl: improve completion
This improves the completion output by removing the nested special handling. It never fully worked as expected and required a lot of hacks to even keep it working halfway reliable. Our tests did not cover syntax errors though and those can not be handled by this implementation. Those break the layout and confuse the REPL. Besides that the completion now also works in case the current line has leading whitespace. Also improve the error output in case the completion fails. PR-URL: #30907 Reviewed-By: Michaël Zasso <targos@protonmail.com> Reviewed-By: Rich Trott <rtrott@gmail.com>
1 parent 3906e14 commit 1a8f828

File tree

6 files changed

+27
-83
lines changed

6 files changed

+27
-83
lines changed

lib/readline.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -501,7 +501,7 @@ Interface.prototype._tabComplete = function(lastKeypressWasTab) {
501501
self.resume();
502502

503503
if (err) {
504-
self._writeToOutput(`tab completion error ${inspect(err)}`);
504+
self._writeToOutput(`Tab completion error: ${inspect(err)}`);
505505
return;
506506
}
507507

lib/repl.js

Lines changed: 11 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,6 @@ const {
7171
deprecate
7272
} = require('internal/util');
7373
const { inspect } = require('internal/util/inspect');
74-
const Stream = require('stream');
7574
const vm = require('vm');
7675
const path = require('path');
7776
const fs = require('fs');
@@ -115,7 +114,6 @@ const {
115114
} = internalBinding('contextify');
116115

117116
const history = require('internal/repl/history');
118-
const { setImmediate } = require('timers');
119117

120118
// Lazy-loaded.
121119
let processTopLevelAwait;
@@ -124,7 +122,6 @@ const globalBuiltins =
124122
new Set(vm.runInNewContext('Object.getOwnPropertyNames(globalThis)'));
125123

126124
const parentModule = module;
127-
const replMap = new WeakMap();
128125
const domainSet = new WeakSet();
129126

130127
const kBufferedCommandSymbol = Symbol('bufferedCommand');
@@ -550,14 +547,13 @@ function REPLServer(prompt,
550547
self.lastError = e;
551548
}
552549

553-
const top = replMap.get(self);
554550
if (options[kStandaloneREPL] &&
555551
process.listenerCount('uncaughtException') !== 0) {
556552
process.nextTick(() => {
557553
process.emit('uncaughtException', e);
558-
top.clearBufferedCommand();
559-
top.lines.level = [];
560-
top.displayPrompt();
554+
self.clearBufferedCommand();
555+
self.lines.level = [];
556+
self.displayPrompt();
561557
});
562558
} else {
563559
if (errStack === '') {
@@ -583,10 +579,10 @@ function REPLServer(prompt,
583579
}
584580
// Normalize line endings.
585581
errStack += errStack.endsWith('\n') ? '' : '\n';
586-
top.outputStream.write(errStack);
587-
top.clearBufferedCommand();
588-
top.lines.level = [];
589-
top.displayPrompt();
582+
self.outputStream.write(errStack);
583+
self.clearBufferedCommand();
584+
self.lines.level = [];
585+
self.displayPrompt();
590586
}
591587
});
592588

@@ -897,7 +893,6 @@ exports.start = function(prompt,
897893
ignoreUndefined,
898894
replMode);
899895
if (!exports.repl) exports.repl = repl;
900-
replMap.set(repl, repl);
901896
return repl;
902897
};
903898

@@ -1034,23 +1029,6 @@ REPLServer.prototype.turnOffEditorMode = deprecate(
10341029
'REPLServer.turnOffEditorMode() is deprecated',
10351030
'DEP0078');
10361031

1037-
// A stream to push an array into a REPL
1038-
// used in REPLServer.complete
1039-
function ArrayStream() {
1040-
Stream.call(this);
1041-
1042-
this.run = function(data) {
1043-
for (let n = 0; n < data.length; n++)
1044-
this.emit('data', `${data[n]}\n`);
1045-
};
1046-
}
1047-
ObjectSetPrototypeOf(ArrayStream.prototype, Stream.prototype);
1048-
ObjectSetPrototypeOf(ArrayStream, Stream);
1049-
ArrayStream.prototype.readable = true;
1050-
ArrayStream.prototype.writable = true;
1051-
ArrayStream.prototype.resume = function() {};
1052-
ArrayStream.prototype.write = function() {};
1053-
10541032
const requireRE = /\brequire\s*\(['"](([\w@./-]+\/)?(?:[\w@./-]*))/;
10551033
const fsAutoCompleteRE = /fs(?:\.promises)?\.\s*[a-z][a-zA-Z]+\(\s*["'](.*)/;
10561034
const simpleExpressionRE =
@@ -1110,38 +1088,13 @@ REPLServer.prototype.complete = function() {
11101088
// Warning: This eval's code like "foo.bar.baz", so it will run property
11111089
// getter code.
11121090
function complete(line, callback) {
1113-
// There may be local variables to evaluate, try a nested REPL
1114-
if (this[kBufferedCommandSymbol] !== undefined &&
1115-
this[kBufferedCommandSymbol].length) {
1116-
// Get a new array of inputted lines
1117-
const tmp = this.lines.slice();
1118-
// Kill off all function declarations to push all local variables into
1119-
// global scope
1120-
for (let n = 0; n < this.lines.level.length; n++) {
1121-
const kill = this.lines.level[n];
1122-
if (kill.isFunction)
1123-
tmp[kill.line] = '';
1124-
}
1125-
const flat = new ArrayStream(); // Make a new "input" stream.
1126-
const magic = new REPLServer('', flat); // Make a nested REPL.
1127-
replMap.set(magic, replMap.get(this));
1128-
flat.run(tmp); // `eval` the flattened code.
1129-
// All this is only profitable if the nested REPL does not have a
1130-
// bufferedCommand.
1131-
if (!magic[kBufferedCommandSymbol]) {
1132-
magic._domain.on('error', (err) => {
1133-
setImmediate(() => {
1134-
throw err;
1135-
});
1136-
});
1137-
return magic.complete(line, callback);
1138-
}
1139-
}
1140-
11411091
// List of completion lists, one for each inheritance "level"
11421092
let completionGroups = [];
11431093
let completeOn, group;
11441094

1095+
// Ignore right whitespace. It could change the outcome.
1096+
line = line.trimLeft();
1097+
11451098
// REPL commands (e.g. ".break").
11461099
let filter;
11471100
let match = line.match(/^\s*\.(\w*)$/);
@@ -1465,8 +1418,7 @@ function _memory(cmd) {
14651418
// scope will not work for this function.
14661419
self.lines.level.push({
14671420
line: self.lines.length - 1,
1468-
depth: depth,
1469-
isFunction: /\bfunction\b/.test(cmd)
1421+
depth: depth
14701422
});
14711423
} else if (depth < 0) {
14721424
// Going... up.

test/fixtures/repl-tab-completion-nested-repls.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ const repl = require('repl');
3131
const putIn = new ArrayStream();
3232
const testMe = repl.start('', putIn);
3333

34-
// Some errors are passed to the domain, but do not callback
34+
// Some errors are passed to the domain, but do not callback.
3535
testMe._domain.on('error', function(err) {
3636
throw err;
3737
});

test/parallel/test-repl-save-load.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,8 +44,9 @@ testMe._domain.on('error', function(reason) {
4444
});
4545

4646
const testFile = [
47-
'let top = function() {',
48-
'let inner = {one:1};'
47+
'let inner = (function() {',
48+
' return {one:1};',
49+
'})()'
4950
];
5051
const saveFileName = join(tmpdir.path, 'test.save.js');
5152

test/parallel/test-repl-tab-complete-nested-repls.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,3 +19,5 @@ const result = spawnSync(process.execPath, [testFile]);
1919
// test here is to make sure that the error information bubbles up to the
2020
// calling process.
2121
assert.ok(result.status, 'testFile swallowed its error');
22+
const err = result.stderr.toString();
23+
assert.ok(err.includes('fhqwhgads'), err);

test/parallel/test-repl-tab-complete.js

Lines changed: 9 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -54,12 +54,9 @@ const putIn = new ArrayStream();
5454
const testMe = repl.start('', putIn);
5555

5656
// Some errors are passed to the domain, but do not callback
57-
testMe._domain.on('error', function(err) {
58-
assert.ifError(err);
59-
});
57+
testMe._domain.on('error', assert.ifError);
6058

6159
// Tab Complete will not break in an object literal
62-
putIn.run(['.clear']);
6360
putIn.run([
6461
'var inner = {',
6562
'one:1'
@@ -93,9 +90,7 @@ putIn.run([
9390
'var top = function() {',
9491
'var inner = {one:1};'
9592
]);
96-
testMe.complete('inner.o', common.mustCall(function(error, data) {
97-
assert.deepStrictEqual(data, works);
98-
}));
93+
testMe.complete('inner.o', getNoResultsFunction());
9994

10095
// When you close the function scope tab complete will not return the
10196
// locally scoped variable
@@ -111,9 +106,7 @@ putIn.run([
111106
' one:1',
112107
'};'
113108
]);
114-
testMe.complete('inner.o', common.mustCall(function(error, data) {
115-
assert.deepStrictEqual(data, works);
116-
}));
109+
testMe.complete('inner.o', getNoResultsFunction());
117110

118111
putIn.run(['.clear']);
119112

@@ -125,9 +118,7 @@ putIn.run([
125118
' one:1',
126119
'};'
127120
]);
128-
testMe.complete('inner.o', common.mustCall(function(error, data) {
129-
assert.deepStrictEqual(data, works);
130-
}));
121+
testMe.complete('inner.o', getNoResultsFunction());
131122

132123
putIn.run(['.clear']);
133124

@@ -140,9 +131,7 @@ putIn.run([
140131
' one:1',
141132
'};'
142133
]);
143-
testMe.complete('inner.o', common.mustCall(function(error, data) {
144-
assert.deepStrictEqual(data, works);
145-
}));
134+
testMe.complete('inner.o', getNoResultsFunction());
146135

147136
putIn.run(['.clear']);
148137

@@ -155,9 +144,7 @@ putIn.run([
155144
' one:1',
156145
'};'
157146
]);
158-
testMe.complete('inner.o', common.mustCall(function(error, data) {
159-
assert.deepStrictEqual(data, works);
160-
}));
147+
testMe.complete('inner.o', getNoResultsFunction());
161148

162149
putIn.run(['.clear']);
163150

@@ -204,7 +191,9 @@ const spaceTimeout = setTimeout(function() {
204191
}, 1000);
205192

206193
testMe.complete(' ', common.mustCall(function(error, data) {
207-
assert.deepStrictEqual(data, [[], undefined]);
194+
assert.ifError(error);
195+
assert.strictEqual(data[1], '');
196+
assert.ok(data[0].includes('globalThis'));
208197
clearTimeout(spaceTimeout);
209198
}));
210199

0 commit comments

Comments
 (0)