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;
|