Resolver.js 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800
  1. /*
  2. MIT License http://www.opensource.org/licenses/mit-license.php
  3. Author Tobias Koppers @sokra
  4. */
  5. "use strict";
  6. const { AsyncSeriesBailHook, AsyncSeriesHook, SyncHook } = require("tapable");
  7. const createInnerContext = require("./createInnerContext");
  8. const { parseIdentifier } = require("./util/identifier");
  9. const {
  10. normalize,
  11. cachedJoin: join,
  12. getType,
  13. PathType
  14. } = require("./util/path");
  15. /** @typedef {import("./ResolverFactory").ResolveOptions} ResolveOptions */
  16. /** @typedef {Error & { details?: string }} ErrorWithDetail */
  17. /** @typedef {(err: ErrorWithDetail | null, res?: string | false, req?: ResolveRequest) => void} ResolveCallback */
  18. /**
  19. * @typedef {Object} PossibleFileSystemError
  20. * @property {string=} code
  21. * @property {number=} errno
  22. * @property {string=} path
  23. * @property {string=} syscall
  24. */
  25. /**
  26. * @template T
  27. * @callback FileSystemCallback
  28. * @param {PossibleFileSystemError & Error | null} err
  29. * @param {T=} result
  30. */
  31. /**
  32. * @typedef {string | Buffer | URL} PathLike
  33. */
  34. /**
  35. * @typedef {PathLike | number} PathOrFileDescriptor
  36. */
  37. /**
  38. * @typedef {Object} ObjectEncodingOptions
  39. * @property {BufferEncoding | null | undefined} [encoding]
  40. */
  41. /** @typedef {function(NodeJS.ErrnoException | null, string=): void} StringCallback */
  42. /** @typedef {function(NodeJS.ErrnoException | null, Buffer=): void} BufferCallback */
  43. /** @typedef {function(NodeJS.ErrnoException | null, (string | Buffer)=): void} StringOrBufferCallback */
  44. /** @typedef {function(NodeJS.ErrnoException | null, IStats=): void} StatsCallback */
  45. /** @typedef {function(NodeJS.ErrnoException | null, IBigIntStats=): void} BigIntStatsCallback */
  46. /** @typedef {function(NodeJS.ErrnoException | null, (IStats | IBigIntStats)=): void} StatsOrBigIntStatsCallback */
  47. /** @typedef {function(NodeJS.ErrnoException | Error | null, JsonObject=): void} ReadJsonCallback */
  48. /** @typedef {function(NodeJS.ErrnoException | null, string[]=): void} ReaddirStringCallback */
  49. /** @typedef {function(NodeJS.ErrnoException | null, Buffer[]=): void} ReaddirBufferCallback */
  50. /** @typedef {function(NodeJS.ErrnoException | null, (string[] | Buffer[])=): void} ReaddirStringOrBufferCallback */
  51. /** @typedef {function(NodeJS.ErrnoException | null, Dirent[]=): void} ReaddirDirentCallback */
  52. /**
  53. * @template T
  54. * @typedef {Object} IStatsBase
  55. * @property {() => boolean} isFile
  56. * @property {() => boolean} isDirectory
  57. * @property {() => boolean} isBlockDevice
  58. * @property {() => boolean} isCharacterDevice
  59. * @property {() => boolean} isSymbolicLink
  60. * @property {() => boolean} isFIFO
  61. * @property {() => boolean} isSocket
  62. * @property {T} dev
  63. * @property {T} ino
  64. * @property {T} mode
  65. * @property {T} nlink
  66. * @property {T} uid
  67. * @property {T} gid
  68. * @property {T} rdev
  69. * @property {T} size
  70. * @property {T} blksize
  71. * @property {T} blocks
  72. * @property {T} atimeMs
  73. * @property {T} mtimeMs
  74. * @property {T} ctimeMs
  75. * @property {T} birthtimeMs
  76. * @property {Date} atime
  77. * @property {Date} mtime
  78. * @property {Date} ctime
  79. * @property {Date} birthtime
  80. */
  81. /**
  82. * @typedef {IStatsBase<number>} IStats
  83. */
  84. /**
  85. * @typedef {IStatsBase<bigint> & { atimeNs: bigint, mtimeNs: bigint, ctimeNs: bigint, birthtimeNs: bigint }} IBigIntStats
  86. */
  87. /**
  88. * @typedef {Object} Dirent
  89. * @property {() => boolean} isFile
  90. * @property {() => boolean} isDirectory
  91. * @property {() => boolean} isBlockDevice
  92. * @property {() => boolean} isCharacterDevice
  93. * @property {() => boolean} isSymbolicLink
  94. * @property {() => boolean} isFIFO
  95. * @property {() => boolean} isSocket
  96. * @property {string} name
  97. * @property {string} path
  98. */
  99. /**
  100. * @typedef {Object} StatOptions
  101. * @property {(boolean | undefined)=} bigint
  102. */
  103. /**
  104. * @typedef {Object} StatSyncOptions
  105. * @property {(boolean | undefined)=} bigint
  106. * @property {(boolean | undefined)=} throwIfNoEntry
  107. */
  108. /**
  109. * @typedef {{
  110. * (path: PathOrFileDescriptor, options: ({ encoding?: null | undefined, flag?: string | undefined } & import("events").Abortable) | undefined | null, callback: BufferCallback): void;
  111. * (path: PathOrFileDescriptor, options: ({ encoding: BufferEncoding, flag?: string | undefined } & import("events").Abortable) | BufferEncoding, callback: StringCallback): void;
  112. * (path: PathOrFileDescriptor, options: (ObjectEncodingOptions & { flag?: string | undefined } & import("events").Abortable) | BufferEncoding | undefined | null, callback: StringOrBufferCallback): void;
  113. * (path: PathOrFileDescriptor, callback: BufferCallback): void;
  114. * }} ReadFile
  115. */
  116. /**
  117. * @typedef {ObjectEncodingOptions | BufferEncoding | undefined | null} EncodingOption
  118. */
  119. /**
  120. * @typedef {'buffer'| { encoding: 'buffer' }} BufferEncodingOption
  121. */
  122. /**
  123. * @typedef {{
  124. * (path: PathOrFileDescriptor, options?: { encoding?: null | undefined, flag?: string | undefined } | null): Buffer;
  125. * (path: PathOrFileDescriptor, options: { encoding: BufferEncoding, flag?: string | undefined } | BufferEncoding): string;
  126. * (path: PathOrFileDescriptor, options?: (ObjectEncodingOptions & { flag?: string | undefined }) | BufferEncoding | null): string | Buffer;
  127. * }} ReadFileSync
  128. */
  129. /**
  130. * @typedef {{
  131. * (path: PathLike, options: { encoding: BufferEncoding | null, withFileTypes?: false | undefined, recursive?: boolean | undefined } | BufferEncoding | undefined | null, callback: ReaddirStringCallback): void;
  132. * (path: PathLike, options: { encoding: 'buffer', withFileTypes?: false | undefined, recursive?: boolean | undefined } | 'buffer', callback: ReaddirBufferCallback): void;
  133. * (path: PathLike, callback: ReaddirStringCallback): void;
  134. * (path: PathLike, options: (ObjectEncodingOptions & { withFileTypes?: false | undefined, recursive?: boolean | undefined }) | BufferEncoding | undefined | null, callback: ReaddirStringOrBufferCallback): void;
  135. * (path: PathLike, options: ObjectEncodingOptions & { withFileTypes: true, recursive?: boolean | undefined }, callback: ReaddirDirentCallback): void;
  136. * }} Readdir
  137. */
  138. /**
  139. * @typedef {{
  140. * (path: PathLike, options?: { encoding: BufferEncoding | null, withFileTypes?: false | undefined, recursive?: boolean | undefined } | BufferEncoding | null): string[];
  141. * (path: PathLike, options: { encoding: 'buffer', withFileTypes?: false | undefined, recursive?: boolean | undefined } | 'buffer'): Buffer[];
  142. * (path: PathLike, options?: (ObjectEncodingOptions & { withFileTypes?: false | undefined, recursive?: boolean | undefined }) | BufferEncoding | null): string[] | Buffer[];
  143. * (path: PathLike, options: ObjectEncodingOptions & { withFileTypes: true, recursive?: boolean | undefined }): Dirent[];
  144. * }} ReaddirSync
  145. /**
  146. * @typedef {function(PathOrFileDescriptor, ReadJsonCallback): void} ReadJson
  147. */
  148. /**
  149. * @typedef {function(PathOrFileDescriptor): JsonObject} ReadJsonSync
  150. */
  151. /**
  152. * @typedef {{
  153. * (path: PathLike, options: EncodingOption, callback: StringCallback): void;
  154. * (path: PathLike, options: BufferEncodingOption, callback: BufferCallback): void;
  155. * (path: PathLike, options: EncodingOption, callback: StringOrBufferCallback): void;
  156. * (path: PathLike, callback: StringCallback): void;
  157. * }} Readlink
  158. */
  159. /**
  160. * @typedef {{
  161. * (path: PathLike, options?: EncodingOption): string;
  162. * (path: PathLike, options: BufferEncodingOption): Buffer;
  163. * (path: PathLike, options?: EncodingOption): string | Buffer;
  164. * }} ReadlinkSync
  165. */
  166. /**
  167. * @typedef {{
  168. * (path: PathLike, callback: StatsCallback): void;
  169. * (path: PathLike, options: (StatOptions & { bigint?: false | undefined }) | undefined, callback: StatsCallback): void;
  170. * (path: PathLike, options: StatOptions & { bigint: true }, callback: BigIntStatsCallback): void;
  171. * (path: PathLike, options: StatOptions | undefined, callback: StatsOrBigIntStatsCallback): void;
  172. * }} LStat
  173. */
  174. /**
  175. * @typedef {{
  176. * (path: PathLike, options?: undefined): IStats;
  177. * (path: PathLike, options?: StatSyncOptions & { bigint?: false | undefined, throwIfNoEntry: false }): IStats | undefined;
  178. * (path: PathLike, options: StatSyncOptions & { bigint: true, throwIfNoEntry: false }): IBigIntStats | undefined;
  179. * (path: PathLike, options?: StatSyncOptions & { bigint?: false | undefined }): IStats;
  180. * (path: PathLike, options: StatSyncOptions & { bigint: true }): IBigIntStats;
  181. * (path: PathLike, options: StatSyncOptions & { bigint: boolean, throwIfNoEntry?: false | undefined }): IStats | IBigIntStats;
  182. * (path: PathLike, options?: StatSyncOptions): IStats | IBigIntStats | undefined;
  183. * }} LStatSync
  184. */
  185. /**
  186. * @typedef {{
  187. * (path: PathLike, callback: StatsCallback): void;
  188. * (path: PathLike, options: (StatOptions & { bigint?: false | undefined }) | undefined, callback: StatsCallback): void;
  189. * (path: PathLike, options: StatOptions & { bigint: true }, callback: BigIntStatsCallback): void;
  190. * (path: PathLike, options: StatOptions | undefined, callback: StatsOrBigIntStatsCallback): void;
  191. * }} Stat
  192. */
  193. /**
  194. * @typedef {{
  195. * (path: PathLike, options?: undefined): IStats;
  196. * (path: PathLike, options?: StatSyncOptions & { bigint?: false | undefined, throwIfNoEntry: false }): IStats | undefined;
  197. * (path: PathLike, options: StatSyncOptions & { bigint: true, throwIfNoEntry: false }): IBigIntStats | undefined;
  198. * (path: PathLike, options?: StatSyncOptions & { bigint?: false | undefined }): IStats;
  199. * (path: PathLike, options: StatSyncOptions & { bigint: true }): IBigIntStats;
  200. * (path: PathLike, options: StatSyncOptions & { bigint: boolean, throwIfNoEntry?: false | undefined }): IStats | IBigIntStats;
  201. * (path: PathLike, options?: StatSyncOptions): IStats | IBigIntStats | undefined;
  202. * }} StatSync
  203. */
  204. /**
  205. * @typedef {{
  206. * (path: PathLike, options: EncodingOption, callback: StringCallback): void;
  207. * (path: PathLike, options: BufferEncodingOption, callback: BufferCallback): void;
  208. * (path: PathLike, options: EncodingOption, callback: StringOrBufferCallback): void;
  209. * (path: PathLike, callback: StringCallback): void;
  210. * }} RealPath
  211. */
  212. /**
  213. * @typedef {{
  214. * (path: PathLike, options?: EncodingOption): string;
  215. * (path: PathLike, options: BufferEncodingOption): Buffer;
  216. * (path: PathLike, options?: EncodingOption): string | Buffer;
  217. * }} RealPathSync
  218. */
  219. /**
  220. * @typedef {Object} FileSystem
  221. * @property {ReadFile} readFile
  222. * @property {Readdir} readdir
  223. * @property {ReadJson=} readJson
  224. * @property {Readlink} readlink
  225. * @property {LStat=} lstat
  226. * @property {Stat} stat
  227. * @property {RealPath=} realpath
  228. */
  229. /**
  230. * @typedef {Object} SyncFileSystem
  231. * @property {ReadFileSync} readFileSync
  232. * @property {ReaddirSync} readdirSync
  233. * @property {ReadJsonSync=} readJsonSync
  234. * @property {ReadlinkSync} readlinkSync
  235. * @property {LStatSync=} lstatSync
  236. * @property {StatSync} statSync
  237. * @property {RealPathSync=} realpathSync
  238. */
  239. /**
  240. * @typedef {Object} ParsedIdentifier
  241. * @property {string} request
  242. * @property {string} query
  243. * @property {string} fragment
  244. * @property {boolean} directory
  245. * @property {boolean} module
  246. * @property {boolean} file
  247. * @property {boolean} internal
  248. */
  249. /** @typedef {string | number | boolean | null} JsonPrimitive */
  250. /** @typedef {JsonValue[]} JsonArray */
  251. /** @typedef {JsonPrimitive | JsonObject | JsonArray} JsonValue */
  252. /** @typedef {{[Key in string]: JsonValue} & {[Key in string]?: JsonValue | undefined}} JsonObject */
  253. /**
  254. * @typedef {Object} BaseResolveRequest
  255. * @property {string | false} path
  256. * @property {object=} context
  257. * @property {string=} descriptionFilePath
  258. * @property {string=} descriptionFileRoot
  259. * @property {JsonObject=} descriptionFileData
  260. * @property {string=} relativePath
  261. * @property {boolean=} ignoreSymlinks
  262. * @property {boolean=} fullySpecified
  263. * @property {string=} __innerRequest
  264. * @property {string=} __innerRequest_request
  265. * @property {string=} __innerRequest_relativePath
  266. */
  267. /** @typedef {BaseResolveRequest & Partial<ParsedIdentifier>} ResolveRequest */
  268. /**
  269. * String with special formatting
  270. * @typedef {string} StackEntry
  271. */
  272. /**
  273. * @template T
  274. * @typedef {{ add: (item: T) => void }} WriteOnlySet
  275. */
  276. /** @typedef {(function (ResolveRequest): void)} ResolveContextYield */
  277. /**
  278. * Resolve context
  279. * @typedef {Object} ResolveContext
  280. * @property {WriteOnlySet<string>=} contextDependencies
  281. * @property {WriteOnlySet<string>=} fileDependencies files that was found on file system
  282. * @property {WriteOnlySet<string>=} missingDependencies dependencies that was not found on file system
  283. * @property {Set<StackEntry>=} stack set of hooks' calls. For instance, `resolve → parsedResolve → describedResolve`,
  284. * @property {(function(string): void)=} log log function
  285. * @property {ResolveContextYield=} yield yield result, if provided plugins can return several results
  286. */
  287. /** @typedef {AsyncSeriesBailHook<[ResolveRequest, ResolveContext], ResolveRequest | null>} ResolveStepHook */
  288. /**
  289. * @typedef {Object} KnownHooks
  290. * @property {SyncHook<[ResolveStepHook, ResolveRequest], void>} resolveStep
  291. * @property {SyncHook<[ResolveRequest, Error]>} noResolve
  292. * @property {ResolveStepHook} resolve
  293. * @property {AsyncSeriesHook<[ResolveRequest, ResolveContext]>} result
  294. */
  295. /**
  296. * @typedef {{[key: string]: ResolveStepHook}} EnsuredHooks
  297. */
  298. /**
  299. * @param {string} str input string
  300. * @returns {string} in camel case
  301. */
  302. function toCamelCase(str) {
  303. return str.replace(/-([a-z])/g, str => str.slice(1).toUpperCase());
  304. }
  305. class Resolver {
  306. /**
  307. * @param {ResolveStepHook} hook hook
  308. * @param {ResolveRequest} request request
  309. * @returns {StackEntry} stack entry
  310. */
  311. static createStackEntry(hook, request) {
  312. return (
  313. hook.name +
  314. ": (" +
  315. request.path +
  316. ") " +
  317. (request.request || "") +
  318. (request.query || "") +
  319. (request.fragment || "") +
  320. (request.directory ? " directory" : "") +
  321. (request.module ? " module" : "")
  322. );
  323. }
  324. /**
  325. * @param {FileSystem} fileSystem a filesystem
  326. * @param {ResolveOptions} options options
  327. */
  328. constructor(fileSystem, options) {
  329. this.fileSystem = fileSystem;
  330. this.options = options;
  331. /** @type {KnownHooks} */
  332. this.hooks = {
  333. resolveStep: new SyncHook(["hook", "request"], "resolveStep"),
  334. noResolve: new SyncHook(["request", "error"], "noResolve"),
  335. resolve: new AsyncSeriesBailHook(
  336. ["request", "resolveContext"],
  337. "resolve"
  338. ),
  339. result: new AsyncSeriesHook(["result", "resolveContext"], "result")
  340. };
  341. }
  342. /**
  343. * @param {string | ResolveStepHook} name hook name or hook itself
  344. * @returns {ResolveStepHook} the hook
  345. */
  346. ensureHook(name) {
  347. if (typeof name !== "string") {
  348. return name;
  349. }
  350. name = toCamelCase(name);
  351. if (/^before/.test(name)) {
  352. return /** @type {ResolveStepHook} */ (
  353. this.ensureHook(name[6].toLowerCase() + name.slice(7)).withOptions({
  354. stage: -10
  355. })
  356. );
  357. }
  358. if (/^after/.test(name)) {
  359. return /** @type {ResolveStepHook} */ (
  360. this.ensureHook(name[5].toLowerCase() + name.slice(6)).withOptions({
  361. stage: 10
  362. })
  363. );
  364. }
  365. /** @type {ResolveStepHook} */
  366. const hook = /** @type {KnownHooks & EnsuredHooks} */ (this.hooks)[name];
  367. if (!hook) {
  368. /** @type {KnownHooks & EnsuredHooks} */
  369. (this.hooks)[name] = new AsyncSeriesBailHook(
  370. ["request", "resolveContext"],
  371. name
  372. );
  373. return /** @type {KnownHooks & EnsuredHooks} */ (this.hooks)[name];
  374. }
  375. return hook;
  376. }
  377. /**
  378. * @param {string | ResolveStepHook} name hook name or hook itself
  379. * @returns {ResolveStepHook} the hook
  380. */
  381. getHook(name) {
  382. if (typeof name !== "string") {
  383. return name;
  384. }
  385. name = toCamelCase(name);
  386. if (/^before/.test(name)) {
  387. return /** @type {ResolveStepHook} */ (
  388. this.getHook(name[6].toLowerCase() + name.slice(7)).withOptions({
  389. stage: -10
  390. })
  391. );
  392. }
  393. if (/^after/.test(name)) {
  394. return /** @type {ResolveStepHook} */ (
  395. this.getHook(name[5].toLowerCase() + name.slice(6)).withOptions({
  396. stage: 10
  397. })
  398. );
  399. }
  400. /** @type {ResolveStepHook} */
  401. const hook = /** @type {KnownHooks & EnsuredHooks} */ (this.hooks)[name];
  402. if (!hook) {
  403. throw new Error(`Hook ${name} doesn't exist`);
  404. }
  405. return hook;
  406. }
  407. /**
  408. * @param {object} context context information object
  409. * @param {string} path context path
  410. * @param {string} request request string
  411. * @returns {string | false} result
  412. */
  413. resolveSync(context, path, request) {
  414. /** @type {Error | null | undefined} */
  415. let err = undefined;
  416. /** @type {string | false | undefined} */
  417. let result = undefined;
  418. let sync = false;
  419. this.resolve(context, path, request, {}, (e, r) => {
  420. err = e;
  421. result = r;
  422. sync = true;
  423. });
  424. if (!sync) {
  425. throw new Error(
  426. "Cannot 'resolveSync' because the fileSystem is not sync. Use 'resolve'!"
  427. );
  428. }
  429. if (err) throw err;
  430. if (result === undefined) throw new Error("No result");
  431. return result;
  432. }
  433. /**
  434. * @param {object} context context information object
  435. * @param {string} path context path
  436. * @param {string} request request string
  437. * @param {ResolveContext} resolveContext resolve context
  438. * @param {ResolveCallback} callback callback function
  439. * @returns {void}
  440. */
  441. resolve(context, path, request, resolveContext, callback) {
  442. if (!context || typeof context !== "object")
  443. return callback(new Error("context argument is not an object"));
  444. if (typeof path !== "string")
  445. return callback(new Error("path argument is not a string"));
  446. if (typeof request !== "string")
  447. return callback(new Error("request argument is not a string"));
  448. if (!resolveContext)
  449. return callback(new Error("resolveContext argument is not set"));
  450. /** @type {ResolveRequest} */
  451. const obj = {
  452. context: context,
  453. path: path,
  454. request: request
  455. };
  456. /** @type {ResolveContextYield | undefined} */
  457. let yield_;
  458. let yieldCalled = false;
  459. /** @type {ResolveContextYield | undefined} */
  460. let finishYield;
  461. if (typeof resolveContext.yield === "function") {
  462. const old = resolveContext.yield;
  463. /**
  464. * @param {ResolveRequest} obj object
  465. */
  466. yield_ = obj => {
  467. old(obj);
  468. yieldCalled = true;
  469. };
  470. /**
  471. * @param {ResolveRequest} result result
  472. * @returns {void}
  473. */
  474. finishYield = result => {
  475. if (result) {
  476. /** @type {ResolveContextYield} */ (yield_)(result);
  477. }
  478. callback(null);
  479. };
  480. }
  481. const message = `resolve '${request}' in '${path}'`;
  482. /**
  483. * @param {ResolveRequest} result result
  484. * @returns {void}
  485. */
  486. const finishResolved = result => {
  487. return callback(
  488. null,
  489. result.path === false
  490. ? false
  491. : `${result.path.replace(/#/g, "\0#")}${
  492. result.query ? result.query.replace(/#/g, "\0#") : ""
  493. }${result.fragment || ""}`,
  494. result
  495. );
  496. };
  497. /**
  498. * @param {string[]} log logs
  499. * @returns {void}
  500. */
  501. const finishWithoutResolve = log => {
  502. /**
  503. * @type {ErrorWithDetail}
  504. */
  505. const error = new Error("Can't " + message);
  506. error.details = log.join("\n");
  507. this.hooks.noResolve.call(obj, error);
  508. return callback(error);
  509. };
  510. if (resolveContext.log) {
  511. // We need log anyway to capture it in case of an error
  512. const parentLog = resolveContext.log;
  513. /** @type {string[]} */
  514. const log = [];
  515. return this.doResolve(
  516. this.hooks.resolve,
  517. obj,
  518. message,
  519. {
  520. log: msg => {
  521. parentLog(msg);
  522. log.push(msg);
  523. },
  524. yield: yield_,
  525. fileDependencies: resolveContext.fileDependencies,
  526. contextDependencies: resolveContext.contextDependencies,
  527. missingDependencies: resolveContext.missingDependencies,
  528. stack: resolveContext.stack
  529. },
  530. (err, result) => {
  531. if (err) return callback(err);
  532. if (yieldCalled || (result && yield_)) {
  533. return /** @type {ResolveContextYield} */ (finishYield)(
  534. /** @type {ResolveRequest} */ (result)
  535. );
  536. }
  537. if (result) return finishResolved(result);
  538. return finishWithoutResolve(log);
  539. }
  540. );
  541. } else {
  542. // Try to resolve assuming there is no error
  543. // We don't log stuff in this case
  544. return this.doResolve(
  545. this.hooks.resolve,
  546. obj,
  547. message,
  548. {
  549. log: undefined,
  550. yield: yield_,
  551. fileDependencies: resolveContext.fileDependencies,
  552. contextDependencies: resolveContext.contextDependencies,
  553. missingDependencies: resolveContext.missingDependencies,
  554. stack: resolveContext.stack
  555. },
  556. (err, result) => {
  557. if (err) return callback(err);
  558. if (yieldCalled || (result && yield_)) {
  559. return /** @type {ResolveContextYield} */ (finishYield)(
  560. /** @type {ResolveRequest} */ (result)
  561. );
  562. }
  563. if (result) return finishResolved(result);
  564. // log is missing for the error details
  565. // so we redo the resolving for the log info
  566. // this is more expensive to the success case
  567. // is assumed by default
  568. /** @type {string[]} */
  569. const log = [];
  570. return this.doResolve(
  571. this.hooks.resolve,
  572. obj,
  573. message,
  574. {
  575. log: msg => log.push(msg),
  576. yield: yield_,
  577. stack: resolveContext.stack
  578. },
  579. (err, result) => {
  580. if (err) return callback(err);
  581. // In a case that there is a race condition and yield will be called
  582. if (yieldCalled || (result && yield_)) {
  583. return /** @type {ResolveContextYield} */ (finishYield)(
  584. /** @type {ResolveRequest} */ (result)
  585. );
  586. }
  587. return finishWithoutResolve(log);
  588. }
  589. );
  590. }
  591. );
  592. }
  593. }
  594. /**
  595. * @param {ResolveStepHook} hook hook
  596. * @param {ResolveRequest} request request
  597. * @param {null|string} message string
  598. * @param {ResolveContext} resolveContext resolver context
  599. * @param {(err?: null|Error, result?: ResolveRequest) => void} callback callback
  600. * @returns {void}
  601. */
  602. doResolve(hook, request, message, resolveContext, callback) {
  603. const stackEntry = Resolver.createStackEntry(hook, request);
  604. /** @type {Set<string> | undefined} */
  605. let newStack;
  606. if (resolveContext.stack) {
  607. newStack = new Set(resolveContext.stack);
  608. if (resolveContext.stack.has(stackEntry)) {
  609. /**
  610. * Prevent recursion
  611. * @type {Error & {recursion?: boolean}}
  612. */
  613. const recursionError = new Error(
  614. "Recursion in resolving\nStack:\n " +
  615. Array.from(newStack).join("\n ")
  616. );
  617. recursionError.recursion = true;
  618. if (resolveContext.log)
  619. resolveContext.log("abort resolving because of recursion");
  620. return callback(recursionError);
  621. }
  622. newStack.add(stackEntry);
  623. } else {
  624. // creating a set with new Set([item])
  625. // allocates a new array that has to be garbage collected
  626. // this is an EXTREMELY hot path, so let's avoid it
  627. newStack = new Set();
  628. newStack.add(stackEntry);
  629. }
  630. this.hooks.resolveStep.call(hook, request);
  631. if (hook.isUsed()) {
  632. const innerContext = createInnerContext(
  633. {
  634. log: resolveContext.log,
  635. yield: resolveContext.yield,
  636. fileDependencies: resolveContext.fileDependencies,
  637. contextDependencies: resolveContext.contextDependencies,
  638. missingDependencies: resolveContext.missingDependencies,
  639. stack: newStack
  640. },
  641. message
  642. );
  643. return hook.callAsync(request, innerContext, (err, result) => {
  644. if (err) return callback(err);
  645. if (result) return callback(null, result);
  646. callback();
  647. });
  648. } else {
  649. callback();
  650. }
  651. }
  652. /**
  653. * @param {string} identifier identifier
  654. * @returns {ParsedIdentifier} parsed identifier
  655. */
  656. parse(identifier) {
  657. const part = {
  658. request: "",
  659. query: "",
  660. fragment: "",
  661. module: false,
  662. directory: false,
  663. file: false,
  664. internal: false
  665. };
  666. const parsedIdentifier = parseIdentifier(identifier);
  667. if (!parsedIdentifier) return part;
  668. [part.request, part.query, part.fragment] = parsedIdentifier;
  669. if (part.request.length > 0) {
  670. part.internal = this.isPrivate(identifier);
  671. part.module = this.isModule(part.request);
  672. part.directory = this.isDirectory(part.request);
  673. if (part.directory) {
  674. part.request = part.request.slice(0, -1);
  675. }
  676. }
  677. return part;
  678. }
  679. /**
  680. * @param {string} path path
  681. * @returns {boolean} true, if the path is a module
  682. */
  683. isModule(path) {
  684. return getType(path) === PathType.Normal;
  685. }
  686. /**
  687. * @param {string} path path
  688. * @returns {boolean} true, if the path is private
  689. */
  690. isPrivate(path) {
  691. return getType(path) === PathType.Internal;
  692. }
  693. /**
  694. * @param {string} path a path
  695. * @returns {boolean} true, if the path is a directory path
  696. */
  697. isDirectory(path) {
  698. return path.endsWith("/");
  699. }
  700. /**
  701. * @param {string} path path
  702. * @param {string} request request
  703. * @returns {string} joined path
  704. */
  705. join(path, request) {
  706. return join(path, request);
  707. }
  708. /**
  709. * @param {string} path path
  710. * @returns {string} normalized path
  711. */
  712. normalize(path) {
  713. return normalize(path);
  714. }
  715. }
  716. module.exports = Resolver;