| 1 | /*
|
|---|
| 2 | MIT License http://www.opensource.org/licenses/mit-license.php
|
|---|
| 3 | Author Tobias Koppers @sokra
|
|---|
| 4 | */
|
|---|
| 5 |
|
|---|
| 6 | "use strict";
|
|---|
| 7 |
|
|---|
| 8 | const { AsyncSeriesBailHook, AsyncSeriesHook, SyncHook } = require("tapable");
|
|---|
| 9 | const createInnerContext = require("./createInnerContext");
|
|---|
| 10 | const { parseIdentifier } = require("./util/identifier");
|
|---|
| 11 | const {
|
|---|
| 12 | PathType,
|
|---|
| 13 | createCachedBasename,
|
|---|
| 14 | createCachedDirname,
|
|---|
| 15 | createCachedJoin,
|
|---|
| 16 | getType,
|
|---|
| 17 | normalize,
|
|---|
| 18 | } = require("./util/path");
|
|---|
| 19 |
|
|---|
| 20 | /* eslint-disable jsdoc/check-alignment */
|
|---|
| 21 | // TODO in the next major release use only `Promise.withResolvers()`
|
|---|
| 22 | const _withResolvers =
|
|---|
| 23 | // eslint-disable-next-line n/no-unsupported-features/es-syntax
|
|---|
| 24 | Promise.withResolvers
|
|---|
| 25 | ? /**
|
|---|
| 26 | * @param {Resolver} self resolver
|
|---|
| 27 | * @param {Context} context context information object
|
|---|
| 28 | * @param {string} path context path
|
|---|
| 29 | * @param {string} request request string
|
|---|
| 30 | * @param {ResolveContext} resolveContext resolve context
|
|---|
| 31 | * @returns {Promise<string | false>} result
|
|---|
| 32 | */
|
|---|
| 33 | (self, context, path, request, resolveContext) => {
|
|---|
| 34 | // eslint-disable-next-line n/no-unsupported-features/es-syntax
|
|---|
| 35 | const { promise, resolve, reject } = Promise.withResolvers();
|
|---|
| 36 | self.resolve(context, path, request, resolveContext, (err, res) => {
|
|---|
| 37 | if (err) reject(err);
|
|---|
| 38 | else resolve(/** @type {string | false} */ (res));
|
|---|
| 39 | });
|
|---|
| 40 | return promise;
|
|---|
| 41 | }
|
|---|
| 42 | : /**
|
|---|
| 43 | * @param {Resolver} self resolver
|
|---|
| 44 | * @param {Context} context context information object
|
|---|
| 45 | * @param {string} path context path
|
|---|
| 46 | * @param {string} request request string
|
|---|
| 47 | * @param {ResolveContext} resolveContext resolve context
|
|---|
| 48 | * @returns {Promise<string | false>} result
|
|---|
| 49 | */
|
|---|
| 50 | (self, context, path, request, resolveContext) =>
|
|---|
| 51 | new Promise((resolve, reject) => {
|
|---|
| 52 | self.resolve(context, path, request, resolveContext, (err, res) => {
|
|---|
| 53 | if (err) reject(err);
|
|---|
| 54 | else resolve(/** @type {string | false} */ (res));
|
|---|
| 55 | });
|
|---|
| 56 | });
|
|---|
| 57 | /* eslint-enable jsdoc/check-alignment */
|
|---|
| 58 |
|
|---|
| 59 | /** @typedef {import("./AliasUtils").AliasOption} AliasOption */
|
|---|
| 60 | /** @typedef {import("./util/path").CachedJoin} CachedJoin */
|
|---|
| 61 | /** @typedef {import("./util/path").CachedDirname} CachedDirname */
|
|---|
| 62 | /** @typedef {import("./util/path").CachedBasename} CachedBasename */
|
|---|
| 63 |
|
|---|
| 64 | /**
|
|---|
| 65 | * @typedef {object} JoinCacheEntry
|
|---|
| 66 | * @property {CachedJoin["fn"]} fn cached join function
|
|---|
| 67 | * @property {CachedJoin["cache"]} cache the underlying cache map
|
|---|
| 68 | */
|
|---|
| 69 |
|
|---|
| 70 | /**
|
|---|
| 71 | * @typedef {object} DirnameCacheEntry
|
|---|
| 72 | * @property {CachedDirname["fn"]} fn cached dirname function
|
|---|
| 73 | * @property {CachedDirname["cache"]} cache the underlying cache map
|
|---|
| 74 | */
|
|---|
| 75 |
|
|---|
| 76 | /**
|
|---|
| 77 | * @typedef {object} BasenameCacheEntry
|
|---|
| 78 | * @property {CachedBasename["fn"]} fn cached dirname function
|
|---|
| 79 | * @property {CachedBasename["cache"]} cache the underlying cache map
|
|---|
| 80 | */
|
|---|
| 81 |
|
|---|
| 82 | /**
|
|---|
| 83 | * @typedef {object} PathCacheFunctions
|
|---|
| 84 | * @property {JoinCacheEntry} join cached join
|
|---|
| 85 | * @property {DirnameCacheEntry} dirname cached dirname
|
|---|
| 86 | * @property {BasenameCacheEntry} basename cached basename
|
|---|
| 87 | */
|
|---|
| 88 |
|
|---|
| 89 | /** @type {WeakMap<FileSystem, PathCacheFunctions>} */
|
|---|
| 90 | const _pathCacheByFs = new WeakMap();
|
|---|
| 91 |
|
|---|
| 92 | const HASH_ESCAPE_RE = /#/g;
|
|---|
| 93 |
|
|---|
| 94 | /** @typedef {import("./ResolverFactory").ResolveOptions} ResolveOptions */
|
|---|
| 95 |
|
|---|
| 96 | /**
|
|---|
| 97 | * @typedef {object} KnownContext
|
|---|
| 98 | * @property {string[]=} environments environments
|
|---|
| 99 | */
|
|---|
| 100 |
|
|---|
| 101 | // eslint-disable-next-line jsdoc/reject-any-type
|
|---|
| 102 | /** @typedef {KnownContext & Record<any, any>} Context */
|
|---|
| 103 |
|
|---|
| 104 | /** @typedef {Error & { details?: string }} ErrorWithDetail */
|
|---|
| 105 |
|
|---|
| 106 | /** @typedef {(err: ErrorWithDetail | null, res?: string | false, req?: ResolveRequest) => void} ResolveCallback */
|
|---|
| 107 |
|
|---|
| 108 | /**
|
|---|
| 109 | * @typedef {object} PossibleFileSystemError
|
|---|
| 110 | * @property {string=} code code
|
|---|
| 111 | * @property {number=} errno number
|
|---|
| 112 | * @property {string=} path path
|
|---|
| 113 | * @property {string=} syscall syscall
|
|---|
| 114 | */
|
|---|
| 115 |
|
|---|
| 116 | /**
|
|---|
| 117 | * @template T
|
|---|
| 118 | * @callback FileSystemCallback
|
|---|
| 119 | * @param {PossibleFileSystemError & Error | null} err
|
|---|
| 120 | * @param {T=} result
|
|---|
| 121 | */
|
|---|
| 122 |
|
|---|
| 123 | /**
|
|---|
| 124 | * @typedef {string | Buffer | URL} PathLike
|
|---|
| 125 | */
|
|---|
| 126 |
|
|---|
| 127 | /**
|
|---|
| 128 | * @typedef {PathLike | number} PathOrFileDescriptor
|
|---|
| 129 | */
|
|---|
| 130 |
|
|---|
| 131 | /**
|
|---|
| 132 | * @typedef {object} ObjectEncodingOptions
|
|---|
| 133 | * @property {BufferEncoding | null | undefined=} encoding encoding
|
|---|
| 134 | */
|
|---|
| 135 |
|
|---|
| 136 | /**
|
|---|
| 137 | * @typedef {ObjectEncodingOptions | BufferEncoding | undefined | null} EncodingOption
|
|---|
| 138 | */
|
|---|
| 139 |
|
|---|
| 140 | /** @typedef {(err: NodeJS.ErrnoException | null, result?: string) => void} StringCallback */
|
|---|
| 141 | /** @typedef {(err: NodeJS.ErrnoException | null, result?: Buffer) => void} BufferCallback */
|
|---|
| 142 | /** @typedef {(err: NodeJS.ErrnoException | null, result?: (string | Buffer)) => void} StringOrBufferCallback */
|
|---|
| 143 | /** @typedef {(err: NodeJS.ErrnoException | null, result?: IStats) => void} StatsCallback */
|
|---|
| 144 | /** @typedef {(err: NodeJS.ErrnoException | null, result?: IBigIntStats) => void} BigIntStatsCallback */
|
|---|
| 145 | /** @typedef {(err: NodeJS.ErrnoException | null, result?: (IStats | IBigIntStats)) => void} StatsOrBigIntStatsCallback */
|
|---|
| 146 | /** @typedef {(err: NodeJS.ErrnoException | Error | null, result?: JsonObject) => void} ReadJsonCallback */
|
|---|
| 147 |
|
|---|
| 148 | /**
|
|---|
| 149 | * @template T
|
|---|
| 150 | * @typedef {object} IStatsBase
|
|---|
| 151 | * @property {() => boolean} isFile is file
|
|---|
| 152 | * @property {() => boolean} isDirectory is directory
|
|---|
| 153 | * @property {() => boolean} isBlockDevice is block device
|
|---|
| 154 | * @property {() => boolean} isCharacterDevice is character device
|
|---|
| 155 | * @property {() => boolean} isSymbolicLink is symbolic link
|
|---|
| 156 | * @property {() => boolean} isFIFO is FIFO
|
|---|
| 157 | * @property {() => boolean} isSocket is socket
|
|---|
| 158 | * @property {T} dev dev
|
|---|
| 159 | * @property {T} ino ino
|
|---|
| 160 | * @property {T} mode mode
|
|---|
| 161 | * @property {T} nlink nlink
|
|---|
| 162 | * @property {T} uid uid
|
|---|
| 163 | * @property {T} gid gid
|
|---|
| 164 | * @property {T} rdev rdev
|
|---|
| 165 | * @property {T} size size
|
|---|
| 166 | * @property {T} blksize blksize
|
|---|
| 167 | * @property {T} blocks blocks
|
|---|
| 168 | * @property {T} atimeMs atime ms
|
|---|
| 169 | * @property {T} mtimeMs mtime ms
|
|---|
| 170 | * @property {T} ctimeMs ctime ms
|
|---|
| 171 | * @property {T} birthtimeMs birthtime ms
|
|---|
| 172 | * @property {Date} atime atime
|
|---|
| 173 | * @property {Date} mtime mtime
|
|---|
| 174 | * @property {Date} ctime ctime
|
|---|
| 175 | * @property {Date} birthtime birthtime
|
|---|
| 176 | */
|
|---|
| 177 |
|
|---|
| 178 | /**
|
|---|
| 179 | * @typedef {IStatsBase<number>} IStats
|
|---|
| 180 | */
|
|---|
| 181 |
|
|---|
| 182 | /**
|
|---|
| 183 | * @typedef {IStatsBase<bigint> & { atimeNs: bigint, mtimeNs: bigint, ctimeNs: bigint, birthtimeNs: bigint }} IBigIntStats
|
|---|
| 184 | */
|
|---|
| 185 |
|
|---|
| 186 | /**
|
|---|
| 187 | * @template {string | Buffer} [T=string]
|
|---|
| 188 | * @typedef {object} Dirent
|
|---|
| 189 | * @property {() => boolean} isFile true when is file, otherwise false
|
|---|
| 190 | * @property {() => boolean} isDirectory true when is directory, otherwise false
|
|---|
| 191 | * @property {() => boolean} isBlockDevice true when is block device, otherwise false
|
|---|
| 192 | * @property {() => boolean} isCharacterDevice true when is character device, otherwise false
|
|---|
| 193 | * @property {() => boolean} isSymbolicLink true when is symbolic link, otherwise false
|
|---|
| 194 | * @property {() => boolean} isFIFO true when is FIFO, otherwise false
|
|---|
| 195 | * @property {() => boolean} isSocket true when is socket, otherwise false
|
|---|
| 196 | * @property {T} name name
|
|---|
| 197 | * @property {string} parentPath path
|
|---|
| 198 | * @property {string=} path path
|
|---|
| 199 | */
|
|---|
| 200 |
|
|---|
| 201 | /**
|
|---|
| 202 | * @typedef {object} StatOptions
|
|---|
| 203 | * @property {(boolean | undefined)=} bigint need bigint values
|
|---|
| 204 | */
|
|---|
| 205 |
|
|---|
| 206 | /**
|
|---|
| 207 | * @typedef {object} StatSyncOptions
|
|---|
| 208 | * @property {(boolean | undefined)=} bigint need bigint values
|
|---|
| 209 | * @property {(boolean | undefined)=} throwIfNoEntry throw if no entry
|
|---|
| 210 | */
|
|---|
| 211 |
|
|---|
| 212 | /**
|
|---|
| 213 | * @typedef {{
|
|---|
| 214 | * (path: PathOrFileDescriptor, options: ({ encoding?: null | undefined, flag?: string | undefined } & import("events").Abortable) | undefined | null, callback: BufferCallback): void,
|
|---|
| 215 | * (path: PathOrFileDescriptor, options: ({ encoding: BufferEncoding, flag?: string | undefined } & import("events").Abortable) | BufferEncoding, callback: StringCallback): void,
|
|---|
| 216 | * (path: PathOrFileDescriptor, options: (ObjectEncodingOptions & { flag?: string | undefined } & import("events").Abortable) | BufferEncoding | undefined | null, callback: StringOrBufferCallback): void,
|
|---|
| 217 | * (path: PathOrFileDescriptor, callback: BufferCallback): void,
|
|---|
| 218 | * }} ReadFile
|
|---|
| 219 | */
|
|---|
| 220 |
|
|---|
| 221 | /**
|
|---|
| 222 | * @typedef {"buffer" | { encoding: "buffer" }} BufferEncodingOption
|
|---|
| 223 | */
|
|---|
| 224 |
|
|---|
| 225 | /**
|
|---|
| 226 | * @typedef {{
|
|---|
| 227 | * (path: PathOrFileDescriptor, options?: { encoding?: null | undefined, flag?: string | undefined } | null): Buffer,
|
|---|
| 228 | * (path: PathOrFileDescriptor, options: { encoding: BufferEncoding, flag?: string | undefined } | BufferEncoding): string,
|
|---|
| 229 | * (path: PathOrFileDescriptor, options?: (ObjectEncodingOptions & { flag?: string | undefined }) | BufferEncoding | null): string | Buffer,
|
|---|
| 230 | * }} ReadFileSync
|
|---|
| 231 | */
|
|---|
| 232 |
|
|---|
| 233 | /**
|
|---|
| 234 | * @typedef {{
|
|---|
| 235 | * (path: PathLike, options: { encoding: BufferEncoding | null, withFileTypes?: false | undefined, recursive?: boolean | undefined } | BufferEncoding | undefined | null, callback: (err: NodeJS.ErrnoException | null, files?: string[]) => void): void,
|
|---|
| 236 | * (path: PathLike, options: { encoding: "buffer", withFileTypes?: false | undefined, recursive?: boolean | undefined } | "buffer", callback: (err: NodeJS.ErrnoException | null, files?: Buffer[]) => void): void,
|
|---|
| 237 | * (path: PathLike, options: (ObjectEncodingOptions & { withFileTypes?: false | undefined, recursive?: boolean | undefined }) | BufferEncoding | undefined | null, callback: (err: NodeJS.ErrnoException | null, files?: string[] | Buffer[]) => void): void,
|
|---|
| 238 | * (path: PathLike, callback: (err: NodeJS.ErrnoException | null, files?: string[]) => void): void,
|
|---|
| 239 | * (path: PathLike, options: ObjectEncodingOptions & { withFileTypes: true, recursive?: boolean | undefined }, callback: (err: NodeJS.ErrnoException | null, files?: Dirent<string>[]) => void): void,
|
|---|
| 240 | * (path: PathLike, options: { encoding: "buffer", withFileTypes: true, recursive?: boolean | undefined }, callback: (err: NodeJS.ErrnoException | null, files: Dirent<Buffer>[]) => void): void,
|
|---|
| 241 | * }} Readdir
|
|---|
| 242 | */
|
|---|
| 243 |
|
|---|
| 244 | /**
|
|---|
| 245 | * @typedef {{
|
|---|
| 246 | * (path: PathLike, options?: { encoding: BufferEncoding | null, withFileTypes?: false | undefined, recursive?: boolean | undefined } | BufferEncoding | null): string[],
|
|---|
| 247 | * (path: PathLike, options: { encoding: "buffer", withFileTypes?: false | undefined, recursive?: boolean | undefined } | "buffer"): Buffer[],
|
|---|
| 248 | * (path: PathLike, options?: (ObjectEncodingOptions & { withFileTypes?: false | undefined, recursive?: boolean | undefined }) | BufferEncoding | null): string[] | Buffer[],
|
|---|
| 249 | * (path: PathLike, options: ObjectEncodingOptions & { withFileTypes: true, recursive?: boolean | undefined }): Dirent[],
|
|---|
| 250 | * (path: PathLike, options: { encoding: "buffer", withFileTypes: true, recursive?: boolean | undefined }): Dirent<Buffer>[],
|
|---|
| 251 | * }} ReaddirSync
|
|---|
| 252 | */
|
|---|
| 253 |
|
|---|
| 254 | /**
|
|---|
| 255 | * @typedef {(pathOrFileDescription: PathOrFileDescriptor, callback: ReadJsonCallback) => void} ReadJson
|
|---|
| 256 | */
|
|---|
| 257 |
|
|---|
| 258 | /**
|
|---|
| 259 | * @typedef {(pathOrFileDescription: PathOrFileDescriptor) => JsonObject} ReadJsonSync
|
|---|
| 260 | */
|
|---|
| 261 |
|
|---|
| 262 | /**
|
|---|
| 263 | * @typedef {{
|
|---|
| 264 | * (path: PathLike, options: EncodingOption, callback: StringCallback): void,
|
|---|
| 265 | * (path: PathLike, options: BufferEncodingOption, callback: BufferCallback): void,
|
|---|
| 266 | * (path: PathLike, options: EncodingOption, callback: StringOrBufferCallback): void,
|
|---|
| 267 | * (path: PathLike, callback: StringCallback): void,
|
|---|
| 268 | * }} Readlink
|
|---|
| 269 | */
|
|---|
| 270 |
|
|---|
| 271 | /**
|
|---|
| 272 | * @typedef {{
|
|---|
| 273 | * (path: PathLike, options?: EncodingOption): string,
|
|---|
| 274 | * (path: PathLike, options: BufferEncodingOption): Buffer,
|
|---|
| 275 | * (path: PathLike, options?: EncodingOption): string | Buffer,
|
|---|
| 276 | * }} ReadlinkSync
|
|---|
| 277 | */
|
|---|
| 278 |
|
|---|
| 279 | /**
|
|---|
| 280 | * @typedef {{
|
|---|
| 281 | * (path: PathLike, callback: StatsCallback): void,
|
|---|
| 282 | * (path: PathLike, options: (StatOptions & { bigint?: false | undefined }) | undefined, callback: StatsCallback): void,
|
|---|
| 283 | * (path: PathLike, options: StatOptions & { bigint: true }, callback: BigIntStatsCallback): void,
|
|---|
| 284 | * (path: PathLike, options: StatOptions | undefined, callback: StatsOrBigIntStatsCallback): void,
|
|---|
| 285 | * }} LStat
|
|---|
| 286 | */
|
|---|
| 287 |
|
|---|
| 288 | /**
|
|---|
| 289 | * @typedef {{
|
|---|
| 290 | * (path: PathLike, options?: undefined): IStats,
|
|---|
| 291 | * (path: PathLike, options?: StatSyncOptions & { bigint?: false | undefined, throwIfNoEntry: false }): IStats | undefined,
|
|---|
| 292 | * (path: PathLike, options: StatSyncOptions & { bigint: true, throwIfNoEntry: false }): IBigIntStats | undefined,
|
|---|
| 293 | * (path: PathLike, options?: StatSyncOptions & { bigint?: false | undefined }): IStats,
|
|---|
| 294 | * (path: PathLike, options: StatSyncOptions & { bigint: true }): IBigIntStats,
|
|---|
| 295 | * (path: PathLike, options: StatSyncOptions & { bigint: boolean, throwIfNoEntry?: false | undefined }): IStats | IBigIntStats,
|
|---|
| 296 | * (path: PathLike, options?: StatSyncOptions): IStats | IBigIntStats | undefined,
|
|---|
| 297 | * }} LStatSync
|
|---|
| 298 | */
|
|---|
| 299 |
|
|---|
| 300 | /**
|
|---|
| 301 | * @typedef {{
|
|---|
| 302 | * (path: PathLike, callback: StatsCallback): void,
|
|---|
| 303 | * (path: PathLike, options: (StatOptions & { bigint?: false | undefined }) | undefined, callback: StatsCallback): void,
|
|---|
| 304 | * (path: PathLike, options: StatOptions & { bigint: true }, callback: BigIntStatsCallback): void,
|
|---|
| 305 | * (path: PathLike, options: StatOptions | undefined, callback: StatsOrBigIntStatsCallback): void,
|
|---|
| 306 | * }} Stat
|
|---|
| 307 | */
|
|---|
| 308 |
|
|---|
| 309 | /**
|
|---|
| 310 | * @typedef {{
|
|---|
| 311 | * (path: PathLike, options?: undefined): IStats,
|
|---|
| 312 | * (path: PathLike, options?: StatSyncOptions & { bigint?: false | undefined, throwIfNoEntry: false }): IStats | undefined,
|
|---|
| 313 | * (path: PathLike, options: StatSyncOptions & { bigint: true, throwIfNoEntry: false }): IBigIntStats | undefined,
|
|---|
| 314 | * (path: PathLike, options?: StatSyncOptions & { bigint?: false | undefined }): IStats,
|
|---|
| 315 | * (path: PathLike, options: StatSyncOptions & { bigint: true }): IBigIntStats,
|
|---|
| 316 | * (path: PathLike, options: StatSyncOptions & { bigint: boolean, throwIfNoEntry?: false | undefined }): IStats | IBigIntStats,
|
|---|
| 317 | * (path: PathLike, options?: StatSyncOptions): IStats | IBigIntStats | undefined,
|
|---|
| 318 | * }} StatSync
|
|---|
| 319 | */
|
|---|
| 320 |
|
|---|
| 321 | /**
|
|---|
| 322 | * @typedef {{
|
|---|
| 323 | * (path: PathLike, options: EncodingOption, callback: StringCallback): void,
|
|---|
| 324 | * (path: PathLike, options: BufferEncodingOption, callback: BufferCallback): void,
|
|---|
| 325 | * (path: PathLike, options: EncodingOption, callback: StringOrBufferCallback): void,
|
|---|
| 326 | * (path: PathLike, callback: StringCallback): void,
|
|---|
| 327 | * }} RealPath
|
|---|
| 328 | */
|
|---|
| 329 |
|
|---|
| 330 | /**
|
|---|
| 331 | * @typedef {{
|
|---|
| 332 | * (path: PathLike, options?: EncodingOption): string,
|
|---|
| 333 | * (path: PathLike, options: BufferEncodingOption): Buffer,
|
|---|
| 334 | * (path: PathLike, options?: EncodingOption): string | Buffer,
|
|---|
| 335 | * }} RealPathSync
|
|---|
| 336 | */
|
|---|
| 337 |
|
|---|
| 338 | /**
|
|---|
| 339 | * @typedef {object} FileSystem
|
|---|
| 340 | * @property {ReadFile} readFile read file method
|
|---|
| 341 | * @property {Readdir} readdir readdir method
|
|---|
| 342 | * @property {ReadJson=} readJson read json method
|
|---|
| 343 | * @property {Readlink} readlink read link method
|
|---|
| 344 | * @property {LStat=} lstat lstat method
|
|---|
| 345 | * @property {Stat} stat stat method
|
|---|
| 346 | * @property {RealPath=} realpath realpath method
|
|---|
| 347 | */
|
|---|
| 348 |
|
|---|
| 349 | /**
|
|---|
| 350 | * @typedef {object} SyncFileSystem
|
|---|
| 351 | * @property {ReadFileSync} readFileSync read file sync method
|
|---|
| 352 | * @property {ReaddirSync} readdirSync read dir sync method
|
|---|
| 353 | * @property {ReadJsonSync=} readJsonSync read json sync method
|
|---|
| 354 | * @property {ReadlinkSync} readlinkSync read link sync method
|
|---|
| 355 | * @property {LStatSync=} lstatSync lstat sync method
|
|---|
| 356 | * @property {StatSync} statSync stat sync method
|
|---|
| 357 | * @property {RealPathSync=} realpathSync real path sync method
|
|---|
| 358 | */
|
|---|
| 359 |
|
|---|
| 360 | /**
|
|---|
| 361 | * @typedef {object} ParsedIdentifier
|
|---|
| 362 | * @property {string} request request
|
|---|
| 363 | * @property {string} query query
|
|---|
| 364 | * @property {string} fragment fragment
|
|---|
| 365 | * @property {boolean} directory is directory
|
|---|
| 366 | * @property {boolean} module is module
|
|---|
| 367 | * @property {boolean} file is file
|
|---|
| 368 | * @property {boolean} internal is internal
|
|---|
| 369 | */
|
|---|
| 370 |
|
|---|
| 371 | /** @typedef {string | number | boolean | null} JsonPrimitive */
|
|---|
| 372 | /** @typedef {JsonValue[]} JsonArray */
|
|---|
| 373 | /** @typedef {JsonPrimitive | JsonObject | JsonArray} JsonValue */
|
|---|
| 374 | /** @typedef {{ [Key in string]?: JsonValue | undefined }} JsonObject */
|
|---|
| 375 |
|
|---|
| 376 | /**
|
|---|
| 377 | * @typedef {object} TsconfigPathsMap
|
|---|
| 378 | * @property {TsconfigPathsData} main main tsconfig paths data
|
|---|
| 379 | * @property {string} mainContext main tsconfig base URL (absolute path)
|
|---|
| 380 | * @property {{ [baseUrl: string]: TsconfigPathsData }} refs referenced tsconfig paths data mapped by baseUrl
|
|---|
| 381 | * @property {{ [context: string]: TsconfigPathsData }} allContexts all contexts (main + refs) for quick lookup
|
|---|
| 382 | * @property {string[]} contextList precomputed `Object.keys(allContexts)` — read-only; used on the `_selectPathsDataForContext` hot path
|
|---|
| 383 | * @property {Set<string>} fileDependencies file dependencies
|
|---|
| 384 | */
|
|---|
| 385 |
|
|---|
| 386 | /**
|
|---|
| 387 | * @typedef {object} TsconfigPathsData
|
|---|
| 388 | * @property {import("./AliasUtils").CompiledAliasOptions} alias tsconfig file data
|
|---|
| 389 | * @property {string[]} modules tsconfig file data
|
|---|
| 390 | */
|
|---|
| 391 |
|
|---|
| 392 | /**
|
|---|
| 393 | * @typedef {object} BaseResolveRequest
|
|---|
| 394 | * @property {string | false} path path
|
|---|
| 395 | * @property {Context=} context content
|
|---|
| 396 | * @property {string=} descriptionFilePath description file path
|
|---|
| 397 | * @property {string=} descriptionFileRoot description file root
|
|---|
| 398 | * @property {JsonObject=} descriptionFileData description file data
|
|---|
| 399 | * @property {TsconfigPathsMap | null | undefined=} tsconfigPathsMap tsconfig paths map
|
|---|
| 400 | * @property {string=} relativePath relative path
|
|---|
| 401 | * @property {boolean=} ignoreSymlinks true when need to ignore symlinks, otherwise false
|
|---|
| 402 | * @property {boolean=} fullySpecified true when full specified, otherwise false
|
|---|
| 403 | * @property {string=} __innerRequest inner request for internal usage
|
|---|
| 404 | * @property {string=} __innerRequest_request inner request for internal usage
|
|---|
| 405 | * @property {string=} __innerRequest_relativePath inner relative path for internal usage
|
|---|
| 406 | */
|
|---|
| 407 |
|
|---|
| 408 | /** @typedef {BaseResolveRequest & Partial<ParsedIdentifier>} ResolveRequest */
|
|---|
| 409 |
|
|---|
| 410 | /**
|
|---|
| 411 | * @template T
|
|---|
| 412 | * @typedef {{ add: (item: T) => void }} WriteOnlySet
|
|---|
| 413 | */
|
|---|
| 414 |
|
|---|
| 415 | /** @typedef {(request: ResolveRequest) => void} ResolveContextYield */
|
|---|
| 416 |
|
|---|
| 417 | /**
|
|---|
| 418 | * Singly-linked stack entry that also exposes a Set-like API
|
|---|
| 419 | * (`has`, `size`, iteration). Each `doResolve` call prepends a new
|
|---|
| 420 | * `StackEntry` that points at the previous tip via `.parent`, so pushing
|
|---|
| 421 | * is O(1) in time and memory. Recursion detection walks the linked list
|
|---|
| 422 | * (O(n)) but the stack is typically shallow, so this is cheaper overall
|
|---|
| 423 | * than cloning a `Set` per call.
|
|---|
| 424 | */
|
|---|
| 425 | class StackEntry {
|
|---|
| 426 | /**
|
|---|
| 427 | * @param {ResolveStepHook} hook hook
|
|---|
| 428 | * @param {ResolveRequest} request request
|
|---|
| 429 | * @param {StackEntry=} parent previous tip
|
|---|
| 430 | * @param {Set<string>=} preSeeded entries pre-seeded via the legacy `Set<string>` API
|
|---|
| 431 | */
|
|---|
| 432 | constructor(hook, request, parent, preSeeded) {
|
|---|
| 433 | this.name = hook.name;
|
|---|
| 434 | this.path = request.path;
|
|---|
| 435 | this.request = request.request || "";
|
|---|
| 436 | this.query = request.query || "";
|
|---|
| 437 | this.fragment = request.fragment || "";
|
|---|
| 438 | this.directory = Boolean(request.directory);
|
|---|
| 439 | this.module = Boolean(request.module);
|
|---|
| 440 | /** @type {StackEntry | undefined} */
|
|---|
| 441 | this.parent = parent;
|
|---|
| 442 | /**
|
|---|
| 443 | * Strings seeded by callers that still pass `stack: new Set([...])`.
|
|---|
| 444 | * Propagated through the chain so deeper `doResolve` calls still see
|
|---|
| 445 | * them during recursion checks. `undefined` in the common case so
|
|---|
| 446 | * there is no extra work on the hot path.
|
|---|
| 447 | * @type {Set<string> | undefined}
|
|---|
| 448 | */
|
|---|
| 449 | this.preSeeded = preSeeded;
|
|---|
| 450 | }
|
|---|
| 451 |
|
|---|
| 452 | /**
|
|---|
| 453 | * Walk the linked list looking for an entry with the same request shape.
|
|---|
| 454 | * Set-compatible: callers that used `stack.has(entry)` keep working.
|
|---|
| 455 | *
|
|---|
| 456 | * NOTE: kept monomorphic on purpose. An earlier draft accepted a string
|
|---|
| 457 | * query too (so pre-5.21 plugins keeping their own `Set<string>` of
|
|---|
| 458 | * seen entries could probe the live stack with the formatted form),
|
|---|
| 459 | * but adding the second shape regressed `doResolve`'s heap profile by
|
|---|
| 460 | * ~1 MiB / 200 resolves on stack-churn — V8 keeps a polymorphic
|
|---|
| 461 | * call-site state for `parent.has(stackEntry)` once `has` has two
|
|---|
| 462 | * argument shapes. Plugins that need string membership can reach for
|
|---|
| 463 | * `[...stack].find(e => e.includes(formattedString))` via the
|
|---|
| 464 | * `String`-method proxies on `StackEntry` instead.
|
|---|
| 465 | * @param {StackEntry} query entry to look for
|
|---|
| 466 | * @returns {boolean} whether the stack already contains an equivalent entry
|
|---|
| 467 | */
|
|---|
| 468 | has(query) {
|
|---|
| 469 | /** @type {StackEntry | undefined} */
|
|---|
| 470 | let node = this;
|
|---|
| 471 | while (node) {
|
|---|
| 472 | if (
|
|---|
| 473 | node.name === query.name &&
|
|---|
| 474 | node.path === query.path &&
|
|---|
| 475 | node.request === query.request &&
|
|---|
| 476 | node.query === query.query &&
|
|---|
| 477 | node.fragment === query.fragment &&
|
|---|
| 478 | node.directory === query.directory &&
|
|---|
| 479 | node.module === query.module
|
|---|
| 480 | ) {
|
|---|
| 481 | return true;
|
|---|
| 482 | }
|
|---|
| 483 | node = node.parent;
|
|---|
| 484 | }
|
|---|
| 485 | return this.preSeeded !== undefined && this.preSeeded.has(query.toString());
|
|---|
| 486 | }
|
|---|
| 487 |
|
|---|
| 488 | /**
|
|---|
| 489 | * Number of entries on the stack (oldest-to-newest length).
|
|---|
| 490 | * @returns {number} size
|
|---|
| 491 | */
|
|---|
| 492 | get size() {
|
|---|
| 493 | let count = this.preSeeded ? this.preSeeded.size : 0;
|
|---|
| 494 | /** @type {StackEntry | undefined} */
|
|---|
| 495 | let node = this;
|
|---|
| 496 | while (node) {
|
|---|
| 497 | count++;
|
|---|
| 498 | node = node.parent;
|
|---|
| 499 | }
|
|---|
| 500 | return count;
|
|---|
| 501 | }
|
|---|
| 502 |
|
|---|
| 503 | /**
|
|---|
| 504 | * Iterate entries from oldest (root) to newest (tip), matching how a
|
|---|
| 505 | * `Set` that was populated in insertion order would iterate. Pre-seeded
|
|---|
| 506 | * legacy `Set<string>` entries come first so error-message output stays
|
|---|
| 507 | * ordered oldest-to-newest.
|
|---|
| 508 | *
|
|---|
| 509 | * Yields each entry as its formatted `toString()` form. Plugins written
|
|---|
| 510 | * against the pre-5.21 `Set<string>` shape — e.g.
|
|---|
| 511 | * `[...resolveContext.stack].find(a => a.includes("module:"))` — keep
|
|---|
| 512 | * working unchanged because each yielded value is a plain string with
|
|---|
| 513 | * all of `String.prototype` available natively. Resolves that never
|
|---|
| 514 | * iterate the stack pay nothing; iteration costs one `toString()`
|
|---|
| 515 | * allocation per stack frame.
|
|---|
| 516 | * @returns {IterableIterator<string>} iterator
|
|---|
| 517 | */
|
|---|
| 518 | *[Symbol.iterator]() {
|
|---|
| 519 | if (this.preSeeded !== undefined) {
|
|---|
| 520 | for (const entry of this.preSeeded) yield entry;
|
|---|
| 521 | }
|
|---|
| 522 | /** @type {StackEntry[]} */
|
|---|
| 523 | const entries = [];
|
|---|
| 524 | /** @type {StackEntry | undefined} */
|
|---|
| 525 | let node = this;
|
|---|
| 526 | while (node) {
|
|---|
| 527 | entries.push(node);
|
|---|
| 528 | node = node.parent;
|
|---|
| 529 | }
|
|---|
| 530 | for (let i = entries.length - 1; i >= 0; i--) yield entries[i].toString();
|
|---|
| 531 | }
|
|---|
| 532 |
|
|---|
| 533 | /**
|
|---|
| 534 | * Human-readable form used in recursion error messages, logs, and the
|
|---|
| 535 | * iterator above. Not memoized: caching would require an extra slot on
|
|---|
| 536 | * every `StackEntry`, which costs heap even on resolves that never look
|
|---|
| 537 | * at the formatted form.
|
|---|
| 538 | * @returns {string} formatted entry
|
|---|
| 539 | */
|
|---|
| 540 | toString() {
|
|---|
| 541 | return `${this.name}: (${this.path}) ${this.request}${this.query}${
|
|---|
| 542 | this.fragment
|
|---|
| 543 | }${this.directory ? " directory" : ""}${this.module ? " module" : ""}`;
|
|---|
| 544 | }
|
|---|
| 545 | }
|
|---|
| 546 |
|
|---|
| 547 | /**
|
|---|
| 548 | * Resolve context
|
|---|
| 549 | * @typedef {object} ResolveContext
|
|---|
| 550 | * @property {WriteOnlySet<string>=} contextDependencies directories that was found on file system
|
|---|
| 551 | * @property {WriteOnlySet<string>=} fileDependencies files that was found on file system
|
|---|
| 552 | * @property {WriteOnlySet<string>=} missingDependencies dependencies that was not found on file system
|
|---|
| 553 | * @property {StackEntry | Set<string>=} stack tip of the resolver call stack (a singly-linked list with Set-like API). For instance, `resolve → parsedResolve → describedResolve`. Accepts a legacy `Set<string>` for back-compat with older callers; it is normalized internally without a hot-path branch.
|
|---|
| 554 | * @property {((str: string) => void)=} log log function
|
|---|
| 555 | * @property {ResolveContextYield=} yield yield result, if provided plugins can return several results
|
|---|
| 556 | */
|
|---|
| 557 |
|
|---|
| 558 | /** @typedef {AsyncSeriesBailHook<[ResolveRequest, ResolveContext], ResolveRequest | null>} ResolveStepHook */
|
|---|
| 559 |
|
|---|
| 560 | /**
|
|---|
| 561 | * @typedef {object} KnownHooks
|
|---|
| 562 | * @property {SyncHook<[ResolveStepHook, ResolveRequest], void>} resolveStep resolve step hook
|
|---|
| 563 | * @property {SyncHook<[ResolveRequest, Error]>} noResolve no resolve hook
|
|---|
| 564 | * @property {ResolveStepHook} resolve resolve hook
|
|---|
| 565 | * @property {AsyncSeriesHook<[ResolveRequest, ResolveContext]>} result result hook
|
|---|
| 566 | */
|
|---|
| 567 |
|
|---|
| 568 | /**
|
|---|
| 569 | * @typedef {{ [key: string]: ResolveStepHook }} EnsuredHooks
|
|---|
| 570 | */
|
|---|
| 571 |
|
|---|
| 572 | /**
|
|---|
| 573 | * @param {string} str input string
|
|---|
| 574 | * @returns {string} in camel case
|
|---|
| 575 | */
|
|---|
| 576 | function toCamelCase(str) {
|
|---|
| 577 | return str.replace(/-([a-z])/g, (str) => str.slice(1).toUpperCase());
|
|---|
| 578 | }
|
|---|
| 579 |
|
|---|
| 580 | class Resolver {
|
|---|
| 581 | /**
|
|---|
| 582 | * @param {ResolveStepHook} hook hook
|
|---|
| 583 | * @param {ResolveRequest} request request
|
|---|
| 584 | * @param {StackEntry=} parent previous tip of the stack
|
|---|
| 585 | * @param {Set<string>=} preSeeded entries pre-seeded via the legacy `Set<string>` API
|
|---|
| 586 | * @returns {StackEntry} stack entry
|
|---|
| 587 | */
|
|---|
| 588 | static createStackEntry(hook, request, parent, preSeeded) {
|
|---|
| 589 | return new StackEntry(hook, request, parent, preSeeded);
|
|---|
| 590 | }
|
|---|
| 591 |
|
|---|
| 592 | /**
|
|---|
| 593 | * @param {FileSystem} fileSystem a filesystem
|
|---|
| 594 | * @param {ResolveOptions} options options
|
|---|
| 595 | */
|
|---|
| 596 | constructor(fileSystem, options) {
|
|---|
| 597 | /** @type {FileSystem} */
|
|---|
| 598 | this.fileSystem = fileSystem;
|
|---|
| 599 | /** @type {ResolveOptions} */
|
|---|
| 600 | this.options = options;
|
|---|
| 601 | let pathCache = _pathCacheByFs.get(fileSystem);
|
|---|
| 602 | if (!pathCache) {
|
|---|
| 603 | pathCache = {
|
|---|
| 604 | join: createCachedJoin(),
|
|---|
| 605 | dirname: createCachedDirname(),
|
|---|
| 606 | basename: createCachedBasename(),
|
|---|
| 607 | };
|
|---|
| 608 | _pathCacheByFs.set(fileSystem, pathCache);
|
|---|
| 609 | }
|
|---|
| 610 | /** @type {PathCacheFunctions} */
|
|---|
| 611 | this.pathCache = pathCache;
|
|---|
| 612 | /** @type {KnownHooks} */
|
|---|
| 613 | this.hooks = {
|
|---|
| 614 | resolveStep: new SyncHook(["hook", "request"], "resolveStep"),
|
|---|
| 615 | noResolve: new SyncHook(["request", "error"], "noResolve"),
|
|---|
| 616 | resolve: new AsyncSeriesBailHook(
|
|---|
| 617 | ["request", "resolveContext"],
|
|---|
| 618 | "resolve",
|
|---|
| 619 | ),
|
|---|
| 620 | result: new AsyncSeriesHook(["result", "resolveContext"], "result"),
|
|---|
| 621 | };
|
|---|
| 622 | }
|
|---|
| 623 |
|
|---|
| 624 | /**
|
|---|
| 625 | * @param {string | ResolveStepHook} name hook name or hook itself
|
|---|
| 626 | * @returns {ResolveStepHook} the hook
|
|---|
| 627 | */
|
|---|
| 628 | ensureHook(name) {
|
|---|
| 629 | if (typeof name !== "string") {
|
|---|
| 630 | return name;
|
|---|
| 631 | }
|
|---|
| 632 | name = toCamelCase(name);
|
|---|
| 633 | if (name.startsWith("before")) {
|
|---|
| 634 | return /** @type {ResolveStepHook} */ (
|
|---|
| 635 | this.ensureHook(name[6].toLowerCase() + name.slice(7)).withOptions({
|
|---|
| 636 | stage: -10,
|
|---|
| 637 | })
|
|---|
| 638 | );
|
|---|
| 639 | }
|
|---|
| 640 | if (name.startsWith("after")) {
|
|---|
| 641 | return /** @type {ResolveStepHook} */ (
|
|---|
| 642 | this.ensureHook(name[5].toLowerCase() + name.slice(6)).withOptions({
|
|---|
| 643 | stage: 10,
|
|---|
| 644 | })
|
|---|
| 645 | );
|
|---|
| 646 | }
|
|---|
| 647 | /** @type {ResolveStepHook} */
|
|---|
| 648 | const hook = /** @type {KnownHooks & EnsuredHooks} */ (this.hooks)[name];
|
|---|
| 649 | if (!hook) {
|
|---|
| 650 | /** @type {KnownHooks & EnsuredHooks} */
|
|---|
| 651 | (this.hooks)[name] = new AsyncSeriesBailHook(
|
|---|
| 652 | ["request", "resolveContext"],
|
|---|
| 653 | name,
|
|---|
| 654 | );
|
|---|
| 655 |
|
|---|
| 656 | return /** @type {KnownHooks & EnsuredHooks} */ (this.hooks)[name];
|
|---|
| 657 | }
|
|---|
| 658 | return hook;
|
|---|
| 659 | }
|
|---|
| 660 |
|
|---|
| 661 | /**
|
|---|
| 662 | * @param {string | ResolveStepHook} name hook name or hook itself
|
|---|
| 663 | * @returns {ResolveStepHook} the hook
|
|---|
| 664 | */
|
|---|
| 665 | getHook(name) {
|
|---|
| 666 | if (typeof name !== "string") {
|
|---|
| 667 | return name;
|
|---|
| 668 | }
|
|---|
| 669 | name = toCamelCase(name);
|
|---|
| 670 | if (name.startsWith("before")) {
|
|---|
| 671 | return /** @type {ResolveStepHook} */ (
|
|---|
| 672 | this.getHook(name[6].toLowerCase() + name.slice(7)).withOptions({
|
|---|
| 673 | stage: -10,
|
|---|
| 674 | })
|
|---|
| 675 | );
|
|---|
| 676 | }
|
|---|
| 677 | if (name.startsWith("after")) {
|
|---|
| 678 | return /** @type {ResolveStepHook} */ (
|
|---|
| 679 | this.getHook(name[5].toLowerCase() + name.slice(6)).withOptions({
|
|---|
| 680 | stage: 10,
|
|---|
| 681 | })
|
|---|
| 682 | );
|
|---|
| 683 | }
|
|---|
| 684 | /** @type {ResolveStepHook} */
|
|---|
| 685 | const hook = /** @type {KnownHooks & EnsuredHooks} */ (this.hooks)[name];
|
|---|
| 686 | if (!hook) {
|
|---|
| 687 | throw new Error(`Hook ${name} doesn't exist`);
|
|---|
| 688 | }
|
|---|
| 689 | return hook;
|
|---|
| 690 | }
|
|---|
| 691 |
|
|---|
| 692 | /**
|
|---|
| 693 | * @overload
|
|---|
| 694 | * @param {string} path context path
|
|---|
| 695 | * @param {string} request request string
|
|---|
| 696 | * @param {ResolveContext=} resolveContext resolve context
|
|---|
| 697 | * @returns {string | false} result
|
|---|
| 698 | */
|
|---|
| 699 | /**
|
|---|
| 700 | * @overload
|
|---|
| 701 | * @param {Context} context context information object
|
|---|
| 702 | * @param {string} path context path
|
|---|
| 703 | * @param {string} request request string
|
|---|
| 704 | * @param {ResolveContext=} resolveContext resolve context
|
|---|
| 705 | * @returns {string | false} result
|
|---|
| 706 | */
|
|---|
| 707 | /**
|
|---|
| 708 | * @param {Context | string} context context information object or context path when no context is provided
|
|---|
| 709 | * @param {string | ResolveContext=} path context path or resolve context when no context is provided
|
|---|
| 710 | * @param {string | ResolveContext=} request request string or resolve context when no context is provided
|
|---|
| 711 | * @param {ResolveContext=} resolveContext resolve context
|
|---|
| 712 | * @returns {string | false} result
|
|---|
| 713 | */
|
|---|
| 714 | resolveSync(context, path, request, resolveContext) {
|
|---|
| 715 | /** @type {Error | null | undefined} */
|
|---|
| 716 | let err;
|
|---|
| 717 | /** @type {string | false | undefined} */
|
|---|
| 718 | let result;
|
|---|
| 719 | let sync = false;
|
|---|
| 720 | // `|| {}` so the underlying `resolve()` hits its 5-arg fast path
|
|---|
| 721 | // (skips the overload-shifting prologue) regardless of whether the
|
|---|
| 722 | // caller supplied a resolveContext.
|
|---|
| 723 | this.resolve(
|
|---|
| 724 | /** @type {Context} */ (context),
|
|---|
| 725 | /** @type {string} */ (path),
|
|---|
| 726 | /** @type {string} */ (request),
|
|---|
| 727 | /** @type {ResolveContext} */ (resolveContext) || {},
|
|---|
| 728 | (_err, r) => {
|
|---|
| 729 | err = _err;
|
|---|
| 730 | result = r;
|
|---|
| 731 | sync = true;
|
|---|
| 732 | },
|
|---|
| 733 | );
|
|---|
| 734 | if (!sync) {
|
|---|
| 735 | throw new Error(
|
|---|
| 736 | "Cannot 'resolveSync' because the fileSystem is not sync. Use 'resolve'!",
|
|---|
| 737 | );
|
|---|
| 738 | }
|
|---|
| 739 | if (err) throw err;
|
|---|
| 740 | if (result === undefined) throw new Error("No result");
|
|---|
| 741 | return result;
|
|---|
| 742 | }
|
|---|
| 743 |
|
|---|
| 744 | /**
|
|---|
| 745 | * @overload
|
|---|
| 746 | * @param {string} path context path
|
|---|
| 747 | * @param {string} request request string
|
|---|
| 748 | * @param {ResolveContext=} resolveContext resolve context
|
|---|
| 749 | * @returns {Promise<string | false>} result
|
|---|
| 750 | */
|
|---|
| 751 | /**
|
|---|
| 752 | * @overload
|
|---|
| 753 | * @param {Context} context context information object
|
|---|
| 754 | * @param {string} path context path
|
|---|
| 755 | * @param {string} request request string
|
|---|
| 756 | * @param {ResolveContext=} resolveContext resolve context
|
|---|
| 757 | * @returns {Promise<string | false>} result
|
|---|
| 758 | */
|
|---|
| 759 | /**
|
|---|
| 760 | * @param {Context | string} context context information object or context path when no context is provided
|
|---|
| 761 | * @param {string | ResolveContext=} path context path or resolve context when no context is provided
|
|---|
| 762 | * @param {string | ResolveContext=} request request string or resolve context when no context is provided
|
|---|
| 763 | * @param {ResolveContext=} resolveContext resolve context
|
|---|
| 764 | * @returns {Promise<string | false>} result
|
|---|
| 765 | */
|
|---|
| 766 | resolvePromise(context, path, request, resolveContext) {
|
|---|
| 767 | // `|| {}` ensures the 5-arg fast path inside `resolve()` is reached
|
|---|
| 768 | // even when the caller doesn't pass a resolveContext.
|
|---|
| 769 | return _withResolvers(
|
|---|
| 770 | this,
|
|---|
| 771 | /** @type {Context} */ (context),
|
|---|
| 772 | /** @type {string} */ (path),
|
|---|
| 773 | /** @type {string} */ (request),
|
|---|
| 774 | /** @type {ResolveContext} */ (resolveContext) || {},
|
|---|
| 775 | );
|
|---|
| 776 | }
|
|---|
| 777 |
|
|---|
| 778 | /**
|
|---|
| 779 | * @overload
|
|---|
| 780 | * @param {string} path context path
|
|---|
| 781 | * @param {string} request request string
|
|---|
| 782 | * @param {ResolveCallback} callback callback function
|
|---|
| 783 | * @returns {void}
|
|---|
| 784 | */
|
|---|
| 785 | /**
|
|---|
| 786 | * @overload
|
|---|
| 787 | * @param {string} path context path
|
|---|
| 788 | * @param {string} request request string
|
|---|
| 789 | * @param {ResolveContext} resolveContext resolve context
|
|---|
| 790 | * @param {ResolveCallback} callback callback function
|
|---|
| 791 | * @returns {void}
|
|---|
| 792 | */
|
|---|
| 793 | /**
|
|---|
| 794 | * @overload
|
|---|
| 795 | * @param {Context} context context information object
|
|---|
| 796 | * @param {string} path context path
|
|---|
| 797 | * @param {string} request request string
|
|---|
| 798 | * @param {ResolveCallback} callback callback function
|
|---|
| 799 | * @returns {void}
|
|---|
| 800 | */
|
|---|
| 801 | /**
|
|---|
| 802 | * @overload
|
|---|
| 803 | * @param {Context} context context information object
|
|---|
| 804 | * @param {string} path context path
|
|---|
| 805 | * @param {string} request request string
|
|---|
| 806 | * @param {ResolveContext} resolveContext resolve context
|
|---|
| 807 | * @param {ResolveCallback} callback callback function
|
|---|
| 808 | * @returns {void}
|
|---|
| 809 | */
|
|---|
| 810 | /**
|
|---|
| 811 | * @param {Context | string} context context information object or context path when no context is provided
|
|---|
| 812 | * @param {string | ResolveContext | ResolveCallback=} path context path or (when no context) resolve context or callback
|
|---|
| 813 | * @param {string | ResolveContext | ResolveCallback=} request request string or (when no context) resolve context or callback
|
|---|
| 814 | * @param {ResolveContext | ResolveCallback=} resolveContext resolve context or callback when no resolve context is provided
|
|---|
| 815 | * @param {ResolveCallback=} callback callback function
|
|---|
| 816 | * @returns {void}
|
|---|
| 817 | */
|
|---|
| 818 | resolve(context, path, request, resolveContext, callback) {
|
|---|
| 819 | // Fast path for the common 5-arg call (`resolver.resolve(ctx, from,
|
|---|
| 820 | // req, resolveCtx, cb)`) — every call from `resolveSync` /
|
|---|
| 821 | // `resolvePromise` plus the vast majority of direct API callers.
|
|---|
| 822 | // PR #536 added runtime overload-shifting to support optional
|
|---|
| 823 | // `context` / `resolveContext`; that adds several `typeof` checks
|
|---|
| 824 | // per resolve which show up as a measurable instruction-count
|
|---|
| 825 | // regression on every benchmark that calls into this method. Skip
|
|---|
| 826 | // the shifting entirely when all 5 args are already well-typed.
|
|---|
| 827 | if (
|
|---|
| 828 | typeof callback === "function" &&
|
|---|
| 829 | typeof context === "object" &&
|
|---|
| 830 | context !== null &&
|
|---|
| 831 | typeof resolveContext === "object" &&
|
|---|
| 832 | resolveContext !== null
|
|---|
| 833 | ) {
|
|---|
| 834 | // proceed straight to per-arg validation below
|
|---|
| 835 | } else {
|
|---|
| 836 | // Slow path: shift positional args based on what was supplied.
|
|---|
| 837 | // Shift when context is omitted (first positional arg is the path string).
|
|---|
| 838 | if (typeof context === "string") {
|
|---|
| 839 | // Keep an already-supplied callback (resolveSync / resolvePromise
|
|---|
| 840 | // always pass one in the 5th position).
|
|---|
| 841 | if (typeof callback !== "function") {
|
|---|
| 842 | callback = /** @type {ResolveCallback | undefined} */ (
|
|---|
| 843 | resolveContext
|
|---|
| 844 | );
|
|---|
| 845 | }
|
|---|
| 846 | resolveContext =
|
|---|
| 847 | /** @type {ResolveContext | ResolveCallback | undefined} */ (request);
|
|---|
| 848 | request = /** @type {string} */ (path);
|
|---|
| 849 | path = context;
|
|---|
| 850 | context = {};
|
|---|
| 851 | }
|
|---|
| 852 | // 4-arg form: the resolveContext slot holds the callback.
|
|---|
| 853 | if (typeof resolveContext === "function") {
|
|---|
| 854 | callback = resolveContext;
|
|---|
| 855 | resolveContext = {};
|
|---|
| 856 | } else if (!resolveContext || typeof resolveContext !== "object") {
|
|---|
| 857 | resolveContext = {};
|
|---|
| 858 | }
|
|---|
| 859 | if (typeof callback !== "function") {
|
|---|
| 860 | throw new TypeError("callback argument is not a function");
|
|---|
| 861 | }
|
|---|
| 862 | if (!context || typeof context !== "object") {
|
|---|
| 863 | context = {};
|
|---|
| 864 | }
|
|---|
| 865 | }
|
|---|
| 866 | if (typeof path !== "string") {
|
|---|
| 867 | return callback(new Error("path argument is not a string"));
|
|---|
| 868 | }
|
|---|
| 869 | if (typeof request !== "string") {
|
|---|
| 870 | return callback(new Error("request argument is not a string"));
|
|---|
| 871 | }
|
|---|
| 872 |
|
|---|
| 873 | /** @type {ResolveRequest} */
|
|---|
| 874 | const obj = {
|
|---|
| 875 | context,
|
|---|
| 876 | path,
|
|---|
| 877 | request,
|
|---|
| 878 | };
|
|---|
| 879 |
|
|---|
| 880 | /** @type {ResolveContextYield | undefined} */
|
|---|
| 881 | let yield_;
|
|---|
| 882 | let yieldCalled = false;
|
|---|
| 883 | /** @type {ResolveContextYield | undefined} */
|
|---|
| 884 | let finishYield;
|
|---|
| 885 | if (typeof resolveContext.yield === "function") {
|
|---|
| 886 | const old = resolveContext.yield;
|
|---|
| 887 | /**
|
|---|
| 888 | * @param {ResolveRequest} obj object
|
|---|
| 889 | */
|
|---|
| 890 | yield_ = (obj) => {
|
|---|
| 891 | old(obj);
|
|---|
| 892 | yieldCalled = true;
|
|---|
| 893 | };
|
|---|
| 894 | /**
|
|---|
| 895 | * @param {ResolveRequest} result result
|
|---|
| 896 | * @returns {void}
|
|---|
| 897 | */
|
|---|
| 898 | finishYield = (result) => {
|
|---|
| 899 | if (result) {
|
|---|
| 900 | /** @type {ResolveContextYield} */ (yield_)(result);
|
|---|
| 901 | }
|
|---|
| 902 | callback(null);
|
|---|
| 903 | };
|
|---|
| 904 | }
|
|---|
| 905 |
|
|---|
| 906 | const message = `resolve '${request}' in '${path}'`;
|
|---|
| 907 |
|
|---|
| 908 | /**
|
|---|
| 909 | * @param {ResolveRequest} result result
|
|---|
| 910 | * @returns {void}
|
|---|
| 911 | */
|
|---|
| 912 | const finishResolved = (result) => {
|
|---|
| 913 | const resultPath = result.path;
|
|---|
| 914 | if (resultPath === false) return callback(null, false, result);
|
|---|
| 915 | const escapedPath = resultPath.includes("#")
|
|---|
| 916 | ? resultPath.replace(HASH_ESCAPE_RE, "\0#")
|
|---|
| 917 | : resultPath;
|
|---|
| 918 | const resultQuery = result.query;
|
|---|
| 919 | let escapedQuery;
|
|---|
| 920 | if (resultQuery) {
|
|---|
| 921 | escapedQuery = resultQuery.includes("#")
|
|---|
| 922 | ? resultQuery.replace(HASH_ESCAPE_RE, "\0#")
|
|---|
| 923 | : resultQuery;
|
|---|
| 924 | } else {
|
|---|
| 925 | escapedQuery = "";
|
|---|
| 926 | }
|
|---|
| 927 | return callback(
|
|---|
| 928 | null,
|
|---|
| 929 | `${escapedPath}${escapedQuery}${result.fragment || ""}`,
|
|---|
| 930 | result,
|
|---|
| 931 | );
|
|---|
| 932 | };
|
|---|
| 933 |
|
|---|
| 934 | /**
|
|---|
| 935 | * @param {string[]} log logs
|
|---|
| 936 | * @returns {void}
|
|---|
| 937 | */
|
|---|
| 938 | const finishWithoutResolve = (log) => {
|
|---|
| 939 | /**
|
|---|
| 940 | * @type {ErrorWithDetail}
|
|---|
| 941 | */
|
|---|
| 942 | const error = new Error(`Can't ${message}`);
|
|---|
| 943 | error.details = log.join("\n");
|
|---|
| 944 | this.hooks.noResolve.call(obj, error);
|
|---|
| 945 | return callback(error);
|
|---|
| 946 | };
|
|---|
| 947 |
|
|---|
| 948 | if (resolveContext.log) {
|
|---|
| 949 | // We need log anyway to capture it in case of an error
|
|---|
| 950 | const parentLog = resolveContext.log;
|
|---|
| 951 | /** @type {string[]} */
|
|---|
| 952 | const log = [];
|
|---|
| 953 | return this.doResolve(
|
|---|
| 954 | this.hooks.resolve,
|
|---|
| 955 | obj,
|
|---|
| 956 | message,
|
|---|
| 957 | {
|
|---|
| 958 | log: (msg) => {
|
|---|
| 959 | parentLog(msg);
|
|---|
| 960 | log.push(msg);
|
|---|
| 961 | },
|
|---|
| 962 | yield: yield_,
|
|---|
| 963 | fileDependencies: resolveContext.fileDependencies,
|
|---|
| 964 | contextDependencies: resolveContext.contextDependencies,
|
|---|
| 965 | missingDependencies: resolveContext.missingDependencies,
|
|---|
| 966 | stack: resolveContext.stack,
|
|---|
| 967 | },
|
|---|
| 968 | (err, result) => {
|
|---|
| 969 | if (err) return callback(err);
|
|---|
| 970 |
|
|---|
| 971 | if (yieldCalled || (result && yield_)) {
|
|---|
| 972 | return /** @type {ResolveContextYield} */ (finishYield)(
|
|---|
| 973 | /** @type {ResolveRequest} */ (result),
|
|---|
| 974 | );
|
|---|
| 975 | }
|
|---|
| 976 |
|
|---|
| 977 | if (result) return finishResolved(result);
|
|---|
| 978 |
|
|---|
| 979 | return finishWithoutResolve(log);
|
|---|
| 980 | },
|
|---|
| 981 | );
|
|---|
| 982 | }
|
|---|
| 983 | // Try to resolve assuming there is no error
|
|---|
| 984 | // We don't log stuff in this case
|
|---|
| 985 | return this.doResolve(
|
|---|
| 986 | this.hooks.resolve,
|
|---|
| 987 | obj,
|
|---|
| 988 | message,
|
|---|
| 989 | {
|
|---|
| 990 | log: undefined,
|
|---|
| 991 | yield: yield_,
|
|---|
| 992 | fileDependencies: resolveContext.fileDependencies,
|
|---|
| 993 | contextDependencies: resolveContext.contextDependencies,
|
|---|
| 994 | missingDependencies: resolveContext.missingDependencies,
|
|---|
| 995 | stack: resolveContext.stack,
|
|---|
| 996 | },
|
|---|
| 997 | (err, result) => {
|
|---|
| 998 | if (err) return callback(err);
|
|---|
| 999 |
|
|---|
| 1000 | if (yieldCalled || (result && yield_)) {
|
|---|
| 1001 | return /** @type {ResolveContextYield} */ (finishYield)(
|
|---|
| 1002 | /** @type {ResolveRequest} */ (result),
|
|---|
| 1003 | );
|
|---|
| 1004 | }
|
|---|
| 1005 |
|
|---|
| 1006 | if (result) return finishResolved(result);
|
|---|
| 1007 |
|
|---|
| 1008 | // log is missing for the error details
|
|---|
| 1009 | // so we redo the resolving for the log info
|
|---|
| 1010 | // this is more expensive to the success case
|
|---|
| 1011 | // is assumed by default
|
|---|
| 1012 | /** @type {string[]} */
|
|---|
| 1013 | const log = [];
|
|---|
| 1014 |
|
|---|
| 1015 | return this.doResolve(
|
|---|
| 1016 | this.hooks.resolve,
|
|---|
| 1017 | obj,
|
|---|
| 1018 | message,
|
|---|
| 1019 | {
|
|---|
| 1020 | log: (msg) => log.push(msg),
|
|---|
| 1021 | yield: yield_,
|
|---|
| 1022 | stack: resolveContext.stack,
|
|---|
| 1023 | },
|
|---|
| 1024 | (err, result) => {
|
|---|
| 1025 | if (err) return callback(err);
|
|---|
| 1026 |
|
|---|
| 1027 | // In a case that there is a race condition and yield will be called
|
|---|
| 1028 | if (yieldCalled || (result && yield_)) {
|
|---|
| 1029 | return /** @type {ResolveContextYield} */ (finishYield)(
|
|---|
| 1030 | /** @type {ResolveRequest} */ (result),
|
|---|
| 1031 | );
|
|---|
| 1032 | }
|
|---|
| 1033 |
|
|---|
| 1034 | return finishWithoutResolve(log);
|
|---|
| 1035 | },
|
|---|
| 1036 | );
|
|---|
| 1037 | },
|
|---|
| 1038 | );
|
|---|
| 1039 | }
|
|---|
| 1040 |
|
|---|
| 1041 | /**
|
|---|
| 1042 | * @param {ResolveStepHook} hook hook
|
|---|
| 1043 | * @param {ResolveRequest} request request
|
|---|
| 1044 | * @param {null | string} message string
|
|---|
| 1045 | * @param {ResolveContext} resolveContext resolver context
|
|---|
| 1046 | * @param {(err?: null | Error, result?: ResolveRequest) => void} callback callback
|
|---|
| 1047 | * @returns {void}
|
|---|
| 1048 | */
|
|---|
| 1049 | doResolve(hook, request, message, resolveContext, callback) {
|
|---|
| 1050 | const rawStack = resolveContext.stack;
|
|---|
| 1051 | /** @type {StackEntry | undefined} */
|
|---|
| 1052 | let parent;
|
|---|
| 1053 | /** @type {Set<string> | undefined} */
|
|---|
| 1054 | let preSeeded;
|
|---|
| 1055 | if (rawStack instanceof StackEntry) {
|
|---|
| 1056 | parent = rawStack;
|
|---|
| 1057 | preSeeded = rawStack.preSeeded;
|
|---|
| 1058 | } else if (rawStack) {
|
|---|
| 1059 | // TODO in the next major remove `Set<string>` support in favor of `StackEntry`
|
|---|
| 1060 | // Legacy `stack: new Set<string>()` API: don't link the Set into
|
|---|
| 1061 | // the parent chain (it would pollute iteration and field-compare
|
|---|
| 1062 | // walks). Carry the strings on the StackEntry itself instead so
|
|---|
| 1063 | // deeper `doResolve` calls keep seeing pre-seeded entries.
|
|---|
| 1064 | preSeeded = /** @type {Set<string>} */ (rawStack);
|
|---|
| 1065 | }
|
|---|
| 1066 | // Prepend a new linked-list node. O(1) allocation, no Set clone.
|
|---|
| 1067 | const stackEntry = Resolver.createStackEntry(
|
|---|
| 1068 | hook,
|
|---|
| 1069 | request,
|
|---|
| 1070 | parent,
|
|---|
| 1071 | preSeeded,
|
|---|
| 1072 | );
|
|---|
| 1073 |
|
|---|
| 1074 | // When `parent` exists, its `has()` already consults `preSeeded`
|
|---|
| 1075 | // (inherited from the same chain), so we only need the direct Set
|
|---|
| 1076 | // lookup on the very first `doResolve` call (no parent yet).
|
|---|
| 1077 | if (
|
|---|
| 1078 | parent !== undefined
|
|---|
| 1079 | ? parent.has(stackEntry)
|
|---|
| 1080 | : preSeeded !== undefined && preSeeded.has(stackEntry.toString())
|
|---|
| 1081 | ) {
|
|---|
| 1082 | /**
|
|---|
| 1083 | * Prevent recursion
|
|---|
| 1084 | * @type {Error & { recursion?: boolean }}
|
|---|
| 1085 | */
|
|---|
| 1086 | const recursionError = new Error(
|
|---|
| 1087 | `Recursion in resolving\nStack:\n ${[...stackEntry].join("\n ")}`,
|
|---|
| 1088 | );
|
|---|
| 1089 | recursionError.recursion = true;
|
|---|
| 1090 | if (resolveContext.log) {
|
|---|
| 1091 | resolveContext.log("abort resolving because of recursion");
|
|---|
| 1092 | }
|
|---|
| 1093 | return callback(recursionError);
|
|---|
| 1094 | }
|
|---|
| 1095 | this.hooks.resolveStep.call(hook, request);
|
|---|
| 1096 |
|
|---|
| 1097 | if (hook.isUsed()) {
|
|---|
| 1098 | // Pass `resolveContext` and the override fields (stack, message)
|
|---|
| 1099 | // directly instead of constructing an intermediate options-object
|
|---|
| 1100 | // literal — `createInnerContext` reads from the parent and
|
|---|
| 1101 | // allocates exactly one inner context per step. See the comment
|
|---|
| 1102 | // on `createInnerContext` itself for the allocation rationale.
|
|---|
| 1103 | const innerContext = createInnerContext(
|
|---|
| 1104 | resolveContext,
|
|---|
| 1105 | stackEntry,
|
|---|
| 1106 | message,
|
|---|
| 1107 | );
|
|---|
| 1108 | return hook.callAsync(request, innerContext, (err, result) => {
|
|---|
| 1109 | if (err) return callback(err);
|
|---|
| 1110 | if (result) return callback(null, result);
|
|---|
| 1111 | callback();
|
|---|
| 1112 | });
|
|---|
| 1113 | }
|
|---|
| 1114 | callback();
|
|---|
| 1115 | }
|
|---|
| 1116 |
|
|---|
| 1117 | /**
|
|---|
| 1118 | * @param {string} identifier identifier
|
|---|
| 1119 | * @returns {ParsedIdentifier} parsed identifier
|
|---|
| 1120 | */
|
|---|
| 1121 | parse(identifier) {
|
|---|
| 1122 | /** @type {ParsedIdentifier} */
|
|---|
| 1123 | const part = {
|
|---|
| 1124 | request: "",
|
|---|
| 1125 | query: "",
|
|---|
| 1126 | fragment: "",
|
|---|
| 1127 | module: false,
|
|---|
| 1128 | directory: false,
|
|---|
| 1129 | file: false,
|
|---|
| 1130 | internal: false,
|
|---|
| 1131 | };
|
|---|
| 1132 |
|
|---|
| 1133 | const parsedIdentifier = parseIdentifier(identifier);
|
|---|
| 1134 |
|
|---|
| 1135 | if (!parsedIdentifier) return part;
|
|---|
| 1136 |
|
|---|
| 1137 | [part.request, part.query, part.fragment] = parsedIdentifier;
|
|---|
| 1138 |
|
|---|
| 1139 | if (part.request.length > 0) {
|
|---|
| 1140 | // `getType` looks at the prefix of its input and the prefix is
|
|---|
| 1141 | // identical between `identifier` and `part.request` in every
|
|---|
| 1142 | // non-`\0`-escape case (slicing off `?query` / `#fragment` doesn't
|
|---|
| 1143 | // touch the head). `parseIdentifier`'s common fast path returns
|
|---|
| 1144 | // the same `identifier` reference as `parsedIdentifier[0]`, so a
|
|---|
| 1145 | // pointer-equality check detects the case where we can compute
|
|---|
| 1146 | // `getType` once and use it for both `module` and `internal`. The
|
|---|
| 1147 | // `\0#…` escape path produces a fresh `part.request` and falls
|
|---|
| 1148 | // through to the second `getType(identifier)` call to preserve
|
|---|
| 1149 | // the original `internal` flag.
|
|---|
| 1150 | const requestType = getType(part.request);
|
|---|
| 1151 | part.module = requestType === PathType.Normal;
|
|---|
| 1152 | part.internal =
|
|---|
| 1153 | identifier === part.request
|
|---|
| 1154 | ? requestType === PathType.Internal
|
|---|
| 1155 | : getType(identifier) === PathType.Internal;
|
|---|
| 1156 | // `isDirectory` is just `endsWith("/")` — inline so `parse()`
|
|---|
| 1157 | // doesn't pay for the extra method dispatch on every resolve.
|
|---|
| 1158 | part.directory = part.request.endsWith("/");
|
|---|
| 1159 | if (part.directory) {
|
|---|
| 1160 | part.request = part.request.slice(0, -1);
|
|---|
| 1161 | }
|
|---|
| 1162 | }
|
|---|
| 1163 |
|
|---|
| 1164 | return part;
|
|---|
| 1165 | }
|
|---|
| 1166 |
|
|---|
| 1167 | /**
|
|---|
| 1168 | * @param {string} path path
|
|---|
| 1169 | * @returns {boolean} true, if the path is a module
|
|---|
| 1170 | */
|
|---|
| 1171 | isModule(path) {
|
|---|
| 1172 | return getType(path) === PathType.Normal;
|
|---|
| 1173 | }
|
|---|
| 1174 |
|
|---|
| 1175 | /**
|
|---|
| 1176 | * @param {string} path path
|
|---|
| 1177 | * @returns {boolean} true, if the path is private
|
|---|
| 1178 | */
|
|---|
| 1179 | isPrivate(path) {
|
|---|
| 1180 | return getType(path) === PathType.Internal;
|
|---|
| 1181 | }
|
|---|
| 1182 |
|
|---|
| 1183 | /**
|
|---|
| 1184 | * @param {string} path a path
|
|---|
| 1185 | * @returns {boolean} true, if the path is a directory path
|
|---|
| 1186 | */
|
|---|
| 1187 | isDirectory(path) {
|
|---|
| 1188 | return path.endsWith("/");
|
|---|
| 1189 | }
|
|---|
| 1190 |
|
|---|
| 1191 | /**
|
|---|
| 1192 | * @param {string} path path
|
|---|
| 1193 | * @returns {string} normalized path
|
|---|
| 1194 | */
|
|---|
| 1195 | normalize(path) {
|
|---|
| 1196 | return normalize(path);
|
|---|
| 1197 | }
|
|---|
| 1198 |
|
|---|
| 1199 | /**
|
|---|
| 1200 | * @param {string} path path
|
|---|
| 1201 | * @param {string} request request
|
|---|
| 1202 | * @returns {string} joined path
|
|---|
| 1203 | */
|
|---|
| 1204 | join(path, request) {
|
|---|
| 1205 | return this.pathCache.join.fn(path, request);
|
|---|
| 1206 | }
|
|---|
| 1207 |
|
|---|
| 1208 | /**
|
|---|
| 1209 | * @param {string} path path
|
|---|
| 1210 | * @returns {string} parent directory
|
|---|
| 1211 | */
|
|---|
| 1212 | dirname(path) {
|
|---|
| 1213 | return this.pathCache.dirname.fn(path);
|
|---|
| 1214 | }
|
|---|
| 1215 |
|
|---|
| 1216 | /**
|
|---|
| 1217 | * @param {string} path the path to evaluate
|
|---|
| 1218 | * @param {string=} suffix an extension to remove from the result
|
|---|
| 1219 | * @returns {string} the last portion of a path
|
|---|
| 1220 | */
|
|---|
| 1221 | basename(path, suffix) {
|
|---|
| 1222 | return this.pathCache.basename.fn(path, suffix);
|
|---|
| 1223 | }
|
|---|
| 1224 | }
|
|---|
| 1225 |
|
|---|
| 1226 | module.exports = Resolver;
|
|---|