Skip to content

Commit 71d6599

Browse files
BridgeARcodebytere
authored andcommitted
repl: simplify repl autocompletion
This refactors the repl autocompletion code for simplicity and readability. 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 1330cfc commit 71d6599

File tree

1 file changed

+58
-79
lines changed

1 file changed

+58
-79
lines changed

lib/repl.js

+58-79
Original file line numberDiff line numberDiff line change
@@ -1111,12 +1111,28 @@ REPLServer.prototype.complete = function() {
11111111
this.completer.apply(this, arguments);
11121112
};
11131113

1114-
function gracefulOperation(fn, args, alternative) {
1114+
function gracefulReaddir(...args) {
11151115
try {
1116-
return fn(...args);
1117-
} catch {
1118-
return alternative;
1116+
return fs.readdirSync(...args);
1117+
} catch {}
1118+
}
1119+
1120+
function completeFSFunctions(line) {
1121+
let baseName = '';
1122+
let filePath = line.match(fsAutoCompleteRE)[1];
1123+
let fileList = gracefulReaddir(filePath, { withFileTypes: true });
1124+
1125+
if (!fileList) {
1126+
baseName = path.basename(filePath);
1127+
filePath = path.dirname(filePath);
1128+
fileList = gracefulReaddir(filePath, { withFileTypes: true }) || [];
11191129
}
1130+
1131+
const completions = fileList
1132+
.filter((dirent) => dirent.name.startsWith(baseName))
1133+
.map((d) => d.name);
1134+
1135+
return [[completions], baseName];
11201136
}
11211137

11221138
// Provide a list of completions for the given leading text. This is
@@ -1145,8 +1161,6 @@ function complete(line, callback) {
11451161
if (completeOn.length) {
11461162
filter = completeOn;
11471163
}
1148-
1149-
completionGroupsLoaded();
11501164
} else if (requireRE.test(line)) {
11511165
// require('...<Tab>')
11521166
const extensions = ObjectKeys(this.context.require.extensions);
@@ -1173,11 +1187,7 @@ function complete(line, callback) {
11731187

11741188
for (let dir of paths) {
11751189
dir = path.resolve(dir, subdir);
1176-
const dirents = gracefulOperation(
1177-
fs.readdirSync,
1178-
[dir, { withFileTypes: true }],
1179-
[]
1180-
);
1190+
const dirents = gracefulReaddir(dir, { withFileTypes: true }) || [];
11811191
for (const dirent of dirents) {
11821192
if (versionedFileNamesRe.test(dirent.name) || dirent.name === '.npm') {
11831193
// Exclude versioned names that 'npm' installs.
@@ -1193,7 +1203,7 @@ function complete(line, callback) {
11931203
}
11941204
group.push(`${subdir}${dirent.name}/`);
11951205
const absolute = path.resolve(dir, dirent.name);
1196-
const subfiles = gracefulOperation(fs.readdirSync, [absolute], []);
1206+
const subfiles = gracefulReaddir(absolute) || [];
11971207
for (const subfile of subfiles) {
11981208
if (indexes.includes(subfile)) {
11991209
group.push(`${subdir}${dirent.name}`);
@@ -1209,31 +1219,8 @@ function complete(line, callback) {
12091219
if (!subdir) {
12101220
completionGroups.push(_builtinLibs);
12111221
}
1212-
1213-
completionGroupsLoaded();
12141222
} else if (fsAutoCompleteRE.test(line)) {
1215-
filter = '';
1216-
let filePath = line.match(fsAutoCompleteRE)[1];
1217-
let fileList;
1218-
1219-
try {
1220-
fileList = fs.readdirSync(filePath, { withFileTypes: true });
1221-
completionGroups.push(fileList.map((dirent) => dirent.name));
1222-
completeOn = '';
1223-
} catch {
1224-
try {
1225-
const baseName = path.basename(filePath);
1226-
filePath = path.dirname(filePath);
1227-
fileList = fs.readdirSync(filePath, { withFileTypes: true });
1228-
const filteredValue = fileList.filter((d) =>
1229-
d.name.startsWith(baseName))
1230-
.map((d) => d.name);
1231-
completionGroups.push(filteredValue);
1232-
completeOn = baseName;
1233-
} catch {}
1234-
}
1235-
1236-
completionGroupsLoaded();
1223+
[completionGroups, completeOn] = completeFSFunctions(line);
12371224
// Handle variable member lookup.
12381225
// We support simple chained expressions like the following (no function
12391226
// calls, etc.). That is for simplicity and also because we *eval* that
@@ -1245,25 +1232,22 @@ function complete(line, callback) {
12451232
// foo<|> # all scope vars with filter 'foo'
12461233
// foo.<|> # completions for 'foo' with filter ''
12471234
} else if (line.length === 0 || /\w|\.|\$/.test(line[line.length - 1])) {
1248-
const match = simpleExpressionRE.exec(line);
1235+
const [match] = simpleExpressionRE.exec(line) || [''];
12491236
if (line.length !== 0 && !match) {
12501237
completionGroupsLoaded();
12511238
return;
12521239
}
1253-
let expr;
1254-
completeOn = (match ? match[0] : '');
1255-
if (line.length === 0) {
1256-
expr = '';
1257-
} else if (line[line.length - 1] === '.') {
1258-
expr = match[0].slice(0, match[0].length - 1);
1259-
} else {
1260-
const bits = match[0].split('.');
1240+
let expr = '';
1241+
completeOn = match;
1242+
if (line.endsWith('.')) {
1243+
expr = match.slice(0, -1);
1244+
} else if (line.length !== 0) {
1245+
const bits = match.split('.');
12611246
filter = bits.pop();
12621247
expr = bits.join('.');
12631248
}
12641249

12651250
// Resolve expr and get its completions.
1266-
const memberGroups = [];
12671251
if (!expr) {
12681252
// Get global vars synchronously
12691253
completionGroups.push(getGlobalLexicalScopeNames(this[kContextId]));
@@ -1284,39 +1268,34 @@ function complete(line, callback) {
12841268
}
12851269

12861270
let chaining = '.';
1287-
if (expr[expr.length - 1] === '?') {
1271+
if (expr.endsWith('?')) {
12881272
expr = expr.slice(0, -1);
12891273
chaining = '?.';
12901274
}
12911275

1276+
const memberGroups = [];
12921277
const evalExpr = `try { ${expr} } catch {}`;
12931278
this.eval(evalExpr, this.context, 'repl', (e, obj) => {
1294-
if (obj != null) {
1295-
if (typeof obj === 'object' || typeof obj === 'function') {
1296-
try {
1297-
memberGroups.push(filteredOwnPropertyNames(obj));
1298-
} catch {
1299-
// Probably a Proxy object without `getOwnPropertyNames` trap.
1300-
// We simply ignore it here, as we don't want to break the
1301-
// autocompletion. Fixes the bug
1302-
// https://github.com/nodejs/node/issues/2119
1303-
}
1279+
try {
1280+
let p;
1281+
if ((typeof obj === 'object' && obj !== null) ||
1282+
typeof obj === 'function') {
1283+
memberGroups.push(filteredOwnPropertyNames(obj));
1284+
p = ObjectGetPrototypeOf(obj);
1285+
} else {
1286+
p = obj.constructor ? obj.constructor.prototype : null;
13041287
}
1305-
// Works for non-objects
1306-
try {
1307-
let p;
1308-
if (typeof obj === 'object' || typeof obj === 'function') {
1309-
p = ObjectGetPrototypeOf(obj);
1310-
} else {
1311-
p = obj.constructor ? obj.constructor.prototype : null;
1312-
}
1313-
// Circular refs possible? Let's guard against that.
1314-
let sentinel = 5;
1315-
while (p !== null && sentinel-- !== 0) {
1316-
memberGroups.push(filteredOwnPropertyNames(p));
1317-
p = ObjectGetPrototypeOf(p);
1318-
}
1319-
} catch {}
1288+
// Circular refs possible? Let's guard against that.
1289+
let sentinel = 5;
1290+
while (p !== null && sentinel-- !== 0) {
1291+
memberGroups.push(filteredOwnPropertyNames(p));
1292+
p = ObjectGetPrototypeOf(p);
1293+
}
1294+
} catch {
1295+
// Maybe a Proxy object without `getOwnPropertyNames` trap.
1296+
// We simply ignore it here, as we don't want to break the
1297+
// autocompletion. Fixes the bug
1298+
// https://github.com/nodejs/node/issues/2119
13201299
}
13211300

13221301
if (memberGroups.length) {
@@ -1331,21 +1310,21 @@ function complete(line, callback) {
13311310

13321311
completionGroupsLoaded();
13331312
});
1334-
} else {
1335-
completionGroupsLoaded();
1313+
return;
13361314
}
13371315

1316+
return completionGroupsLoaded();
1317+
13381318
// Will be called when all completionGroups are in place
13391319
// Useful for async autocompletion
13401320
function completionGroupsLoaded() {
13411321
// Filter, sort (within each group), uniq and merge the completion groups.
13421322
if (completionGroups.length && filter) {
13431323
const newCompletionGroups = [];
1344-
for (let i = 0; i < completionGroups.length; i++) {
1345-
group = completionGroups[i]
1346-
.filter((elem) => elem.indexOf(filter) === 0);
1347-
if (group.length) {
1348-
newCompletionGroups.push(group);
1324+
for (const group of completionGroups) {
1325+
const filteredGroup = group.filter((str) => str.startsWith(filter));
1326+
if (filteredGroup.length) {
1327+
newCompletionGroups.push(filteredGroup);
13491328
}
13501329
}
13511330
completionGroups = newCompletionGroups;

0 commit comments

Comments
 (0)