Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

module: self referential modules in repl or -r #32261

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
34 changes: 27 additions & 7 deletions lib/internal/modules/cjs/loader.js
Expand Up @@ -29,6 +29,7 @@ module.exports = {

const {
ArrayIsArray,
ArrayPrototypeJoin,
Error,
JSONParse,
Map,
Expand Down Expand Up @@ -424,7 +425,23 @@ function findLongestRegisteredExtension(filename) {
return '.js';
}

function trySelfParentPath(parent) {
if (!parent) return false;

if (parent.filename) {
return parent.filename;
} else if (parent.id === '<repl>' || parent.id === 'internal/preload') {
try {
return process.cwd() + path.sep;
} catch {
return false;
}
}
}

function trySelf(parentPath, request) {
if (!parentPath) return false;
guybedford marked this conversation as resolved.
Show resolved Hide resolved

const { data: pkg, path: basePath } = readPackageScope(parentPath) || {};
if (!pkg || pkg.exports === undefined) return false;
if (typeof pkg.name !== 'string') return false;
Expand Down Expand Up @@ -1055,13 +1072,16 @@ Module._resolveFilename = function(request, parent, isMain, options) {
}
}
}
const filename = trySelf(parent.filename, request);
if (filename) {
const cacheKey = request + '\x00' +
(paths.length === 1 ? paths[0] : paths.join('\x00'));
Module._pathCache[cacheKey] = filename;
return filename;
}
}

// Try module self resoultion first
const parentPath = trySelfParentPath(parent);
const selfResolved = trySelf(parentPath, request);
if (selfResolved) {
const cacheKey = request + '\x00' +
(paths.length === 1 ? paths[0] : ArrayPrototypeJoin(paths, '\x00'));
dnlup marked this conversation as resolved.
Show resolved Hide resolved
Module._pathCache[cacheKey] = selfResolved;
return selfResolved;
}

// Look up the filename first, since that's the cache key.
Expand Down
3 changes: 3 additions & 0 deletions lib/internal/modules/esm/loader.js
@@ -1,5 +1,8 @@
'use strict';

// This is needed to avoid cycles in esm/resolve <-> cjs/loader
require('internal/modules/cjs/loader');

const {
FunctionPrototypeBind,
ObjectSetPrototypeOf,
Expand Down
3 changes: 2 additions & 1 deletion lib/internal/modules/esm/resolve.js
Expand Up @@ -32,7 +32,6 @@ const {
} = require('fs');
const { getOptionValue } = require('internal/options');
const { sep, relative } = require('path');
const { Module: CJSModule } = require('internal/modules/cjs/loader');
const preserveSymlinks = getOptionValue('--preserve-symlinks');
const preserveSymlinksMain = getOptionValue('--preserve-symlinks-main');
const typeFlag = getOptionValue('--input-type');
Expand All @@ -49,11 +48,13 @@ const {
ERR_UNSUPPORTED_DIR_IMPORT,
ERR_UNSUPPORTED_ESM_URL_SCHEME,
} = require('internal/errors').codes;
const { Module: CJSModule } = require('internal/modules/cjs/loader');

const packageJsonReader = require('internal/modules/package_json_reader');
const DEFAULT_CONDITIONS = ObjectFreeze(['node', 'import']);
const DEFAULT_CONDITIONS_SET = new SafeSet(DEFAULT_CONDITIONS);


function getConditionsSet(conditions) {
if (conditions !== undefined && conditions !== DEFAULT_CONDITIONS) {
if (!ArrayIsArray(conditions)) {
Expand Down
4 changes: 4 additions & 0 deletions test/fixtures/self_ref_module/index.js
@@ -0,0 +1,4 @@
'use strict'

module.exports = 'Self resolution working';

13 changes: 13 additions & 0 deletions test/fixtures/self_ref_module/package.json
@@ -0,0 +1,13 @@
{
"name": "self_ref",
"version": "1.0.0",
"description": "",
"main": "index.js",
"exports": "./index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC"
}
20 changes: 20 additions & 0 deletions test/parallel/test-preload-self-referential.js
@@ -0,0 +1,20 @@
'use strict';

const common = require('../common');
const fixtures = require('../common/fixtures');
const assert = require('assert');
const { exec } = require('child_process');

const nodeBinary = process.argv[0];

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

const selfRefModule = fixtures.path('self_ref_module');
const fixtureA = fixtures.path('printA.js');

exec(`"${nodeBinary}" -r self_ref "${fixtureA}"`, { cwd: selfRefModule },
(err, stdout, stderr) => {
assert.ifError(err);
assert.strictEqual(stdout, 'A\n');
});
25 changes: 25 additions & 0 deletions test/parallel/test-repl-require-self-referential.js
@@ -0,0 +1,25 @@
'use strict';

const common = require('../common');
const fixtures = require('../common/fixtures');
const assert = require('assert');
const { spawn } = require('child_process');

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

const selfRefModule = fixtures.path('self_ref_module');
const child = spawn(process.execPath,
['--interactive'],
{ cwd: selfRefModule }
);
let output = '';
child.stdout.on('data', (chunk) => output += chunk);
child.on('exit', common.mustCall(() => {
const results = output.replace(/^> /mg, '').split('\n').slice(2);
assert.deepStrictEqual(results, [ "'Self resolution working'", '' ]);
}));

child.stdin.write('require("self_ref");\n');
child.stdin.write('.exit');
child.stdin.end();