Skip to content

module: add some typings to internal/modules/esm/resolve #39504

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

Merged
merged 1 commit into from
Aug 2, 2021
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
131 changes: 121 additions & 10 deletions lib/internal/modules/esm/resolve.js
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,26 @@ const userConditions = getOptionValue('--conditions');
const DEFAULT_CONDITIONS = ObjectFreeze(['node', 'import', ...userConditions]);
const DEFAULT_CONDITIONS_SET = new SafeSet(DEFAULT_CONDITIONS);

/**
* @typedef {string | string[] | Record<string, unknown>} Exports
* @typedef {'module' | 'commonjs'} PackageType
* @typedef {{
* exports?: ExportConfig;
* name?: string;
* main?: string;
* type?: PackageType;
* }} PackageConfig
*/

const emittedPackageWarnings = new SafeSet();

/**
* @param {string} match
* @param {URL} pjsonUrl
* @param {boolean} isExports
* @param {string | URL | undefined} base
* @returns {void}
*/
function emitFolderMapDeprecation(match, pjsonUrl, isExports, base) {
const pjsonPath = fileURLToPath(pjsonUrl);

Expand All @@ -76,6 +95,13 @@ function emitFolderMapDeprecation(match, pjsonUrl, isExports, base) {
);
}

/**
* @param {URL} url
* @param {URL} packageJSONUrl
* @param {string | URL | undefined} base
* @param {string} main
* @returns
*/
function emitLegacyIndexDeprecation(url, packageJSONUrl, base, main) {
const { format } = defaultGetFormat(url);
if (format !== 'module')
Expand Down Expand Up @@ -104,6 +130,10 @@ function emitLegacyIndexDeprecation(url, packageJSONUrl, base, main) {
);
}

/**
* @param {string[]} [conditions]
* @returns {Set<string>}
*/
function getConditionsSet(conditions) {
if (conditions !== undefined && conditions !== DEFAULT_CONDITIONS) {
if (!ArrayIsArray(conditions)) {
Expand All @@ -118,9 +148,19 @@ function getConditionsSet(conditions) {
const realpathCache = new SafeMap();
const packageJSONCache = new SafeMap(); /* string -> PackageConfig */

/**
* @param {string | URL} path
* @returns {import('fs').Stats}
*/
const tryStatSync =
(path) => statSync(path, { throwIfNoEntry: false }) ?? new Stats();

/**
* @param {string} path
* @param {string} specifier
* @param {string | URL | undefined} base
* @returns {PackageConfig}
*/
function getPackageConfig(path, specifier, base) {
const existing = packageJSONCache.get(path);
if (existing !== undefined) {
Expand Down Expand Up @@ -173,6 +213,10 @@ function getPackageConfig(path, specifier, base) {
return packageConfig;
}

/**
* @param {URL | string} resolved
* @returns {PackageConfig}
*/
function getPackageScopeConfig(resolved) {
let packageJSONUrl = new URL('./package.json', resolved);
while (true) {
Expand Down Expand Up @@ -205,19 +249,25 @@ function getPackageScopeConfig(resolved) {
}

/**
* 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 {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) {
Expand Down Expand Up @@ -259,12 +309,21 @@ function legacyMainResolve(packageJSONUrl, packageConfig, base) {
fileURLToPath(new URL('.', packageJSONUrl)), fileURLToPath(base));
}

/**
* @param {URL} search
* @returns {URL | undefined}
*/
function resolveExtensionsWithTryExactName(search) {
if (fileExists(search)) return search;
return resolveExtensions(search);
}

const extensions = ['.js', '.json', '.node', '.mjs'];

/**
* @param {URL} search
* @returns {URL | undefined}
*/
function resolveExtensions(search) {
for (let i = 0; i < extensions.length; i++) {
const extension = extensions[i];
Expand All @@ -274,6 +333,10 @@ function resolveExtensions(search) {
return undefined;
}

/**
* @param {URL} search
* @returns {URL | undefined}
*/
function resolveDirectoryEntry(search) {
const dirPath = fileURLToPath(search);
const pkgJsonPath = resolve(dirPath, 'package.json');
Expand All @@ -291,6 +354,11 @@ function resolveDirectoryEntry(search) {
}

const encodedSepRegEx = /%2F|%2C/i;
/**
* @param {URL} resolved
* @param {string | URL | undefined} base
* @returns {URL | undefined}
*/
function finalizeResolution(resolved, base) {
if (RegExpPrototypeTest(encodedSepRegEx, resolved.pathname))
throw new ERR_INVALID_MODULE_SPECIFIER(
Expand Down Expand Up @@ -325,18 +393,35 @@ function finalizeResolution(resolved, base) {
return resolved;
}

/**
* @param {string} specifier
* @param {URL} packageJSONUrl
* @param {string | URL | undefined} base
*/
function throwImportNotDefined(specifier, packageJSONUrl, base) {
throw new ERR_PACKAGE_IMPORT_NOT_DEFINED(
specifier, packageJSONUrl && fileURLToPath(new URL('.', packageJSONUrl)),
fileURLToPath(base));
}

/**
* @param {string} specifier
* @param {URL} packageJSONUrl
* @param {string | URL | undefined} base
*/
function throwExportsNotFound(subpath, packageJSONUrl, base) {
throw new ERR_PACKAGE_PATH_NOT_EXPORTED(
fileURLToPath(new URL('.', packageJSONUrl)), subpath,
base && fileURLToPath(base));
}

/**
*
* @param {string | URL} subpath
* @param {URL} packageJSONUrl
* @param {boolean} internal
* @param {string | URL | undefined} base
*/
function throwInvalidSubpath(subpath, packageJSONUrl, internal, base) {
const reason = `request is not a valid subpath for the "${internal ?
'imports' : 'exports'}" resolution of ${fileURLToPath(packageJSONUrl)}`;
Expand Down Expand Up @@ -478,6 +563,13 @@ function resolvePackageTarget(packageJSONUrl, target, subpath, packageSubpath,
base);
}

/**
*
* @param {Exports} exports
* @param {URL} packageJSONUrl
* @param {string | URL | undefined} base
* @returns
*/
function isConditionalExportsMainSugar(exports, packageJSONUrl, base) {
if (typeof exports === 'string' || ArrayIsArray(exports)) return true;
if (typeof exports !== 'object' || exports === null) return false;
Expand All @@ -504,8 +596,8 @@ function isConditionalExportsMainSugar(exports, packageJSONUrl, base) {
/**
* @param {URL} packageJSONUrl
* @param {string} packageSubpath
* @param {object} packageConfig
* @param {string} base
* @param {PackageConfig} packageConfig
* @param {string | URL | undefined} base
* @param {Set<string>} conditions
* @returns {URL}
*/
Expand Down Expand Up @@ -560,6 +652,12 @@ function packageExportsResolve(
throwExportsNotFound(packageSubpath, packageJSONUrl, base);
}

/**
* @param {string} name
* @param {string | URL | undefined} base
* @param {Set<string>} conditions
* @returns
*/
function packageImportsResolve(name, base, conditions) {
if (name === '#' || StringPrototypeStartsWith(name, '#/')) {
const reason = 'is not a valid internal imports specifier name';
Expand Down Expand Up @@ -615,11 +713,20 @@ function packageImportsResolve(name, base, conditions) {
throwImportNotDefined(name, packageJSONUrl, base);
}

/**
* @param {URL} url
* @returns {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;
Expand Down Expand Up @@ -659,7 +766,7 @@ function parsePackageName(specifier, base) {

/**
* @param {string} specifier
* @param {URL} base
* @param {string | URL | undefined} base
* @param {Set<string>} conditions
* @returns {URL}
*/
Expand Down Expand Up @@ -712,6 +819,10 @@ function packageResolve(specifier, base, conditions) {
throw new ERR_MODULE_NOT_FOUND(packageName, fileURLToPath(base));
}

/**
* @param {string} specifier
* @returns {boolean}
*/
function isBareSpecifier(specifier) {
return specifier[0] && specifier[0] !== '/' && specifier[0] !== '.';
}
Expand All @@ -734,7 +845,7 @@ function shouldBeTreatedAsRelativeOrAbsolutePath(specifier) {

/**
* @param {string} specifier
* @param {URL} base
* @param {string | URL | undefined} base
* @param {Set<string>} conditions
* @returns {URL}
*/
Expand Down