Skip to content

Commit

Permalink
module,repl: add subpaths builtins in addBuiltinLibsToObject
Browse files Browse the repository at this point in the history
This also fixes unrelated completions with e.g. `require('`.
  • Loading branch information
Mesteery committed Sep 25, 2021
1 parent fd86dad commit 10f6fd1
Show file tree
Hide file tree
Showing 6 changed files with 145 additions and 127 deletions.
7 changes: 7 additions & 0 deletions doc/api/repl.md
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,13 @@ global or scoped variable, the input `fs` will be evaluated on-demand as
> fs.createReadStream('./some/file');
```

Subpaths Node.js core modules are exposed with a underscore,
for example, `stream/consumers` will become `stream_consumers`.

```console
> stream_consumers.text(process.stdin);
```

#### Global uncaught exceptions
<!-- YAML
changes:
Expand Down
18 changes: 9 additions & 9 deletions lib/internal/modules/cjs/helpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ const {
SafeMap,
SafeSet,
StringPrototypeCharCodeAt,
StringPrototypeIncludes,
StringPrototypeReplace,
StringPrototypeSlice,
StringPrototypeStartsWith,
} = primordials;
Expand Down Expand Up @@ -150,10 +150,8 @@ function addBuiltinLibsToObject(object, dummyModuleName) {
const dummyModule = new Module(dummyModuleName);

ArrayPrototypeForEach(builtinModules, (name) => {
// Neither add underscored modules, nor ones that contain slashes (e.g.,
// 'fs/promises') or ones that are already defined.
// Neither add underscored modules or ones that are already defined.
if (StringPrototypeStartsWith(name, '_') ||
StringPrototypeIncludes(name, '/') ||
ObjectPrototypeHasOwnProperty(object, name)) {
return;
}
Expand All @@ -163,21 +161,23 @@ function addBuiltinLibsToObject(object, dummyModuleName) {
// - Allowing the user to re-assign these variables as if there were no
// pre-existing globals with the same name.

const realName = StringPrototypeReplace(name, '/', '_')

const setReal = (val) => {
// Deleting the property before re-assigning it disables the
// getter/setter mechanism.
delete object[name];
object[name] = val;
delete object[realName];
object[realName] = val;
};

ObjectDefineProperty(object, name, {
ObjectDefineProperty(object, realName, {
get: () => {
const lib = dummyModule.require(name);

// Disable the current getter/setter and set up a new
// non-enumerable property.
delete object[name];
ObjectDefineProperty(object, name, {
delete object[realName];
ObjectDefineProperty(object, realName, {
get: () => lib,
set: setReal,
configurable: true,
Expand Down
220 changes: 109 additions & 111 deletions lib/repl.js
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ const { Console } = require('console');
const CJSModule = require('internal/modules/cjs/loader').Module;
let _builtinLibs = ArrayPrototypeFilter(
CJSModule.builtinModules,
(e) => !StringPrototypeStartsWith(e, '_') && !StringPrototypeIncludes(e, '/')
(e) => !StringPrototypeStartsWith(e, '_'),
);
const nodeSchemeBuiltinLibs = ArrayPrototypeMap(
_builtinLibs, (lib) => `node:${lib}`);
Expand Down Expand Up @@ -178,6 +178,7 @@ const {
extensionFormatMap,
legacyExtensionFormatMap,
} = require('internal/modules/esm/get_format');
const console = require('console');

let nextREPLResourceNumber = 1;
// This prevents v8 code cache from getting confused and using a different
Expand Down Expand Up @@ -1276,7 +1277,6 @@ function complete(line, callback) {

// Ignore right whitespace. It could change the outcome.
line = StringPrototypeTrimLeft(line);

// REPL commands (e.g. ".break").
let filter = '';
if (RegExpPrototypeTest(/^\s*\.(\w*)$/, line)) {
Expand All @@ -1285,138 +1285,137 @@ function complete(line, callback) {
if (completeOn.length) {
filter = completeOn;
}
} else if (RegExpPrototypeTest(requireRE, line) &&
this.allowBlockingCompletions) {
} else if (RegExpPrototypeTest(requireRE, line)) {
// require('...<Tab>')
const extensions = ObjectKeys(this.context.require.extensions);
const indexes = ArrayPrototypeMap(extensions,
(extension) => `index${extension}`);
ArrayPrototypePush(indexes, 'package.json', 'index');

const match = StringPrototypeMatch(line, requireRE);
completeOn = match[1];
const subdir = match[2] || '';
filter = completeOn;
group = [];
let paths = [];

if (completeOn === '.') {
group = ['./', '../'];
} else if (completeOn === '..') {
group = ['../'];
} else if (RegExpPrototypeTest(/^\.\.?\//, completeOn)) {
paths = [process.cwd()];
} else {
paths = ArrayPrototypeConcat(module.paths, CJSModule.globalPaths);
}

ArrayPrototypeForEach(paths, (dir) => {
dir = path.resolve(dir, subdir);
const dirents = gracefulReaddir(dir, { withFileTypes: true }) || [];
ArrayPrototypeForEach(dirents, (dirent) => {
if (RegExpPrototypeTest(versionedFileNamesRe, dirent.name) ||
dirent.name === '.npm') {
// Exclude versioned names that 'npm' installs.
return;
}
const extension = path.extname(dirent.name);
const base = StringPrototypeSlice(dirent.name, 0, -extension.length);
if (!dirent.isDirectory()) {
if (StringPrototypeIncludes(extensions, extension) &&
(!subdir || base !== 'index')) {
ArrayPrototypePush(group, `${subdir}${base}`);
if (this.allowBlockingCompletions) {
const extensions = ObjectKeys(this.context.require.extensions);
const indexes = ArrayPrototypeMap(extensions,
(extension) => `index${extension}`);
ArrayPrototypePush(indexes, 'package.json', 'index');

group = [];
let paths = [];

if (completeOn === '.') {
group = ['./', '../'];
} else if (completeOn === '..') {
group = ['../'];
} else if (RegExpPrototypeTest(/^\.\.?\//, completeOn)) {
paths = [process.cwd()];
} else {
paths = ArrayPrototypeConcat(module.paths, CJSModule.globalPaths);
}

ArrayPrototypeForEach(paths, (dir) => {
dir = path.resolve(dir, subdir);
const dirents = gracefulReaddir(dir, { withFileTypes: true }) || [];
ArrayPrototypeForEach(dirents, (dirent) => {
if (RegExpPrototypeTest(versionedFileNamesRe, dirent.name) ||
dirent.name === '.npm') {
// Exclude versioned names that 'npm' installs.
return;
}
return;
}
ArrayPrototypePush(group, `${subdir}${dirent.name}/`);
const absolute = path.resolve(dir, dirent.name);
if (ArrayPrototypeSome(
gracefulReaddir(absolute) || [],
(subfile) => ArrayPrototypeIncludes(indexes, subfile)
)) {
ArrayPrototypePush(group, `${subdir}${dirent.name}`);
}
const extension = path.extname(dirent.name);
const base = StringPrototypeSlice(dirent.name, 0, -extension.length);
if (!dirent.isDirectory()) {
if (StringPrototypeIncludes(extensions, extension) &&
(!subdir || base !== 'index')) {
ArrayPrototypePush(group, `${subdir}${base}`);
}
return;
}
ArrayPrototypePush(group, `${subdir}${dirent.name}/`);
const absolute = path.resolve(dir, dirent.name);
if (ArrayPrototypeSome(
gracefulReaddir(absolute) || [],
(subfile) => ArrayPrototypeIncludes(indexes, subfile)
)) {
ArrayPrototypePush(group, `${subdir}${dirent.name}`);
}
});
});
});
if (group.length) {
ArrayPrototypePush(completionGroups, group);
if (group.length) {
ArrayPrototypePush(completionGroups, group);
}
}

if (!subdir) {
ArrayPrototypePush(completionGroups, _builtinLibs, nodeSchemeBuiltinLibs);
}
} else if (RegExpPrototypeTest(importRE, line) &&
this.allowBlockingCompletions) {
ArrayPrototypePush(completionGroups, _builtinLibs, nodeSchemeBuiltinLibs);
} else if (RegExpPrototypeTest(importRE, line)) {
// import('...<Tab>')
// File extensions that can be imported:
const extensions = ObjectKeys(
getOptionValue('--experimental-specifier-resolution') === 'node' ?
legacyExtensionFormatMap :
extensionFormatMap);

// Only used when loading bare module specifiers from `node_modules`:
const indexes = ArrayPrototypeMap(extensions, (ext) => `index${ext}`);
ArrayPrototypePush(indexes, 'package.json');

const match = StringPrototypeMatch(line, importRE);
completeOn = match[1];
const subdir = match[2] || '';
filter = completeOn;
group = [];
let paths = [];
if (completeOn === '.') {
group = ['./', '../'];
} else if (completeOn === '..') {
group = ['../'];
} else if (RegExpPrototypeTest(/^\.\.?\//, completeOn)) {
paths = [process.cwd()];
} else {
paths = ArrayPrototypeSlice(module.paths);
}
if (this.allowBlockingCompletions) {
// File extensions that can be imported:
const extensions = ObjectKeys(
getOptionValue('--experimental-specifier-resolution') === 'node' ?
legacyExtensionFormatMap :
extensionFormatMap);

// Only used when loading bare module specifiers from `node_modules`:
const indexes = ArrayPrototypeMap(extensions, (ext) => `index${ext}`);
ArrayPrototypePush(indexes, 'package.json');

group = [];
let paths = [];
if (completeOn === '.') {
group = ['./', '../'];
} else if (completeOn === '..') {
group = ['../'];
} else if (RegExpPrototypeTest(/^\.\.?\//, completeOn)) {
paths = [process.cwd()];
} else {
paths = ArrayPrototypeSlice(module.paths);
}

ArrayPrototypeForEach(paths, (dir) => {
dir = path.resolve(dir, subdir);
const isInNodeModules = path.basename(dir) === 'node_modules';
const dirents = gracefulReaddir(dir, { withFileTypes: true }) || [];
ArrayPrototypeForEach(dirents, (dirent) => {
const { name } = dirent;
if (RegExpPrototypeTest(versionedFileNamesRe, name) ||
name === '.npm') {
// Exclude versioned names that 'npm' installs.
return;
}
ArrayPrototypeForEach(paths, (dir) => {
dir = path.resolve(dir, subdir);
const isInNodeModules = path.basename(dir) === 'node_modules';
const dirents = gracefulReaddir(dir, { withFileTypes: true }) || [];
ArrayPrototypeForEach(dirents, (dirent) => {
const { name } = dirent;
if (RegExpPrototypeTest(versionedFileNamesRe, name) ||
name === '.npm') {
// Exclude versioned names that 'npm' installs.
return;
}

if (!dirent.isDirectory()) {
const extension = path.extname(name);
if (StringPrototypeIncludes(extensions, extension)) {
ArrayPrototypePush(group, `${subdir}${name}`);
if (!dirent.isDirectory()) {
const extension = path.extname(name);
if (StringPrototypeIncludes(extensions, extension)) {
ArrayPrototypePush(group, `${subdir}${name}`);
}
return;
}
return;
}

ArrayPrototypePush(group, `${subdir}${name}/`);
if (!subdir && isInNodeModules) {
const absolute = path.resolve(dir, name);
const subfiles = gracefulReaddir(absolute) || [];
if (ArrayPrototypeSome(subfiles, (subfile) => {
return ArrayPrototypeIncludes(indexes, subfile);
})) {
ArrayPrototypePush(group, `${subdir}${name}`);
ArrayPrototypePush(group, `${subdir}${name}/`);
if (!subdir && isInNodeModules) {
const absolute = path.resolve(dir, name);
const subfiles = gracefulReaddir(absolute) || [];
if (ArrayPrototypeSome(subfiles, (subfile) => {
return ArrayPrototypeIncludes(indexes, subfile);
})) {
ArrayPrototypePush(group, `${subdir}${name}`);
}
}
}
});
});
});

if (group.length) {
ArrayPrototypePush(completionGroups, group);
if (group.length) {
ArrayPrototypePush(completionGroups, group);
}
}

if (!subdir) {
ArrayPrototypePush(completionGroups, _builtinLibs, nodeSchemeBuiltinLibs);
ArrayPrototypePush(completionGroups, _builtinLibs, nodeSchemeBuiltinLibs);
} else if (RegExpPrototypeTest(fsAutoCompleteRE, line)) {
if (this.allowBlockingCompletions) {
({ 0: completionGroups, 1: completeOn } = completeFSFunctions(line));
}
} else if (RegExpPrototypeTest(fsAutoCompleteRE, line) &&
this.allowBlockingCompletions) {
({ 0: completionGroups, 1: completeOn } = completeFSFunctions(line));
// Handle variable member lookup.
// We support simple chained expressions like the following (no function
// calls, etc.). That is for simplicity and also because we *eval* that
Expand Down Expand Up @@ -1560,7 +1559,6 @@ function complete(line, callback) {
if (completions[0] === '') {
ArrayPrototypeShift(completions);
}

callback(null, [completions, completeOn]);
}
}
Expand Down
19 changes: 18 additions & 1 deletion test/parallel/test-repl-autolibs.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,6 @@ function test1() {
putIn.write = function(data) {
gotWrite = true;
if (data.length) {

// Inspect output matches repl output
assert.strictEqual(data,
`${util.inspect(require('fs'), null, 2, false)}\n`);
Expand All @@ -59,6 +58,7 @@ function test2() {
assert.strictEqual(data, '{}\n');
// Original value wasn't overwritten
assert.strictEqual(val, global.url);
test3();
}
};
const val = {};
Expand All @@ -68,3 +68,20 @@ function test2() {
putIn.run(['url']);
assert(gotWrite);
}

function test3() {
let gotWrite = false;
putIn.write = function(data) {
gotWrite = true;
if (data.length) {
// Inspect output matches repl output
assert.strictEqual(data,
`${util.inspect(require('stream/consumers'), null, 2, false)}\n`);
// Globally added lib matches required lib
assert.strictEqual(global.stream_consumers, require('stream/consumers'));
}
};
assert(!gotWrite);
putIn.run(['stream_consumers']);
assert(gotWrite);
}
4 changes: 1 addition & 3 deletions test/parallel/test-repl-tab-complete-import.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,7 @@ const ArrayStream = require('../common/arraystream');
const fixtures = require('../common/fixtures');
const assert = require('assert');
const { builtinModules } = require('module');
const publicModules = builtinModules.filter(
(lib) => !lib.startsWith('_') && !lib.includes('/'),
);
const publicModules = builtinModules.filter((lib) => !lib.startsWith('_'));

if (!common.isMainThread)
common.skip('process.chdir is not available in Workers');
Expand Down

0 comments on commit 10f6fd1

Please sign in to comment.