comparators.js 12 KB

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