Skip to content

Commit 1330cfc

Browse files
BridgeARcodebytere
authored andcommitted
repl: support optional chaining during autocompletion
This makes sure the autocompletion is able to handle optional chaining notations. Signed-off-by: Ruben Bridgewater <[email protected]> PR-URL: #33450 Reviewed-By: James M Snell <[email protected]> Reviewed-By: Benjamin Gruenbaum <[email protected]>
1 parent a90b96f commit 1330cfc

File tree

2 files changed

+25
-8
lines changed

2 files changed

+25
-8
lines changed

lib/repl.js

+12-8
Original file line numberDiff line numberDiff line change
@@ -1066,7 +1066,7 @@ REPLServer.prototype.turnOffEditorMode = deprecate(
10661066
const requireRE = /\brequire\s*\(\s*['"`](([\w@./-]+\/)?(?:[\w@./-]*))(?![^'"`])$/;
10671067
const fsAutoCompleteRE = /fs(?:\.promises)?\.\s*[a-z][a-zA-Z]+\(\s*["'](.*)/;
10681068
const simpleExpressionRE =
1069-
/(?:[a-zA-Z_$](?:\w|\$)*\.)*[a-zA-Z_$](?:\w|\$)*\.?$/;
1069+
/(?:[a-zA-Z_$](?:\w|\$)*\??\.)*[a-zA-Z_$](?:\w|\$)*\??\.?$/;
10701070

10711071
function isIdentifier(str) {
10721072
if (str === '') {
@@ -1138,7 +1138,7 @@ function complete(line, callback) {
11381138
line = line.trimLeft();
11391139

11401140
// REPL commands (e.g. ".break").
1141-
let filter;
1141+
let filter = '';
11421142
if (/^\s*\.(\w*)$/.test(line)) {
11431143
completionGroups.push(ObjectKeys(this.commands));
11441144
completeOn = line.match(/^\s*\.(\w*)$/)[1];
@@ -1253,10 +1253,8 @@ function complete(line, callback) {
12531253
let expr;
12541254
completeOn = (match ? match[0] : '');
12551255
if (line.length === 0) {
1256-
filter = '';
12571256
expr = '';
12581257
} else if (line[line.length - 1] === '.') {
1259-
filter = '';
12601258
expr = match[0].slice(0, match[0].length - 1);
12611259
} else {
12621260
const bits = match[0].split('.');
@@ -1285,6 +1283,12 @@ function complete(line, callback) {
12851283
return;
12861284
}
12871285

1286+
let chaining = '.';
1287+
if (expr[expr.length - 1] === '?') {
1288+
expr = expr.slice(0, -1);
1289+
chaining = '?.';
1290+
}
1291+
12881292
const evalExpr = `try { ${expr} } catch {}`;
12891293
this.eval(evalExpr, this.context, 'repl', (e, obj) => {
12901294
if (obj != null) {
@@ -1316,12 +1320,12 @@ function complete(line, callback) {
13161320
}
13171321

13181322
if (memberGroups.length) {
1319-
for (let i = 0; i < memberGroups.length; i++) {
1320-
completionGroups.push(
1321-
memberGroups[i].map((member) => `${expr}.${member}`));
1323+
expr += chaining;
1324+
for (const group of memberGroups) {
1325+
completionGroups.push(group.map((member) => `${expr}${member}`));
13221326
}
13231327
if (filter) {
1324-
filter = `${expr}.${filter}`;
1328+
filter = `${expr}${filter}`;
13251329
}
13261330
}
13271331

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

+13
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,19 @@ testMe.complete('console.lo', common.mustCall(function(error, data) {
6868
assert.deepStrictEqual(data, [['console.log'], 'console.lo']);
6969
}));
7070

71+
testMe.complete('console?.lo', common.mustCall((error, data) => {
72+
assert.deepStrictEqual(data, [['console?.log'], 'console?.lo']);
73+
}));
74+
75+
testMe.complete('console?.zzz', common.mustCall((error, data) => {
76+
assert.deepStrictEqual(data, [[], 'console?.zzz']);
77+
}));
78+
79+
testMe.complete('console?.', common.mustCall((error, data) => {
80+
assert(data[0].includes('console?.log'));
81+
assert.strictEqual(data[1], 'console?.');
82+
}));
83+
7184
// Tab Complete will return globally scoped variables
7285
putIn.run(['};']);
7386
testMe.complete('inner.o', common.mustCall(function(error, data) {

0 commit comments

Comments
 (0)