index.js 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493
  1. 'use strict';
  2. /* globals Symbol: false, Uint8Array: false, WeakMap: false */
  3. /*!
  4. * deep-eql
  5. * Copyright(c) 2013 Jake Luer <jake@alogicalparadox.com>
  6. * MIT Licensed
  7. */
  8. var type = require('type-detect');
  9. function FakeMap() {
  10. this._key = 'chai/deep-eql__' + Math.random() + Date.now();
  11. }
  12. FakeMap.prototype = {
  13. get: function get(key) {
  14. return key[this._key];
  15. },
  16. set: function set(key, value) {
  17. if (Object.isExtensible(key)) {
  18. Object.defineProperty(key, this._key, {
  19. value: value,
  20. configurable: true,
  21. });
  22. }
  23. },
  24. };
  25. var MemoizeMap = typeof WeakMap === 'function' ? WeakMap : FakeMap;
  26. /*!
  27. * Check to see if the MemoizeMap has recorded a result of the two operands
  28. *
  29. * @param {Mixed} leftHandOperand
  30. * @param {Mixed} rightHandOperand
  31. * @param {MemoizeMap} memoizeMap
  32. * @returns {Boolean|null} result
  33. */
  34. function memoizeCompare(leftHandOperand, rightHandOperand, memoizeMap) {
  35. // Technically, WeakMap keys can *only* be objects, not primitives.
  36. if (!memoizeMap || isPrimitive(leftHandOperand) || isPrimitive(rightHandOperand)) {
  37. return null;
  38. }
  39. var leftHandMap = memoizeMap.get(leftHandOperand);
  40. if (leftHandMap) {
  41. var result = leftHandMap.get(rightHandOperand);
  42. if (typeof result === 'boolean') {
  43. return result;
  44. }
  45. }
  46. return null;
  47. }
  48. /*!
  49. * Set the result of the equality into the MemoizeMap
  50. *
  51. * @param {Mixed} leftHandOperand
  52. * @param {Mixed} rightHandOperand
  53. * @param {MemoizeMap} memoizeMap
  54. * @param {Boolean} result
  55. */
  56. function memoizeSet(leftHandOperand, rightHandOperand, memoizeMap, result) {
  57. // Technically, WeakMap keys can *only* be objects, not primitives.
  58. if (!memoizeMap || isPrimitive(leftHandOperand) || isPrimitive(rightHandOperand)) {
  59. return;
  60. }
  61. var leftHandMap = memoizeMap.get(leftHandOperand);
  62. if (leftHandMap) {
  63. leftHandMap.set(rightHandOperand, result);
  64. } else {
  65. leftHandMap = new MemoizeMap();
  66. leftHandMap.set(rightHandOperand, result);
  67. memoizeMap.set(leftHandOperand, leftHandMap);
  68. }
  69. }
  70. /*!
  71. * Primary Export
  72. */
  73. module.exports = deepEqual;
  74. module.exports.MemoizeMap = MemoizeMap;
  75. /**
  76. * Assert deeply nested sameValue equality between two objects of any type.
  77. *
  78. * @param {Mixed} leftHandOperand
  79. * @param {Mixed} rightHandOperand
  80. * @param {Object} [options] (optional) Additional options
  81. * @param {Array} [options.comparator] (optional) Override default algorithm, determining custom equality.
  82. * @param {Array} [options.memoize] (optional) Provide a custom memoization object which will cache the results of
  83. complex objects for a speed boost. By passing `false` you can disable memoization, but this will cause circular
  84. references to blow the stack.
  85. * @return {Boolean} equal match
  86. */
  87. function deepEqual(leftHandOperand, rightHandOperand, options) {
  88. // If we have a comparator, we can't assume anything; so bail to its check first.
  89. if (options && options.comparator) {
  90. return extensiveDeepEqual(leftHandOperand, rightHandOperand, options);
  91. }
  92. var simpleResult = simpleEqual(leftHandOperand, rightHandOperand);
  93. if (simpleResult !== null) {
  94. return simpleResult;
  95. }
  96. // Deeper comparisons are pushed through to a larger function
  97. return extensiveDeepEqual(leftHandOperand, rightHandOperand, options);
  98. }
  99. /**
  100. * Many comparisons can be canceled out early via simple equality or primitive checks.
  101. * @param {Mixed} leftHandOperand
  102. * @param {Mixed} rightHandOperand
  103. * @return {Boolean|null} equal match
  104. */
  105. function simpleEqual(leftHandOperand, rightHandOperand) {
  106. // Equal references (except for Numbers) can be returned early
  107. if (leftHandOperand === rightHandOperand) {
  108. // Handle +-0 cases
  109. return leftHandOperand !== 0 || 1 / leftHandOperand === 1 / rightHandOperand;
  110. }
  111. // handle NaN cases
  112. if (
  113. leftHandOperand !== leftHandOperand && // eslint-disable-line no-self-compare
  114. rightHandOperand !== rightHandOperand // eslint-disable-line no-self-compare
  115. ) {
  116. return true;
  117. }
  118. // Anything that is not an 'object', i.e. symbols, functions, booleans, numbers,
  119. // strings, and undefined, can be compared by reference.
  120. if (isPrimitive(leftHandOperand) || isPrimitive(rightHandOperand)) {
  121. // Easy out b/c it would have passed the first equality check
  122. return false;
  123. }
  124. return null;
  125. }
  126. /*!
  127. * The main logic of the `deepEqual` function.
  128. *
  129. * @param {Mixed} leftHandOperand
  130. * @param {Mixed} rightHandOperand
  131. * @param {Object} [options] (optional) Additional options
  132. * @param {Array} [options.comparator] (optional) Override default algorithm, determining custom equality.
  133. * @param {Array} [options.memoize] (optional) Provide a custom memoization object which will cache the results of
  134. complex objects for a speed boost. By passing `false` you can disable memoization, but this will cause circular
  135. references to blow the stack.
  136. * @return {Boolean} equal match
  137. */
  138. function extensiveDeepEqual(leftHandOperand, rightHandOperand, options) {
  139. options = options || {};
  140. options.memoize = options.memoize === false ? false : options.memoize || new MemoizeMap();
  141. var comparator = options && options.comparator;
  142. // Check if a memoized result exists.
  143. var memoizeResultLeft = memoizeCompare(leftHandOperand, rightHandOperand, options.memoize);
  144. if (memoizeResultLeft !== null) {
  145. return memoizeResultLeft;
  146. }
  147. var memoizeResultRight = memoizeCompare(rightHandOperand, leftHandOperand, options.memoize);
  148. if (memoizeResultRight !== null) {
  149. return memoizeResultRight;
  150. }
  151. // If a comparator is present, use it.
  152. if (comparator) {
  153. var comparatorResult = comparator(leftHandOperand, rightHandOperand);
  154. // Comparators may return null, in which case we want to go back to default behavior.
  155. if (comparatorResult === false || comparatorResult === true) {
  156. memoizeSet(leftHandOperand, rightHandOperand, options.memoize, comparatorResult);
  157. return comparatorResult;
  158. }
  159. // To allow comparators to override *any* behavior, we ran them first. Since it didn't decide
  160. // what to do, we need to make sure to return the basic tests first before we move on.
  161. var simpleResult = simpleEqual(leftHandOperand, rightHandOperand);
  162. if (simpleResult !== null) {
  163. // Don't memoize this, it takes longer to set/retrieve than to just compare.
  164. return simpleResult;
  165. }
  166. }
  167. var leftHandType = type(leftHandOperand);
  168. if (leftHandType !== type(rightHandOperand)) {
  169. memoizeSet(leftHandOperand, rightHandOperand, options.memoize, false);
  170. return false;
  171. }
  172. // Temporarily set the operands in the memoize object to prevent blowing the stack
  173. memoizeSet(leftHandOperand, rightHandOperand, options.memoize, true);
  174. var result = extensiveDeepEqualByType(leftHandOperand, rightHandOperand, leftHandType, options);
  175. memoizeSet(leftHandOperand, rightHandOperand, options.memoize, result);
  176. return result;
  177. }
  178. function extensiveDeepEqualByType(leftHandOperand, rightHandOperand, leftHandType, options) {
  179. switch (leftHandType) {
  180. case 'String':
  181. case 'Number':
  182. case 'Boolean':
  183. case 'Date':
  184. // If these types are their instance types (e.g. `new Number`) then re-deepEqual against their values
  185. return deepEqual(leftHandOperand.valueOf(), rightHandOperand.valueOf());
  186. case 'Promise':
  187. case 'Symbol':
  188. case 'function':
  189. case 'WeakMap':
  190. case 'WeakSet':
  191. return leftHandOperand === rightHandOperand;
  192. case 'Error':
  193. return keysEqual(leftHandOperand, rightHandOperand, [ 'name', 'message', 'code' ], options);
  194. case 'Arguments':
  195. case 'Int8Array':
  196. case 'Uint8Array':
  197. case 'Uint8ClampedArray':
  198. case 'Int16Array':
  199. case 'Uint16Array':
  200. case 'Int32Array':
  201. case 'Uint32Array':
  202. case 'Float32Array':
  203. case 'Float64Array':
  204. case 'Array':
  205. return iterableEqual(leftHandOperand, rightHandOperand, options);
  206. case 'RegExp':
  207. return regexpEqual(leftHandOperand, rightHandOperand);
  208. case 'Generator':
  209. return generatorEqual(leftHandOperand, rightHandOperand, options);
  210. case 'DataView':
  211. return iterableEqual(new Uint8Array(leftHandOperand.buffer), new Uint8Array(rightHandOperand.buffer), options);
  212. case 'ArrayBuffer':
  213. return iterableEqual(new Uint8Array(leftHandOperand), new Uint8Array(rightHandOperand), options);
  214. case 'Set':
  215. return entriesEqual(leftHandOperand, rightHandOperand, options);
  216. case 'Map':
  217. return entriesEqual(leftHandOperand, rightHandOperand, options);
  218. case 'Temporal.PlainDate':
  219. case 'Temporal.PlainTime':
  220. case 'Temporal.PlainDateTime':
  221. case 'Temporal.Instant':
  222. case 'Temporal.ZonedDateTime':
  223. case 'Temporal.PlainYearMonth':
  224. case 'Temporal.PlainMonthDay':
  225. return leftHandOperand.equals(rightHandOperand);
  226. case 'Temporal.Duration':
  227. return leftHandOperand.total('nanoseconds') === rightHandOperand.total('nanoseconds');
  228. case 'Temporal.TimeZone':
  229. case 'Temporal.Calendar':
  230. return leftHandOperand.toString() === rightHandOperand.toString();
  231. default:
  232. return objectEqual(leftHandOperand, rightHandOperand, options);
  233. }
  234. }
  235. /*!
  236. * Compare two Regular Expressions for equality.
  237. *
  238. * @param {RegExp} leftHandOperand
  239. * @param {RegExp} rightHandOperand
  240. * @return {Boolean} result
  241. */
  242. function regexpEqual(leftHandOperand, rightHandOperand) {
  243. return leftHandOperand.toString() === rightHandOperand.toString();
  244. }
  245. /*!
  246. * Compare two Sets/Maps for equality. Faster than other equality functions.
  247. *
  248. * @param {Set} leftHandOperand
  249. * @param {Set} rightHandOperand
  250. * @param {Object} [options] (Optional)
  251. * @return {Boolean} result
  252. */
  253. function entriesEqual(leftHandOperand, rightHandOperand, options) {
  254. // IE11 doesn't support Set#entries or Set#@@iterator, so we need manually populate using Set#forEach
  255. if (leftHandOperand.size !== rightHandOperand.size) {
  256. return false;
  257. }
  258. if (leftHandOperand.size === 0) {
  259. return true;
  260. }
  261. var leftHandItems = [];
  262. var rightHandItems = [];
  263. leftHandOperand.forEach(function gatherEntries(key, value) {
  264. leftHandItems.push([ key, value ]);
  265. });
  266. rightHandOperand.forEach(function gatherEntries(key, value) {
  267. rightHandItems.push([ key, value ]);
  268. });
  269. return iterableEqual(leftHandItems.sort(), rightHandItems.sort(), options);
  270. }
  271. /*!
  272. * Simple equality for flat iterable objects such as Arrays, TypedArrays or Node.js buffers.
  273. *
  274. * @param {Iterable} leftHandOperand
  275. * @param {Iterable} rightHandOperand
  276. * @param {Object} [options] (Optional)
  277. * @return {Boolean} result
  278. */
  279. function iterableEqual(leftHandOperand, rightHandOperand, options) {
  280. var length = leftHandOperand.length;
  281. if (length !== rightHandOperand.length) {
  282. return false;
  283. }
  284. if (length === 0) {
  285. return true;
  286. }
  287. var index = -1;
  288. while (++index < length) {
  289. if (deepEqual(leftHandOperand[index], rightHandOperand[index], options) === false) {
  290. return false;
  291. }
  292. }
  293. return true;
  294. }
  295. /*!
  296. * Simple equality for generator objects such as those returned by generator functions.
  297. *
  298. * @param {Iterable} leftHandOperand
  299. * @param {Iterable} rightHandOperand
  300. * @param {Object} [options] (Optional)
  301. * @return {Boolean} result
  302. */
  303. function generatorEqual(leftHandOperand, rightHandOperand, options) {
  304. return iterableEqual(getGeneratorEntries(leftHandOperand), getGeneratorEntries(rightHandOperand), options);
  305. }
  306. /*!
  307. * Determine if the given object has an @@iterator function.
  308. *
  309. * @param {Object} target
  310. * @return {Boolean} `true` if the object has an @@iterator function.
  311. */
  312. function hasIteratorFunction(target) {
  313. return typeof Symbol !== 'undefined' &&
  314. typeof target === 'object' &&
  315. typeof Symbol.iterator !== 'undefined' &&
  316. typeof target[Symbol.iterator] === 'function';
  317. }
  318. /*!
  319. * Gets all iterator entries from the given Object. If the Object has no @@iterator function, returns an empty array.
  320. * This will consume the iterator - which could have side effects depending on the @@iterator implementation.
  321. *
  322. * @param {Object} target
  323. * @returns {Array} an array of entries from the @@iterator function
  324. */
  325. function getIteratorEntries(target) {
  326. if (hasIteratorFunction(target)) {
  327. try {
  328. return getGeneratorEntries(target[Symbol.iterator]());
  329. } catch (iteratorError) {
  330. return [];
  331. }
  332. }
  333. return [];
  334. }
  335. /*!
  336. * Gets all entries from a Generator. This will consume the generator - which could have side effects.
  337. *
  338. * @param {Generator} target
  339. * @returns {Array} an array of entries from the Generator.
  340. */
  341. function getGeneratorEntries(generator) {
  342. var generatorResult = generator.next();
  343. var accumulator = [ generatorResult.value ];
  344. while (generatorResult.done === false) {
  345. generatorResult = generator.next();
  346. accumulator.push(generatorResult.value);
  347. }
  348. return accumulator;
  349. }
  350. /*!
  351. * Gets all own and inherited enumerable keys from a target.
  352. *
  353. * @param {Object} target
  354. * @returns {Array} an array of own and inherited enumerable keys from the target.
  355. */
  356. function getEnumerableKeys(target) {
  357. var keys = [];
  358. for (var key in target) {
  359. keys.push(key);
  360. }
  361. return keys;
  362. }
  363. function getEnumerableSymbols(target) {
  364. var keys = [];
  365. var allKeys = Object.getOwnPropertySymbols(target);
  366. for (var i = 0; i < allKeys.length; i += 1) {
  367. var key = allKeys[i];
  368. if (Object.getOwnPropertyDescriptor(target, key).enumerable) {
  369. keys.push(key);
  370. }
  371. }
  372. return keys;
  373. }
  374. /*!
  375. * Determines if two objects have matching values, given a set of keys. Defers to deepEqual for the equality check of
  376. * each key. If any value of the given key is not equal, the function will return false (early).
  377. *
  378. * @param {Mixed} leftHandOperand
  379. * @param {Mixed} rightHandOperand
  380. * @param {Array} keys An array of keys to compare the values of leftHandOperand and rightHandOperand against
  381. * @param {Object} [options] (Optional)
  382. * @return {Boolean} result
  383. */
  384. function keysEqual(leftHandOperand, rightHandOperand, keys, options) {
  385. var length = keys.length;
  386. if (length === 0) {
  387. return true;
  388. }
  389. for (var i = 0; i < length; i += 1) {
  390. if (deepEqual(leftHandOperand[keys[i]], rightHandOperand[keys[i]], options) === false) {
  391. return false;
  392. }
  393. }
  394. return true;
  395. }
  396. /*!
  397. * Recursively check the equality of two Objects. Once basic sameness has been established it will defer to `deepEqual`
  398. * for each enumerable key in the object.
  399. *
  400. * @param {Mixed} leftHandOperand
  401. * @param {Mixed} rightHandOperand
  402. * @param {Object} [options] (Optional)
  403. * @return {Boolean} result
  404. */
  405. function objectEqual(leftHandOperand, rightHandOperand, options) {
  406. var leftHandKeys = getEnumerableKeys(leftHandOperand);
  407. var rightHandKeys = getEnumerableKeys(rightHandOperand);
  408. var leftHandSymbols = getEnumerableSymbols(leftHandOperand);
  409. var rightHandSymbols = getEnumerableSymbols(rightHandOperand);
  410. leftHandKeys = leftHandKeys.concat(leftHandSymbols);
  411. rightHandKeys = rightHandKeys.concat(rightHandSymbols);
  412. if (leftHandKeys.length && leftHandKeys.length === rightHandKeys.length) {
  413. if (iterableEqual(mapSymbols(leftHandKeys).sort(), mapSymbols(rightHandKeys).sort()) === false) {
  414. return false;
  415. }
  416. return keysEqual(leftHandOperand, rightHandOperand, leftHandKeys, options);
  417. }
  418. var leftHandEntries = getIteratorEntries(leftHandOperand);
  419. var rightHandEntries = getIteratorEntries(rightHandOperand);
  420. if (leftHandEntries.length && leftHandEntries.length === rightHandEntries.length) {
  421. leftHandEntries.sort();
  422. rightHandEntries.sort();
  423. return iterableEqual(leftHandEntries, rightHandEntries, options);
  424. }
  425. if (leftHandKeys.length === 0 &&
  426. leftHandEntries.length === 0 &&
  427. rightHandKeys.length === 0 &&
  428. rightHandEntries.length === 0) {
  429. return true;
  430. }
  431. return false;
  432. }
  433. /*!
  434. * Returns true if the argument is a primitive.
  435. *
  436. * This intentionally returns true for all objects that can be compared by reference,
  437. * including functions and symbols.
  438. *
  439. * @param {Mixed} value
  440. * @return {Boolean} result
  441. */
  442. function isPrimitive(value) {
  443. return value === null || typeof value !== 'object';
  444. }
  445. function mapSymbols(arr) {
  446. return arr.map(function mapSymbol(entry) {
  447. if (typeof entry === 'symbol') {
  448. return entry.toString();
  449. }
  450. return entry;
  451. });
  452. }