comparators.js 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522
  1. /*
  2. MIT License http://www.opensource.org/licenses/mit-license.php
  3. Author Tobias Koppers @sokra
  4. */
  5. "use strict";
  6. const { compareRuntime } = require("./runtime");
  7. /** @typedef {import("../Chunk")} Chunk */
  8. /** @typedef {import("../Chunk").ChunkId} ChunkId */
  9. /** @typedef {import("../ChunkGraph")} ChunkGraph */
  10. /** @typedef {import("../ChunkGraph").ModuleId} ModuleId */
  11. /** @typedef {import("../ChunkGroup")} ChunkGroup */
  12. /** @typedef {import("../Dependency").DependencyLocation} DependencyLocation */
  13. /** @typedef {import("../Module")} Module */
  14. /** @typedef {import("../ModuleGraph")} ModuleGraph */
  15. /**
  16. * @template T
  17. * @typedef {function(T, T): -1|0|1} Comparator
  18. */
  19. /**
  20. * @template TArg
  21. * @template T
  22. * @typedef {function(TArg, T, T): -1|0|1} RawParameterizedComparator
  23. */
  24. /**
  25. * @template TArg
  26. * @template T
  27. * @typedef {function(TArg): Comparator<T>} ParameterizedComparator
  28. */
  29. /**
  30. * @template T
  31. * @param {RawParameterizedComparator<any, T>} fn comparator with argument
  32. * @returns {ParameterizedComparator<any, T>} comparator
  33. */
  34. const createCachedParameterizedComparator = fn => {
  35. /** @type {WeakMap<object, Comparator<T>>} */
  36. const map = new WeakMap();
  37. return arg => {
  38. const cachedResult = map.get(arg);
  39. if (cachedResult !== undefined) return cachedResult;
  40. /**
  41. * @param {T} a first item
  42. * @param {T} b second item
  43. * @returns {-1|0|1} compare result
  44. */
  45. const result = fn.bind(null, arg);
  46. map.set(arg, result);
  47. return result;
  48. };
  49. };
  50. /**
  51. * @param {Chunk} a chunk
  52. * @param {Chunk} b chunk
  53. * @returns {-1|0|1} compare result
  54. */
  55. module.exports.compareChunksById = (a, b) =>
  56. compareIds(/** @type {ChunkId} */ (a.id), /** @type {ChunkId} */ (b.id));
  57. /**
  58. * @param {Module} a module
  59. * @param {Module} b module
  60. * @returns {-1|0|1} compare result
  61. */
  62. module.exports.compareModulesByIdentifier = (a, b) =>
  63. compareIds(a.identifier(), b.identifier());
  64. /**
  65. * @param {ChunkGraph} chunkGraph the chunk graph
  66. * @param {Module} a module
  67. * @param {Module} b module
  68. * @returns {-1|0|1} compare result
  69. */
  70. const compareModulesById = (chunkGraph, a, b) =>
  71. compareIds(
  72. /** @type {ModuleId} */ (chunkGraph.getModuleId(a)),
  73. /** @type {ModuleId} */ (chunkGraph.getModuleId(b))
  74. );
  75. /** @type {ParameterizedComparator<ChunkGraph, Module>} */
  76. module.exports.compareModulesById =
  77. createCachedParameterizedComparator(compareModulesById);
  78. /**
  79. * @param {number} a number
  80. * @param {number} b number
  81. * @returns {-1|0|1} compare result
  82. */
  83. const compareNumbers = (a, b) => {
  84. if (typeof a !== typeof b) {
  85. return typeof a < typeof b ? -1 : 1;
  86. }
  87. if (a < b) return -1;
  88. if (a > b) return 1;
  89. return 0;
  90. };
  91. module.exports.compareNumbers = compareNumbers;
  92. /**
  93. * @param {string} a string
  94. * @param {string} b string
  95. * @returns {-1|0|1} compare result
  96. */
  97. const compareStringsNumeric = (a, b) => {
  98. const aLength = a.length;
  99. const bLength = b.length;
  100. let aChar = 0;
  101. let bChar = 0;
  102. let aIsDigit = false;
  103. let bIsDigit = false;
  104. let i = 0;
  105. let j = 0;
  106. while (i < aLength && j < bLength) {
  107. aChar = a.charCodeAt(i);
  108. bChar = b.charCodeAt(j);
  109. aIsDigit = aChar >= 48 && aChar <= 57;
  110. bIsDigit = bChar >= 48 && bChar <= 57;
  111. if (!aIsDigit && !bIsDigit) {
  112. if (aChar < bChar) return -1;
  113. if (aChar > bChar) return 1;
  114. i++;
  115. j++;
  116. } else if (aIsDigit && !bIsDigit) {
  117. // This segment of a is shorter than in b
  118. return 1;
  119. } else if (!aIsDigit && bIsDigit) {
  120. // This segment of b is shorter than in a
  121. return -1;
  122. } else {
  123. let aNumber = aChar - 48;
  124. let bNumber = bChar - 48;
  125. while (++i < aLength) {
  126. aChar = a.charCodeAt(i);
  127. if (aChar < 48 || aChar > 57) break;
  128. aNumber = aNumber * 10 + aChar - 48;
  129. }
  130. while (++j < bLength) {
  131. bChar = b.charCodeAt(j);
  132. if (bChar < 48 || bChar > 57) break;
  133. bNumber = bNumber * 10 + bChar - 48;
  134. }
  135. if (aNumber < bNumber) return -1;
  136. if (aNumber > bNumber) return 1;
  137. }
  138. }
  139. if (j < bLength) {
  140. // a is shorter than b
  141. bChar = b.charCodeAt(j);
  142. bIsDigit = bChar >= 48 && bChar <= 57;
  143. return bIsDigit ? -1 : 1;
  144. }
  145. if (i < aLength) {
  146. // b is shorter than a
  147. aChar = a.charCodeAt(i);
  148. aIsDigit = aChar >= 48 && aChar <= 57;
  149. return aIsDigit ? 1 : -1;
  150. }
  151. return 0;
  152. };
  153. module.exports.compareStringsNumeric = compareStringsNumeric;
  154. /**
  155. * @param {ModuleGraph} moduleGraph the module graph
  156. * @param {Module} a module
  157. * @param {Module} b module
  158. * @returns {-1|0|1} compare result
  159. */
  160. const compareModulesByPostOrderIndexOrIdentifier = (moduleGraph, a, b) => {
  161. const cmp = compareNumbers(
  162. /** @type {number} */ (moduleGraph.getPostOrderIndex(a)),
  163. /** @type {number} */ (moduleGraph.getPostOrderIndex(b))
  164. );
  165. if (cmp !== 0) return cmp;
  166. return compareIds(a.identifier(), b.identifier());
  167. };
  168. /** @type {ParameterizedComparator<ModuleGraph, Module>} */
  169. module.exports.compareModulesByPostOrderIndexOrIdentifier =
  170. createCachedParameterizedComparator(
  171. compareModulesByPostOrderIndexOrIdentifier
  172. );
  173. /**
  174. * @param {ModuleGraph} moduleGraph the module graph
  175. * @param {Module} a module
  176. * @param {Module} b module
  177. * @returns {-1|0|1} compare result
  178. */
  179. const compareModulesByPreOrderIndexOrIdentifier = (moduleGraph, a, b) => {
  180. const cmp = compareNumbers(
  181. /** @type {number} */ (moduleGraph.getPreOrderIndex(a)),
  182. /** @type {number} */ (moduleGraph.getPreOrderIndex(b))
  183. );
  184. if (cmp !== 0) return cmp;
  185. return compareIds(a.identifier(), b.identifier());
  186. };
  187. /** @type {ParameterizedComparator<ModuleGraph, Module>} */
  188. module.exports.compareModulesByPreOrderIndexOrIdentifier =
  189. createCachedParameterizedComparator(
  190. compareModulesByPreOrderIndexOrIdentifier
  191. );
  192. /**
  193. * @param {ChunkGraph} chunkGraph the chunk graph
  194. * @param {Module} a module
  195. * @param {Module} b module
  196. * @returns {-1|0|1} compare result
  197. */
  198. const compareModulesByIdOrIdentifier = (chunkGraph, a, b) => {
  199. const cmp = compareIds(
  200. /** @type {ModuleId} */ (chunkGraph.getModuleId(a)),
  201. /** @type {ModuleId} */ (chunkGraph.getModuleId(b))
  202. );
  203. if (cmp !== 0) return cmp;
  204. return compareIds(a.identifier(), b.identifier());
  205. };
  206. /** @type {ParameterizedComparator<ChunkGraph, Module>} */
  207. module.exports.compareModulesByIdOrIdentifier =
  208. createCachedParameterizedComparator(compareModulesByIdOrIdentifier);
  209. /**
  210. * @param {ChunkGraph} chunkGraph the chunk graph
  211. * @param {Chunk} a chunk
  212. * @param {Chunk} b chunk
  213. * @returns {-1|0|1} compare result
  214. */
  215. const compareChunks = (chunkGraph, a, b) => chunkGraph.compareChunks(a, b);
  216. /** @type {ParameterizedComparator<ChunkGraph, Chunk>} */
  217. module.exports.compareChunks =
  218. createCachedParameterizedComparator(compareChunks);
  219. /**
  220. * @param {string|number} a first id
  221. * @param {string|number} b second id
  222. * @returns {-1|0|1} compare result
  223. */
  224. const compareIds = (a, b) => {
  225. if (typeof a !== typeof b) {
  226. return typeof a < typeof b ? -1 : 1;
  227. }
  228. if (a < b) return -1;
  229. if (a > b) return 1;
  230. return 0;
  231. };
  232. module.exports.compareIds = compareIds;
  233. /**
  234. * @param {string} a first string
  235. * @param {string} b second string
  236. * @returns {-1|0|1} compare result
  237. */
  238. const compareStrings = (a, b) => {
  239. if (a < b) return -1;
  240. if (a > b) return 1;
  241. return 0;
  242. };
  243. module.exports.compareStrings = compareStrings;
  244. /**
  245. * @param {ChunkGroup} a first chunk group
  246. * @param {ChunkGroup} b second chunk group
  247. * @returns {-1|0|1} compare result
  248. */
  249. const compareChunkGroupsByIndex = (a, b) =>
  250. /** @type {number} */ (a.index) < /** @type {number} */ (b.index) ? -1 : 1;
  251. module.exports.compareChunkGroupsByIndex = compareChunkGroupsByIndex;
  252. /**
  253. * @template K1 {Object}
  254. * @template K2
  255. * @template T
  256. */
  257. class TwoKeyWeakMap {
  258. constructor() {
  259. /**
  260. * @private
  261. * @type {WeakMap<any, WeakMap<any, T | undefined>>}
  262. */
  263. this._map = new WeakMap();
  264. }
  265. /**
  266. * @param {K1} key1 first key
  267. * @param {K2} key2 second key
  268. * @returns {T | undefined} value
  269. */
  270. get(key1, key2) {
  271. const childMap = this._map.get(key1);
  272. if (childMap === undefined) {
  273. return;
  274. }
  275. return childMap.get(key2);
  276. }
  277. /**
  278. * @param {K1} key1 first key
  279. * @param {K2} key2 second key
  280. * @param {T | undefined} value new value
  281. * @returns {void}
  282. */
  283. set(key1, key2, value) {
  284. let childMap = this._map.get(key1);
  285. if (childMap === undefined) {
  286. childMap = new WeakMap();
  287. this._map.set(key1, childMap);
  288. }
  289. childMap.set(key2, value);
  290. }
  291. }
  292. /** @type {TwoKeyWeakMap<Comparator<any>, Comparator<any>, Comparator<any>>}} */
  293. const concatComparatorsCache = new TwoKeyWeakMap();
  294. /**
  295. * @template T
  296. * @param {Comparator<T>} c1 comparator
  297. * @param {Comparator<T>} c2 comparator
  298. * @param {Comparator<T>[]} cRest comparators
  299. * @returns {Comparator<T>} comparator
  300. */
  301. const concatComparators = (c1, c2, ...cRest) => {
  302. if (cRest.length > 0) {
  303. const [c3, ...cRest2] = cRest;
  304. return concatComparators(c1, concatComparators(c2, c3, ...cRest2));
  305. }
  306. const cacheEntry = /** @type {Comparator<T>} */ (
  307. concatComparatorsCache.get(c1, c2)
  308. );
  309. if (cacheEntry !== undefined) return cacheEntry;
  310. /**
  311. * @param {T} a first value
  312. * @param {T} b second value
  313. * @returns {-1|0|1} compare result
  314. */
  315. const result = (a, b) => {
  316. const res = c1(a, b);
  317. if (res !== 0) return res;
  318. return c2(a, b);
  319. };
  320. concatComparatorsCache.set(c1, c2, result);
  321. return result;
  322. };
  323. module.exports.concatComparators = concatComparators;
  324. /**
  325. * @template A, B
  326. * @typedef {(input: A) => B | undefined | null} Selector
  327. */
  328. /** @type {TwoKeyWeakMap<Selector<any, any>, Comparator<any>, Comparator<any>>}} */
  329. const compareSelectCache = new TwoKeyWeakMap();
  330. /**
  331. * @template T
  332. * @template R
  333. * @param {Selector<T, R>} getter getter for value
  334. * @param {Comparator<R>} comparator comparator
  335. * @returns {Comparator<T>} comparator
  336. */
  337. const compareSelect = (getter, comparator) => {
  338. const cacheEntry = compareSelectCache.get(getter, comparator);
  339. if (cacheEntry !== undefined) return cacheEntry;
  340. /**
  341. * @param {T} a first value
  342. * @param {T} b second value
  343. * @returns {-1|0|1} compare result
  344. */
  345. const result = (a, b) => {
  346. const aValue = getter(a);
  347. const bValue = getter(b);
  348. if (aValue !== undefined && aValue !== null) {
  349. if (bValue !== undefined && bValue !== null) {
  350. return comparator(aValue, bValue);
  351. }
  352. return -1;
  353. }
  354. if (bValue !== undefined && bValue !== null) {
  355. return 1;
  356. }
  357. return 0;
  358. };
  359. compareSelectCache.set(getter, comparator, result);
  360. return result;
  361. };
  362. module.exports.compareSelect = compareSelect;
  363. /** @type {WeakMap<Comparator<any>, Comparator<Iterable<any>>>} */
  364. const compareIteratorsCache = new WeakMap();
  365. /**
  366. * @template T
  367. * @param {Comparator<T>} elementComparator comparator for elements
  368. * @returns {Comparator<Iterable<T>>} comparator for iterables of elements
  369. */
  370. const compareIterables = elementComparator => {
  371. const cacheEntry = compareIteratorsCache.get(elementComparator);
  372. if (cacheEntry !== undefined) return cacheEntry;
  373. /**
  374. * @param {Iterable<T>} a first value
  375. * @param {Iterable<T>} b second value
  376. * @returns {-1|0|1} compare result
  377. */
  378. const result = (a, b) => {
  379. const aI = a[Symbol.iterator]();
  380. const bI = b[Symbol.iterator]();
  381. while (true) {
  382. const aItem = aI.next();
  383. const bItem = bI.next();
  384. if (aItem.done) {
  385. return bItem.done ? 0 : -1;
  386. } else if (bItem.done) {
  387. return 1;
  388. }
  389. const res = elementComparator(aItem.value, bItem.value);
  390. if (res !== 0) return res;
  391. }
  392. };
  393. compareIteratorsCache.set(elementComparator, result);
  394. return result;
  395. };
  396. module.exports.compareIterables = compareIterables;
  397. // TODO this is no longer needed when minimum node.js version is >= 12
  398. // since these versions ship with a stable sort function
  399. /**
  400. * @template T
  401. * @param {Iterable<T>} iterable original ordered list
  402. * @returns {Comparator<T>} comparator
  403. */
  404. module.exports.keepOriginalOrder = iterable => {
  405. /** @type {Map<T, number>} */
  406. const map = new Map();
  407. let i = 0;
  408. for (const item of iterable) {
  409. map.set(item, i++);
  410. }
  411. return (a, b) =>
  412. compareNumbers(
  413. /** @type {number} */ (map.get(a)),
  414. /** @type {number} */ (map.get(b))
  415. );
  416. };
  417. /**
  418. * @param {ChunkGraph} chunkGraph the chunk graph
  419. * @returns {Comparator<Chunk>} comparator
  420. */
  421. module.exports.compareChunksNatural = chunkGraph => {
  422. const cmpFn = module.exports.compareModulesById(chunkGraph);
  423. const cmpIterableFn = compareIterables(cmpFn);
  424. return concatComparators(
  425. compareSelect(
  426. chunk => /** @type {string|number} */ (chunk.name),
  427. compareIds
  428. ),
  429. compareSelect(chunk => chunk.runtime, compareRuntime),
  430. compareSelect(
  431. /**
  432. * @param {Chunk} chunk a chunk
  433. * @returns {Iterable<Module>} modules
  434. */
  435. chunk => chunkGraph.getOrderedChunkModulesIterable(chunk, cmpFn),
  436. cmpIterableFn
  437. )
  438. );
  439. };
  440. /**
  441. * Compare two locations
  442. * @param {DependencyLocation} a A location node
  443. * @param {DependencyLocation} b A location node
  444. * @returns {-1|0|1} sorting comparator value
  445. */
  446. module.exports.compareLocations = (a, b) => {
  447. const isObjectA = typeof a === "object" && a !== null;
  448. const isObjectB = typeof b === "object" && b !== null;
  449. if (!isObjectA || !isObjectB) {
  450. if (isObjectA) return 1;
  451. if (isObjectB) return -1;
  452. return 0;
  453. }
  454. if ("start" in a) {
  455. if ("start" in b) {
  456. const ap = a.start;
  457. const bp = b.start;
  458. if (ap.line < bp.line) return -1;
  459. if (ap.line > bp.line) return 1;
  460. if (/** @type {number} */ (ap.column) < /** @type {number} */ (bp.column))
  461. return -1;
  462. if (/** @type {number} */ (ap.column) > /** @type {number} */ (bp.column))
  463. return 1;
  464. } else return -1;
  465. } else if ("start" in b) return 1;
  466. if ("name" in a) {
  467. if ("name" in b) {
  468. if (a.name < b.name) return -1;
  469. if (a.name > b.name) return 1;
  470. } else return -1;
  471. } else if ("name" in b) return 1;
  472. if ("index" in a) {
  473. if ("index" in b) {
  474. if (/** @type {number} */ (a.index) < /** @type {number} */ (b.index))
  475. return -1;
  476. if (/** @type {number} */ (a.index) > /** @type {number} */ (b.index))
  477. return 1;
  478. } else return -1;
  479. } else if ("index" in b) return 1;
  480. return 0;
  481. };