node/lib/internal/modules/esm/resolve.js
Rich Trott c370ec124c lib: fix JSDoc issues
Updating ESLint and dependencies will start flagging a few additional
JSDoc issues. One or two of these are simple fixes. The ESM stuff
requires throwing explicitly in JSDoc'ed functions rather than calling
another function to throw. I think this makes the code easier to
understand--you don't need to know that a particular function that
starts with `throwsIf` *might* throw but something that starts with
`throwsAnythingElse` will always throw. Instead, it's right there in the
code. This also might make it easier to improve stack traces if that's
something we'd like to do at some point.

PR-URL: https://github.com/nodejs/node/pull/45243
Reviewed-By: Moshe Atlow <moshe@atlow.co.il>
Reviewed-By: Tobias Nießen <tniessen@tnie.de>
Reviewed-By: Jan Krems <jan.krems@gmail.com>
Reviewed-By: Juan José Arboleda <soyjuanarbol@gmail.com>
Reviewed-By: Antoine du Hamel <duhamelantoine1995@gmail.com>
Reviewed-By: Darshan Sen <raisinten@gmail.com>
Reviewed-By: Michaël Zasso <targos@protonmail.com>
Reviewed-By: Luigi Pinca <luigipinca@gmail.com>
Reviewed-By: Yagiz Nizipli <yagiz@nizipli.com>
Reviewed-By: Zeyu "Alex" Yang <himself65@outlook.com>
2022-11-07 07:04:29 +00:00

1157 lines
37 KiB
JavaScript

'use strict';
const {
ArrayIsArray,
ArrayPrototypeConcat,
ArrayPrototypeJoin,
ArrayPrototypeShift,
JSONStringify,
ObjectFreeze,
ObjectGetOwnPropertyNames,
ObjectPrototypeHasOwnProperty,
RegExp,
RegExpPrototypeExec,
RegExpPrototypeSymbolReplace,
SafeMap,
SafeSet,
String,
StringPrototypeEndsWith,
StringPrototypeIncludes,
StringPrototypeIndexOf,
StringPrototypeLastIndexOf,
StringPrototypeReplace,
StringPrototypeSlice,
StringPrototypeSplit,
StringPrototypeStartsWith,
} = primordials;
const internalFS = require('internal/fs/utils');
const { BuiltinModule } = require('internal/bootstrap/loaders');
const {
realpathSync,
statSync,
Stats,
} = require('fs');
const { getOptionValue } = require('internal/options');
// Do not eagerly grab .manifest, it may be in TDZ
const policy = getOptionValue('--experimental-policy') ?
require('internal/process/policy') :
null;
const { sep, relative } = require('path');
const preserveSymlinks = getOptionValue('--preserve-symlinks');
const preserveSymlinksMain = getOptionValue('--preserve-symlinks-main');
const experimentalNetworkImports =
getOptionValue('--experimental-network-imports');
const typeFlag = getOptionValue('--input-type');
const { URL, pathToFileURL, fileURLToPath } = require('internal/url');
const {
ERR_INPUT_TYPE_NOT_ALLOWED,
ERR_INVALID_ARG_VALUE,
ERR_INVALID_MODULE_SPECIFIER,
ERR_INVALID_PACKAGE_CONFIG,
ERR_INVALID_PACKAGE_TARGET,
ERR_MANIFEST_DEPENDENCY_MISSING,
ERR_MODULE_NOT_FOUND,
ERR_PACKAGE_IMPORT_NOT_DEFINED,
ERR_PACKAGE_PATH_NOT_EXPORTED,
ERR_UNSUPPORTED_DIR_IMPORT,
ERR_NETWORK_IMPORT_DISALLOWED,
ERR_UNSUPPORTED_ESM_URL_SCHEME,
} = require('internal/errors').codes;
const { Module: CJSModule } = require('internal/modules/cjs/loader');
const { getPackageConfig, getPackageScopeConfig } = require('internal/modules/esm/package_config');
/**
* @typedef {import('internal/modules/esm/package_config.js').PackageConfig} PackageConfig
*/
const userConditions = getOptionValue('--conditions');
const noAddons = getOptionValue('--no-addons');
const addonConditions = noAddons ? [] : ['node-addons'];
const DEFAULT_CONDITIONS = ObjectFreeze([
'node',
'import',
...addonConditions,
...userConditions,
]);
const DEFAULT_CONDITIONS_SET = new SafeSet(DEFAULT_CONDITIONS);
const emittedPackageWarnings = new SafeSet();
function emitTrailingSlashPatternDeprecation(match, pjsonUrl, base) {
const pjsonPath = fileURLToPath(pjsonUrl);
if (emittedPackageWarnings.has(pjsonPath + '|' + match))
return;
emittedPackageWarnings.add(pjsonPath + '|' + match);
process.emitWarning(
`Use of deprecated trailing slash pattern mapping "${match}" in the ` +
`"exports" field module resolution of the package at ${pjsonPath}${
base ? ` imported from ${fileURLToPath(base)}` :
''}. Mapping specifiers ending in "/" is no longer supported.`,
'DeprecationWarning',
'DEP0155'
);
}
const doubleSlashRegEx = /[/\\][/\\]/;
function emitInvalidSegmentDeprecation(target, request, match, pjsonUrl, internal, base, isTarget) {
const pjsonPath = fileURLToPath(pjsonUrl);
const double = RegExpPrototypeExec(doubleSlashRegEx, isTarget ? target : request) !== null;
process.emitWarning(
`Use of deprecated ${double ? 'double slash' :
'leading or trailing slash matching'} resolving "${target}" for module ` +
`request "${request}" ${request !== match ? `matched to "${match}" ` : ''
}in the "${internal ? 'imports' : 'exports'}" field module resolution of the package at ${
pjsonPath}${base ? ` imported from ${fileURLToPath(base)}` : ''}.`,
'DeprecationWarning',
'DEP0166'
);
}
/**
* @param {URL} url
* @param {URL} packageJSONUrl
* @param {string | URL | undefined} base
* @param {string} main
* @returns {void}
*/
function emitLegacyIndexDeprecation(url, packageJSONUrl, base, main) {
const format = defaultGetFormatWithoutErrors(url);
if (format !== 'module')
return;
const path = fileURLToPath(url);
const pkgPath = fileURLToPath(new URL('.', packageJSONUrl));
const basePath = fileURLToPath(base);
if (main)
process.emitWarning(
`Package ${pkgPath} has a "main" field set to ${JSONStringify(main)}, ` +
`excluding the full filename and extension to the resolved file at "${
StringPrototypeSlice(path, pkgPath.length)}", imported from ${
basePath}.\n Automatic extension resolution of the "main" field is ` +
'deprecated for ES modules.',
'DeprecationWarning',
'DEP0151'
);
else
process.emitWarning(
`No "main" or "exports" field defined in the package.json for ${pkgPath
} resolving the main entry point "${
StringPrototypeSlice(path, pkgPath.length)}", imported from ${basePath
}.\nDefault "index" lookups for the main are deprecated for ES modules.`,
'DeprecationWarning',
'DEP0151'
);
}
/**
* @param {string[]} [conditions]
* @returns {Set<string>}
*/
function getConditionsSet(conditions) {
if (conditions !== undefined && conditions !== DEFAULT_CONDITIONS) {
if (!ArrayIsArray(conditions)) {
throw new ERR_INVALID_ARG_VALUE('conditions', conditions,
'expected an array');
}
return new SafeSet(conditions);
}
return DEFAULT_CONDITIONS_SET;
}
const realpathCache = new SafeMap();
/**
* @param {string | URL} path
* @returns {import('fs').Stats}
*/
const tryStatSync =
(path) => statSync(path, { throwIfNoEntry: false }) ?? new Stats();
/**
* @param {string | URL} url
* @returns {boolean}
*/
function fileExists(url) {
return statSync(url, { throwIfNoEntry: false })?.isFile() ?? false;
}
/**
* Legacy CommonJS main resolution:
* 1. let M = pkg_url + (json main field)
* 2. TRY(M, M.js, M.json, M.node)
* 3. TRY(M/index.js, M/index.json, M/index.node)
* 4. TRY(pkg_url/index.js, pkg_url/index.json, pkg_url/index.node)
* 5. NOT_FOUND
* @param {URL} packageJSONUrl
* @param {PackageConfig} packageConfig
* @param {string | URL | undefined} base
* @returns {URL}
*/
function legacyMainResolve(packageJSONUrl, packageConfig, base) {
let guess;
if (packageConfig.main !== undefined) {
// Note: fs check redundances will be handled by Descriptor cache here.
if (fileExists(guess = new URL(`./${packageConfig.main}`,
packageJSONUrl))) {
return guess;
} else if (fileExists(guess = new URL(`./${packageConfig.main}.js`,
packageJSONUrl)));
else if (fileExists(guess = new URL(`./${packageConfig.main}.json`,
packageJSONUrl)));
else if (fileExists(guess = new URL(`./${packageConfig.main}.node`,
packageJSONUrl)));
else if (fileExists(guess = new URL(`./${packageConfig.main}/index.js`,
packageJSONUrl)));
else if (fileExists(guess = new URL(`./${packageConfig.main}/index.json`,
packageJSONUrl)));
else if (fileExists(guess = new URL(`./${packageConfig.main}/index.node`,
packageJSONUrl)));
else guess = undefined;
if (guess) {
emitLegacyIndexDeprecation(guess, packageJSONUrl, base,
packageConfig.main);
return guess;
}
// Fallthrough.
}
if (fileExists(guess = new URL('./index.js', packageJSONUrl)));
// So fs.
else if (fileExists(guess = new URL('./index.json', packageJSONUrl)));
else if (fileExists(guess = new URL('./index.node', packageJSONUrl)));
else guess = undefined;
if (guess) {
emitLegacyIndexDeprecation(guess, packageJSONUrl, base, packageConfig.main);
return guess;
}
// Not found.
throw new ERR_MODULE_NOT_FOUND(
fileURLToPath(new URL('.', packageJSONUrl)), fileURLToPath(base));
}
const encodedSepRegEx = /%2F|%5C/i;
/**
* @param {URL} resolved
* @param {string | URL | undefined} base
* @param {boolean} preserveSymlinks
* @returns {URL | undefined}
*/
function finalizeResolution(resolved, base, preserveSymlinks) {
if (RegExpPrototypeExec(encodedSepRegEx, resolved.pathname) !== null)
throw new ERR_INVALID_MODULE_SPECIFIER(
resolved.pathname, 'must not include encoded "/" or "\\" characters',
fileURLToPath(base));
const path = fileURLToPath(resolved);
const stats = tryStatSync(StringPrototypeEndsWith(path, '/') ?
StringPrototypeSlice(path, -1) : path);
if (stats.isDirectory()) {
const err = new ERR_UNSUPPORTED_DIR_IMPORT(path, fileURLToPath(base));
err.url = String(resolved);
throw err;
} else if (!stats.isFile()) {
throw new ERR_MODULE_NOT_FOUND(
path || resolved.pathname, base && fileURLToPath(base), 'module');
}
if (!preserveSymlinks) {
const real = realpathSync(path, {
[internalFS.realpathCacheKey]: realpathCache
});
const { search, hash } = resolved;
resolved =
pathToFileURL(real + (StringPrototypeEndsWith(path, sep) ? '/' : ''));
resolved.search = search;
resolved.hash = hash;
}
return resolved;
}
/**
* @param {string} specifier
* @param {URL} packageJSONUrl
* @param {string | URL | undefined} base
*/
function importNotDefined(specifier, packageJSONUrl, base) {
return new ERR_PACKAGE_IMPORT_NOT_DEFINED(
specifier, packageJSONUrl && fileURLToPath(new URL('.', packageJSONUrl)),
fileURLToPath(base));
}
/**
* @param {string} subpath
* @param {URL} packageJSONUrl
* @param {string | URL | undefined} base
*/
function exportsNotFound(subpath, packageJSONUrl, base) {
return new ERR_PACKAGE_PATH_NOT_EXPORTED(
fileURLToPath(new URL('.', packageJSONUrl)), subpath,
base && fileURLToPath(base));
}
/**
*
* @param {string} request
* @param {string} match
* @param {URL} packageJSONUrl
* @param {boolean} internal
* @param {string | URL | undefined} base
*/
function throwInvalidSubpath(request, match, packageJSONUrl, internal, base) {
const reason = `request is not a valid match in pattern "${match}" for the "${
internal ? 'imports' : 'exports'}" resolution of ${
fileURLToPath(packageJSONUrl)}`;
throw new ERR_INVALID_MODULE_SPECIFIER(request, reason,
base && fileURLToPath(base));
}
function invalidPackageTarget(
subpath, target, packageJSONUrl, internal, base) {
if (typeof target === 'object' && target !== null) {
target = JSONStringify(target, null, '');
} else {
target = `${target}`;
}
return new ERR_INVALID_PACKAGE_TARGET(
fileURLToPath(new URL('.', packageJSONUrl)), subpath, target,
internal, base && fileURLToPath(base));
}
const invalidSegmentRegEx = /(^|\\|\/)((\.|%2e)(\.|%2e)?|(n|%6e|%4e)(o|%6f|%4f)(d|%64|%44)(e|%65|%45)(_|%5f)(m|%6d|%4d)(o|%6f|%4f)(d|%64|%44)(u|%75|%55)(l|%6c|%4c)(e|%65|%45)(s|%73|%53))?(\\|\/|$)/i;
const deprecatedInvalidSegmentRegEx = /(^|\\|\/)((\.|%2e)(\.|%2e)?|(n|%6e|%4e)(o|%6f|%4f)(d|%64|%44)(e|%65|%45)(_|%5f)(m|%6d|%4d)(o|%6f|%4f)(d|%64|%44)(u|%75|%55)(l|%6c|%4c)(e|%65|%45)(s|%73|%53))(\\|\/|$)/i;
const invalidPackageNameRegEx = /^\.|%|\\/;
const patternRegEx = /\*/g;
/**
*
* @param {string} target
* @param {*} subpath
* @param {*} match
* @param {*} packageJSONUrl
* @param {*} base
* @param {*} pattern
* @param {*} internal
* @param {*} isPathMap
* @param {*} conditions
* @returns {URL}
*/
function resolvePackageTargetString(
target,
subpath,
match,
packageJSONUrl,
base,
pattern,
internal,
isPathMap,
conditions,
) {
if (subpath !== '' && !pattern && target[target.length - 1] !== '/')
throw invalidPackageTarget(match, target, packageJSONUrl, internal, base);
if (!StringPrototypeStartsWith(target, './')) {
if (internal && !StringPrototypeStartsWith(target, '../') &&
!StringPrototypeStartsWith(target, '/')) {
let isURL = false;
try {
new URL(target);
isURL = true;
} catch {
// Continue regardless of error.
}
if (!isURL) {
const exportTarget = pattern ?
RegExpPrototypeSymbolReplace(patternRegEx, target, () => subpath) :
target + subpath;
return packageResolve(
exportTarget, packageJSONUrl, conditions);
}
}
throw invalidPackageTarget(match, target, packageJSONUrl, internal, base);
}
if (RegExpPrototypeExec(invalidSegmentRegEx, StringPrototypeSlice(target, 2)) !== null) {
if (RegExpPrototypeExec(deprecatedInvalidSegmentRegEx, StringPrototypeSlice(target, 2)) === null) {
if (!isPathMap) {
const request = pattern ?
StringPrototypeReplace(match, '*', () => subpath) :
match + subpath;
const resolvedTarget = pattern ?
RegExpPrototypeSymbolReplace(patternRegEx, target, () => subpath) :
target;
emitInvalidSegmentDeprecation(resolvedTarget, request, match, packageJSONUrl, internal, base, true);
}
} else {
throw invalidPackageTarget(match, target, packageJSONUrl, internal, base);
}
}
const resolved = new URL(target, packageJSONUrl);
const resolvedPath = resolved.pathname;
const packagePath = new URL('.', packageJSONUrl).pathname;
if (!StringPrototypeStartsWith(resolvedPath, packagePath))
throw invalidPackageTarget(match, target, packageJSONUrl, internal, base);
if (subpath === '') return resolved;
if (RegExpPrototypeExec(invalidSegmentRegEx, subpath) !== null) {
const request = pattern ? StringPrototypeReplace(match, '*', () => subpath) : match + subpath;
if (RegExpPrototypeExec(deprecatedInvalidSegmentRegEx, subpath) === null) {
if (!isPathMap) {
const resolvedTarget = pattern ?
RegExpPrototypeSymbolReplace(patternRegEx, target, () => subpath) :
target;
emitInvalidSegmentDeprecation(resolvedTarget, request, match, packageJSONUrl, internal, base, false);
}
} else {
throwInvalidSubpath(request, match, packageJSONUrl, internal, base);
}
}
if (pattern) {
return new URL(
RegExpPrototypeSymbolReplace(patternRegEx, resolved.href, () => subpath)
);
}
return new URL(subpath, resolved);
}
/**
* @param {string} key
* @returns {boolean}
*/
function isArrayIndex(key) {
const keyNum = +key;
if (`${keyNum}` !== key) return false;
return keyNum >= 0 && keyNum < 0xFFFF_FFFF;
}
/**
*
* @param {*} packageJSONUrl
* @param {string|[string]} target
* @param {*} subpath
* @param {*} packageSubpath
* @param {*} base
* @param {*} pattern
* @param {*} internal
* @param {*} isPathMap
* @param {*} conditions
* @returns {URL|null}
*/
function resolvePackageTarget(packageJSONUrl, target, subpath, packageSubpath,
base, pattern, internal, isPathMap, conditions) {
if (typeof target === 'string') {
return resolvePackageTargetString(
target, subpath, packageSubpath, packageJSONUrl, base, pattern, internal,
isPathMap, conditions);
} else if (ArrayIsArray(target)) {
if (target.length === 0) {
return null;
}
let lastException;
for (let i = 0; i < target.length; i++) {
const targetItem = target[i];
let resolveResult;
try {
resolveResult = resolvePackageTarget(
packageJSONUrl, targetItem, subpath, packageSubpath, base, pattern,
internal, isPathMap, conditions);
} catch (e) {
lastException = e;
if (e.code === 'ERR_INVALID_PACKAGE_TARGET') {
continue;
}
throw e;
}
if (resolveResult === undefined) {
continue;
}
if (resolveResult === null) {
lastException = null;
continue;
}
return resolveResult;
}
if (lastException === undefined || lastException === null)
return lastException;
throw lastException;
} else if (typeof target === 'object' && target !== null) {
const keys = ObjectGetOwnPropertyNames(target);
for (let i = 0; i < keys.length; i++) {
const key = keys[i];
if (isArrayIndex(key)) {
throw new ERR_INVALID_PACKAGE_CONFIG(
fileURLToPath(packageJSONUrl), base,
'"exports" cannot contain numeric property keys.');
}
}
for (let i = 0; i < keys.length; i++) {
const key = keys[i];
if (key === 'default' || conditions.has(key)) {
const conditionalTarget = target[key];
const resolveResult = resolvePackageTarget(
packageJSONUrl, conditionalTarget, subpath, packageSubpath, base,
pattern, internal, isPathMap, conditions);
if (resolveResult === undefined)
continue;
return resolveResult;
}
}
return undefined;
} else if (target === null) {
return null;
}
throw invalidPackageTarget(packageSubpath, target, packageJSONUrl, internal,
base);
}
/**
*
* @param {import('internal/modules/esm/package_config.js').Exports} exports
* @param {URL} packageJSONUrl
* @param {string | URL | undefined} base
* @returns {boolean}
*/
function isConditionalExportsMainSugar(exports, packageJSONUrl, base) {
if (typeof exports === 'string' || ArrayIsArray(exports)) return true;
if (typeof exports !== 'object' || exports === null) return false;
const keys = ObjectGetOwnPropertyNames(exports);
let isConditionalSugar = false;
let i = 0;
for (let j = 0; j < keys.length; j++) {
const key = keys[j];
const curIsConditionalSugar = key === '' || key[0] !== '.';
if (i++ === 0) {
isConditionalSugar = curIsConditionalSugar;
} else if (isConditionalSugar !== curIsConditionalSugar) {
throw new ERR_INVALID_PACKAGE_CONFIG(
fileURLToPath(packageJSONUrl), base,
'"exports" cannot contain some keys starting with \'.\' and some not.' +
' The exports object must either be an object of package subpath keys' +
' or an object of main entry condition name keys only.');
}
}
return isConditionalSugar;
}
/**
* @param {URL} packageJSONUrl
* @param {string} packageSubpath
* @param {PackageConfig} packageConfig
* @param {string | URL | undefined} base
* @param {Set<string>} conditions
* @returns {URL}
*/
function packageExportsResolve(
packageJSONUrl, packageSubpath, packageConfig, base, conditions) {
let exports = packageConfig.exports;
if (isConditionalExportsMainSugar(exports, packageJSONUrl, base))
exports = { '.': exports };
if (ObjectPrototypeHasOwnProperty(exports, packageSubpath) &&
!StringPrototypeIncludes(packageSubpath, '*') &&
!StringPrototypeEndsWith(packageSubpath, '/')) {
const target = exports[packageSubpath];
const resolveResult = resolvePackageTarget(
packageJSONUrl, target, '', packageSubpath, base, false, false, false,
conditions
);
if (resolveResult == null) {
throw exportsNotFound(packageSubpath, packageJSONUrl, base);
}
return resolveResult;
}
let bestMatch = '';
let bestMatchSubpath;
const keys = ObjectGetOwnPropertyNames(exports);
for (let i = 0; i < keys.length; i++) {
const key = keys[i];
const patternIndex = StringPrototypeIndexOf(key, '*');
if (patternIndex !== -1 &&
StringPrototypeStartsWith(packageSubpath,
StringPrototypeSlice(key, 0, patternIndex))) {
// When this reaches EOL, this can throw at the top of the whole function:
//
// if (StringPrototypeEndsWith(packageSubpath, '/'))
// throwInvalidSubpath(packageSubpath)
//
// To match "imports" and the spec.
if (StringPrototypeEndsWith(packageSubpath, '/'))
emitTrailingSlashPatternDeprecation(packageSubpath, packageJSONUrl,
base);
const patternTrailer = StringPrototypeSlice(key, patternIndex + 1);
if (packageSubpath.length >= key.length &&
StringPrototypeEndsWith(packageSubpath, patternTrailer) &&
patternKeyCompare(bestMatch, key) === 1 &&
StringPrototypeLastIndexOf(key, '*') === patternIndex) {
bestMatch = key;
bestMatchSubpath = StringPrototypeSlice(
packageSubpath, patternIndex,
packageSubpath.length - patternTrailer.length);
}
}
}
if (bestMatch) {
const target = exports[bestMatch];
const resolveResult = resolvePackageTarget(
packageJSONUrl,
target,
bestMatchSubpath,
bestMatch,
base,
true,
false,
StringPrototypeEndsWith(packageSubpath, '/'),
conditions);
if (resolveResult == null) {
throw exportsNotFound(packageSubpath, packageJSONUrl, base);
}
return resolveResult;
}
throw exportsNotFound(packageSubpath, packageJSONUrl, base);
}
function patternKeyCompare(a, b) {
const aPatternIndex = StringPrototypeIndexOf(a, '*');
const bPatternIndex = StringPrototypeIndexOf(b, '*');
const baseLenA = aPatternIndex === -1 ? a.length : aPatternIndex + 1;
const baseLenB = bPatternIndex === -1 ? b.length : bPatternIndex + 1;
if (baseLenA > baseLenB) return -1;
if (baseLenB > baseLenA) return 1;
if (aPatternIndex === -1) return 1;
if (bPatternIndex === -1) return -1;
if (a.length > b.length) return -1;
if (b.length > a.length) return 1;
return 0;
}
/**
* @param {string} name
* @param {string | URL | undefined} base
* @param {Set<string>} conditions
* @returns {URL}
*/
function packageImportsResolve(name, base, conditions) {
if (name === '#' || StringPrototypeStartsWith(name, '#/') ||
StringPrototypeEndsWith(name, '/')) {
const reason = 'is not a valid internal imports specifier name';
throw new ERR_INVALID_MODULE_SPECIFIER(name, reason, fileURLToPath(base));
}
let packageJSONUrl;
const packageConfig = getPackageScopeConfig(base);
if (packageConfig.exists) {
packageJSONUrl = pathToFileURL(packageConfig.pjsonPath);
const imports = packageConfig.imports;
if (imports) {
if (ObjectPrototypeHasOwnProperty(imports, name) &&
!StringPrototypeIncludes(name, '*')) {
const resolveResult = resolvePackageTarget(
packageJSONUrl, imports[name], '', name, base, false, true, false,
conditions
);
if (resolveResult != null) {
return resolveResult;
}
} else {
let bestMatch = '';
let bestMatchSubpath;
const keys = ObjectGetOwnPropertyNames(imports);
for (let i = 0; i < keys.length; i++) {
const key = keys[i];
const patternIndex = StringPrototypeIndexOf(key, '*');
if (patternIndex !== -1 &&
StringPrototypeStartsWith(name,
StringPrototypeSlice(key, 0,
patternIndex))) {
const patternTrailer = StringPrototypeSlice(key, patternIndex + 1);
if (name.length >= key.length &&
StringPrototypeEndsWith(name, patternTrailer) &&
patternKeyCompare(bestMatch, key) === 1 &&
StringPrototypeLastIndexOf(key, '*') === patternIndex) {
bestMatch = key;
bestMatchSubpath = StringPrototypeSlice(
name, patternIndex, name.length - patternTrailer.length);
}
}
}
if (bestMatch) {
const target = imports[bestMatch];
const resolveResult = resolvePackageTarget(packageJSONUrl, target,
bestMatchSubpath,
bestMatch, base, true,
true, false, conditions);
if (resolveResult != null) {
return resolveResult;
}
}
}
}
}
throw importNotDefined(name, packageJSONUrl, base);
}
/**
* @param {URL} url
* @returns {import('internal/modules/esm/package_config.js').PackageType}
*/
function getPackageType(url) {
const packageConfig = getPackageScopeConfig(url);
return packageConfig.type;
}
/**
* @param {string} specifier
* @param {string | URL | undefined} base
* @returns {{ packageName: string, packageSubpath: string, isScoped: boolean }}
*/
function parsePackageName(specifier, base) {
let separatorIndex = StringPrototypeIndexOf(specifier, '/');
let validPackageName = true;
let isScoped = false;
if (specifier[0] === '@') {
isScoped = true;
if (separatorIndex === -1 || specifier.length === 0) {
validPackageName = false;
} else {
separatorIndex = StringPrototypeIndexOf(
specifier, '/', separatorIndex + 1);
}
}
const packageName = separatorIndex === -1 ?
specifier : StringPrototypeSlice(specifier, 0, separatorIndex);
// Package name cannot have leading . and cannot have percent-encoding or
// \\ separators.
if (RegExpPrototypeExec(invalidPackageNameRegEx, packageName) !== null)
validPackageName = false;
if (!validPackageName) {
throw new ERR_INVALID_MODULE_SPECIFIER(
specifier, 'is not a valid package name', fileURLToPath(base));
}
const packageSubpath = '.' + (separatorIndex === -1 ? '' :
StringPrototypeSlice(specifier, separatorIndex));
return { packageName, packageSubpath, isScoped };
}
/**
* @param {string} specifier
* @param {string | URL | undefined} base
* @param {Set<string>} conditions
* @returns {resolved: URL, format? : string}
*/
function packageResolve(specifier, base, conditions) {
if (BuiltinModule.canBeRequiredByUsers(specifier) &&
BuiltinModule.canBeRequiredWithoutScheme(specifier)) {
return new URL('node:' + specifier);
}
const { packageName, packageSubpath, isScoped } =
parsePackageName(specifier, base);
// ResolveSelf
const packageConfig = getPackageScopeConfig(base);
if (packageConfig.exists) {
const packageJSONUrl = pathToFileURL(packageConfig.pjsonPath);
if (packageConfig.name === packageName &&
packageConfig.exports !== undefined && packageConfig.exports !== null) {
return packageExportsResolve(
packageJSONUrl, packageSubpath, packageConfig, base, conditions);
}
}
let packageJSONUrl =
new URL('./node_modules/' + packageName + '/package.json', base);
let packageJSONPath = fileURLToPath(packageJSONUrl);
let lastPath;
do {
const stat = tryStatSync(StringPrototypeSlice(packageJSONPath, 0,
packageJSONPath.length - 13));
if (!stat.isDirectory()) {
lastPath = packageJSONPath;
packageJSONUrl = new URL((isScoped ?
'../../../../node_modules/' : '../../../node_modules/') +
packageName + '/package.json', packageJSONUrl);
packageJSONPath = fileURLToPath(packageJSONUrl);
continue;
}
// Package match.
const packageConfig = getPackageConfig(packageJSONPath, specifier, base);
if (packageConfig.exports !== undefined && packageConfig.exports !== null) {
return packageExportsResolve(
packageJSONUrl, packageSubpath, packageConfig, base, conditions);
}
if (packageSubpath === '.') {
return legacyMainResolve(
packageJSONUrl,
packageConfig,
base
);
}
return new URL(packageSubpath, packageJSONUrl);
// Cross-platform root check.
} while (packageJSONPath.length !== lastPath.length);
// eslint can't handle the above code.
// eslint-disable-next-line no-unreachable
throw new ERR_MODULE_NOT_FOUND(packageName, fileURLToPath(base));
}
/**
* @param {string} specifier
* @returns {boolean}
*/
function isBareSpecifier(specifier) {
return specifier[0] && specifier[0] !== '/' && specifier[0] !== '.';
}
function isRelativeSpecifier(specifier) {
if (specifier[0] === '.') {
if (specifier.length === 1 || specifier[1] === '/') return true;
if (specifier[1] === '.') {
if (specifier.length === 2 || specifier[2] === '/') return true;
}
}
return false;
}
function shouldBeTreatedAsRelativeOrAbsolutePath(specifier) {
if (specifier === '') return false;
if (specifier[0] === '/') return true;
return isRelativeSpecifier(specifier);
}
/**
* @param {string} specifier
* @param {string | URL | undefined} base
* @param {Set<string>} conditions
* @param {boolean} preserveSymlinks
* @returns {url: URL, format?: string}
*/
function moduleResolve(specifier, base, conditions, preserveSymlinks) {
const isRemote = base.protocol === 'http:' ||
base.protocol === 'https:';
// Order swapped from spec for minor perf gain.
// Ok since relative URLs cannot parse as URLs.
let resolved;
if (shouldBeTreatedAsRelativeOrAbsolutePath(specifier)) {
resolved = new URL(specifier, base);
} else if (!isRemote && specifier[0] === '#') {
resolved = packageImportsResolve(specifier, base, conditions);
} else {
try {
resolved = new URL(specifier);
} catch {
if (!isRemote) {
resolved = packageResolve(specifier, base, conditions);
}
}
}
if (resolved.protocol !== 'file:') {
return resolved;
}
return finalizeResolution(resolved, base, preserveSymlinks);
}
/**
* Try to resolve an import as a CommonJS module
* @param {string} specifier
* @param {string} parentURL
* @returns {boolean|string}
*/
function resolveAsCommonJS(specifier, parentURL) {
try {
const parent = fileURLToPath(parentURL);
const tmpModule = new CJSModule(parent, null);
tmpModule.paths = CJSModule._nodeModulePaths(parent);
let found = CJSModule._resolveFilename(specifier, tmpModule, false);
// If it is a relative specifier return the relative path
// to the parent
if (isRelativeSpecifier(specifier)) {
found = relative(parent, found);
// Add '.separator if the path does not start with '..separator'
// This should be a safe assumption because when loading
// esm modules there should be always a file specified so
// there should not be a specifier like '..' or '.'
if (!StringPrototypeStartsWith(found, `..${sep}`)) {
found = `.${sep}${found}`;
}
} else if (isBareSpecifier(specifier)) {
// If it is a bare specifier return the relative path within the
// module
const pkg = StringPrototypeSplit(specifier, '/')[0];
const index = StringPrototypeIndexOf(found, pkg);
if (index !== -1) {
found = StringPrototypeSlice(found, index);
}
}
// Normalize the path separator to give a valid suggestion
// on Windows
if (process.platform === 'win32') {
found = RegExpPrototypeSymbolReplace(new RegExp(`\\${sep}`, 'g'),
found, '/');
}
return found;
} catch {
return false;
}
}
// TODO(@JakobJingleheimer): de-dupe `specifier` & `parsed`
function checkIfDisallowedImport(specifier, parsed, parsedParentURL) {
if (parsedParentURL) {
if (
parsedParentURL.protocol === 'http:' ||
parsedParentURL.protocol === 'https:'
) {
if (shouldBeTreatedAsRelativeOrAbsolutePath(specifier)) {
// data: and blob: disallowed due to allowing file: access via
// indirection
if (parsed &&
parsed.protocol !== 'https:' &&
parsed.protocol !== 'http:'
) {
throw new ERR_NETWORK_IMPORT_DISALLOWED(
specifier,
parsedParentURL,
'remote imports cannot import from a local location.'
);
}
return { url: parsed.href };
}
if (BuiltinModule.canBeRequiredByUsers(specifier) &&
BuiltinModule.canBeRequiredWithoutScheme(specifier)) {
throw new ERR_NETWORK_IMPORT_DISALLOWED(
specifier,
parsedParentURL,
'remote imports cannot import from a local location.'
);
}
throw new ERR_NETWORK_IMPORT_DISALLOWED(
specifier,
parsedParentURL,
'only relative and absolute specifiers are supported.'
);
}
}
}
function throwIfUnsupportedURLProtocol(url) {
if (url.protocol !== 'file:' && url.protocol !== 'data:' &&
url.protocol !== 'node:') {
throw new ERR_UNSUPPORTED_ESM_URL_SCHEME(url);
}
}
function throwIfUnsupportedURLScheme(parsed, experimentalNetworkImports) {
if (
parsed &&
parsed.protocol !== 'file:' &&
parsed.protocol !== 'data:' &&
(
!experimentalNetworkImports ||
(
parsed.protocol !== 'https:' &&
parsed.protocol !== 'http:'
)
)
) {
throw new ERR_UNSUPPORTED_ESM_URL_SCHEME(parsed, ArrayPrototypeConcat(
'file',
'data',
experimentalNetworkImports ? ['https', 'http'] : [],
));
}
}
async function defaultResolve(specifier, context = {}) {
let { parentURL, conditions } = context;
if (parentURL && policy?.manifest) {
const redirects = policy.manifest.getDependencyMapper(parentURL);
if (redirects) {
const { resolve, reaction } = redirects;
const destination = resolve(specifier, new SafeSet(conditions));
let missing = true;
if (destination === true) {
missing = false;
} else if (destination) {
const href = destination.href;
return { __proto__: null, url: href };
}
if (missing) {
// Prevent network requests from firing if resolution would be banned.
// Network requests can extract data by doing things like putting
// secrets in query params
reaction(new ERR_MANIFEST_DEPENDENCY_MISSING(
parentURL,
specifier,
ArrayPrototypeJoin([...conditions], ', '))
);
}
}
}
let parsedParentURL;
if (parentURL) {
try {
parsedParentURL = new URL(parentURL);
} catch {
// Ignore exception
}
}
let parsed;
try {
if (shouldBeTreatedAsRelativeOrAbsolutePath(specifier)) {
parsed = new URL(specifier, parsedParentURL);
} else {
parsed = new URL(specifier);
}
if (parsed.protocol === 'data:' ||
(experimentalNetworkImports &&
(
parsed.protocol === 'https:' ||
parsed.protocol === 'http:'
)
)
) {
return { __proto__: null, url: parsed.href };
}
} catch {
// Ignore exception
}
// There are multiple deep branches that can either throw or return; instead
// of duplicating that deeply nested logic for the possible returns, DRY and
// check for a return. This seems the least gnarly.
const maybeReturn = checkIfDisallowedImport(
specifier,
parsed,
parsedParentURL,
);
if (maybeReturn) return maybeReturn;
// This must come after checkIfDisallowedImport
if (parsed && parsed.protocol === 'node:') return { __proto__: null, url: specifier };
throwIfUnsupportedURLScheme(parsed, experimentalNetworkImports);
const isMain = parentURL === undefined;
if (isMain) {
parentURL = pathToFileURL(`${process.cwd()}/`).href;
// This is the initial entry point to the program, and --input-type has
// been passed as an option; but --input-type can only be used with
// --eval, --print or STDIN string input. It is not allowed with file
// input, to avoid user confusion over how expansive the effect of the
// flag should be (i.e. entry point only, package scope surrounding the
// entry point, etc.).
if (typeFlag) throw new ERR_INPUT_TYPE_NOT_ALLOWED();
}
conditions = getConditionsSet(conditions);
let url;
try {
url = moduleResolve(
specifier,
parentURL,
conditions,
isMain ? preserveSymlinksMain : preserveSymlinks
);
} catch (error) {
// Try to give the user a hint of what would have been the
// resolved CommonJS module
if (error.code === 'ERR_MODULE_NOT_FOUND' ||
error.code === 'ERR_UNSUPPORTED_DIR_IMPORT') {
if (StringPrototypeStartsWith(specifier, 'file://')) {
specifier = fileURLToPath(specifier);
}
const found = resolveAsCommonJS(specifier, parentURL);
if (found) {
// Modify the stack and message string to include the hint
const lines = StringPrototypeSplit(error.stack, '\n');
const hint = `Did you mean to import ${found}?`;
error.stack =
ArrayPrototypeShift(lines) + '\n' +
hint + '\n' +
ArrayPrototypeJoin(lines, '\n');
error.message += `\n${hint}`;
}
}
throw error;
}
throwIfUnsupportedURLProtocol(url);
return {
__proto__: null,
// Do NOT cast `url` to a string: that will work even when there are real
// problems, silencing them
url: url.href,
format: defaultGetFormatWithoutErrors(url, context),
};
}
module.exports = {
DEFAULT_CONDITIONS,
defaultResolve,
encodedSepRegEx,
getPackageScopeConfig,
getPackageType,
packageExportsResolve,
packageImportsResolve,
};
// cycle
const {
defaultGetFormatWithoutErrors,
} = require('internal/modules/esm/get_format');
if (policy) {
const $defaultResolve = defaultResolve;
module.exports.defaultResolve = async function defaultResolve(
specifier,
context
) {
const ret = await $defaultResolve(specifier, context);
// This is a preflight check to avoid data exfiltration by query params etc.
policy.manifest.mightAllow(ret.url, () =>
new ERR_MANIFEST_DEPENDENCY_MISSING(
context.parentURL,
specifier,
context.conditions
)
);
return ret;
};
}