comparators.js 14 KB

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