| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801 | /*	MIT License http://www.opensource.org/licenses/mit-license.php	Author Tobias Koppers @sokra*/"use strict";const { AsyncSeriesBailHook, AsyncSeriesHook, SyncHook } = require("tapable");const createInnerContext = require("./createInnerContext");const { parseIdentifier } = require("./util/identifier");const {	normalize,	cachedJoin: join,	getType,	PathType,} = require("./util/path");/** @typedef {import("./ResolverFactory").ResolveOptions} ResolveOptions *//** @typedef {Error & { details?: string }} ErrorWithDetail *//** @typedef {(err: ErrorWithDetail | null, res?: string | false, req?: ResolveRequest) => void} ResolveCallback *//** * @typedef {object} PossibleFileSystemError * @property {string=} code code * @property {number=} errno number * @property {string=} path path * @property {string=} syscall syscall *//** * @template T * @callback FileSystemCallback * @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 encoding *//** * @typedef {ObjectEncodingOptions | BufferEncoding | undefined | null} EncodingOption *//** @typedef {(err: NodeJS.ErrnoException | null, result?: string) => void} StringCallback *//** @typedef {(err: NodeJS.ErrnoException | null, result?: Buffer) => void} BufferCallback *//** @typedef {(err: NodeJS.ErrnoException | null, result?: (string | Buffer)) => void} StringOrBufferCallback *//** @typedef {(err: NodeJS.ErrnoException | null, result?: IStats) => void} StatsCallback *//** @typedef {(err: NodeJS.ErrnoException | null, result?: IBigIntStats) => void} BigIntStatsCallback *//** @typedef {(err: NodeJS.ErrnoException | null, result?: (IStats | IBigIntStats)) => void} StatsOrBigIntStatsCallback *//** @typedef {(err: NodeJS.ErrnoException | Error | null, result?: JsonObject) => void} ReadJsonCallback *//** * @template T * @typedef {object} IStatsBase * @property {() => boolean} isFile is file * @property {() => boolean} isDirectory is directory * @property {() => boolean} isBlockDevice is block device * @property {() => boolean} isCharacterDevice is character device * @property {() => boolean} isSymbolicLink is symbolic link * @property {() => boolean} isFIFO is FIFO * @property {() => boolean} isSocket is socket * @property {T} dev dev * @property {T} ino ino * @property {T} mode mode * @property {T} nlink nlink * @property {T} uid uid * @property {T} gid gid * @property {T} rdev rdev * @property {T} size size * @property {T} blksize blksize * @property {T} blocks blocks * @property {T} atimeMs atime ms * @property {T} mtimeMs mtime ms * @property {T} ctimeMs ctime ms * @property {T} birthtimeMs birthtime ms * @property {Date} atime atime * @property {Date} mtime mtime * @property {Date} ctime ctime * @property {Date} birthtime birthtime *//** * @typedef {IStatsBase<number>} IStats *//** * @typedef {IStatsBase<bigint> & { atimeNs: bigint, mtimeNs: bigint, ctimeNs: bigint, birthtimeNs: bigint }} IBigIntStats *//* eslint-disable jsdoc/require-template *//** * @template {string | Buffer} [T=string] * @typedef {object} Dirent * @property {() => boolean} isFile true when is file, otherwise false * @property {() => boolean} isDirectory true when is directory, otherwise false * @property {() => boolean} isBlockDevice true when is block device, otherwise false * @property {() => boolean} isCharacterDevice true when is character device, otherwise false * @property {() => boolean} isSymbolicLink true when is symbolic link, otherwise false * @property {() => boolean} isFIFO true when is FIFO, otherwise false * @property {() => boolean} isSocket true when is socket, otherwise false * @property {T} name name * @property {string} parentPath path * @property {string=} path path *//* eslint-enable jsdoc/require-template *//** * @typedef {object} StatOptions * @property {(boolean | undefined)=} bigint need bigint values *//** * @typedef {object} StatSyncOptions * @property {(boolean | undefined)=} bigint need bigint values * @property {(boolean | undefined)=} throwIfNoEntry throw if no entry *//** * @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 {'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: (err: NodeJS.ErrnoException | null, files?: string[]) => void): void; * (path: PathLike, options: { encoding: 'buffer', withFileTypes?: false | undefined, recursive?: boolean | undefined } | 'buffer', callback: (err: NodeJS.ErrnoException | null, files?: Buffer[]) => void): void; * (path: PathLike, options: (ObjectEncodingOptions & { withFileTypes?: false | undefined, recursive?: boolean | undefined }) | BufferEncoding | undefined | null, callback: (err: NodeJS.ErrnoException | null, files?: string[] | Buffer[]) => void): void; * (path: PathLike, callback: (err: NodeJS.ErrnoException | null, files?: string[]) => void): void; * (path: PathLike, options: ObjectEncodingOptions & { withFileTypes: true, recursive?: boolean | undefined }, callback: (err: NodeJS.ErrnoException | null, files?: Dirent<string>[]) => void): void; * (path: PathLike, options: { encoding: 'buffer', withFileTypes: true, recursive?: boolean | undefined }, callback: (err: NodeJS.ErrnoException | null, files: Dirent<Buffer>[]) => void): 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[]; * (path: PathLike, options: { encoding: "buffer", withFileTypes: true, recursive?: boolean | undefined }): Dirent<Buffer>[]; * }} ReaddirSync *//** * @typedef {(pathOrFileDescription: PathOrFileDescriptor, callback: ReadJsonCallback) => void} ReadJson *//** * @typedef {(pathOrFileDescription: 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 {ReadFile} readFile read file method * @property {Readdir} readdir readdir method * @property {ReadJson=} readJson read json method * @property {Readlink} readlink read link method * @property {LStat=} lstat lstat method * @property {Stat} stat stat method * @property {RealPath=} realpath realpath method *//** * @typedef {object} SyncFileSystem * @property {ReadFileSync} readFileSync read file sync method * @property {ReaddirSync} readdirSync read dir sync method * @property {ReadJsonSync=} readJsonSync read json sync method * @property {ReadlinkSync} readlinkSync read link sync method * @property {LStatSync=} lstatSync lstat sync method * @property {StatSync} statSync stat sync method * @property {RealPathSync=} realpathSync real path sync method *//** * @typedef {object} ParsedIdentifier * @property {string} request request * @property {string} query query * @property {string} fragment fragment * @property {boolean} directory is directory * @property {boolean} module is module * @property {boolean} file is file * @property {boolean} internal is 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 */// eslint-disable-next-line jsdoc/require-property/** @typedef {object} Context *//** * @typedef {object} BaseResolveRequest * @property {string | false} path path * @property {Context=} context content * @property {string=} descriptionFilePath description file path * @property {string=} descriptionFileRoot description file root * @property {JsonObject=} descriptionFileData description file data * @property {string=} relativePath relative path * @property {boolean=} ignoreSymlinks true when need to ignore symlinks, otherwise false * @property {boolean=} fullySpecified true when full specified, otherwise false * @property {string=} __innerRequest inner request for internal usage * @property {string=} __innerRequest_request inner request for internal usage * @property {string=} __innerRequest_relativePath inner relative path for internal usage *//** @typedef {BaseResolveRequest & Partial<ParsedIdentifier>} ResolveRequest *//** * String with special formatting * @typedef {string} StackEntry *//** * @template T * @typedef {{ add: (item: T) => void }} WriteOnlySet *//** @typedef {(request: ResolveRequest) => void} ResolveContextYield *//** * Resolve context * @typedef {object} ResolveContext * @property {WriteOnlySet<string>=} contextDependencies directories that was found on file system * @property {WriteOnlySet<string>=} fileDependencies files that was found on file system * @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 {((str: string) => void)=} log log function * @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 resolve step hook * @property {SyncHook<[ResolveRequest, Error]>} noResolve no resolve hook * @property {ResolveStepHook} resolve resolve hook * @property {AsyncSeriesHook<[ResolveRequest, ResolveContext]>} result result hook *//** * @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.slice(1).toUpperCase());}class Resolver {	/**	 * @param {ResolveStepHook} hook hook	 * @param {ResolveRequest} request request	 * @returns {StackEntry} stack entry	 */	static createStackEntry(hook, request) {		return `${hook.name}: (${request.path}) ${request.request || ""}${			request.query || ""		}${request.fragment || ""}${			request.directory ? " directory" : ""		}${request.module ? " module" : ""}`;	}	/**	 * @param {FileSystem} fileSystem a filesystem	 * @param {ResolveOptions} options options	 */	constructor(fileSystem, options) {		this.fileSystem = fileSystem;		this.options = options;		/** @type {KnownHooks} */		this.hooks = {			resolveStep: new SyncHook(["hook", "request"], "resolveStep"),			noResolve: new SyncHook(["request", "error"], "noResolve"),			resolve: new AsyncSeriesBailHook(				["request", "resolveContext"],				"resolve",			),			result: new AsyncSeriesHook(["result", "resolveContext"], "result"),		};	}	/**	 * @param {string | ResolveStepHook} name hook name or hook itself	 * @returns {ResolveStepHook} the hook	 */	ensureHook(name) {		if (typeof name !== "string") {			return name;		}		name = toCamelCase(name);		if (name.startsWith("before")) {			return /** @type {ResolveStepHook} */ (				this.ensureHook(name[6].toLowerCase() + name.slice(7)).withOptions({					stage: -10,				})			);		}		if (name.startsWith("after")) {			return /** @type {ResolveStepHook} */ (				this.ensureHook(name[5].toLowerCase() + name.slice(6)).withOptions({					stage: 10,				})			);		}		/** @type {ResolveStepHook} */		const hook = /** @type {KnownHooks & EnsuredHooks} */ (this.hooks)[name];		if (!hook) {			/** @type {KnownHooks & EnsuredHooks} */			(this.hooks)[name] = new AsyncSeriesBailHook(				["request", "resolveContext"],				name,			);			return /** @type {KnownHooks & EnsuredHooks} */ (this.hooks)[name];		}		return hook;	}	/**	 * @param {string | ResolveStepHook} name hook name or hook itself	 * @returns {ResolveStepHook} the hook	 */	getHook(name) {		if (typeof name !== "string") {			return name;		}		name = toCamelCase(name);		if (name.startsWith("before")) {			return /** @type {ResolveStepHook} */ (				this.getHook(name[6].toLowerCase() + name.slice(7)).withOptions({					stage: -10,				})			);		}		if (name.startsWith("after")) {			return /** @type {ResolveStepHook} */ (				this.getHook(name[5].toLowerCase() + name.slice(6)).withOptions({					stage: 10,				})			);		}		/** @type {ResolveStepHook} */		const hook = /** @type {KnownHooks & EnsuredHooks} */ (this.hooks)[name];		if (!hook) {			throw new Error(`Hook ${name} doesn't exist`);		}		return hook;	}	/**	 * @param {object} context context information object	 * @param {string} path context path	 * @param {string} request request string	 * @returns {string | false} result	 */	resolveSync(context, path, request) {		/** @type {Error | null | undefined} */		let err;		/** @type {string | false | undefined} */		let result;		let sync = false;		this.resolve(context, path, request, {}, (_err, r) => {			err = _err;			result = r;			sync = true;		});		if (!sync) {			throw new Error(				"Cannot 'resolveSync' because the fileSystem is not sync. Use 'resolve'!",			);		}		if (err) throw err;		if (result === undefined) throw new Error("No result");		return result;	}	/**	 * @param {object} context context information object	 * @param {string} path context path	 * @param {string} request request string	 * @param {ResolveContext} resolveContext resolve context	 * @param {ResolveCallback} callback callback function	 * @returns {void}	 */	resolve(context, path, request, resolveContext, callback) {		if (!context || typeof context !== "object") {			return callback(new Error("context argument is not an object"));		}		if (typeof path !== "string") {			return callback(new Error("path argument is not a string"));		}		if (typeof request !== "string") {			return callback(new Error("request argument is not a string"));		}		if (!resolveContext) {			return callback(new Error("resolveContext argument is not set"));		}		/** @type {ResolveRequest} */		const obj = {			context,			path,			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) {					/** @type {ResolveContextYield} */ (yield_)(result);				}				callback(null);			};		}		const message = `resolve '${request}' in '${path}'`;		/**		 * @param {ResolveRequest} result result		 * @returns {void}		 */		const finishResolved = (result) =>			callback(				null,				result.path === false					? false					: `${result.path.replace(/#/g, "\0#")}${							result.query ? result.query.replace(/#/g, "\0#") : ""						}${result.fragment || ""}`,				result,			);		/**		 * @param {string[]} log logs		 * @returns {void}		 */		const finishWithoutResolve = (log) => {			/**			 * @type {ErrorWithDetail}			 */			const error = new Error(`Can't ${message}`);			error.details = log.join("\n");			this.hooks.noResolve.call(obj, error);			return callback(error);		};		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,				obj,				message,				{					log: (msg) => {						parentLog(msg);						log.push(msg);					},					yield: yield_,					fileDependencies: resolveContext.fileDependencies,					contextDependencies: resolveContext.contextDependencies,					missingDependencies: resolveContext.missingDependencies,					stack: resolveContext.stack,				},				(err, result) => {					if (err) return callback(err);					if (yieldCalled || (result && yield_)) {						return /** @type {ResolveContextYield} */ (finishYield)(							/** @type {ResolveRequest} */ (result),						);					}					if (result) return finishResolved(result);					return finishWithoutResolve(log);				},			);		}		// Try to resolve assuming there is no error		// We don't log stuff in this case		return this.doResolve(			this.hooks.resolve,			obj,			message,			{				log: undefined,				yield: yield_,				fileDependencies: resolveContext.fileDependencies,				contextDependencies: resolveContext.contextDependencies,				missingDependencies: resolveContext.missingDependencies,				stack: resolveContext.stack,			},			(err, result) => {				if (err) return callback(err);				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(					this.hooks.resolve,					obj,					message,					{						log: (msg) => log.push(msg),						yield: yield_,						stack: resolveContext.stack,					},					(err, result) => {						if (err) return callback(err);						// In a case that there is a race condition and yield will be called						if (yieldCalled || (result && yield_)) {							return /** @type {ResolveContextYield} */ (finishYield)(								/** @type {ResolveRequest} */ (result),							);						}						return finishWithoutResolve(log);					},				);			},		);	}	/**	 * @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);			if (resolveContext.stack.has(stackEntry)) {				/**				 * Prevent recursion				 * @type {Error & {recursion?: boolean}}				 */				const recursionError = new Error(					`Recursion in resolving\nStack:\n  ${[...newStack].join("\n  ")}`,				);				recursionError.recursion = true;				if (resolveContext.log) {					resolveContext.log("abort resolving because of recursion");				}				return callback(recursionError);			}			newStack.add(stackEntry);		} else {			// 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);		if (hook.isUsed()) {			const innerContext = createInnerContext(				{					log: resolveContext.log,					yield: resolveContext.yield,					fileDependencies: resolveContext.fileDependencies,					contextDependencies: resolveContext.contextDependencies,					missingDependencies: resolveContext.missingDependencies,					stack: newStack,				},				message,			);			return hook.callAsync(request, innerContext, (err, result) => {				if (err) return callback(err);				if (result) return callback(null, result);				callback();			});		}		callback();	}	/**	 * @param {string} identifier identifier	 * @returns {ParsedIdentifier} parsed identifier	 */	parse(identifier) {		const part = {			request: "",			query: "",			fragment: "",			module: false,			directory: false,			file: false,			internal: false,		};		const parsedIdentifier = parseIdentifier(identifier);		if (!parsedIdentifier) return part;		[part.request, part.query, part.fragment] = parsedIdentifier;		if (part.request.length > 0) {			part.internal = this.isPrivate(identifier);			part.module = this.isModule(part.request);			part.directory = this.isDirectory(part.request);			if (part.directory) {				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;	}	/**	 * @param {string} path a path	 * @returns {boolean} true, if the path is a directory path	 */	isDirectory(path) {		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);	}}module.exports = Resolver;
 |