@@ -1046,7 +1046,7 @@ REPLServer.prototype.turnOffEditorMode = deprecate( |
1046 | 1046 | 'REPLServer.turnOffEditorMode() is deprecated', |
1047 | 1047 | 'DEP0078'); |
1048 | 1048 |
|
1049 | | -const requireRE = /\brequire\s*\(['"](([\w@./-]+\/)?(?:[\w@./-]*))/; |
| 1049 | +const requireRE = /\brequire\s*\(\s*['"`](([\w@./-]+\/)?(?:[\w@./-]*))(?![^'"`])$/; |
1050 | 1050 | const fsAutoCompleteRE = /fs(?:\.promises)?\.\s*[a-z][a-zA-Z]+\(\s*["'](.*)/; |
1051 | 1051 | const simpleExpressionRE = |
1052 | 1052 | /(?:[a-zA-Z_$](?:\w|\$)*\.)*[a-zA-Z_$](?:\w|\$)*\.?$/; |
@@ -1094,8 +1094,13 @@ REPLServer.prototype.complete = function() { |
1094 | 1094 | this.completer.apply(this, arguments); |
1095 | 1095 | }; |
1096 | 1096 |
|
1097 | | -// TODO: Native module names should be auto-resolved. |
1098 | | -// That improves the auto completion. |
| 1097 | +function gracefulOperation(fn, args, alternative) { |
| 1098 | + try { |
| 1099 | + return fn(...args); |
| 1100 | + } catch { |
| 1101 | + return alternative; |
| 1102 | + } |
| 1103 | +} |
1099 | 1104 |
|
1100 | 1105 | // Provide a list of completions for the given leading text. This is |
1101 | 1106 | // given to the readline interface for handling tab completion. |
@@ -1117,26 +1122,25 @@ function complete(line, callback) { |
1117 | 1122 |
|
1118 | 1123 | // REPL commands (e.g. ".break"). |
1119 | 1124 | let filter; |
1120 | | - let match = line.match(/^\s*\.(\w*)$/); |
1121 | | - if (match) { |
| 1125 | + if (/^\s*\.(\w*)$/.test(line)) { |
1122 | 1126 | completionGroups.push(ObjectKeys(this.commands)); |
1123 | | - completeOn = match[1]; |
1124 | | - if (match[1].length) { |
1125 | | - filter = match[1]; |
| 1127 | + completeOn = line.match(/^\s*\.(\w*)$/)[1]; |
| 1128 | + if (completeOn.length) { |
| 1129 | + filter = completeOn; |
1126 | 1130 | } |
1127 | 1131 |
|
1128 | 1132 | completionGroupsLoaded(); |
1129 | | - } else if (match = line.match(requireRE)) { |
| 1133 | + } else if (requireRE.test(line)) { |
1130 | 1134 | // require('...<Tab>') |
1131 | | - const exts = ObjectKeys(this.context.require.extensions); |
1132 | | - const indexRe = new RegExp('^index(?:' + exts.map(regexpEscape).join('|') + |
1133 | | - ')$'); |
| 1135 | + const extensions = ObjectKeys(this.context.require.extensions); |
| 1136 | + const indexes = extensions.map((extension) => `index${extension}`); |
| 1137 | + indexes.push('package.json', 'index'); |
1134 | 1138 | const versionedFileNamesRe = /-\d+\.\d+/; |
1135 | 1139 |
|
| 1140 | + const match = line.match(requireRE); |
1136 | 1141 | completeOn = match[1]; |
1137 | 1142 | const subdir = match[2] || ''; |
1138 | | - filter = match[1]; |
1139 | | - let dir, files, subfiles, isDirectory; |
| 1143 | + filter = completeOn; |
1140 | 1144 | group = []; |
1141 | 1145 | let paths = []; |
1142 | 1146 |
|
@@ -1150,41 +1154,34 @@ function complete(line, callback) { |
1150 | 1154 | paths = module.paths.concat(CJSModule.globalPaths); |
1151 | 1155 | } |
1152 | 1156 |
|
1153 | | - for (let i = 0; i < paths.length; i++) { |
1154 | | - dir = path.resolve(paths[i], subdir); |
1155 | | - try { |
1156 | | - files = fs.readdirSync(dir); |
1157 | | - } catch { |
1158 | | - continue; |
1159 | | - } |
1160 | | - for (let f = 0; f < files.length; f++) { |
1161 | | - const name = files[f]; |
1162 | | - const ext = path.extname(name); |
1163 | | - const base = name.slice(0, -ext.length); |
1164 | | - if (versionedFileNamesRe.test(base) || name === '.npm') { |
| 1157 | + for (let dir of paths) { |
| 1158 | + dir = path.resolve(dir, subdir); |
| 1159 | + const dirents = gracefulOperation( |
| 1160 | + fs.readdirSync, |
| 1161 | + [dir, { withFileTypes: true }], |
| 1162 | + [] |
| 1163 | + ); |
| 1164 | + for (const dirent of dirents) { |
| 1165 | + if (versionedFileNamesRe.test(dirent.name) || dirent.name === '.npm') { |
1165 | 1166 | // Exclude versioned names that 'npm' installs. |
1166 | 1167 | continue; |
1167 | 1168 | } |
1168 | | - const abs = path.resolve(dir, name); |
1169 | | - try { |
1170 | | - isDirectory = fs.statSync(abs).isDirectory(); |
1171 | | - } catch { |
| 1169 | + const extension = path.extname(dirent.name); |
| 1170 | + const base = dirent.name.slice(0, -extension.length); |
| 1171 | + if (!dirent.isDirectory()) { |
| 1172 | + if (extensions.includes(extension) && (!subdir || base !== 'index')) { |
| 1173 | + group.push(`${subdir}${base}`); |
| 1174 | + } |
1172 | 1175 | continue; |
1173 | 1176 | } |
1174 | | - if (isDirectory) { |
1175 | | - group.push(subdir + name + '/'); |
1176 | | - try { |
1177 | | - subfiles = fs.readdirSync(abs); |
1178 | | - } catch { |
1179 | | - continue; |
| 1177 | + group.push(`${subdir}${dirent.name}/`); |
| 1178 | + const absolute = path.resolve(dir, dirent.name); |
| 1179 | + const subfiles = gracefulOperation(fs.readdirSync, [absolute], []); |
| 1180 | + for (const subfile of subfiles) { |
| 1181 | + if (indexes.includes(subfile)) { |
| 1182 | + group.push(`${subdir}${dirent.name}`); |
| 1183 | + break; |
1180 | 1184 | } |
1181 | | - for (let s = 0; s < subfiles.length; s++) { |
1182 | | - if (indexRe.test(subfiles[s])) { |
1183 | | - group.push(subdir + name); |
1184 | | - } |
1185 | | - } |
1186 | | - } else if (exts.includes(ext) && (!subdir || base !== 'index')) { |
1187 | | - group.push(subdir + base); |
1188 | 1185 | } |
1189 | 1186 | } |
1190 | 1187 | } |
@@ -1197,11 +1194,10 @@ function complete(line, callback) { |
1197 | 1194 | } |
1198 | 1195 |
|
1199 | 1196 | completionGroupsLoaded(); |
1200 | | - } else if (match = line.match(fsAutoCompleteRE)) { |
1201 | | - |
1202 | | - let filePath = match[1]; |
1203 | | - let fileList; |
| 1197 | + } else if (fsAutoCompleteRE.test(line)) { |
1204 | 1198 | filter = ''; |
| 1199 | + let filePath = line.match(fsAutoCompleteRE)[1]; |
| 1200 | + let fileList; |
1205 | 1201 |
|
1206 | 1202 | try { |
1207 | 1203 | fileList = fs.readdirSync(filePath, { withFileTypes: true }); |
@@ -1232,7 +1228,7 @@ function complete(line, callback) { |
1232 | 1228 | // foo<|> # all scope vars with filter 'foo' |
1233 | 1229 | // foo.<|> # completions for 'foo' with filter '' |
1234 | 1230 | } else if (line.length === 0 || /\w|\.|\$/.test(line[line.length - 1])) { |
1235 | | - match = simpleExpressionRE.exec(line); |
| 1231 | + const match = simpleExpressionRE.exec(line); |
1236 | 1232 | if (line.length !== 0 && !match) { |
1237 | 1233 | completionGroupsLoaded(); |
1238 | 1234 | return; |
@@ -1582,10 +1578,6 @@ function defineDefaultCommands(repl) { |
1582 | 1578 | } |
1583 | 1579 | } |
1584 | 1580 |
|
1585 | | -function regexpEscape(s) { |
1586 | | - return s.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&'); |
1587 | | -} |
1588 | | - |
1589 | 1581 | function Recoverable(err) { |
1590 | 1582 | this.err = err; |
1591 | 1583 | } |
|