Update checked-in dependencies

This commit is contained in:
github-actions[bot] 2024-08-26 17:13:37 +00:00
parent fa428daf9c
commit b3bf514df4
216 changed files with 4342 additions and 1611 deletions

View file

@ -1,5 +1,11 @@
# enhanced-resolve
[![npm][npm]][npm-url]
[![Build Status][build-status]][build-status-url]
[![codecov][codecov-badge]][codecov-url]
[![Install Size][size]][size-url]
[![GitHub Discussions][discussion]][discussion-url]
Offers an async require.resolve function. It's highly configurable.
## Features
@ -63,10 +69,10 @@ const myResolver = ResolverFactory.createResolver({
// resolve a file with the new resolver
const context = {};
const resolveContext = {};
const lookupStartPath = "/Users/webpack/some/root/dir";
const request = "./path/to-look-up.js";
myResolver.resolve({}, lookupStartPath, request, resolveContext, (
const resolveContext = {};
myResolver.resolve(context, lookupStartPath, request, resolveContext, (
err /*Error*/,
filepath /*string*/
) => {
@ -83,7 +89,7 @@ myResolver.resolve({}, lookupStartPath, request, resolveContext, (
| extensionAlias | {} | An object which maps extension to extension aliases |
| cachePredicate | function() { return true }; | A function which decides whether a request should be cached or not. An object is passed to the function with `path` and `request` properties. |
| cacheWithContext | true | If unsafe cache is enabled, includes `request.context` in the cache key |
| conditionNames | ["node"] | A list of exports field condition names |
| conditionNames | [] | A list of exports field condition names |
| descriptionFiles | ["package.json"] | A list of description files to read from |
| enforceExtension | false | Enforce that a extension from extensions must be used |
| exportsFields | ["exports"] | A list of exports fields in description files |
@ -146,8 +152,6 @@ enhanced-resolve will try to resolve requests containing `#` as path and as frag
yarn test
```
[![Build Status](https://secure.travis-ci.org/webpack/enhanced-resolve.png?branch=main)](http://travis-ci.org/webpack/enhanced-resolve)
## Passing options from webpack
If you are using `webpack`, and you want to pass custom options to `enhanced-resolve`, the options are passed from the `resolve` key of your webpack configuration e.g.:
@ -166,3 +170,14 @@ resolve: {
Copyright (c) 2012-2019 JS Foundation and other contributors
MIT (http://www.opensource.org/licenses/mit-license.php)
[npm]: https://img.shields.io/npm/v/enhanced-resolve.svg
[npm-url]: https://www.npmjs.com/package/enhanced-resolve
[build-status]: https://github.com/webpack/enhanced-resolve/actions/workflows/test.yml/badge.svg?branch=master
[build-status-url]: https://github.com/webpack/enhanced-resolve/actions
[codecov-badge]: https://codecov.io/gh/webpack/enhanced-resolve/branch/main/graph/badge.svg?token=6B6NxtsZc3
[codecov-url]: https://codecov.io/gh/webpack/enhanced-resolve
[size]: https://packagephobia.com/badge?p=enhanced-resolve
[size-url]: https://packagephobia.com/result?p=enhanced-resolve
[discussion]: https://img.shields.io/github/discussions/webpack/webpack
[discussion-url]: https://github.com/webpack/webpack/discussions

View file

@ -9,6 +9,7 @@ const DescriptionFileUtils = require("./DescriptionFileUtils");
const getInnerRequest = require("./getInnerRequest");
/** @typedef {import("./Resolver")} Resolver */
/** @typedef {import("./Resolver").JsonPrimitive} JsonPrimitive */
/** @typedef {import("./Resolver").ResolveRequest} ResolveRequest */
/** @typedef {import("./Resolver").ResolveStepHook} ResolveStepHook */
@ -49,13 +50,18 @@ module.exports = class AliasFieldPlugin {
);
return callback();
}
/** @type {JsonPrimitive | undefined} */
const data = Object.prototype.hasOwnProperty.call(
fieldData,
innerRequest
)
? fieldData[innerRequest]
? /** @type {{[Key in string]: JsonPrimitive}} */ (fieldData)[
innerRequest
]
: innerRequest.startsWith("./")
? fieldData[innerRequest.slice(2)]
? /** @type {{[Key in string]: JsonPrimitive}} */ (fieldData)[
innerRequest.slice(2)
]
: undefined;
if (data === innerRequest) return callback();
if (data === undefined) return callback();
@ -71,10 +77,11 @@ module.exports = class AliasFieldPlugin {
}
return callback(null, ignoreObj);
}
/** @type {ResolveRequest} */
const obj = {
...request,
path: request.descriptionFileRoot,
request: data,
path: /** @type {string} */ (request.descriptionFileRoot),
request: /** @type {string} */ (data),
fullySpecified: false
};
resolver.doResolve(
@ -85,7 +92,7 @@ module.exports = class AliasFieldPlugin {
" with mapping '" +
innerRequest +
"' to '" +
data +
/** @type {string} */ (data) +
"'",
resolveContext,
(err, result) => {

View file

@ -11,7 +11,8 @@ const { PathType, getType } = require("./util/path");
/** @typedef {import("./Resolver")} Resolver */
/** @typedef {import("./Resolver").ResolveRequest} ResolveRequest */
/** @typedef {import("./Resolver").ResolveStepHook} ResolveStepHook */
/** @typedef {{alias: string|Array<string>|false, name: string, onlyModule?: boolean}} AliasOption */
/** @typedef {string | Array<string> | false} Alias */
/** @typedef {{alias: Alias, name: string, onlyModule?: boolean}} AliasOption */
module.exports = class AliasPlugin {
/**
@ -31,6 +32,10 @@ module.exports = class AliasPlugin {
*/
apply(resolver) {
const target = resolver.ensureHook(this.target);
/**
* @param {string} maybeAbsolutePath path
* @returns {null|string} absolute path with slash ending
*/
const getAbsolutePathWithSlashEnding = maybeAbsolutePath => {
const type = getType(maybeAbsolutePath);
if (type === PathType.AbsolutePosix || type === PathType.AbsoluteWin) {
@ -38,6 +43,11 @@ module.exports = class AliasPlugin {
}
return null;
};
/**
* @param {string} path path
* @param {string} maybeSubPath sub path
* @returns {boolean} true, if path is sub path
*/
const isSubPath = (path, maybeSubPath) => {
const absolutePath = getAbsolutePathWithSlashEnding(maybeSubPath);
if (!absolutePath) return false;
@ -51,6 +61,7 @@ module.exports = class AliasPlugin {
forEachBail(
this.options,
(item, callback) => {
/** @type {boolean} */
let shouldStop = false;
if (
innerRequest === item.name ||
@ -59,7 +70,13 @@ module.exports = class AliasPlugin {
? innerRequest.startsWith(`${item.name}/`)
: isSubPath(innerRequest, item.name)))
) {
const remainingRequest = innerRequest.substr(item.name.length);
/** @type {string} */
const remainingRequest = innerRequest.slice(item.name.length);
/**
* @param {Alias} alias alias
* @param {(err?: null|Error, result?: null|ResolveRequest) => void} callback callback
* @returns {void}
*/
const resolveWithAlias = (alias, callback) => {
if (alias === false) {
/** @type {ResolveRequest} */
@ -79,6 +96,7 @@ module.exports = class AliasPlugin {
) {
shouldStop = true;
const newRequestStr = alias + remainingRequest;
/** @type {ResolveRequest} */
const obj = {
...request,
request: newRequestStr,
@ -104,6 +122,11 @@ module.exports = class AliasPlugin {
}
return callback();
};
/**
* @param {null|Error} [err] error
* @param {null|ResolveRequest} [result] result
* @returns {void}
*/
const stoppingCallback = (err, result) => {
if (err) return callback(err);

View file

@ -6,6 +6,7 @@
"use strict";
/** @typedef {import("./Resolver")} Resolver */
/** @typedef {import("./Resolver").ResolveRequest} ResolveRequest */
/** @typedef {import("./Resolver").ResolveStepHook} ResolveStepHook */
module.exports = class AppendPlugin {
@ -29,6 +30,7 @@ module.exports = class AppendPlugin {
resolver
.getHook(this.source)
.tapAsync("AppendPlugin", (request, resolveContext, callback) => {
/** @type {ResolveRequest} */
const obj = {
...request,
path: request.path + this.appending,

View file

@ -8,8 +8,20 @@
const nextTick = require("process").nextTick;
/** @typedef {import("./Resolver").FileSystem} FileSystem */
/** @typedef {import("./Resolver").PathLike} PathLike */
/** @typedef {import("./Resolver").PathOrFileDescriptor} PathOrFileDescriptor */
/** @typedef {import("./Resolver").SyncFileSystem} SyncFileSystem */
/** @typedef {FileSystem & SyncFileSystem} BaseFileSystem */
/**
* @template T
* @typedef {import("./Resolver").FileSystemCallback<T>} FileSystemCallback<T>
*/
/**
* @param {string} path path
* @returns {string} dirname
*/
const dirname = path => {
let idx = path.length - 1;
while (idx >= 0) {
@ -22,6 +34,12 @@ const dirname = path => {
return path.slice(0, idx);
};
/**
* @template T
* @param {FileSystemCallback<T>[]} callbacks callbacks
* @param {Error | null} err error
* @param {T} result result
*/
const runCallbacks = (callbacks, err, result) => {
if (callbacks.length === 1) {
callbacks[0](err, result);
@ -42,9 +60,9 @@ const runCallbacks = (callbacks, err, result) => {
class OperationMergerBackend {
/**
* @param {any} provider async method
* @param {any} syncProvider sync method
* @param {any} providerContext call context for the provider methods
* @param {Function | undefined} provider async method in filesystem
* @param {Function | undefined} syncProvider sync method in filesystem
* @param {BaseFileSystem} providerContext call context for the provider methods
*/
constructor(provider, syncProvider, providerContext) {
this._provider = provider;
@ -53,38 +71,69 @@ class OperationMergerBackend {
this._activeAsyncOperations = new Map();
this.provide = this._provider
? (path, options, callback) => {
? /**
* @param {PathLike | PathOrFileDescriptor} path path
* @param {object | FileSystemCallback<any> | undefined} options options
* @param {FileSystemCallback<any>=} callback callback
* @returns {any} result
*/
(path, options, callback) => {
if (typeof options === "function") {
callback = options;
callback = /** @type {FileSystemCallback<any>} */ (options);
options = undefined;
}
if (
typeof path !== "string" &&
!Buffer.isBuffer(path) &&
!(path instanceof URL) &&
typeof path !== "number"
) {
/** @type {Function} */
(callback)(
new TypeError("path must be a string, Buffer, URL or number")
);
return;
}
if (options) {
return this._provider.call(
return /** @type {Function} */ (this._provider).call(
this._providerContext,
path,
options,
callback
);
}
if (typeof path !== "string") {
callback(new TypeError("path must be a string"));
return;
}
let callbacks = this._activeAsyncOperations.get(path);
if (callbacks) {
callbacks.push(callback);
return;
}
this._activeAsyncOperations.set(path, (callbacks = [callback]));
provider(path, (err, result) => {
this._activeAsyncOperations.delete(path);
runCallbacks(callbacks, err, result);
});
/** @type {Function} */
(provider)(
path,
/**
* @param {Error} err error
* @param {any} result result
*/
(err, result) => {
this._activeAsyncOperations.delete(path);
runCallbacks(callbacks, err, result);
}
);
}
: null;
this.provideSync = this._syncProvider
? (path, options) => {
return this._syncProvider.call(this._providerContext, path, options);
? /**
* @param {PathLike | PathOrFileDescriptor} path path
* @param {object=} options options
* @returns {any} result
*/
(path, options) => {
return /** @type {Function} */ (this._syncProvider).call(
this._providerContext,
path,
options
);
}
: null;
}
@ -117,21 +166,29 @@ const STORAGE_MODE_IDLE = 0;
const STORAGE_MODE_SYNC = 1;
const STORAGE_MODE_ASYNC = 2;
/**
* @callback Provide
* @param {PathLike | PathOrFileDescriptor} path path
* @param {any} options options
* @param {FileSystemCallback<any>} callback callback
* @returns {void}
*/
class CacheBackend {
/**
* @param {number} duration max cache duration of items
* @param {any} provider async method
* @param {any} syncProvider sync method
* @param {any} providerContext call context for the provider methods
* @param {function | undefined} provider async method
* @param {function | undefined} syncProvider sync method
* @param {BaseFileSystem} providerContext call context for the provider methods
*/
constructor(duration, provider, syncProvider, providerContext) {
this._duration = duration;
this._provider = provider;
this._syncProvider = syncProvider;
this._providerContext = providerContext;
/** @type {Map<string, (function(Error, any): void)[]>} */
/** @type {Map<string, FileSystemCallback<any>[]>} */
this._activeAsyncOperations = new Map();
/** @type {Map<string, { err: Error, result: any, level: Set<string> }>} */
/** @type {Map<string, { err: Error | null, result?: any, level: Set<string> }>} */
this._data = new Map();
/** @type {Set<string>[]} */
this._levels = [];
@ -147,21 +204,35 @@ class CacheBackend {
/** @type {number | undefined} */
this._nextDecay = undefined;
// @ts-ignore
this.provide = provider ? this.provide.bind(this) : null;
// @ts-ignore
this.provideSync = syncProvider ? this.provideSync.bind(this) : null;
}
/**
* @param {PathLike | PathOrFileDescriptor} path path
* @param {any} options options
* @param {FileSystemCallback<any>} callback callback
* @returns {void}
*/
provide(path, options, callback) {
if (typeof options === "function") {
callback = options;
options = undefined;
}
if (typeof path !== "string") {
callback(new TypeError("path must be a string"));
if (
typeof path !== "string" &&
!Buffer.isBuffer(path) &&
!(path instanceof URL) &&
typeof path !== "number"
) {
callback(new TypeError("path must be a string, Buffer, URL or number"));
return;
}
const strPath = typeof path !== "string" ? path.toString() : path;
if (options) {
return this._provider.call(
return /** @type {Function} */ (this._provider).call(
this._providerContext,
path,
options,
@ -175,38 +246,66 @@ class CacheBackend {
}
// Check in cache
let cacheEntry = this._data.get(path);
let cacheEntry = this._data.get(strPath);
if (cacheEntry !== undefined) {
if (cacheEntry.err) return nextTick(callback, cacheEntry.err);
return nextTick(callback, null, cacheEntry.result);
}
// Check if there is already the same operation running
let callbacks = this._activeAsyncOperations.get(path);
let callbacks = this._activeAsyncOperations.get(strPath);
if (callbacks !== undefined) {
callbacks.push(callback);
return;
}
this._activeAsyncOperations.set(path, (callbacks = [callback]));
this._activeAsyncOperations.set(strPath, (callbacks = [callback]));
// Run the operation
this._provider.call(this._providerContext, path, (err, result) => {
this._activeAsyncOperations.delete(path);
this._storeResult(path, err, result);
/** @type {Function} */
(this._provider).call(
this._providerContext,
path,
/**
* @param {Error | null} err error
* @param {any} [result] result
*/
(err, result) => {
this._activeAsyncOperations.delete(strPath);
this._storeResult(strPath, err, result);
// Enter async mode if not yet done
this._enterAsyncMode();
// Enter async mode if not yet done
this._enterAsyncMode();
runCallbacks(callbacks, err, result);
});
runCallbacks(
/** @type {FileSystemCallback<any>[]} */ (callbacks),
err,
result
);
}
);
}
/**
* @param {PathLike | PathOrFileDescriptor} path path
* @param {any} options options
* @returns {any} result
*/
provideSync(path, options) {
if (typeof path !== "string") {
if (
typeof path !== "string" &&
!Buffer.isBuffer(path) &&
!(path instanceof URL) &&
typeof path !== "number"
) {
throw new TypeError("path must be a string");
}
const strPath = typeof path !== "string" ? path.toString() : path;
if (options) {
return this._syncProvider.call(this._providerContext, path, options);
return /** @type {Function} */ (this._syncProvider).call(
this._providerContext,
path,
options
);
}
// In sync mode we may have to decay some cache items
@ -215,7 +314,7 @@ class CacheBackend {
}
// Check in cache
let cacheEntry = this._data.get(path);
let cacheEntry = this._data.get(strPath);
if (cacheEntry !== undefined) {
if (cacheEntry.err) throw cacheEntry.err;
return cacheEntry.result;
@ -223,26 +322,36 @@ class CacheBackend {
// Get all active async operations
// This sync operation will also complete them
const callbacks = this._activeAsyncOperations.get(path);
this._activeAsyncOperations.delete(path);
const callbacks = this._activeAsyncOperations.get(strPath);
this._activeAsyncOperations.delete(strPath);
// Run the operation
// When in idle mode, we will enter sync mode
let result;
try {
result = this._syncProvider.call(this._providerContext, path);
result = /** @type {Function} */ (this._syncProvider).call(
this._providerContext,
path
);
} catch (err) {
this._storeResult(path, err, undefined);
this._storeResult(strPath, /** @type {Error} */ (err), undefined);
this._enterSyncModeWhenIdle();
if (callbacks) runCallbacks(callbacks, err, undefined);
if (callbacks) {
runCallbacks(callbacks, /** @type {Error} */ (err), undefined);
}
throw err;
}
this._storeResult(path, undefined, result);
this._storeResult(strPath, null, result);
this._enterSyncModeWhenIdle();
if (callbacks) runCallbacks(callbacks, undefined, result);
if (callbacks) {
runCallbacks(callbacks, null, result);
}
return result;
}
/**
* @param {string | Buffer | URL | number | (string | URL | Buffer | number)[] | Set<string | URL | Buffer | number>} [what] what to purge
*/
purge(what) {
if (!what) {
if (this._mode !== STORAGE_MODE_IDLE) {
@ -252,9 +361,15 @@ class CacheBackend {
}
this._enterIdleMode();
}
} else if (typeof what === "string") {
} else if (
typeof what === "string" ||
Buffer.isBuffer(what) ||
what instanceof URL ||
typeof what === "number"
) {
const strWhat = typeof what !== "string" ? what.toString() : what;
for (let [key, data] of this._data) {
if (key.startsWith(what)) {
if (key.startsWith(strWhat)) {
this._data.delete(key);
data.level.delete(key);
}
@ -265,7 +380,8 @@ class CacheBackend {
} else {
for (let [key, data] of this._data) {
for (const item of what) {
if (key.startsWith(item)) {
const strItem = typeof item !== "string" ? item.toString() : item;
if (key.startsWith(strItem)) {
this._data.delete(key);
data.level.delete(key);
break;
@ -278,20 +394,35 @@ class CacheBackend {
}
}
/**
* @param {string | Buffer | URL | number | (string | URL | Buffer | number)[] | Set<string | URL | Buffer | number>} [what] what to purge
*/
purgeParent(what) {
if (!what) {
this.purge();
} else if (typeof what === "string") {
this.purge(dirname(what));
} else if (
typeof what === "string" ||
Buffer.isBuffer(what) ||
what instanceof URL ||
typeof what === "number"
) {
const strWhat = typeof what !== "string" ? what.toString() : what;
this.purge(dirname(strWhat));
} else {
const set = new Set();
for (const item of what) {
set.add(dirname(item));
const strItem = typeof item !== "string" ? item.toString() : item;
set.add(dirname(strItem));
}
this.purge(set);
}
}
/**
* @param {string} path path
* @param {Error | null} err error
* @param {any} result result
*/
_storeResult(path, err, result) {
if (this._data.has(path)) return;
const level = this._levels[this._currentLevel];
@ -310,8 +441,8 @@ class CacheBackend {
if (this._data.size === 0) {
this._enterIdleMode();
} else {
// @ts-ignore _nextDecay is always a number in sync mode
this._nextDecay += this._tickInterval;
/** @type {number} */
(this._nextDecay) += this._tickInterval;
}
}
@ -335,8 +466,12 @@ class CacheBackend {
break;
case STORAGE_MODE_SYNC:
this._runDecays();
// @ts-ignore _runDecays may change the mode
if (this._mode === STORAGE_MODE_IDLE) return;
// _runDecays may change the mode
if (
/** @type {STORAGE_MODE_IDLE | STORAGE_MODE_SYNC | STORAGE_MODE_ASYNC}*/
(this._mode) === STORAGE_MODE_IDLE
)
return;
timeout = Math.max(
0,
/** @type {number} */ (this._nextDecay) - Date.now()
@ -366,6 +501,16 @@ class CacheBackend {
}
}
/**
* @template {function} Provider
* @template {function} AsyncProvider
* @template FileSystem
* @param {number} duration duration in ms files are cached
* @param {Provider | undefined} provider provider
* @param {AsyncProvider | undefined} syncProvider sync provider
* @param {BaseFileSystem} providerContext provider context
* @returns {OperationMergerBackend | CacheBackend} backend
*/
const createBackend = (duration, provider, syncProvider, providerContext) => {
if (duration > 0) {
return new CacheBackend(duration, provider, syncProvider, providerContext);
@ -374,6 +519,10 @@ const createBackend = (duration, provider, syncProvider, providerContext) => {
};
module.exports = class CachedInputFileSystem {
/**
* @param {BaseFileSystem} fileSystem file system
* @param {number} duration duration in ms files are cached
*/
constructor(fileSystem, duration) {
this.fileSystem = fileSystem;
@ -408,7 +557,9 @@ module.exports = class CachedInputFileSystem {
const readdir = this._readdirBackend.provide;
this.readdir = /** @type {FileSystem["readdir"]} */ (readdir);
const readdirSync = this._readdirBackend.provideSync;
this.readdirSync = /** @type {SyncFileSystem["readdirSync"]} */ (readdirSync);
this.readdirSync = /** @type {SyncFileSystem["readdirSync"]} */ (
readdirSync
);
this._readFileBackend = createBackend(
duration,
@ -419,40 +570,57 @@ module.exports = class CachedInputFileSystem {
const readFile = this._readFileBackend.provide;
this.readFile = /** @type {FileSystem["readFile"]} */ (readFile);
const readFileSync = this._readFileBackend.provideSync;
this.readFileSync = /** @type {SyncFileSystem["readFileSync"]} */ (readFileSync);
this.readFileSync = /** @type {SyncFileSystem["readFileSync"]} */ (
readFileSync
);
this._readJsonBackend = createBackend(
duration,
// prettier-ignore
this.fileSystem.readJson ||
(this.readFile &&
((path, callback) => {
// @ts-ignore
this.readFile(path, (err, buffer) => {
if (err) return callback(err);
if (!buffer || buffer.length === 0)
return callback(new Error("No file content"));
let data;
try {
data = JSON.parse(buffer.toString("utf-8"));
} catch (e) {
return callback(e);
}
callback(null, data);
});
})),
(
/**
* @param {string} path path
* @param {FileSystemCallback<any>} callback
*/
(path, callback) => {
this.readFile(path, (err, buffer) => {
if (err) return callback(err);
if (!buffer || buffer.length === 0)
return callback(new Error("No file content"));
let data;
try {
data = JSON.parse(buffer.toString("utf-8"));
} catch (e) {
return callback(/** @type {Error} */ (e));
}
callback(null, data);
});
})
),
// prettier-ignore
this.fileSystem.readJsonSync ||
(this.readFileSync &&
(path => {
const buffer = this.readFileSync(path);
const data = JSON.parse(buffer.toString("utf-8"));
return data;
})),
(
/**
* @param {string} path path
* @returns {any} result
*/
(path) => {
const buffer = this.readFileSync(path);
const data = JSON.parse(buffer.toString("utf-8"));
return data;
}
)),
this.fileSystem
);
const readJson = this._readJsonBackend.provide;
this.readJson = /** @type {FileSystem["readJson"]} */ (readJson);
const readJsonSync = this._readJsonBackend.provideSync;
this.readJsonSync = /** @type {SyncFileSystem["readJsonSync"]} */ (readJsonSync);
this.readJsonSync = /** @type {SyncFileSystem["readJsonSync"]} */ (
readJsonSync
);
this._readlinkBackend = createBackend(
duration,
@ -463,9 +631,27 @@ module.exports = class CachedInputFileSystem {
const readlink = this._readlinkBackend.provide;
this.readlink = /** @type {FileSystem["readlink"]} */ (readlink);
const readlinkSync = this._readlinkBackend.provideSync;
this.readlinkSync = /** @type {SyncFileSystem["readlinkSync"]} */ (readlinkSync);
this.readlinkSync = /** @type {SyncFileSystem["readlinkSync"]} */ (
readlinkSync
);
this._realpathBackend = createBackend(
duration,
this.fileSystem.realpath,
this.fileSystem.realpathSync,
this.fileSystem
);
const realpath = this._realpathBackend.provide;
this.realpath = /** @type {FileSystem["realpath"]} */ (realpath);
const realpathSync = this._realpathBackend.provideSync;
this.realpathSync = /** @type {SyncFileSystem["realpathSync"]} */ (
realpathSync
);
}
/**
* @param {string | Buffer | URL | number | (string | URL | Buffer | number)[] | Set<string | URL | Buffer | number>} [what] what to purge
*/
purge(what) {
this._statBackend.purge(what);
this._lstatBackend.purge(what);
@ -473,5 +659,6 @@ module.exports = class CachedInputFileSystem {
this._readFileBackend.purge(what);
this._readlinkBackend.purge(what);
this._readJsonBackend.purge(what);
this._realpathBackend.purge(what);
}
};

View file

@ -8,8 +8,14 @@
const basename = require("./getPaths").basename;
/** @typedef {import("./Resolver")} Resolver */
/** @typedef {import("./Resolver").ResolveRequest} ResolveRequest */
/** @typedef {import("./Resolver").ResolveStepHook} ResolveStepHook */
module.exports = class CloneBasenamePlugin {
/**
* @param {string | ResolveStepHook} source source
* @param {string | ResolveStepHook} target target
*/
constructor(source, target) {
this.source = source;
this.target = target;
@ -24,8 +30,10 @@ module.exports = class CloneBasenamePlugin {
resolver
.getHook(this.source)
.tapAsync("CloneBasenamePlugin", (request, resolveContext, callback) => {
const filename = basename(request.path);
const filePath = resolver.join(request.path, filename);
const requestPath = /** @type {string} */ (request.path);
const filename = /** @type {string} */ (basename(requestPath));
const filePath = resolver.join(requestPath, filename);
/** @type {ResolveRequest} */
const obj = {
...request,
path: filePath,

View file

@ -32,7 +32,7 @@ module.exports = class ConditionalPlugin {
apply(resolver) {
const target = resolver.ensureHook(this.target);
const { test, message, allowAlternatives } = this;
const keys = Object.keys(test);
const keys = /** @type {(keyof ResolveRequest)[]} */ (Object.keys(test));
resolver
.getHook(this.source)
.tapAsync("ConditionalPlugin", (request, resolveContext, callback) => {

View file

@ -8,6 +8,7 @@
const DescriptionFileUtils = require("./DescriptionFileUtils");
/** @typedef {import("./Resolver")} Resolver */
/** @typedef {import("./Resolver").ResolveRequest} ResolveRequest */
/** @typedef {import("./Resolver").ResolveStepHook} ResolveStepHook */
module.exports = class DescriptionFilePlugin {
@ -63,7 +64,8 @@ module.exports = class DescriptionFilePlugin {
return callback();
}
const relativePath =
"." + path.substr(result.directory.length).replace(/\\/g, "/");
"." + path.slice(result.directory.length).replace(/\\/g, "/");
/** @type {ResolveRequest} */
const obj = {
...request,
descriptionFilePath: result.path,

View file

@ -8,11 +8,14 @@
const forEachBail = require("./forEachBail");
/** @typedef {import("./Resolver")} Resolver */
/** @typedef {import("./Resolver").JsonObject} JsonObject */
/** @typedef {import("./Resolver").JsonValue} JsonValue */
/** @typedef {import("./Resolver").ResolveContext} ResolveContext */
/** @typedef {import("./Resolver").ResolveRequest} ResolveRequest */
/**
* @typedef {Object} DescriptionFileInfo
* @property {any=} content
* @property {JsonObject=} content
* @property {string} path
* @property {string} directory
*/
@ -23,6 +26,13 @@ const forEachBail = require("./forEachBail");
* @param {DescriptionFileInfo=} result
*/
/**
* @typedef {Object} Result
* @property {string} path path to description file
* @property {string} directory directory of description file
* @property {JsonObject} content content of description file
*/
/**
* @param {Resolver} resolver resolver
* @param {string} directory directory
@ -46,12 +56,20 @@ function loadDescriptionFile(
}
forEachBail(
filenames,
/**
* @param {string} filename filename
* @param {(err?: null|Error, result?: null|Result) => void} callback callback
* @returns {void}
*/
(filename, callback) => {
const descriptionFilePath = resolver.join(directory, filename);
if (resolver.fileSystem.readJson) {
resolver.fileSystem.readJson(descriptionFilePath, (err, content) => {
if (err) {
if (typeof err.code !== "undefined") {
if (
typeof (/** @type {NodeJS.ErrnoException} */ (err).code) !==
"undefined"
) {
if (resolveContext.missingDependencies) {
resolveContext.missingDependencies.add(descriptionFilePath);
}
@ -78,13 +96,15 @@ function loadDescriptionFile(
if (resolveContext.fileDependencies) {
resolveContext.fileDependencies.add(descriptionFilePath);
}
/** @type {JsonObject | undefined} */
let json;
if (content) {
try {
json = JSON.parse(content.toString());
} catch (e) {
return onJson(e);
} catch (/** @type {unknown} */ e) {
return onJson(/** @type {Error} */ (e));
}
} else {
return onJson(new Error("No content in file"));
@ -94,6 +114,11 @@ function loadDescriptionFile(
});
}
/**
* @param {null|Error} [err] error
* @param {JsonObject} [content] content
* @returns {void}
*/
function onJson(err, content) {
if (err) {
if (resolveContext.log)
@ -106,12 +131,17 @@ function loadDescriptionFile(
return callback(err);
}
callback(null, {
content,
content: /** @type {JsonObject} */ (content),
directory,
path: descriptionFilePath
});
}
},
/**
* @param {null|Error} [err] error
* @param {null|Result} [result] result
* @returns {void}
*/
(err, result) => {
if (err) return callback(err);
if (result) {
@ -131,20 +161,21 @@ function loadDescriptionFile(
}
/**
* @param {any} content content
* @param {JsonObject} content content
* @param {string|string[]} field field
* @returns {object|string|number|boolean|undefined} field data
* @returns {JsonValue | undefined} field data
*/
function getField(content, field) {
if (!content) return undefined;
if (Array.isArray(field)) {
/** @type {JsonValue} */
let current = content;
for (let j = 0; j < field.length; j++) {
if (current === null || typeof current !== "object") {
current = null;
break;
}
current = current[field[j]];
current = /** @type {JsonObject} */ (current)[field[j]];
}
return current;
} else {
@ -162,7 +193,7 @@ function cdUp(directory) {
j = directory.lastIndexOf("\\");
const p = i < 0 ? j : j < 0 ? i : i < j ? j : i;
if (p < 0) return null;
return directory.substr(0, p || 1);
return directory.slice(0, p || 1);
}
exports.loadDescriptionFile = loadDescriptionFile;

View file

@ -5,14 +5,18 @@
"use strict";
const path = require("path");
const DescriptionFileUtils = require("./DescriptionFileUtils");
const forEachBail = require("./forEachBail");
const { processExportsField } = require("./util/entrypoints");
const { parseIdentifier } = require("./util/identifier");
const { checkImportsExportsFieldTarget } = require("./util/path");
const {
invalidSegmentRegEx,
deprecatedInvalidSegmentRegEx
} = require("./util/path");
/** @typedef {import("./Resolver")} Resolver */
/** @typedef {import("./Resolver").JsonObject} JsonObject */
/** @typedef {import("./Resolver").ResolveRequest} ResolveRequest */
/** @typedef {import("./Resolver").ResolveStepHook} ResolveStepHook */
/** @typedef {import("./util/entrypoints").ExportsField} ExportsField */
/** @typedef {import("./util/entrypoints").FieldProcessor} FieldProcessor */
@ -29,7 +33,7 @@ module.exports = class ExportsFieldPlugin {
this.target = target;
this.conditionNames = conditionNames;
this.fieldName = fieldNamePath;
/** @type {WeakMap<any, FieldProcessor>} */
/** @type {WeakMap<JsonObject, FieldProcessor>} */
this.fieldProcessorCache = new WeakMap();
}
@ -58,11 +62,14 @@ module.exports = class ExportsFieldPlugin {
request.query +
request.fragment
: request.request;
/** @type {ExportsField|null} */
const exportsField = DescriptionFileUtils.getField(
request.descriptionFileData,
this.fieldName
);
const exportsField =
/** @type {ExportsField|null|undefined} */
(
DescriptionFileUtils.getField(
/** @type {JsonObject} */ (request.descriptionFileData),
this.fieldName
)
);
if (!exportsField) return callback();
if (request.directory) {
@ -73,30 +80,36 @@ module.exports = class ExportsFieldPlugin {
);
}
/** @type {string[]} */
let paths;
/** @type {string | null} */
let usedField;
try {
// We attach the cache to the description file instead of the exportsField value
// because we use a WeakMap and the exportsField could be a string too.
// Description file is always an object when exports field can be accessed.
let fieldProcessor = this.fieldProcessorCache.get(
request.descriptionFileData
/** @type {JsonObject} */ (request.descriptionFileData)
);
if (fieldProcessor === undefined) {
fieldProcessor = processExportsField(exportsField);
this.fieldProcessorCache.set(
request.descriptionFileData,
/** @type {JsonObject} */ (request.descriptionFileData),
fieldProcessor
);
}
paths = fieldProcessor(remainingRequest, this.conditionNames);
} catch (err) {
[paths, usedField] = fieldProcessor(
remainingRequest,
this.conditionNames
);
} catch (/** @type {unknown} */ err) {
if (resolveContext.log) {
resolveContext.log(
`Exports field in ${request.descriptionFilePath} can't be processed: ${err}`
);
}
return callback(err);
return callback(/** @type {Error} */ (err));
}
if (paths.length === 0) {
@ -109,23 +122,51 @@ module.exports = class ExportsFieldPlugin {
forEachBail(
paths,
(p, callback) => {
/**
* @param {string} p path
* @param {(err?: null|Error, result?: null|ResolveRequest) => void} callback callback
* @param {number} i index
* @returns {void}
*/
(p, callback, i) => {
const parsedIdentifier = parseIdentifier(p);
if (!parsedIdentifier) return callback();
const [relativePath, query, fragment] = parsedIdentifier;
const error = checkImportsExportsFieldTarget(relativePath);
if (relativePath.length === 0 || !relativePath.startsWith("./")) {
if (paths.length === i) {
return callback(
new Error(
`Invalid "exports" target "${p}" defined for "${usedField}" in the package config ${request.descriptionFilePath}, targets must start with "./"`
)
);
}
if (error) {
return callback(error);
return callback();
}
if (
invalidSegmentRegEx.exec(relativePath.slice(2)) !== null &&
deprecatedInvalidSegmentRegEx.test(relativePath.slice(2)) !== null
) {
if (paths.length === i) {
return callback(
new Error(
`Invalid "exports" target "${p}" defined for "${usedField}" in the package config ${request.descriptionFilePath}, targets must start with "./"`
)
);
}
return callback();
}
/** @type {ResolveRequest} */
const obj = {
...request,
request: undefined,
path: path.join(
path: resolver.join(
/** @type {string} */ (request.descriptionFileRoot),
relativePath
),
@ -139,9 +180,19 @@ module.exports = class ExportsFieldPlugin {
obj,
"using exports field: " + p,
resolveContext,
callback
(err, result) => {
if (err) return callback(err);
// Don't allow to continue - https://github.com/webpack/enhanced-resolve/issues/400
if (result === undefined) return callback(null, null);
callback(null, result);
}
);
},
/**
* @param {null|Error} [err] error
* @param {null|ResolveRequest} [result] result
* @returns {void}
*/
(err, result) => callback(err, result || null)
);
});

View file

@ -36,27 +36,60 @@ module.exports = class ExtensionAliasPlugin {
.tapAsync("ExtensionAliasPlugin", (request, resolveContext, callback) => {
const requestPath = request.request;
if (!requestPath || !requestPath.endsWith(extension)) return callback();
const resolve = (alias, callback) => {
resolver.doResolve(
const isAliasString = typeof alias === "string";
/**
* @param {string} alias extension alias
* @param {(err?: null|Error, result?: null|ResolveRequest) => void} callback callback
* @param {number} [index] index
* @returns {void}
*/
const resolve = (alias, callback, index) => {
const newRequest = `${requestPath.slice(
0,
-extension.length
)}${alias}`;
return resolver.doResolve(
target,
{
...request,
request: `${requestPath.slice(0, -extension.length)}${alias}`,
request: newRequest,
fullySpecified: true
},
`aliased from extension alias with mapping '${extension}' to '${alias}'`,
resolveContext,
callback
(err, result) => {
// Throw error if we are on the last alias (for multiple aliases) and it failed, always throw if we are not an array or we have only one alias
if (!isAliasString && index) {
if (index !== this.options.alias.length) {
if (resolveContext.log) {
resolveContext.log(
`Failed to alias from extension alias with mapping '${extension}' to '${alias}' for '${newRequest}': ${err}`
);
}
return callback(null, result);
}
return callback(err, result);
} else {
callback(err, result);
}
}
);
};
/**
* @param {null|Error} [err] error
* @param {null|ResolveRequest} [result] result
* @returns {void}
*/
const stoppingCallback = (err, result) => {
if (err) return callback(err);
if (result) return callback(null, result);
// Don't allow other aliasing or raw request
return callback(null, null);
};
if (typeof alias === "string") {
if (isAliasString) {
resolve(alias, stoppingCallback);
} else if (alias.length > 1) {
forEachBail(alias, resolve, stoppingCallback);

View file

@ -5,14 +5,18 @@
"use strict";
const path = require("path");
const DescriptionFileUtils = require("./DescriptionFileUtils");
const forEachBail = require("./forEachBail");
const { processImportsField } = require("./util/entrypoints");
const { parseIdentifier } = require("./util/identifier");
const { checkImportsExportsFieldTarget } = require("./util/path");
const {
invalidSegmentRegEx,
deprecatedInvalidSegmentRegEx
} = require("./util/path");
/** @typedef {import("./Resolver")} Resolver */
/** @typedef {import("./Resolver").JsonObject} JsonObject */
/** @typedef {import("./Resolver").ResolveRequest} ResolveRequest */
/** @typedef {import("./Resolver").ResolveStepHook} ResolveStepHook */
/** @typedef {import("./util/entrypoints").FieldProcessor} FieldProcessor */
/** @typedef {import("./util/entrypoints").ImportsField} ImportsField */
@ -39,7 +43,7 @@ module.exports = class ImportsFieldPlugin {
this.targetPackage = targetPackage;
this.conditionNames = conditionNames;
this.fieldName = fieldNamePath;
/** @type {WeakMap<any, FieldProcessor>} */
/** @type {WeakMap<JsonObject, FieldProcessor>} */
this.fieldProcessorCache = new WeakMap();
}
@ -61,11 +65,14 @@ module.exports = class ImportsFieldPlugin {
const remainingRequest =
request.request + request.query + request.fragment;
/** @type {ImportsField|null} */
const importsField = DescriptionFileUtils.getField(
request.descriptionFileData,
this.fieldName
);
const importsField =
/** @type {ImportsField|null|undefined} */
(
DescriptionFileUtils.getField(
/** @type {JsonObject} */ (request.descriptionFileData),
this.fieldName
)
);
if (!importsField) return callback();
if (request.directory) {
@ -76,30 +83,36 @@ module.exports = class ImportsFieldPlugin {
);
}
/** @type {string[]} */
let paths;
/** @type {string | null} */
let usedField;
try {
// We attach the cache to the description file instead of the importsField value
// because we use a WeakMap and the importsField could be a string too.
// Description file is always an object when exports field can be accessed.
let fieldProcessor = this.fieldProcessorCache.get(
request.descriptionFileData
/** @type {JsonObject} */ (request.descriptionFileData)
);
if (fieldProcessor === undefined) {
fieldProcessor = processImportsField(importsField);
this.fieldProcessorCache.set(
request.descriptionFileData,
/** @type {JsonObject} */ (request.descriptionFileData),
fieldProcessor
);
}
paths = fieldProcessor(remainingRequest, this.conditionNames);
} catch (err) {
[paths, usedField] = fieldProcessor(
remainingRequest,
this.conditionNames
);
} catch (/** @type {unknown} */ err) {
if (resolveContext.log) {
resolveContext.log(
`Imports field in ${request.descriptionFilePath} can't be processed: ${err}`
);
}
return callback(err);
return callback(/** @type {Error} */ (err));
}
if (paths.length === 0) {
@ -112,26 +125,42 @@ module.exports = class ImportsFieldPlugin {
forEachBail(
paths,
(p, callback) => {
/**
* @param {string} p path
* @param {(err?: null|Error, result?: null|ResolveRequest) => void} callback callback
* @param {number} i index
* @returns {void}
*/
(p, callback, i) => {
const parsedIdentifier = parseIdentifier(p);
if (!parsedIdentifier) return callback();
const [path_, query, fragment] = parsedIdentifier;
const error = checkImportsExportsFieldTarget(path_);
if (error) {
return callback(error);
}
switch (path_.charCodeAt(0)) {
// should be relative
case dotCode: {
if (
invalidSegmentRegEx.exec(path_.slice(2)) !== null &&
deprecatedInvalidSegmentRegEx.test(path_.slice(2)) !== null
) {
if (paths.length === i) {
return callback(
new Error(
`Invalid "imports" target "${p}" defined for "${usedField}" in the package config ${request.descriptionFilePath}, targets must start with "./"`
)
);
}
return callback();
}
/** @type {ResolveRequest} */
const obj = {
...request,
request: undefined,
path: path.join(
path: resolver.join(
/** @type {string} */ (request.descriptionFileRoot),
path_
),
@ -145,13 +174,19 @@ module.exports = class ImportsFieldPlugin {
obj,
"using imports field: " + p,
resolveContext,
callback
(err, result) => {
if (err) return callback(err);
// Don't allow to continue - https://github.com/webpack/enhanced-resolve/issues/400
if (result === undefined) return callback(null, null);
callback(null, result);
}
);
break;
}
// package resolving
default: {
/** @type {ResolveRequest} */
const obj = {
...request,
request: path_,
@ -166,11 +201,21 @@ module.exports = class ImportsFieldPlugin {
obj,
"using imports field: " + p,
resolveContext,
callback
(err, result) => {
if (err) return callback(err);
// Don't allow to continue - https://github.com/webpack/enhanced-resolve/issues/400
if (result === undefined) return callback(null, null);
callback(null, result);
}
);
}
}
},
/**
* @param {null|Error} [err] error
* @param {null|ResolveRequest} [result] result
* @returns {void}
*/
(err, result) => callback(err, result || null)
);
});

View file

@ -6,6 +6,7 @@
"use strict";
/** @typedef {import("./Resolver")} Resolver */
/** @typedef {import("./Resolver").ResolveRequest} ResolveRequest */
/** @typedef {import("./Resolver").ResolveStepHook} ResolveStepHook */
const namespaceStartCharCode = "@".charCodeAt(0);
@ -38,7 +39,12 @@ module.exports = class JoinRequestPartPlugin {
i = req.indexOf("/", i + 1);
}
let moduleName, remainingRequest, fullySpecified;
/** @type {string} */
let moduleName;
/** @type {string} */
let remainingRequest;
/** @type {boolean} */
let fullySpecified;
if (i < 0) {
moduleName = req;
remainingRequest = ".";
@ -46,11 +52,16 @@ module.exports = class JoinRequestPartPlugin {
} else {
moduleName = req.slice(0, i);
remainingRequest = "." + req.slice(i);
fullySpecified = request.fullySpecified;
fullySpecified = /** @type {boolean} */ (request.fullySpecified);
}
/** @type {ResolveRequest} */
const obj = {
...request,
path: resolver.join(request.path, moduleName),
path: resolver.join(
/** @type {string} */
(request.path),
moduleName
),
relativePath:
request.relativePath &&
resolver.join(request.relativePath, moduleName),

View file

@ -6,6 +6,7 @@
"use strict";
/** @typedef {import("./Resolver")} Resolver */
/** @typedef {import("./Resolver").ResolveRequest} ResolveRequest */
/** @typedef {import("./Resolver").ResolveStepHook} ResolveStepHook */
module.exports = class JoinRequestPlugin {
@ -27,12 +28,15 @@ module.exports = class JoinRequestPlugin {
resolver
.getHook(this.source)
.tapAsync("JoinRequestPlugin", (request, resolveContext, callback) => {
const requestPath = /** @type {string} */ (request.path);
const requestRequest = /** @type {string} */ (request.request);
/** @type {ResolveRequest} */
const obj = {
...request,
path: resolver.join(request.path, request.request),
path: resolver.join(requestPath, requestRequest),
relativePath:
request.relativePath &&
resolver.join(request.relativePath, request.request),
resolver.join(request.relativePath, requestRequest),
request: undefined
};
resolver.doResolve(target, obj, null, resolveContext, callback);

View file

@ -6,8 +6,12 @@
"use strict";
/** @typedef {import("./Resolver")} Resolver */
/** @typedef {import("./Resolver").ResolveStepHook} ResolveStepHook */
module.exports = class LogInfoPlugin {
/**
* @param {string | ResolveStepHook} source source
*/
constructor(source) {
this.source = source;
}

View file

@ -9,7 +9,10 @@ const path = require("path");
const DescriptionFileUtils = require("./DescriptionFileUtils");
/** @typedef {import("./Resolver")} Resolver */
/** @typedef {import("./Resolver").JsonObject} JsonObject */
/** @typedef {import("./Resolver").ResolveRequest} ResolveRequest */
/** @typedef {import("./Resolver").ResolveStepHook} ResolveStepHook */
/** @typedef {{name: string|Array<string>, forceRelative: boolean}} MainFieldOptions */
const alreadyTriedMainField = Symbol("alreadyTriedMainField");
@ -37,15 +40,20 @@ module.exports = class MainFieldPlugin {
.tapAsync("MainFieldPlugin", (request, resolveContext, callback) => {
if (
request.path !== request.descriptionFileRoot ||
request[alreadyTriedMainField] === request.descriptionFilePath ||
/** @type {ResolveRequest & { [alreadyTriedMainField]?: string }} */
(request)[alreadyTriedMainField] === request.descriptionFilePath ||
!request.descriptionFilePath
)
return callback();
const filename = path.basename(request.descriptionFilePath);
let mainModule = DescriptionFileUtils.getField(
request.descriptionFileData,
this.options.name
);
let mainModule =
/** @type {string|null|undefined} */
(
DescriptionFileUtils.getField(
/** @type {JsonObject} */ (request.descriptionFileData),
this.options.name
)
);
if (
!mainModule ||
@ -57,6 +65,7 @@ module.exports = class MainFieldPlugin {
}
if (this.options.forceRelative && !/^\.\.?\//.test(mainModule))
mainModule = "./" + mainModule;
/** @type {ResolveRequest & { [alreadyTriedMainField]?: string }} */
const obj = {
...request,
request: mainModule,

View file

@ -9,6 +9,7 @@ const forEachBail = require("./forEachBail");
const getPaths = require("./getPaths");
/** @typedef {import("./Resolver")} Resolver */
/** @typedef {import("./Resolver").ResolveRequest} ResolveRequest */
/** @typedef {import("./Resolver").ResolveStepHook} ResolveStepHook */
module.exports = class ModulesInHierarchicalDirectoriesPlugin {
@ -35,7 +36,7 @@ module.exports = class ModulesInHierarchicalDirectoriesPlugin {
"ModulesInHierarchicalDirectoriesPlugin",
(request, resolveContext, callback) => {
const fs = resolver.fileSystem;
const addrs = getPaths(request.path)
const addrs = getPaths(/** @type {string} */ (request.path))
.paths.map(p => {
return this.directories.map(d => resolver.join(p, d));
})
@ -45,9 +46,15 @@ module.exports = class ModulesInHierarchicalDirectoriesPlugin {
}, []);
forEachBail(
addrs,
/**
* @param {string} addr addr
* @param {(err?: null|Error, result?: null|ResolveRequest) => void} callback callback
* @returns {void}
*/
(addr, callback) => {
fs.stat(addr, (err, stat) => {
if (!err && stat && stat.isDirectory()) {
/** @type {ResolveRequest} */
const obj = {
...request,
path: addr,

View file

@ -6,6 +6,7 @@
"use strict";
/** @typedef {import("./Resolver")} Resolver */
/** @typedef {import("./Resolver").ResolveRequest} ResolveRequest */
/** @typedef {import("./Resolver").ResolveStepHook} ResolveStepHook */
module.exports = class ModulesInRootPlugin {
@ -29,6 +30,7 @@ module.exports = class ModulesInRootPlugin {
resolver
.getHook(this.source)
.tapAsync("ModulesInRootPlugin", (request, resolveContext, callback) => {
/** @type {ResolveRequest} */
const obj = {
...request,
path: this.path,

View file

@ -31,6 +31,7 @@ module.exports = class ParsePlugin {
.getHook(this.source)
.tapAsync("ParsePlugin", (request, resolveContext, callback) => {
const parsed = resolver.parse(/** @type {string} */ (request.request));
/** @type {ResolveRequest} */
const obj = { ...request, ...parsed, ...this.requestOptions };
if (request.query && !parsed.query) {
obj.query = request.query;
@ -46,6 +47,7 @@ module.exports = class ParsePlugin {
// There is an edge-case where a request with # can be a path or a fragment -> try both
if (obj.request && !obj.query && obj.fragment) {
const directory = obj.fragment.endsWith("/");
/** @type {ResolveRequest} */
const alternative = {
...obj,
directory,

View file

@ -7,9 +7,10 @@
/** @typedef {import("./Resolver")} Resolver */
/** @typedef {import("./Resolver").ResolveStepHook} ResolveStepHook */
/** @typedef {import("./Resolver").ResolveRequest} ResolveRequest */
/**
* @typedef {Object} PnpApiImpl
* @property {function(string, string, object): string} resolveToUnqualified
* @property {function(string, string, object): string | null} resolveToUnqualified
*/
module.exports = class PnpPlugin {
@ -17,11 +18,13 @@ module.exports = class PnpPlugin {
* @param {string | ResolveStepHook} source source
* @param {PnpApiImpl} pnpApi pnpApi
* @param {string | ResolveStepHook} target target
* @param {string | ResolveStepHook} alternateTarget alternateTarget
*/
constructor(source, pnpApi, target) {
constructor(source, pnpApi, target, alternateTarget) {
this.source = source;
this.pnpApi = pnpApi;
this.target = target;
this.alternateTarget = alternateTarget;
}
/**
@ -29,7 +32,9 @@ module.exports = class PnpPlugin {
* @returns {void}
*/
apply(resolver) {
/** @type {ResolveStepHook} */
const target = resolver.ensureHook(this.target);
const alternateTarget = resolver.ensureHook(this.alternateTarget);
resolver
.getHook(this.source)
.tapAsync("PnpPlugin", (request, resolveContext, callback) => {
@ -45,32 +50,57 @@ module.exports = class PnpPlugin {
const packageName = packageMatch[0];
const innerRequest = `.${req.slice(packageName.length)}`;
/** @type {string|undefined|null} */
let resolution;
/** @type {string|undefined|null} */
let apiResolution;
try {
resolution = this.pnpApi.resolveToUnqualified(packageName, issuer, {
considerBuiltins: false
});
if (resolution === null) {
// This is either not a PnP managed issuer or it's a Node builtin
// Try to continue resolving with our alternatives
resolver.doResolve(
alternateTarget,
request,
"issuer is not managed by a pnpapi",
resolveContext,
(err, result) => {
if (err) return callback(err);
if (result) return callback(null, result);
// Skip alternatives
return callback(null, null);
}
);
return;
}
if (resolveContext.fileDependencies) {
apiResolution = this.pnpApi.resolveToUnqualified("pnpapi", issuer, {
considerBuiltins: false
});
}
} catch (error) {
} catch (/** @type {unknown} */ error) {
if (
error.code === "MODULE_NOT_FOUND" &&
error.pnpCode === "UNDECLARED_DEPENDENCY"
/** @type {Error & { code: string }} */
(error).code === "MODULE_NOT_FOUND" &&
/** @type {Error & { pnpCode: string }} */
(error).pnpCode === "UNDECLARED_DEPENDENCY"
) {
// This is not a PnP managed dependency.
// Try to continue resolving with our alternatives
if (resolveContext.log) {
resolveContext.log(`request is not managed by the pnpapi`);
for (const line of error.message.split("\n").filter(Boolean))
for (const line of /** @type {Error} */ (error).message
.split("\n")
.filter(Boolean))
resolveContext.log(` ${line}`);
}
return callback();
}
return callback(error);
return callback(/** @type {Error} */ (error));
}
if (resolution === packageName) return callback();
@ -78,7 +108,7 @@ module.exports = class PnpPlugin {
if (apiResolution && resolveContext.fileDependencies) {
resolveContext.fileDependencies.add(apiResolution);
}
/** @type {ResolveRequest} */
const obj = {
...request,
path: resolution,

View file

@ -17,18 +17,9 @@ const {
/** @typedef {import("./ResolverFactory").ResolveOptions} ResolveOptions */
/**
* @typedef {Object} FileSystemStats
* @property {function(): boolean} isDirectory
* @property {function(): boolean} isFile
*/
/** @typedef {Error & { details?: string }} ErrorWithDetail */
/**
* @typedef {Object} FileSystemDirent
* @property {Buffer | string} name
* @property {function(): boolean} isDirectory
* @property {function(): boolean} isFile
*/
/** @typedef {(err: ErrorWithDetail | null, res?: string | false, req?: ResolveRequest) => void} ResolveCallback */
/**
* @typedef {Object} PossibleFileSystemError
@ -41,28 +32,244 @@ const {
/**
* @template T
* @callback FileSystemCallback
* @param {PossibleFileSystemError & Error | null | undefined} err
* @param {PossibleFileSystemError & Error | null} err
* @param {T=} result
*/
/**
* @typedef {string | Buffer | URL} PathLike
*/
/**
* @typedef {PathLike | number} PathOrFileDescriptor
*/
/**
* @typedef {Object} ObjectEncodingOptions
* @property {BufferEncoding | null | undefined} [encoding]
*/
/** @typedef {function(NodeJS.ErrnoException | null, string=): void} StringCallback */
/** @typedef {function(NodeJS.ErrnoException | null, Buffer=): void} BufferCallback */
/** @typedef {function(NodeJS.ErrnoException | null, (string | Buffer)=): void} StringOrBufferCallback */
/** @typedef {function(NodeJS.ErrnoException | null, IStats=): void} StatsCallback */
/** @typedef {function(NodeJS.ErrnoException | null, IBigIntStats=): void} BigIntStatsCallback */
/** @typedef {function(NodeJS.ErrnoException | null, (IStats | IBigIntStats)=): void} StatsOrBigIntStatsCallback */
/** @typedef {function(NodeJS.ErrnoException | Error | null, JsonObject=): void} ReadJsonCallback */
/** @typedef {function(NodeJS.ErrnoException | null, string[]=): void} ReaddirStringCallback */
/** @typedef {function(NodeJS.ErrnoException | null, Buffer[]=): void} ReaddirBufferCallback */
/** @typedef {function(NodeJS.ErrnoException | null, (string[] | Buffer[])=): void} ReaddirStringOrBufferCallback */
/** @typedef {function(NodeJS.ErrnoException | null, Dirent[]=): void} ReaddirDirentCallback */
/**
* @template T
* @typedef {Object} IStatsBase
* @property {() => boolean} isFile
* @property {() => boolean} isDirectory
* @property {() => boolean} isBlockDevice
* @property {() => boolean} isCharacterDevice
* @property {() => boolean} isSymbolicLink
* @property {() => boolean} isFIFO
* @property {() => boolean} isSocket
* @property {T} dev
* @property {T} ino
* @property {T} mode
* @property {T} nlink
* @property {T} uid
* @property {T} gid
* @property {T} rdev
* @property {T} size
* @property {T} blksize
* @property {T} blocks
* @property {T} atimeMs
* @property {T} mtimeMs
* @property {T} ctimeMs
* @property {T} birthtimeMs
* @property {Date} atime
* @property {Date} mtime
* @property {Date} ctime
* @property {Date} birthtime
*/
/**
* @typedef {IStatsBase<number>} IStats
*/
/**
* @typedef {IStatsBase<bigint> & { atimeNs: bigint, mtimeNs: bigint, ctimeNs: bigint, birthtimeNs: bigint }} IBigIntStats
*/
/**
* @typedef {Object} Dirent
* @property {() => boolean} isFile
* @property {() => boolean} isDirectory
* @property {() => boolean} isBlockDevice
* @property {() => boolean} isCharacterDevice
* @property {() => boolean} isSymbolicLink
* @property {() => boolean} isFIFO
* @property {() => boolean} isSocket
* @property {string} name
* @property {string} path
*/
/**
* @typedef {Object} StatOptions
* @property {(boolean | undefined)=} bigint
*/
/**
* @typedef {Object} StatSyncOptions
* @property {(boolean | undefined)=} bigint
* @property {(boolean | undefined)=} throwIfNoEntry
*/
/**
* @typedef {{
* (path: PathOrFileDescriptor, options: ({ encoding?: null | undefined, flag?: string | undefined } & import("events").Abortable) | undefined | null, callback: BufferCallback): void;
* (path: PathOrFileDescriptor, options: ({ encoding: BufferEncoding, flag?: string | undefined } & import("events").Abortable) | BufferEncoding, callback: StringCallback): void;
* (path: PathOrFileDescriptor, options: (ObjectEncodingOptions & { flag?: string | undefined } & import("events").Abortable) | BufferEncoding | undefined | null, callback: StringOrBufferCallback): void;
* (path: PathOrFileDescriptor, callback: BufferCallback): void;
* }} ReadFile
*/
/**
* @typedef {ObjectEncodingOptions | BufferEncoding | undefined | null} EncodingOption
*/
/**
* @typedef {'buffer'| { encoding: 'buffer' }} BufferEncodingOption
*/
/**
* @typedef {{
* (path: PathOrFileDescriptor, options?: { encoding?: null | undefined, flag?: string | undefined } | null): Buffer;
* (path: PathOrFileDescriptor, options: { encoding: BufferEncoding, flag?: string | undefined } | BufferEncoding): string;
* (path: PathOrFileDescriptor, options?: (ObjectEncodingOptions & { flag?: string | undefined }) | BufferEncoding | null): string | Buffer;
* }} ReadFileSync
*/
/**
* @typedef {{
* (path: PathLike, options: { encoding: BufferEncoding | null, withFileTypes?: false | undefined, recursive?: boolean | undefined } | BufferEncoding | undefined | null, callback: ReaddirStringCallback): void;
* (path: PathLike, options: { encoding: 'buffer', withFileTypes?: false | undefined, recursive?: boolean | undefined } | 'buffer', callback: ReaddirBufferCallback): void;
* (path: PathLike, callback: ReaddirStringCallback): void;
* (path: PathLike, options: (ObjectEncodingOptions & { withFileTypes?: false | undefined, recursive?: boolean | undefined }) | BufferEncoding | undefined | null, callback: ReaddirStringOrBufferCallback): void;
* (path: PathLike, options: ObjectEncodingOptions & { withFileTypes: true, recursive?: boolean | undefined }, callback: ReaddirDirentCallback): void;
* }} Readdir
*/
/**
* @typedef {{
* (path: PathLike, options?: { encoding: BufferEncoding | null, withFileTypes?: false | undefined, recursive?: boolean | undefined } | BufferEncoding | null): string[];
* (path: PathLike, options: { encoding: 'buffer', withFileTypes?: false | undefined, recursive?: boolean | undefined } | 'buffer'): Buffer[];
* (path: PathLike, options?: (ObjectEncodingOptions & { withFileTypes?: false | undefined, recursive?: boolean | undefined }) | BufferEncoding | null): string[] | Buffer[];
* (path: PathLike, options: ObjectEncodingOptions & { withFileTypes: true, recursive?: boolean | undefined }): Dirent[];
* }} ReaddirSync
/**
* @typedef {function(PathOrFileDescriptor, ReadJsonCallback): void} ReadJson
*/
/**
* @typedef {function(PathOrFileDescriptor): JsonObject} ReadJsonSync
*/
/**
* @typedef {{
* (path: PathLike, options: EncodingOption, callback: StringCallback): void;
* (path: PathLike, options: BufferEncodingOption, callback: BufferCallback): void;
* (path: PathLike, options: EncodingOption, callback: StringOrBufferCallback): void;
* (path: PathLike, callback: StringCallback): void;
* }} Readlink
*/
/**
* @typedef {{
* (path: PathLike, options?: EncodingOption): string;
* (path: PathLike, options: BufferEncodingOption): Buffer;
* (path: PathLike, options?: EncodingOption): string | Buffer;
* }} ReadlinkSync
*/
/**
* @typedef {{
* (path: PathLike, callback: StatsCallback): void;
* (path: PathLike, options: (StatOptions & { bigint?: false | undefined }) | undefined, callback: StatsCallback): void;
* (path: PathLike, options: StatOptions & { bigint: true }, callback: BigIntStatsCallback): void;
* (path: PathLike, options: StatOptions | undefined, callback: StatsOrBigIntStatsCallback): void;
* }} LStat
*/
/**
* @typedef {{
* (path: PathLike, options?: undefined): IStats;
* (path: PathLike, options?: StatSyncOptions & { bigint?: false | undefined, throwIfNoEntry: false }): IStats | undefined;
* (path: PathLike, options: StatSyncOptions & { bigint: true, throwIfNoEntry: false }): IBigIntStats | undefined;
* (path: PathLike, options?: StatSyncOptions & { bigint?: false | undefined }): IStats;
* (path: PathLike, options: StatSyncOptions & { bigint: true }): IBigIntStats;
* (path: PathLike, options: StatSyncOptions & { bigint: boolean, throwIfNoEntry?: false | undefined }): IStats | IBigIntStats;
* (path: PathLike, options?: StatSyncOptions): IStats | IBigIntStats | undefined;
* }} LStatSync
*/
/**
* @typedef {{
* (path: PathLike, callback: StatsCallback): void;
* (path: PathLike, options: (StatOptions & { bigint?: false | undefined }) | undefined, callback: StatsCallback): void;
* (path: PathLike, options: StatOptions & { bigint: true }, callback: BigIntStatsCallback): void;
* (path: PathLike, options: StatOptions | undefined, callback: StatsOrBigIntStatsCallback): void;
* }} Stat
*/
/**
* @typedef {{
* (path: PathLike, options?: undefined): IStats;
* (path: PathLike, options?: StatSyncOptions & { bigint?: false | undefined, throwIfNoEntry: false }): IStats | undefined;
* (path: PathLike, options: StatSyncOptions & { bigint: true, throwIfNoEntry: false }): IBigIntStats | undefined;
* (path: PathLike, options?: StatSyncOptions & { bigint?: false | undefined }): IStats;
* (path: PathLike, options: StatSyncOptions & { bigint: true }): IBigIntStats;
* (path: PathLike, options: StatSyncOptions & { bigint: boolean, throwIfNoEntry?: false | undefined }): IStats | IBigIntStats;
* (path: PathLike, options?: StatSyncOptions): IStats | IBigIntStats | undefined;
* }} StatSync
*/
/**
* @typedef {{
* (path: PathLike, options: EncodingOption, callback: StringCallback): void;
* (path: PathLike, options: BufferEncodingOption, callback: BufferCallback): void;
* (path: PathLike, options: EncodingOption, callback: StringOrBufferCallback): void;
* (path: PathLike, callback: StringCallback): void;
* }} RealPath
*/
/**
* @typedef {{
* (path: PathLike, options?: EncodingOption): string;
* (path: PathLike, options: BufferEncodingOption): Buffer;
* (path: PathLike, options?: EncodingOption): string | Buffer;
* }} RealPathSync
*/
/**
* @typedef {Object} FileSystem
* @property {(function(string, FileSystemCallback<Buffer | string>): void) & function(string, object, FileSystemCallback<Buffer | string>): void} readFile
* @property {(function(string, FileSystemCallback<(Buffer | string)[] | FileSystemDirent[]>): void) & function(string, object, FileSystemCallback<(Buffer | string)[] | FileSystemDirent[]>): void} readdir
* @property {((function(string, FileSystemCallback<object>): void) & function(string, object, FileSystemCallback<object>): void)=} readJson
* @property {(function(string, FileSystemCallback<Buffer | string>): void) & function(string, object, FileSystemCallback<Buffer | string>): void} readlink
* @property {(function(string, FileSystemCallback<FileSystemStats>): void) & function(string, object, FileSystemCallback<Buffer | string>): void=} lstat
* @property {(function(string, FileSystemCallback<FileSystemStats>): void) & function(string, object, FileSystemCallback<Buffer | string>): void} stat
* @property {ReadFile} readFile
* @property {Readdir} readdir
* @property {ReadJson=} readJson
* @property {Readlink} readlink
* @property {LStat=} lstat
* @property {Stat} stat
* @property {RealPath=} realpath
*/
/**
* @typedef {Object} SyncFileSystem
* @property {function(string, object=): Buffer | string} readFileSync
* @property {function(string, object=): (Buffer | string)[] | FileSystemDirent[]} readdirSync
* @property {(function(string, object=): object)=} readJsonSync
* @property {function(string, object=): Buffer | string} readlinkSync
* @property {function(string, object=): FileSystemStats=} lstatSync
* @property {function(string, object=): FileSystemStats} statSync
* @property {ReadFileSync} readFileSync
* @property {ReaddirSync} readdirSync
* @property {ReadJsonSync=} readJsonSync
* @property {ReadlinkSync} readlinkSync
* @property {LStatSync=} lstatSync
* @property {StatSync} statSync
* @property {RealPathSync=} realpathSync
*/
/**
@ -76,15 +283,24 @@ const {
* @property {boolean} internal
*/
/** @typedef {string | number | boolean | null} JsonPrimitive */
/** @typedef {JsonValue[]} JsonArray */
/** @typedef {JsonPrimitive | JsonObject | JsonArray} JsonValue */
/** @typedef {{[Key in string]: JsonValue} & {[Key in string]?: JsonValue | undefined}} JsonObject */
/**
* @typedef {Object} BaseResolveRequest
* @property {string | false} path
* @property {object=} context
* @property {string=} descriptionFilePath
* @property {string=} descriptionFileRoot
* @property {object=} descriptionFileData
* @property {JsonObject=} descriptionFileData
* @property {string=} relativePath
* @property {boolean=} ignoreSymlinks
* @property {boolean=} fullySpecified
* @property {string=} __innerRequest
* @property {string=} __innerRequest_request
* @property {string=} __innerRequest_relativePath
*/
/** @typedef {BaseResolveRequest & Partial<ParsedIdentifier>} ResolveRequest */
@ -94,7 +310,12 @@ const {
* @typedef {string} StackEntry
*/
/** @template T @typedef {{ add: (T) => void }} WriteOnlySet */
/**
* @template T
* @typedef {{ add: (item: T) => void }} WriteOnlySet
*/
/** @typedef {(function (ResolveRequest): void)} ResolveContextYield */
/**
* Resolve context
@ -104,17 +325,29 @@ const {
* @property {WriteOnlySet<string>=} missingDependencies dependencies that was not found on file system
* @property {Set<StackEntry>=} stack set of hooks' calls. For instance, `resolve → parsedResolve → describedResolve`,
* @property {(function(string): void)=} log log function
* @property {(function (ResolveRequest): void)=} yield yield result, if provided plugins can return several results
* @property {ResolveContextYield=} yield yield result, if provided plugins can return several results
*/
/** @typedef {AsyncSeriesBailHook<[ResolveRequest, ResolveContext], ResolveRequest | null>} ResolveStepHook */
/**
* @typedef {Object} KnownHooks
* @property {SyncHook<[ResolveStepHook, ResolveRequest], void>} resolveStep
* @property {SyncHook<[ResolveRequest, Error]>} noResolve
* @property {ResolveStepHook} resolve
* @property {AsyncSeriesHook<[ResolveRequest, ResolveContext]>} result
*/
/**
* @typedef {{[key: string]: ResolveStepHook}} EnsuredHooks
*/
/**
* @param {string} str input string
* @returns {string} in camel case
*/
function toCamelCase(str) {
return str.replace(/-([a-z])/g, str => str.substr(1).toUpperCase());
return str.replace(/-([a-z])/g, str => str.slice(1).toUpperCase());
}
class Resolver {
@ -144,17 +377,14 @@ class Resolver {
constructor(fileSystem, options) {
this.fileSystem = fileSystem;
this.options = options;
/** @type {KnownHooks} */
this.hooks = {
/** @type {SyncHook<[ResolveStepHook, ResolveRequest], void>} */
resolveStep: new SyncHook(["hook", "request"], "resolveStep"),
/** @type {SyncHook<[ResolveRequest, Error]>} */
noResolve: new SyncHook(["request", "error"], "noResolve"),
/** @type {ResolveStepHook} */
resolve: new AsyncSeriesBailHook(
["request", "resolveContext"],
"resolve"
),
/** @type {AsyncSeriesHook<[ResolveRequest, ResolveContext]>} */
result: new AsyncSeriesHook(["result", "resolveContext"], "result")
};
}
@ -169,25 +399,29 @@ class Resolver {
}
name = toCamelCase(name);
if (/^before/.test(name)) {
return /** @type {ResolveStepHook} */ (this.ensureHook(
name[6].toLowerCase() + name.substr(7)
).withOptions({
stage: -10
}));
return /** @type {ResolveStepHook} */ (
this.ensureHook(name[6].toLowerCase() + name.slice(7)).withOptions({
stage: -10
})
);
}
if (/^after/.test(name)) {
return /** @type {ResolveStepHook} */ (this.ensureHook(
name[5].toLowerCase() + name.substr(6)
).withOptions({
stage: 10
}));
return /** @type {ResolveStepHook} */ (
this.ensureHook(name[5].toLowerCase() + name.slice(6)).withOptions({
stage: 10
})
);
}
const hook = this.hooks[name];
/** @type {ResolveStepHook} */
const hook = /** @type {KnownHooks & EnsuredHooks} */ (this.hooks)[name];
if (!hook) {
return (this.hooks[name] = new AsyncSeriesBailHook(
/** @type {KnownHooks & EnsuredHooks} */
(this.hooks)[name] = new AsyncSeriesBailHook(
["request", "resolveContext"],
name
));
);
return /** @type {KnownHooks & EnsuredHooks} */ (this.hooks)[name];
}
return hook;
}
@ -202,20 +436,21 @@ class Resolver {
}
name = toCamelCase(name);
if (/^before/.test(name)) {
return /** @type {ResolveStepHook} */ (this.getHook(
name[6].toLowerCase() + name.substr(7)
).withOptions({
stage: -10
}));
return /** @type {ResolveStepHook} */ (
this.getHook(name[6].toLowerCase() + name.slice(7)).withOptions({
stage: -10
})
);
}
if (/^after/.test(name)) {
return /** @type {ResolveStepHook} */ (this.getHook(
name[5].toLowerCase() + name.substr(6)
).withOptions({
stage: 10
}));
return /** @type {ResolveStepHook} */ (
this.getHook(name[5].toLowerCase() + name.slice(6)).withOptions({
stage: 10
})
);
}
const hook = this.hooks[name];
/** @type {ResolveStepHook} */
const hook = /** @type {KnownHooks & EnsuredHooks} */ (this.hooks)[name];
if (!hook) {
throw new Error(`Hook ${name} doesn't exist`);
}
@ -254,7 +489,7 @@ class Resolver {
* @param {string} path context path
* @param {string} request request string
* @param {ResolveContext} resolveContext resolve context
* @param {function(Error | null, (string|false)=, ResolveRequest=): void} callback callback function
* @param {ResolveCallback} callback callback function
* @returns {void}
*/
resolve(context, path, request, resolveContext, callback) {
@ -267,29 +502,45 @@ class Resolver {
if (!resolveContext)
return callback(new Error("resolveContext argument is not set"));
/** @type {ResolveRequest} */
const obj = {
context: context,
path: path,
request: request
};
/** @type {ResolveContextYield | undefined} */
let yield_;
let yieldCalled = false;
/** @type {ResolveContextYield | undefined} */
let finishYield;
if (typeof resolveContext.yield === "function") {
const old = resolveContext.yield;
/**
* @param {ResolveRequest} obj object
*/
yield_ = obj => {
old(obj);
yieldCalled = true;
};
/**
* @param {ResolveRequest} result result
* @returns {void}
*/
finishYield = result => {
if (result) yield_(result);
if (result) {
/** @type {ResolveContextYield} */ (yield_)(result);
}
callback(null);
};
}
const message = `resolve '${request}' in '${path}'`;
/**
* @param {ResolveRequest} result result
* @returns {void}
*/
const finishResolved = result => {
return callback(
null,
@ -302,9 +553,13 @@ class Resolver {
);
};
/**
* @param {string[]} log logs
* @returns {void}
*/
const finishWithoutResolve = log => {
/**
* @type {Error & {details?: string}}
* @type {ErrorWithDetail}
*/
const error = new Error("Can't " + message);
error.details = log.join("\n");
@ -315,6 +570,7 @@ class Resolver {
if (resolveContext.log) {
// We need log anyway to capture it in case of an error
const parentLog = resolveContext.log;
/** @type {string[]} */
const log = [];
return this.doResolve(
this.hooks.resolve,
@ -334,7 +590,12 @@ class Resolver {
(err, result) => {
if (err) return callback(err);
if (yieldCalled || (result && yield_)) return finishYield(result);
if (yieldCalled || (result && yield_)) {
return /** @type {ResolveContextYield} */ (finishYield)(
/** @type {ResolveRequest} */ (result)
);
}
if (result) return finishResolved(result);
return finishWithoutResolve(log);
@ -358,14 +619,19 @@ class Resolver {
(err, result) => {
if (err) return callback(err);
if (yieldCalled || (result && yield_)) return finishYield(result);
if (yieldCalled || (result && yield_)) {
return /** @type {ResolveContextYield} */ (finishYield)(
/** @type {ResolveRequest} */ (result)
);
}
if (result) return finishResolved(result);
// log is missing for the error details
// so we redo the resolving for the log info
// this is more expensive to the success case
// is assumed by default
/** @type {string[]} */
const log = [];
return this.doResolve(
@ -381,7 +647,11 @@ class Resolver {
if (err) return callback(err);
// In a case that there is a race condition and yield will be called
if (yieldCalled || (result && yield_)) return finishYield(result);
if (yieldCalled || (result && yield_)) {
return /** @type {ResolveContextYield} */ (finishYield)(
/** @type {ResolveRequest} */ (result)
);
}
return finishWithoutResolve(log);
}
@ -391,9 +661,18 @@ class Resolver {
}
}
/**
* @param {ResolveStepHook} hook hook
* @param {ResolveRequest} request request
* @param {null|string} message string
* @param {ResolveContext} resolveContext resolver context
* @param {(err?: null|Error, result?: ResolveRequest) => void} callback callback
* @returns {void}
*/
doResolve(hook, request, message, resolveContext, callback) {
const stackEntry = Resolver.createStackEntry(hook, request);
/** @type {Set<string> | undefined} */
let newStack;
if (resolveContext.stack) {
newStack = new Set(resolveContext.stack);
@ -413,7 +692,11 @@ class Resolver {
}
newStack.add(stackEntry);
} else {
newStack = new Set([stackEntry]);
// creating a set with new Set([item])
// allocates a new array that has to be garbage collected
// this is an EXTREMELY hot path, so let's avoid it
newStack = new Set();
newStack.add(stackEntry);
}
this.hooks.resolveStep.call(hook, request);
@ -465,17 +748,25 @@ class Resolver {
part.module = this.isModule(part.request);
part.directory = this.isDirectory(part.request);
if (part.directory) {
part.request = part.request.substr(0, part.request.length - 1);
part.request = part.request.slice(0, -1);
}
}
return part;
}
/**
* @param {string} path path
* @returns {boolean} true, if the path is a module
*/
isModule(path) {
return getType(path) === PathType.Normal;
}
/**
* @param {string} path path
* @returns {boolean} true, if the path is private
*/
isPrivate(path) {
return getType(path) === PathType.Internal;
}
@ -488,10 +779,19 @@ class Resolver {
return path.endsWith("/");
}
/**
* @param {string} path path
* @param {string} request request
* @returns {string} joined path
*/
join(path, request) {
return join(path, request);
}
/**
* @param {string} path path
* @returns {string} normalized path
*/
normalize(path) {
return normalize(path);
}

View file

@ -41,14 +41,17 @@ const UseFilePlugin = require("./UseFilePlugin");
/** @typedef {import("./AliasPlugin").AliasOption} AliasOptionEntry */
/** @typedef {import("./ExtensionAliasPlugin").ExtensionAliasOption} ExtensionAliasOption */
/** @typedef {import("./PnpPlugin").PnpApiImpl} PnpApi */
/** @typedef {import("./Resolver").EnsuredHooks} EnsuredHooks */
/** @typedef {import("./Resolver").FileSystem} FileSystem */
/** @typedef {import("./Resolver").KnownHooks} KnownHooks */
/** @typedef {import("./Resolver").ResolveRequest} ResolveRequest */
/** @typedef {import("./Resolver").SyncFileSystem} SyncFileSystem */
/** @typedef {string|string[]|false} AliasOptionNewRequest */
/** @typedef {{[k: string]: AliasOptionNewRequest}} AliasOptions */
/** @typedef {{[k: string]: string|string[] }} ExtensionAliasOptions */
/** @typedef {{apply: function(Resolver): void} | function(this: Resolver, Resolver): void} Plugin */
/** @typedef {false | 0 | "" | null | undefined} Falsy */
/** @typedef {{apply: function(Resolver): void} | (function(this: Resolver, Resolver): void) | Falsy} Plugin */
/**
* @typedef {Object} UserResolveOptions
@ -122,8 +125,27 @@ function processPnpApiOption(option) {
option === undefined &&
/** @type {NodeJS.ProcessVersions & {pnp: string}} */ versions.pnp
) {
// @ts-ignore
return require("pnpapi"); // eslint-disable-line node/no-missing-require
const _findPnpApi =
/** @type {function(string): PnpApi | null}} */
(
// @ts-ignore
require("module").findPnpApi
);
if (_findPnpApi) {
return {
resolveToUnqualified(request, issuer, opts) {
const pnpapi = _findPnpApi(issuer);
if (!pnpapi) {
// Issuer isn't managed by PnP
return null;
}
return pnpapi.resolveToUnqualified(request, issuer, opts);
}
};
}
}
return option || null;
@ -141,7 +163,7 @@ function normalizeAlias(alias) {
if (/\$$/.test(key)) {
obj.onlyModule = true;
obj.name = key.substr(0, key.length - 1);
obj.name = key.slice(0, -1);
}
return obj;
@ -155,6 +177,7 @@ function normalizeAlias(alias) {
*/
function createOptions(options) {
const mainFieldsSet = new Set(options.mainFields || ["main"]);
/** @type {ResolveOptions["mainFields"]} */
const mainFields = [];
for (const item of mainFieldsSet) {
@ -301,6 +324,7 @@ exports.createResolver = function (options) {
resolver.ensureHook("normalResolve");
resolver.ensureHook("internal");
resolver.ensureHook("rawModule");
resolver.ensureHook("alternateRawModule");
resolver.ensureHook("module");
resolver.ensureHook("resolveAsModule");
resolver.ensureHook("undescribedResolveInPackage");
@ -321,6 +345,7 @@ exports.createResolver = function (options) {
// TODO remove in next major
// cspell:word Interal
// Backward-compat
// @ts-ignore
resolver.hooks.newInteralResolve = resolver.hooks.newInternalResolve;
// resolve
@ -333,7 +358,7 @@ exports.createResolver = function (options) {
new UnsafeCachePlugin(
source,
cachePredicate,
unsafeCache,
/** @type {import("./UnsafeCachePlugin").Cache} */ (unsafeCache),
cacheWithContext,
`new-${source}`
)
@ -441,7 +466,20 @@ exports.createResolver = function (options) {
)
);
plugins.push(
new PnpPlugin("raw-module", pnpApi, "undescribed-resolve-in-package")
new PnpPlugin(
"raw-module",
pnpApi,
"undescribed-resolve-in-package",
"alternate-raw-module"
)
);
plugins.push(
new ModulesInHierarchicalDirectoriesPlugin(
"alternate-raw-module",
["node_modules"],
"module"
)
);
} else {
plugins.push(
@ -636,18 +674,24 @@ exports.createResolver = function (options) {
plugins.push(new NextPlugin("existing-file", "resolved"));
}
const resolved =
/** @type {KnownHooks & EnsuredHooks} */
(resolver.hooks).resolved;
// resolved
if (restrictions.size > 0) {
plugins.push(new RestrictionsPlugin(resolver.hooks.resolved, restrictions));
plugins.push(new RestrictionsPlugin(resolved, restrictions));
}
plugins.push(new ResultPlugin(resolver.hooks.resolved));
plugins.push(new ResultPlugin(resolved));
//// RESOLVER ////
for (const plugin of plugins) {
if (typeof plugin === "function") {
plugin.call(resolver, resolver);
} else {
/** @type {function(this: Resolver, Resolver): void} */
(plugin).call(resolver, resolver);
} else if (plugin) {
plugin.apply(resolver);
}
}

View file

@ -11,6 +11,11 @@
const slashCode = "/".charCodeAt(0);
const backslashCode = "\\".charCodeAt(0);
/**
* @param {string} path path
* @param {string} parent parent path
* @returns {boolean} true, if path is inside of parent
*/
const isInside = (path, parent) => {
if (!path.startsWith(parent)) return false;
if (path.length === parent.length) return true;

View file

@ -8,6 +8,7 @@
const forEachBail = require("./forEachBail");
/** @typedef {import("./Resolver")} Resolver */
/** @typedef {import("./Resolver").ResolveRequest} ResolveRequest */
/** @typedef {import("./Resolver").ResolveStepHook} ResolveStepHook */
class RootsPlugin {
@ -38,8 +39,14 @@ class RootsPlugin {
forEachBail(
this.roots,
/**
* @param {string} root root
* @param {(err?: null|Error, result?: null|ResolveRequest) => void} callback callback
* @returns {void}
*/
(root, callback) => {
const path = resolver.join(root, req.slice(1));
/** @type {ResolveRequest} */
const obj = {
...request,
path,

View file

@ -8,6 +8,8 @@
const DescriptionFileUtils = require("./DescriptionFileUtils");
/** @typedef {import("./Resolver")} Resolver */
/** @typedef {import("./Resolver").JsonObject} JsonObject */
/** @typedef {import("./Resolver").ResolveRequest} ResolveRequest */
/** @typedef {import("./Resolver").ResolveStepHook} ResolveStepHook */
const slashCode = "/".charCodeAt(0);
@ -40,13 +42,13 @@ module.exports = class SelfReferencePlugin {
// Feature is only enabled when an exports field is present
const exportsField = DescriptionFileUtils.getField(
request.descriptionFileData,
/** @type {JsonObject} */ (request.descriptionFileData),
this.fieldName
);
if (!exportsField) return callback();
const name = DescriptionFileUtils.getField(
request.descriptionFileData,
/** @type {JsonObject} */ (request.descriptionFileData),
"name"
);
if (typeof name !== "string") return callback();
@ -57,7 +59,7 @@ module.exports = class SelfReferencePlugin {
req.charCodeAt(name.length) === slashCode)
) {
const remainingRequest = `.${req.slice(name.length)}`;
/** @type {ResolveRequest} */
const obj = {
...request,
request: remainingRequest,

View file

@ -10,6 +10,7 @@ const getPaths = require("./getPaths");
const { getType, PathType } = require("./util/path");
/** @typedef {import("./Resolver")} Resolver */
/** @typedef {import("./Resolver").ResolveRequest} ResolveRequest */
/** @typedef {import("./Resolver").ResolveStepHook} ResolveStepHook */
module.exports = class SymlinkPlugin {
@ -33,7 +34,7 @@ module.exports = class SymlinkPlugin {
.getHook(this.source)
.tapAsync("SymlinkPlugin", (request, resolveContext, callback) => {
if (request.ignoreSymlinks) return callback();
const pathsResult = getPaths(request.path);
const pathsResult = getPaths(/** @type {string} */ (request.path));
const pathSegments = pathsResult.segments;
const paths = pathsResult.paths;
@ -41,13 +42,18 @@ module.exports = class SymlinkPlugin {
let idx = -1;
forEachBail(
paths,
/**
* @param {string} path path
* @param {(err?: null|Error, result?: null|number) => void} callback callback
* @returns {void}
*/
(path, callback) => {
idx++;
if (resolveContext.fileDependencies)
resolveContext.fileDependencies.add(path);
fs.readlink(path, (err, result) => {
if (!err && result) {
pathSegments[idx] = result;
pathSegments[idx] = /** @type {string} */ (result);
containsSymlink = true;
// Shortcut when absolute symlink found
const resultType = getType(result.toString());
@ -61,6 +67,11 @@ module.exports = class SymlinkPlugin {
callback();
});
},
/**
* @param {null|Error} [err] error
* @param {null|number} [idx] result
* @returns {void}
*/
(err, idx) => {
if (!containsSymlink) return callback();
const resultSegments =
@ -70,6 +81,7 @@ module.exports = class SymlinkPlugin {
const result = resultSegments.reduceRight((a, b) => {
return resolver.join(a, b);
});
/** @type {ResolveRequest} */
const obj = {
...request,
path: result

View file

@ -6,6 +6,8 @@
"use strict";
/** @typedef {import("./Resolver").FileSystem} FileSystem */
/** @typedef {import("./Resolver").ReaddirStringCallback} ReaddirStringCallback */
/** @typedef {import("./Resolver").StringCallback} StringCallback */
/** @typedef {import("./Resolver").SyncFileSystem} SyncFileSystem */
/**
@ -19,77 +21,200 @@ function SyncAsyncFileSystemDecorator(fs) {
this.lstatSync = undefined;
const lstatSync = fs.lstatSync;
if (lstatSync) {
this.lstat = (arg, options, callback) => {
let result;
try {
result = lstatSync.call(fs, arg);
} catch (e) {
return (callback || options)(e);
}
(callback || options)(null, result);
};
this.lstatSync = (arg, options) => lstatSync.call(fs, arg, options);
this.lstat =
/** @type {FileSystem["lstat"]} */
(
(arg, options, callback) => {
let result;
try {
result = /** @type {Function | undefined} */ (callback)
? lstatSync.call(fs, arg, options)
: lstatSync.call(fs, arg);
} catch (e) {
return (callback || options)(
/** @type {NodeJS.ErrnoException | null} */ (e)
);
}
(callback || options)(null, /** @type {any} */ (result));
}
);
this.lstatSync =
/** @type {SyncFileSystem["lstatSync"]} */
((arg, options) => lstatSync.call(fs, arg, options));
}
this.stat = (arg, options, callback) => {
let result;
try {
result = callback ? fs.statSync(arg, options) : fs.statSync(arg);
} catch (e) {
return (callback || options)(e);
}
(callback || options)(null, result);
};
this.statSync = (arg, options) => fs.statSync(arg, options);
this.stat =
/** @type {FileSystem["stat"]} */
(
(arg, options, callback) => {
let result;
try {
result = /** @type {Function | undefined} */ (callback)
? fs.statSync(arg, options)
: fs.statSync(arg);
} catch (e) {
return (callback || options)(
/** @type {NodeJS.ErrnoException | null} */ (e)
);
}
this.readdir = (arg, options, callback) => {
let result;
try {
result = fs.readdirSync(arg);
} catch (e) {
return (callback || options)(e);
}
(callback || options)(null, result);
};
this.readdirSync = (arg, options) => fs.readdirSync(arg, options);
(callback || options)(null, /** @type {any} */ (result));
}
);
this.statSync =
/** @type {SyncFileSystem["statSync"]} */
((arg, options) => fs.statSync(arg, options));
this.readFile = (arg, options, callback) => {
let result;
try {
result = fs.readFileSync(arg);
} catch (e) {
return (callback || options)(e);
}
(callback || options)(null, result);
};
this.readFileSync = (arg, options) => fs.readFileSync(arg, options);
this.readdir =
/** @type {FileSystem["readdir"]} */
(
(arg, options, callback) => {
let result;
try {
result = /** @type {Function | undefined} */ (callback)
? fs.readdirSync(
arg,
/** @type {Exclude<Parameters<FileSystem["readdir"]>[1], ReaddirStringCallback>} */
(options)
)
: fs.readdirSync(arg);
} catch (e) {
return (callback || options)(
/** @type {NodeJS.ErrnoException | null} */ (e)
);
}
this.readlink = (arg, options, callback) => {
let result;
try {
result = fs.readlinkSync(arg);
} catch (e) {
return (callback || options)(e);
}
(callback || options)(null, result);
};
this.readlinkSync = (arg, options) => fs.readlinkSync(arg, options);
(callback || options)(null, /** @type {any} */ (result));
}
);
this.readdirSync =
/** @type {SyncFileSystem["readdirSync"]} */
(
(arg, options) =>
fs.readdirSync(
arg,
/** @type {Parameters<SyncFileSystem["readdirSync"]>[1]} */ (options)
)
);
this.readFile =
/** @type {FileSystem["readFile"]} */
(
(arg, options, callback) => {
let result;
try {
result = /** @type {Function | undefined} */ (callback)
? fs.readFileSync(arg, options)
: fs.readFileSync(arg);
} catch (e) {
return (callback || options)(
/** @type {NodeJS.ErrnoException | null} */ (e)
);
}
(callback || options)(null, /** @type {any} */ (result));
}
);
this.readFileSync =
/** @type {SyncFileSystem["readFileSync"]} */
((arg, options) => fs.readFileSync(arg, options));
this.readlink =
/** @type {FileSystem["readlink"]} */
(
(arg, options, callback) => {
let result;
try {
result = /** @type {Function | undefined} */ (callback)
? fs.readlinkSync(
arg,
/** @type {Exclude<Parameters<FileSystem["readlink"]>[1], StringCallback>} */
(options)
)
: fs.readlinkSync(arg);
} catch (e) {
return (callback || options)(
/** @type {NodeJS.ErrnoException | null} */ (e)
);
}
(callback || options)(null, /** @type {any} */ (result));
}
);
this.readlinkSync =
/** @type {SyncFileSystem["readlinkSync"]} */
(
(arg, options) =>
fs.readlinkSync(
arg,
/** @type {Parameters<SyncFileSystem["readlinkSync"]>[1]} */ (options)
)
);
this.readJson = undefined;
this.readJsonSync = undefined;
const readJsonSync = fs.readJsonSync;
if (readJsonSync) {
this.readJson = (arg, options, callback) => {
let result;
try {
result = readJsonSync.call(fs, arg);
} catch (e) {
return (callback || options)(e);
}
(callback || options)(null, result);
};
this.readJson =
/** @type {FileSystem["readJson"]} */
(
(arg, callback) => {
let result;
try {
result = readJsonSync.call(fs, arg);
} catch (e) {
return callback(
/** @type {NodeJS.ErrnoException | Error | null} */ (e)
);
}
this.readJsonSync = (arg, options) => readJsonSync.call(fs, arg, options);
callback(null, result);
}
);
this.readJsonSync =
/** @type {SyncFileSystem["readJsonSync"]} */
(arg => readJsonSync.call(fs, arg));
}
this.realpath = undefined;
this.realpathSync = undefined;
const realpathSync = fs.realpathSync;
if (realpathSync) {
this.realpath =
/** @type {FileSystem["realpath"]} */
(
(arg, options, callback) => {
let result;
try {
result = /** @type {Function | undefined} */ (callback)
? realpathSync.call(
fs,
arg,
/** @type {Exclude<Parameters<NonNullable<FileSystem["realpath"]>>[1], StringCallback>} */
(options)
)
: realpathSync.call(fs, arg);
} catch (e) {
return (callback || options)(
/** @type {NodeJS.ErrnoException | null} */ (e)
);
}
(callback || options)(null, /** @type {any} */ (result));
}
);
this.realpathSync =
/** @type {SyncFileSystem["realpathSync"]} */
(
(arg, options) =>
realpathSync.call(
fs,
arg,
/** @type {Parameters<NonNullable<SyncFileSystem["realpathSync"]>>[1]} */
(options)
)
);
}
}
module.exports = SyncAsyncFileSystemDecorator;

View file

@ -8,8 +8,15 @@
/** @typedef {import("./Resolver")} Resolver */
/** @typedef {import("./Resolver").ResolveRequest} ResolveRequest */
/** @typedef {import("./Resolver").ResolveStepHook} ResolveStepHook */
/** @typedef {{[k: string]: any}} Cache */
/** @typedef {import("./Resolver").ResolveContextYield} ResolveContextYield */
/** @typedef {{[k: string]: ResolveRequest | ResolveRequest[] | undefined}} Cache */
/**
* @param {string} type type of cache
* @param {ResolveRequest} request request
* @param {boolean} withContext cache with context?
* @returns {string} cache id
*/
function getCacheId(type, request, withContext) {
return JSON.stringify({
type,
@ -64,11 +71,14 @@ module.exports = class UnsafeCachePlugin {
}
return callback(null, null);
}
return callback(null, cacheEntry);
return callback(null, /** @type {ResolveRequest} */ (cacheEntry));
}
/** @type {ResolveContextYield|undefined} */
let yieldFn;
/** @type {ResolveContextYield|undefined} */
let yield_;
/** @type {ResolveRequest[]} */
const yieldResult = [];
if (isYield) {
yieldFn = resolveContext.yield;
@ -86,7 +96,10 @@ module.exports = class UnsafeCachePlugin {
if (err) return callback(err);
if (isYield) {
if (result) yieldResult.push(result);
for (const result of yieldResult) yieldFn(result);
for (const result of yieldResult) {
/** @type {ResolveContextYield} */
(yieldFn)(result);
}
this.cache[cacheId] = yieldResult;
return callback(null, null);
}

View file

@ -6,6 +6,7 @@
"use strict";
/** @typedef {import("./Resolver")} Resolver */
/** @typedef {import("./Resolver").ResolveRequest} ResolveRequest */
/** @typedef {import("./Resolver").ResolveStepHook} ResolveStepHook */
module.exports = class UseFilePlugin {
@ -29,7 +30,12 @@ module.exports = class UseFilePlugin {
resolver
.getHook(this.source)
.tapAsync("UseFilePlugin", (request, resolveContext, callback) => {
const filePath = resolver.join(request.path, this.filename);
const filePath = resolver.join(
/** @type {string} */ (request.path),
this.filename
);
/** @type {ResolveRequest} */
const obj = {
...request,
path: filePath,

View file

@ -5,27 +5,37 @@
"use strict";
module.exports = function createInnerContext(
options,
message,
messageOptional
) {
/** @typedef {import("./Resolver").ResolveContext} ResolveContext */
/**
* @param {ResolveContext} options options for inner context
* @param {null|string} message message to log
* @returns {ResolveContext} inner context
*/
module.exports = function createInnerContext(options, message) {
let messageReported = false;
let innerLog = undefined;
if (options.log) {
if (message) {
/**
* @param {string} msg message
*/
innerLog = msg => {
if (!messageReported) {
options.log(message);
/** @type {(function(string): void)} */
(options.log)(message);
messageReported = true;
}
options.log(" " + msg);
/** @type {(function(string): void)} */
(options.log)(" " + msg);
};
} else {
innerLog = options.log;
}
}
const childContext = {
return {
log: innerLog,
yield: options.yield,
fileDependencies: options.fileDependencies,
@ -33,5 +43,4 @@ module.exports = function createInnerContext(
missingDependencies: options.missingDependencies,
stack: options.stack
};
return childContext;
};

View file

@ -5,19 +5,44 @@
"use strict";
/** @typedef {import("./Resolver").ResolveRequest} ResolveRequest */
/**
* @template T
* @template Z
* @callback Iterator
* @param {T} item item
* @param {(err?: null|Error, result?: null|Z) => void} callback callback
* @param {number} i index
* @returns {void}
*/
/**
* @template T
* @template Z
* @param {T[]} array array
* @param {Iterator<T, Z>} iterator iterator
* @param {(err?: null|Error, result?: null|Z, i?: number) => void} callback callback after all items are iterated
* @returns {void}
*/
module.exports = function forEachBail(array, iterator, callback) {
if (array.length === 0) return callback();
let i = 0;
const next = () => {
/** @type {boolean|undefined} */
let loop = undefined;
iterator(array[i++], (err, result) => {
if (err || result !== undefined || i >= array.length) {
return callback(err, result);
}
if (loop === false) while (next());
loop = true;
});
iterator(
array[i++],
(err, result) => {
if (err || result !== undefined || i >= array.length) {
return callback(err, result, i);
}
if (loop === false) while (next());
loop = true;
},
i
);
if (!loop) loop = false;
return loop;
};

View file

@ -5,6 +5,14 @@
"use strict";
/** @typedef {import("./Resolver")} Resolver */
/** @typedef {import("./Resolver").ResolveRequest} ResolveRequest */
/**
* @param {Resolver} resolver resolver
* @param {ResolveRequest} request string
* @returns {string} inner request
*/
module.exports = function getInnerRequest(resolver, request) {
if (
typeof request.__innerRequest === "string" &&
@ -12,6 +20,7 @@ module.exports = function getInnerRequest(resolver, request) {
request.__innerRequest_relativePath === request.relativePath
)
return request.__innerRequest;
/** @type {string|undefined} */
let innerRequest;
if (request.request) {
innerRequest = request.request;
@ -23,5 +32,5 @@ module.exports = function getInnerRequest(resolver, request) {
}
request.__innerRequest_request = request.request;
request.__innerRequest_relativePath = request.relativePath;
return (request.__innerRequest = innerRequest);
return (request.__innerRequest = /** @type {string} */ (innerRequest));
};

View file

@ -5,18 +5,22 @@
"use strict";
/**
* @param {string} path path
* @returns {{paths: string[], segments: string[]}}} paths and segments
*/
module.exports = function getPaths(path) {
if (path === "/") return { paths: ["/"], segments: [""] };
const parts = path.split(/(.*?[\\/]+)/);
const paths = [path];
const segments = [parts[parts.length - 1]];
let part = parts[parts.length - 1];
path = path.substr(0, path.length - part.length - 1);
path = path.substring(0, path.length - part.length - 1);
for (let i = parts.length - 2; i > 2; i -= 2) {
paths.push(path);
part = parts[i];
path = path.substr(0, path.length - part.length) || "/";
segments.push(part.substr(0, part.length - 1));
path = path.substring(0, path.length - part.length) || "/";
segments.push(part.slice(0, -1));
}
part = parts[1];
segments.push(part);
@ -27,11 +31,15 @@ module.exports = function getPaths(path) {
};
};
/**
* @param {string} path path
* @returns {string|null} basename or null
*/
module.exports.basename = function basename(path) {
const i = path.lastIndexOf("/"),
j = path.lastIndexOf("\\");
const p = i < 0 ? j : j < 0 ? i : i < j ? j : i;
if (p < 0) return null;
const s = path.substr(p + 1);
const s = path.slice(p + 1);
return s;
};

View file

@ -12,10 +12,23 @@ const ResolverFactory = require("./ResolverFactory");
/** @typedef {import("./PnpPlugin").PnpApiImpl} PnpApi */
/** @typedef {import("./Resolver")} Resolver */
/** @typedef {import("./Resolver").FileSystem} FileSystem */
/** @typedef {import("./Resolver").ResolveCallback} ResolveCallback */
/** @typedef {import("./Resolver").ResolveContext} ResolveContext */
/** @typedef {import("./Resolver").ResolveRequest} ResolveRequest */
/** @typedef {import("./ResolverFactory").Plugin} Plugin */
/** @typedef {import("./ResolverFactory").UserResolveOptions} ResolveOptions */
/** @typedef {{
* (context: object, path: string, request: string, resolveContext: ResolveContext, callback: ResolveCallback): void;
* (context: object, path: string, request: string, callback: ResolveCallback): void;
* (path: string, request: string, resolveContext: ResolveContext, callback: ResolveCallback): void;
* (path: string, request: string, callback: ResolveCallback): void;
* }} ResolveFunctionAsync
*/
/** @typedef {{
* (context: object, path: string, request: string): string|false;
* (path: string, request: string): string|false;
* }} ResolveFunction
*/
const nodeFileSystem = new CachedInputFileSystem(fs, 4000);
@ -28,19 +41,37 @@ const asyncResolver = ResolverFactory.createResolver({
extensions: [".js", ".json", ".node"],
fileSystem: nodeFileSystem
});
function resolve(context, path, request, resolveContext, callback) {
if (typeof context === "string") {
callback = resolveContext;
resolveContext = request;
request = path;
path = context;
context = nodeContext;
}
if (typeof callback !== "function") {
callback = resolveContext;
}
asyncResolver.resolve(context, path, request, resolveContext, callback);
}
/**
* @type {ResolveFunctionAsync}
*/
const resolve =
/**
* @param {object|string} context
* @param {string} path
* @param {string|ResolveContext|ResolveCallback} request
* @param {ResolveContext|ResolveCallback=} resolveContext
* @param {ResolveCallback=} callback
*/
(context, path, request, resolveContext, callback) => {
if (typeof context === "string") {
callback = /** @type {ResolveCallback} */ (resolveContext);
resolveContext = /** @type {ResolveContext} */ (request);
request = path;
path = context;
context = nodeContext;
}
if (typeof callback !== "function") {
callback = /** @type {ResolveCallback} */ (resolveContext);
}
asyncResolver.resolve(
context,
path,
/** @type {string} */ (request),
/** @type {ResolveContext} */ (resolveContext),
/** @type {ResolveCallback} */ (callback)
);
};
const syncResolver = ResolverFactory.createResolver({
conditionNames: ["node"],
@ -48,50 +79,91 @@ const syncResolver = ResolverFactory.createResolver({
useSyncFileSystemCalls: true,
fileSystem: nodeFileSystem
});
function resolveSync(context, path, request) {
if (typeof context === "string") {
request = path;
path = context;
context = nodeContext;
}
return syncResolver.resolveSync(context, path, request);
}
/**
* @type {ResolveFunction}
*/
const resolveSync =
/**
* @param {object|string} context
* @param {string} path
* @param {string=} request
*/
(context, path, request) => {
if (typeof context === "string") {
request = path;
path = context;
context = nodeContext;
}
return syncResolver.resolveSync(
context,
path,
/** @type {string} */ (request)
);
};
/** @typedef {Omit<ResolveOptions, "fileSystem"> & Partial<Pick<ResolveOptions, "fileSystem">>} ResolveOptionsOptionalFS */
/**
* @param {ResolveOptionsOptionalFS} options Resolver options
* @returns {ResolveFunctionAsync} Resolver function
*/
function create(options) {
options = {
const resolver = ResolverFactory.createResolver({
fileSystem: nodeFileSystem,
...options
};
const resolver = ResolverFactory.createResolver(options);
});
/**
* @param {object|string} context Custom context
* @param {string} path Base path
* @param {string|ResolveContext|ResolveCallback} request String to resolve
* @param {ResolveContext|ResolveCallback=} resolveContext Resolve context
* @param {ResolveCallback=} callback Result callback
*/
return function (context, path, request, resolveContext, callback) {
if (typeof context === "string") {
callback = resolveContext;
resolveContext = request;
callback = /** @type {ResolveCallback} */ (resolveContext);
resolveContext = /** @type {ResolveContext} */ (request);
request = path;
path = context;
context = nodeContext;
}
if (typeof callback !== "function") {
callback = resolveContext;
callback = /** @type {ResolveCallback} */ (resolveContext);
}
resolver.resolve(context, path, request, resolveContext, callback);
resolver.resolve(
context,
path,
/** @type {string} */ (request),
/** @type {ResolveContext} */ (resolveContext),
callback
);
};
}
/**
* @param {ResolveOptionsOptionalFS} options Resolver options
* @returns {ResolveFunction} Resolver function
*/
function createSync(options) {
options = {
const resolver = ResolverFactory.createResolver({
useSyncFileSystemCalls: true,
fileSystem: nodeFileSystem,
...options
};
const resolver = ResolverFactory.createResolver(options);
});
/**
* @param {object|string} context custom context
* @param {string} path base path
* @param {string=} request request to resolve
* @returns {string|false} Resolved path or false
*/
return function (context, path, request) {
if (typeof context === "string") {
request = path;
path = context;
context = nodeContext;
}
return resolver.resolveSync(context, path, request);
return resolver.resolveSync(context, path, /** @type {string} */ (request));
};
}

View file

@ -11,20 +11,12 @@
/** @typedef {Record<string, MappingValue>|ConditionalMapping|DirectMapping} ExportsField */
/** @typedef {Record<string, MappingValue>} ImportsField */
/**
* @typedef {Object} PathTreeNode
* @property {Map<string, PathTreeNode>|null} children
* @property {MappingValue} folder
* @property {Map<string, MappingValue>|null} wildcards
* @property {Map<string, MappingValue>} files
*/
/**
* Processing exports/imports field
* @callback FieldProcessor
* @param {string} request request
* @param {Set<string>} conditionNames condition names
* @returns {string[]} resolved paths
* @returns {[string[], string | null]} resolved paths with used field
*/
/*
@ -80,9 +72,11 @@ Conditional mapping nested in another conditional mapping is called nested mappi
*/
const { parseIdentifier } = require("./identifier");
const slashCode = "/".charCodeAt(0);
const dotCode = ".".charCodeAt(0);
const hashCode = "#".charCodeAt(0);
const patternRegEx = /\*/g;
/**
* @param {ExportsField} exportsField the exports field
@ -92,7 +86,8 @@ module.exports.processExportsField = function processExportsField(
exportsField
) {
return createFieldProcessor(
buildExportsFieldPathTree(exportsField),
buildExportsField(exportsField),
request => (request.length === 0 ? "." : "./" + request),
assertExportsFieldRequest,
assertExportTarget
);
@ -106,27 +101,35 @@ module.exports.processImportsField = function processImportsField(
importsField
) {
return createFieldProcessor(
buildImportsFieldPathTree(importsField),
importsField,
request => "#" + request,
assertImportsFieldRequest,
assertImportTarget
);
};
/**
* @param {PathTreeNode} treeRoot root
* @param {ExportsField | ImportsField} field root
* @param {(s: string) => string} normalizeRequest Normalize request, for `imports` field it adds `#`, for `exports` field it adds `.` or `./`
* @param {(s: string) => string} assertRequest assertRequest
* @param {(s: string, f: boolean) => void} assertTarget assertTarget
* @returns {FieldProcessor} field processor
*/
function createFieldProcessor(treeRoot, assertRequest, assertTarget) {
function createFieldProcessor(
field,
normalizeRequest,
assertRequest,
assertTarget
) {
return function fieldProcessor(request, conditionNames) {
request = assertRequest(request);
const match = findMatch(request, treeRoot);
const match = findMatch(normalizeRequest(request), field);
if (match === null) return [];
if (match === null) return [[], null];
const [mapping, remainRequestIndex] = match;
const [mapping, remainingRequest, isSubpathMapping, isPattern, usedField] =
match;
/** @type {DirectMapping|null} */
let direct = null;
@ -138,25 +141,22 @@ function createFieldProcessor(treeRoot, assertRequest, assertTarget) {
);
// matching not found
if (direct === null) return [];
if (direct === null) return [[], null];
} else {
direct = /** @type {DirectMapping} */ (mapping);
}
const remainingRequest =
remainRequestIndex === request.length + 1
? undefined
: remainRequestIndex < 0
? request.slice(-remainRequestIndex - 1)
: request.slice(remainRequestIndex);
return directMapping(
remainingRequest,
remainRequestIndex < 0,
direct,
conditionNames,
assertTarget
);
return [
directMapping(
remainingRequest,
isPattern,
isSubpathMapping,
direct,
conditionNames,
assertTarget
),
usedField
];
};
}
@ -205,18 +205,15 @@ function assertImportsFieldRequest(request) {
* @param {boolean} expectFolder is folder expected
*/
function assertExportTarget(exp, expectFolder) {
if (
exp.charCodeAt(0) === slashCode ||
(exp.charCodeAt(0) === dotCode && exp.charCodeAt(1) !== slashCode)
) {
throw new Error(
`Export should be relative path and start with "./", got ${JSON.stringify(
exp
)}.`
);
const parsedIdentifier = parseIdentifier(exp);
if (!parsedIdentifier) {
return;
}
const isFolder = exp.charCodeAt(exp.length - 1) === slashCode;
const [relativePath] = parsedIdentifier;
const isFolder =
relativePath.charCodeAt(relativePath.length - 1) === slashCode;
if (isFolder !== expectFolder) {
throw new Error(
@ -236,7 +233,15 @@ function assertExportTarget(exp, expectFolder) {
* @param {boolean} expectFolder is folder expected
*/
function assertImportTarget(imp, expectFolder) {
const isFolder = imp.charCodeAt(imp.length - 1) === slashCode;
const parsedIdentifier = parseIdentifier(imp);
if (!parsedIdentifier) {
return;
}
const [relativePath] = parsedIdentifier;
const isFolder =
relativePath.charCodeAt(relativePath.length - 1) === slashCode;
if (isFolder !== expectFolder) {
throw new Error(
@ -251,101 +256,95 @@ function assertImportTarget(imp, expectFolder) {
}
}
/**
* @param {string} a first string
* @param {string} b second string
* @returns {number} compare result
*/
function patternKeyCompare(a, b) {
const aPatternIndex = a.indexOf("*");
const bPatternIndex = b.indexOf("*");
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;
}
/**
* Trying to match request to field
* @param {string} request request
* @param {PathTreeNode} treeRoot path tree root
* @returns {[MappingValue, number]|null} match or null, number is negative and one less when it's a folder mapping, number is request.length + 1 for direct mappings
* @param {ExportsField | ImportsField} field exports or import field
* @returns {[MappingValue, string, boolean, boolean, string]|null} match or null, number is negative and one less when it's a folder mapping, number is request.length + 1 for direct mappings
*/
function findMatch(request, treeRoot) {
if (request.length === 0) {
const value = treeRoot.files.get("");
return value ? [value, 1] : null;
}
function findMatch(request, field) {
if (
treeRoot.children === null &&
treeRoot.folder === null &&
treeRoot.wildcards === null
Object.prototype.hasOwnProperty.call(field, request) &&
!request.includes("*") &&
!request.endsWith("/")
) {
const value = treeRoot.files.get(request);
const target = /** @type {{[k: string]: MappingValue}} */ (field)[request];
return value ? [value, request.length + 1] : null;
return [target, "", false, false, request];
}
let node = treeRoot;
let lastNonSlashIndex = 0;
let slashIndex = request.indexOf("/", 0);
/** @type {string} */
let bestMatch = "";
/** @type {string|undefined} */
let bestMatchSubpath;
/** @type {[MappingValue, number]|null} */
let lastFolderMatch = null;
const keys = Object.getOwnPropertyNames(field);
const applyFolderMapping = () => {
const folderMapping = node.folder;
if (folderMapping) {
if (lastFolderMatch) {
lastFolderMatch[0] = folderMapping;
lastFolderMatch[1] = -lastNonSlashIndex - 1;
} else {
lastFolderMatch = [folderMapping, -lastNonSlashIndex - 1];
for (let i = 0; i < keys.length; i++) {
const key = keys[i];
const patternIndex = key.indexOf("*");
if (patternIndex !== -1 && request.startsWith(key.slice(0, patternIndex))) {
const patternTrailer = key.slice(patternIndex + 1);
if (
request.length >= key.length &&
request.endsWith(patternTrailer) &&
patternKeyCompare(bestMatch, key) === 1 &&
key.lastIndexOf("*") === patternIndex
) {
bestMatch = key;
bestMatchSubpath = request.slice(
patternIndex,
request.length - patternTrailer.length
);
}
}
};
const applyWildcardMappings = (wildcardMappings, remainingRequest) => {
if (wildcardMappings) {
for (const [key, target] of wildcardMappings) {
if (remainingRequest.startsWith(key)) {
if (!lastFolderMatch) {
lastFolderMatch = [target, lastNonSlashIndex + key.length];
} else if (lastFolderMatch[1] < lastNonSlashIndex + key.length) {
lastFolderMatch[0] = target;
lastFolderMatch[1] = lastNonSlashIndex + key.length;
}
}
}
// For legacy `./foo/`
else if (
key[key.length - 1] === "/" &&
request.startsWith(key) &&
patternKeyCompare(bestMatch, key) === 1
) {
bestMatch = key;
bestMatchSubpath = request.slice(key.length);
}
};
while (slashIndex !== -1) {
applyFolderMapping();
const wildcardMappings = node.wildcards;
if (!wildcardMappings && node.children === null) return lastFolderMatch;
const folder = request.slice(lastNonSlashIndex, slashIndex);
applyWildcardMappings(wildcardMappings, folder);
if (node.children === null) return lastFolderMatch;
const newNode = node.children.get(folder);
if (!newNode) {
return lastFolderMatch;
}
node = newNode;
lastNonSlashIndex = slashIndex + 1;
slashIndex = request.indexOf("/", lastNonSlashIndex);
}
const remainingRequest =
lastNonSlashIndex > 0 ? request.slice(lastNonSlashIndex) : request;
if (bestMatch === "") return null;
const value = node.files.get(remainingRequest);
const target = /** @type {{[k: string]: MappingValue}} */ (field)[bestMatch];
const isSubpathMapping = bestMatch.endsWith("/");
const isPattern = bestMatch.includes("*");
if (value) {
return [value, request.length + 1];
}
applyFolderMapping();
applyWildcardMappings(node.wildcards, remainingRequest);
return lastFolderMatch;
return [
target,
/** @type {string} */ (bestMatchSubpath),
isSubpathMapping,
isPattern,
bestMatch
];
}
/**
@ -360,7 +359,8 @@ function isConditionalMapping(mapping) {
/**
* @param {string|undefined} remainingRequest remaining request when folder mapping, undefined for file mappings
* @param {boolean} subpathMapping true, for subpath mappings
* @param {boolean} isPattern true, if mapping is a pattern (contains "*")
* @param {boolean} isSubpathMapping true, for subpath mappings
* @param {DirectMapping|null} mappingTarget direct export
* @param {Set<string>} conditionNames condition names
* @param {(d: string, f: boolean) => void} assert asserting direct value
@ -368,7 +368,8 @@ function isConditionalMapping(mapping) {
*/
function directMapping(
remainingRequest,
subpathMapping,
isPattern,
isSubpathMapping,
mappingTarget,
conditionNames,
assert
@ -377,16 +378,29 @@ function directMapping(
if (typeof mappingTarget === "string") {
return [
targetMapping(remainingRequest, subpathMapping, mappingTarget, assert)
targetMapping(
remainingRequest,
isPattern,
isSubpathMapping,
mappingTarget,
assert
)
];
}
/** @type {string[]} */
const targets = [];
for (const exp of mappingTarget) {
if (typeof exp === "string") {
targets.push(
targetMapping(remainingRequest, subpathMapping, exp, assert)
targetMapping(
remainingRequest,
isPattern,
isSubpathMapping,
exp,
assert
)
);
continue;
}
@ -395,7 +409,8 @@ function directMapping(
if (!mapping) continue;
const innerExports = directMapping(
remainingRequest,
subpathMapping,
isPattern,
isSubpathMapping,
mapping,
conditionNames,
assert
@ -410,27 +425,43 @@ function directMapping(
/**
* @param {string|undefined} remainingRequest remaining request when folder mapping, undefined for file mappings
* @param {boolean} subpathMapping true, for subpath mappings
* @param {boolean} isPattern true, if mapping is a pattern (contains "*")
* @param {boolean} isSubpathMapping true, for subpath mappings
* @param {string} mappingTarget direct export
* @param {(d: string, f: boolean) => void} assert asserting direct value
* @returns {string} mapping result
*/
function targetMapping(
remainingRequest,
subpathMapping,
isPattern,
isSubpathMapping,
mappingTarget,
assert
) {
if (remainingRequest === undefined) {
assert(mappingTarget, false);
return mappingTarget;
}
if (subpathMapping) {
if (isSubpathMapping) {
assert(mappingTarget, true);
return mappingTarget + remainingRequest;
}
assert(mappingTarget, false);
return mappingTarget.replace(/\*/g, remainingRequest.replace(/\$/g, "$$"));
let result = mappingTarget;
if (isPattern) {
result = result.replace(
patternRegEx,
remainingRequest.replace(/\$/g, "$$")
);
}
return result;
}
/**
@ -444,21 +475,17 @@ function conditionalMapping(conditionalMapping_, conditionNames) {
loop: while (lookup.length > 0) {
const [mapping, conditions, j] = lookup[lookup.length - 1];
const last = conditions.length - 1;
for (let i = j; i < conditions.length; i++) {
const condition = conditions[i];
// assert default. Could be last only
if (i !== last) {
if (condition === "default") {
throw new Error("Default condition should be last one");
}
} else if (condition === "default") {
if (condition === "default") {
const innerMapping = mapping[condition];
// is nested
if (isConditionalMapping(innerMapping)) {
const conditionalMapping = /** @type {ConditionalMapping} */ (innerMapping);
const conditionalMapping = /** @type {ConditionalMapping} */ (
innerMapping
);
lookup[lookup.length - 1][2] = i + 1;
lookup.push([conditionalMapping, Object.keys(conditionalMapping), 0]);
continue loop;
@ -471,7 +498,9 @@ function conditionalMapping(conditionalMapping_, conditionNames) {
const innerMapping = mapping[condition];
// is nested
if (isConditionalMapping(innerMapping)) {
const conditionalMapping = /** @type {ConditionalMapping} */ (innerMapping);
const conditionalMapping = /** @type {ConditionalMapping} */ (
innerMapping
);
lookup[lookup.length - 1][2] = i + 1;
lookup.push([conditionalMapping, Object.keys(conditionalMapping), 0]);
continue loop;
@ -487,93 +516,14 @@ function conditionalMapping(conditionalMapping_, conditionNames) {
return null;
}
/**
* Internal helper to create path tree node
* to ensure that each node gets the same hidden class
* @returns {PathTreeNode} node
*/
function createNode() {
return {
children: null,
folder: null,
wildcards: null,
files: new Map()
};
}
/**
* Internal helper for building path tree
* @param {PathTreeNode} root root
* @param {string} path path
* @param {MappingValue} target target
*/
function walkPath(root, path, target) {
if (path.length === 0) {
root.folder = target;
return;
}
let node = root;
// Typical path tree can looks like
// root
// - files: ["a.js", "b.js"]
// - children:
// node1:
// - files: ["a.js", "b.js"]
let lastNonSlashIndex = 0;
let slashIndex = path.indexOf("/", 0);
while (slashIndex !== -1) {
const folder = path.slice(lastNonSlashIndex, slashIndex);
let newNode;
if (node.children === null) {
newNode = createNode();
node.children = new Map();
node.children.set(folder, newNode);
} else {
newNode = node.children.get(folder);
if (!newNode) {
newNode = createNode();
node.children.set(folder, newNode);
}
}
node = newNode;
lastNonSlashIndex = slashIndex + 1;
slashIndex = path.indexOf("/", lastNonSlashIndex);
}
if (lastNonSlashIndex >= path.length) {
node.folder = target;
} else {
const file = lastNonSlashIndex > 0 ? path.slice(lastNonSlashIndex) : path;
if (file.endsWith("*")) {
if (node.wildcards === null) node.wildcards = new Map();
node.wildcards.set(file.slice(0, -1), target);
} else {
node.files.set(file, target);
}
}
}
/**
* @param {ExportsField} field exports field
* @returns {PathTreeNode} tree root
* @returns {ExportsField} normalized exports field
*/
function buildExportsFieldPathTree(field) {
const root = createNode();
function buildExportsField(field) {
// handle syntax sugar, if exports field is direct mapping for "."
if (typeof field === "string") {
root.files.set("", field);
return root;
} else if (Array.isArray(field)) {
root.files.set("", field.slice());
return root;
if (typeof field === "string" || Array.isArray(field)) {
return { ".": field };
}
const keys = Object.keys(field);
@ -596,8 +546,7 @@ function buildExportsFieldPathTree(field) {
i++;
}
root.files.set("", field);
return root;
return { ".": field };
}
throw new Error(
@ -608,7 +557,6 @@ function buildExportsFieldPathTree(field) {
}
if (key.length === 1) {
root.files.set("", field[key]);
continue;
}
@ -619,49 +567,7 @@ function buildExportsFieldPathTree(field) {
)})`
);
}
walkPath(root, key.slice(2), field[key]);
}
return root;
}
/**
* @param {ImportsField} field imports field
* @returns {PathTreeNode} root
*/
function buildImportsFieldPathTree(field) {
const root = createNode();
const keys = Object.keys(field);
for (let i = 0; i < keys.length; i++) {
const key = keys[i];
if (key.charCodeAt(0) !== hashCode) {
throw new Error(
`Imports field key should start with "#" (key: ${JSON.stringify(key)})`
);
}
if (key.length === 1) {
throw new Error(
`Imports field key should have at least 2 characters (key: ${JSON.stringify(
key
)})`
);
}
if (key.charCodeAt(1) === slashCode) {
throw new Error(
`Imports field key should not start with "#/" (key: ${JSON.stringify(
key
)})`
);
}
walkPath(root, key.slice(1), field[key]);
}
return root;
return field;
}

View file

@ -5,7 +5,8 @@
"use strict";
const PATH_QUERY_FRAGMENT_REGEXP = /^(#?(?:\0.|[^?#\0])*)(\?(?:\0.|[^#\0])*)?(#.*)?$/;
const PATH_QUERY_FRAGMENT_REGEXP =
/^(#?(?:\0.|[^?#\0])*)(\?(?:\0.|[^#\0])*)?(#.*)?$/;
/**
* @param {string} identifier identifier

View file

@ -0,0 +1,8 @@
/*
MIT License http://www.opensource.org/licenses/mit-license.php
Author Tobias Koppers @sokra
*/
"use strict";
module.exports = {};

View file

@ -33,6 +33,14 @@ const PathType = Object.freeze({
});
exports.PathType = PathType;
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;
exports.invalidSegmentRegEx = invalidSegmentRegEx;
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;
exports.deprecatedInvalidSegmentRegEx = deprecatedInvalidSegmentRegEx;
/**
* @param {string} p a path
* @returns {PathType} type of path
@ -170,14 +178,16 @@ const join = (rootPath, request) => {
};
exports.join = join;
/** @type {Map<string, Map<string, string | undefined>>} */
const joinCache = new Map();
/**
* @param {string} rootPath the root path
* @param {string | undefined} request the request path
* @param {string} request the request path
* @returns {string} the joined path
*/
const cachedJoin = (rootPath, request) => {
/** @type {string | undefined} */
let cacheEntry;
let cache = joinCache.get(rootPath);
if (cache === undefined) {
@ -191,33 +201,3 @@ const cachedJoin = (rootPath, request) => {
return cacheEntry;
};
exports.cachedJoin = cachedJoin;
const checkImportsExportsFieldTarget = relativePath => {
let lastNonSlashIndex = 0;
let slashIndex = relativePath.indexOf("/", 1);
let cd = 0;
while (slashIndex !== -1) {
const folder = relativePath.slice(lastNonSlashIndex, slashIndex);
switch (folder) {
case "..": {
cd--;
if (cd < 0)
return new Error(
`Trying to access out of package scope. Requesting ${relativePath}`
);
break;
}
case ".":
break;
default:
cd++;
break;
}
lastNonSlashIndex = slashIndex + 1;
slashIndex = relativePath.indexOf("/", lastNonSlashIndex);
}
};
exports.checkImportsExportsFieldTarget = checkImportsExportsFieldTarget;

View file

@ -6,7 +6,13 @@
"use strict";
module.exports = {
/**
* @type {Record<string, string>}
*/
versions: {},
/**
* @param {function} fn function
*/
nextTick(fn) {
const args = Array.prototype.slice.call(arguments, 1);
Promise.resolve().then(function () {

View file

@ -1,6 +1,6 @@
{
"name": "enhanced-resolve",
"version": "5.12.0",
"version": "5.17.1",
"author": "Tobias Koppers @sokra",
"description": "Offers a async require.resolve function. It's highly configurable.",
"files": [
@ -9,8 +9,8 @@
"LICENSE"
],
"browser": {
"pnpapi": false,
"process": "./lib/util/process-browser.js"
"process": "./lib/util/process-browser.js",
"module": "./lib/util/module-browser.js"
},
"dependencies": {
"graceful-fs": "^4.2.4",
@ -18,8 +18,9 @@
},
"license": "MIT",
"devDependencies": {
"@types/mocha": "^8.0.3",
"@types/node": "^14.11.1",
"@types/graceful-fs": "^4.1.6",
"@types/jest": "^27.5.1",
"@types/node": "20.9.5",
"cspell": "4.2.8",
"eslint": "^7.9.0",
"eslint-config-prettier": "^6.11.0",
@ -27,14 +28,12 @@
"eslint-plugin-node": "^11.1.0",
"eslint-plugin-prettier": "^3.1.4",
"husky": "^6.0.0",
"jest": "^27.5.1",
"lint-staged": "^10.4.0",
"memfs": "^3.2.0",
"mocha": "^8.1.3",
"nyc": "^15.1.0",
"prettier": "^2.1.2",
"should": "^13.2.3",
"tooling": "webpack/tooling#v1.14.0",
"typescript": "^4.2.0-beta"
"tooling": "webpack/tooling#v1.23.1",
"typescript": "^5.3.3"
},
"engines": {
"node": ">=10.13.0"
@ -43,24 +42,27 @@
"types": "types.d.ts",
"homepage": "http://github.com/webpack/enhanced-resolve",
"scripts": {
"lint": "yarn run code-lint && yarn run type-lint && yarn run special-lint && yarn run spelling",
"lint": "yarn run code-lint && yarn run type-lint && yarn typings-test && yarn run special-lint && yarn run spelling",
"fix": "yarn run code-lint-fix && yarn run special-lint-fix",
"code-lint": "eslint --cache lib test",
"code-lint-fix": "eslint --cache lib test --fix",
"type-lint": "tsc",
"typings-test": "tsc -p tsconfig.types.test.json",
"type-report": "rimraf coverage && yarn cover:types && yarn cover:report && open-cli coverage/lcov-report/index.html",
"special-lint": "node node_modules/tooling/lockfile-lint && node node_modules/tooling/inherit-types && node node_modules/tooling/format-file-header && node node_modules/tooling/generate-types",
"special-lint-fix": "node node_modules/tooling/inherit-types --write && node node_modules/tooling/format-file-header --write && node node_modules/tooling/generate-types --write",
"pretty": "prettier --loglevel warn --write \"lib/**/*.{js,json}\" \"test/*.js\"",
"pretest": "yarn lint",
"spelling": "cspell \"**/*.*\"",
"test": "mocha --full-trace --check-leaks",
"test:only": "mocha --full-trace --check-leaks",
"spelling": "cspell \"**\"",
"test:only": "node_modules/.bin/jest",
"test:watch": "yarn test:only -- --watch",
"test:coverage": "yarn test:only -- --collectCoverageFrom=\"lib/**/*.js\" --coverage",
"test": "yarn test:coverage",
"precover": "yarn lint",
"cover": "nyc --reporter=html node node_modules/mocha/bin/_mocha --full-trace --check-leaks",
"cover:ci": "nyc --reporter=lcovonly node node_modules/mocha/bin/_mocha --full-trace --check-leaks",
"prepare": "husky install"
},
"lint-staged": {
"*": "cspell --no-must-find-files",
"*.js": "eslint --cache"
},
"repository": {

File diff suppressed because it is too large Load diff