index.js 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392
  1. 'use strict';
  2. var assign = require('object.assign');
  3. var callBound = require('call-bind/callBound');
  4. var flags = require('regexp.prototype.flags');
  5. var GetIntrinsic = require('get-intrinsic');
  6. var getIterator = require('es-get-iterator');
  7. var getSideChannel = require('side-channel');
  8. var is = require('object-is');
  9. var isArguments = require('is-arguments');
  10. var isArray = require('isarray');
  11. var isArrayBuffer = require('is-array-buffer');
  12. var isDate = require('is-date-object');
  13. var isRegex = require('is-regex');
  14. var isSharedArrayBuffer = require('is-shared-array-buffer');
  15. var objectKeys = require('object-keys');
  16. var whichBoxedPrimitive = require('which-boxed-primitive');
  17. var whichCollection = require('which-collection');
  18. var whichTypedArray = require('which-typed-array');
  19. var byteLength = require('array-buffer-byte-length');
  20. var sabByteLength = callBound('SharedArrayBuffer.prototype.byteLength', true);
  21. var $getTime = callBound('Date.prototype.getTime');
  22. var gPO = Object.getPrototypeOf;
  23. var $objToString = callBound('Object.prototype.toString');
  24. var $Set = GetIntrinsic('%Set%', true);
  25. var $mapHas = callBound('Map.prototype.has', true);
  26. var $mapGet = callBound('Map.prototype.get', true);
  27. var $mapSize = callBound('Map.prototype.size', true);
  28. var $setAdd = callBound('Set.prototype.add', true);
  29. var $setDelete = callBound('Set.prototype.delete', true);
  30. var $setHas = callBound('Set.prototype.has', true);
  31. var $setSize = callBound('Set.prototype.size', true);
  32. // taken from https://github.com/browserify/commonjs-assert/blob/bba838e9ba9e28edf3127ce6974624208502f6bc/internal/util/comparisons.js#L401-L414
  33. function setHasEqualElement(set, val1, opts, channel) {
  34. var i = getIterator(set);
  35. var result;
  36. while ((result = i.next()) && !result.done) {
  37. if (internalDeepEqual(val1, result.value, opts, channel)) { // eslint-disable-line no-use-before-define
  38. // Remove the matching element to make sure we do not check that again.
  39. $setDelete(set, result.value);
  40. return true;
  41. }
  42. }
  43. return false;
  44. }
  45. // taken from https://github.com/browserify/commonjs-assert/blob/bba838e9ba9e28edf3127ce6974624208502f6bc/internal/util/comparisons.js#L416-L439
  46. function findLooseMatchingPrimitives(prim) {
  47. if (typeof prim === 'undefined') {
  48. return null;
  49. }
  50. if (typeof prim === 'object') { // Only pass in null as object!
  51. return void 0;
  52. }
  53. if (typeof prim === 'symbol') {
  54. return false;
  55. }
  56. if (typeof prim === 'string' || typeof prim === 'number') {
  57. // Loose equal entries exist only if the string is possible to convert to a regular number and not NaN.
  58. return +prim === +prim; // eslint-disable-line no-implicit-coercion
  59. }
  60. return true;
  61. }
  62. // taken from https://github.com/browserify/commonjs-assert/blob/bba838e9ba9e28edf3127ce6974624208502f6bc/internal/util/comparisons.js#L449-L460
  63. function mapMightHaveLoosePrim(a, b, prim, item, opts, channel) {
  64. var altValue = findLooseMatchingPrimitives(prim);
  65. if (altValue != null) {
  66. return altValue;
  67. }
  68. var curB = $mapGet(b, altValue);
  69. var looseOpts = assign({}, opts, { strict: false });
  70. if (
  71. (typeof curB === 'undefined' && !$mapHas(b, altValue))
  72. // eslint-disable-next-line no-use-before-define
  73. || !internalDeepEqual(item, curB, looseOpts, channel)
  74. ) {
  75. return false;
  76. }
  77. // eslint-disable-next-line no-use-before-define
  78. return !$mapHas(a, altValue) && internalDeepEqual(item, curB, looseOpts, channel);
  79. }
  80. // taken from https://github.com/browserify/commonjs-assert/blob/bba838e9ba9e28edf3127ce6974624208502f6bc/internal/util/comparisons.js#L441-L447
  81. function setMightHaveLoosePrim(a, b, prim) {
  82. var altValue = findLooseMatchingPrimitives(prim);
  83. if (altValue != null) {
  84. return altValue;
  85. }
  86. return $setHas(b, altValue) && !$setHas(a, altValue);
  87. }
  88. // taken from https://github.com/browserify/commonjs-assert/blob/bba838e9ba9e28edf3127ce6974624208502f6bc/internal/util/comparisons.js#L518-L533
  89. function mapHasEqualEntry(set, map, key1, item1, opts, channel) {
  90. var i = getIterator(set);
  91. var result;
  92. var key2;
  93. while ((result = i.next()) && !result.done) {
  94. key2 = result.value;
  95. if (
  96. // eslint-disable-next-line no-use-before-define
  97. internalDeepEqual(key1, key2, opts, channel)
  98. // eslint-disable-next-line no-use-before-define
  99. && internalDeepEqual(item1, $mapGet(map, key2), opts, channel)
  100. ) {
  101. $setDelete(set, key2);
  102. return true;
  103. }
  104. }
  105. return false;
  106. }
  107. function internalDeepEqual(actual, expected, options, channel) {
  108. var opts = options || {};
  109. // 7.1. All identical values are equivalent, as determined by ===.
  110. if (opts.strict ? is(actual, expected) : actual === expected) {
  111. return true;
  112. }
  113. var actualBoxed = whichBoxedPrimitive(actual);
  114. var expectedBoxed = whichBoxedPrimitive(expected);
  115. if (actualBoxed !== expectedBoxed) {
  116. return false;
  117. }
  118. // 7.3. Other pairs that do not both pass typeof value == 'object', equivalence is determined by ==.
  119. if (!actual || !expected || (typeof actual !== 'object' && typeof expected !== 'object')) {
  120. return opts.strict ? is(actual, expected) : actual == expected; // eslint-disable-line eqeqeq
  121. }
  122. /*
  123. * 7.4. For all other Object pairs, including Array objects, equivalence is
  124. * determined by having the same number of owned properties (as verified
  125. * with Object.prototype.hasOwnProperty.call), the same set of keys
  126. * (although not necessarily the same order), equivalent values for every
  127. * corresponding key, and an identical 'prototype' property. Note: this
  128. * accounts for both named and indexed properties on Arrays.
  129. */
  130. // see https://github.com/nodejs/node/commit/d3aafd02efd3a403d646a3044adcf14e63a88d32 for memos/channel inspiration
  131. var hasActual = channel.has(actual);
  132. var hasExpected = channel.has(expected);
  133. var sentinel;
  134. if (hasActual && hasExpected) {
  135. if (channel.get(actual) === channel.get(expected)) {
  136. return true;
  137. }
  138. } else {
  139. sentinel = {};
  140. }
  141. if (!hasActual) { channel.set(actual, sentinel); }
  142. if (!hasExpected) { channel.set(expected, sentinel); }
  143. // eslint-disable-next-line no-use-before-define
  144. return objEquiv(actual, expected, opts, channel);
  145. }
  146. function isBuffer(x) {
  147. if (!x || typeof x !== 'object' || typeof x.length !== 'number') {
  148. return false;
  149. }
  150. if (typeof x.copy !== 'function' || typeof x.slice !== 'function') {
  151. return false;
  152. }
  153. if (x.length > 0 && typeof x[0] !== 'number') {
  154. return false;
  155. }
  156. return !!(x.constructor && x.constructor.isBuffer && x.constructor.isBuffer(x));
  157. }
  158. function setEquiv(a, b, opts, channel) {
  159. if ($setSize(a) !== $setSize(b)) {
  160. return false;
  161. }
  162. var iA = getIterator(a);
  163. var iB = getIterator(b);
  164. var resultA;
  165. var resultB;
  166. var set;
  167. while ((resultA = iA.next()) && !resultA.done) {
  168. if (resultA.value && typeof resultA.value === 'object') {
  169. if (!set) { set = new $Set(); }
  170. $setAdd(set, resultA.value);
  171. } else if (!$setHas(b, resultA.value)) {
  172. if (opts.strict) { return false; }
  173. if (!setMightHaveLoosePrim(a, b, resultA.value)) {
  174. return false;
  175. }
  176. if (!set) { set = new $Set(); }
  177. $setAdd(set, resultA.value);
  178. }
  179. }
  180. if (set) {
  181. while ((resultB = iB.next()) && !resultB.done) {
  182. // We have to check if a primitive value is already matching and only if it's not, go hunting for it.
  183. if (resultB.value && typeof resultB.value === 'object') {
  184. if (!setHasEqualElement(set, resultB.value, opts.strict, channel)) {
  185. return false;
  186. }
  187. } else if (
  188. !opts.strict
  189. && !$setHas(a, resultB.value)
  190. && !setHasEqualElement(set, resultB.value, opts.strict, channel)
  191. ) {
  192. return false;
  193. }
  194. }
  195. return $setSize(set) === 0;
  196. }
  197. return true;
  198. }
  199. function mapEquiv(a, b, opts, channel) {
  200. if ($mapSize(a) !== $mapSize(b)) {
  201. return false;
  202. }
  203. var iA = getIterator(a);
  204. var iB = getIterator(b);
  205. var resultA;
  206. var resultB;
  207. var set;
  208. var key;
  209. var item1;
  210. var item2;
  211. while ((resultA = iA.next()) && !resultA.done) {
  212. key = resultA.value[0];
  213. item1 = resultA.value[1];
  214. if (key && typeof key === 'object') {
  215. if (!set) { set = new $Set(); }
  216. $setAdd(set, key);
  217. } else {
  218. item2 = $mapGet(b, key);
  219. if ((typeof item2 === 'undefined' && !$mapHas(b, key)) || !internalDeepEqual(item1, item2, opts, channel)) {
  220. if (opts.strict) {
  221. return false;
  222. }
  223. if (!mapMightHaveLoosePrim(a, b, key, item1, opts, channel)) {
  224. return false;
  225. }
  226. if (!set) { set = new $Set(); }
  227. $setAdd(set, key);
  228. }
  229. }
  230. }
  231. if (set) {
  232. while ((resultB = iB.next()) && !resultB.done) {
  233. key = resultB.value[0];
  234. item2 = resultB.value[1];
  235. if (key && typeof key === 'object') {
  236. if (!mapHasEqualEntry(set, a, key, item2, opts, channel)) {
  237. return false;
  238. }
  239. } else if (
  240. !opts.strict
  241. && (!a.has(key) || !internalDeepEqual($mapGet(a, key), item2, opts, channel))
  242. && !mapHasEqualEntry(set, a, key, item2, assign({}, opts, { strict: false }), channel)
  243. ) {
  244. return false;
  245. }
  246. }
  247. return $setSize(set) === 0;
  248. }
  249. return true;
  250. }
  251. function objEquiv(a, b, opts, channel) {
  252. /* eslint max-statements: [2, 100], max-lines-per-function: [2, 120], max-depth: [2, 5], max-lines: [2, 400] */
  253. var i, key;
  254. if (typeof a !== typeof b) { return false; }
  255. if (a == null || b == null) { return false; }
  256. if ($objToString(a) !== $objToString(b)) { return false; }
  257. if (isArguments(a) !== isArguments(b)) { return false; }
  258. var aIsArray = isArray(a);
  259. var bIsArray = isArray(b);
  260. if (aIsArray !== bIsArray) { return false; }
  261. // TODO: replace when a cross-realm brand check is available
  262. var aIsError = a instanceof Error;
  263. var bIsError = b instanceof Error;
  264. if (aIsError !== bIsError) { return false; }
  265. if (aIsError || bIsError) {
  266. if (a.name !== b.name || a.message !== b.message) { return false; }
  267. }
  268. var aIsRegex = isRegex(a);
  269. var bIsRegex = isRegex(b);
  270. if (aIsRegex !== bIsRegex) { return false; }
  271. if ((aIsRegex || bIsRegex) && (a.source !== b.source || flags(a) !== flags(b))) {
  272. return false;
  273. }
  274. var aIsDate = isDate(a);
  275. var bIsDate = isDate(b);
  276. if (aIsDate !== bIsDate) { return false; }
  277. if (aIsDate || bIsDate) { // && would work too, because both are true or both false here
  278. if ($getTime(a) !== $getTime(b)) { return false; }
  279. }
  280. if (opts.strict && gPO && gPO(a) !== gPO(b)) { return false; }
  281. var aWhich = whichTypedArray(a);
  282. var bWhich = whichTypedArray(b);
  283. if (aWhich !== bWhich) {
  284. return false;
  285. }
  286. if (aWhich || bWhich) { // && would work too, because both are true or both false here
  287. if (a.length !== b.length) { return false; }
  288. for (i = 0; i < a.length; i++) {
  289. if (a[i] !== b[i]) { return false; }
  290. }
  291. return true;
  292. }
  293. var aIsBuffer = isBuffer(a);
  294. var bIsBuffer = isBuffer(b);
  295. if (aIsBuffer !== bIsBuffer) { return false; }
  296. if (aIsBuffer || bIsBuffer) { // && would work too, because both are true or both false here
  297. if (a.length !== b.length) { return false; }
  298. for (i = 0; i < a.length; i++) {
  299. if (a[i] !== b[i]) { return false; }
  300. }
  301. return true;
  302. }
  303. var aIsArrayBuffer = isArrayBuffer(a);
  304. var bIsArrayBuffer = isArrayBuffer(b);
  305. if (aIsArrayBuffer !== bIsArrayBuffer) { return false; }
  306. if (aIsArrayBuffer || bIsArrayBuffer) { // && would work too, because both are true or both false here
  307. if (byteLength(a) !== byteLength(b)) { return false; }
  308. return typeof Uint8Array === 'function' && internalDeepEqual(new Uint8Array(a), new Uint8Array(b), opts, channel);
  309. }
  310. var aIsSAB = isSharedArrayBuffer(a);
  311. var bIsSAB = isSharedArrayBuffer(b);
  312. if (aIsSAB !== bIsSAB) { return false; }
  313. if (aIsSAB || bIsSAB) { // && would work too, because both are true or both false here
  314. if (sabByteLength(a) !== sabByteLength(b)) { return false; }
  315. return typeof Uint8Array === 'function' && internalDeepEqual(new Uint8Array(a), new Uint8Array(b), opts, channel);
  316. }
  317. if (typeof a !== typeof b) { return false; }
  318. var ka = objectKeys(a);
  319. var kb = objectKeys(b);
  320. // having the same number of owned properties (keys incorporates hasOwnProperty)
  321. if (ka.length !== kb.length) { return false; }
  322. // the same set of keys (although not necessarily the same order),
  323. ka.sort();
  324. kb.sort();
  325. // ~~~cheap key test
  326. for (i = ka.length - 1; i >= 0; i--) {
  327. if (ka[i] != kb[i]) { return false; } // eslint-disable-line eqeqeq
  328. }
  329. // equivalent values for every corresponding key, and ~~~possibly expensive deep test
  330. for (i = ka.length - 1; i >= 0; i--) {
  331. key = ka[i];
  332. if (!internalDeepEqual(a[key], b[key], opts, channel)) { return false; }
  333. }
  334. var aCollection = whichCollection(a);
  335. var bCollection = whichCollection(b);
  336. if (aCollection !== bCollection) {
  337. return false;
  338. }
  339. if (aCollection === 'Set' || bCollection === 'Set') { // aCollection === bCollection
  340. return setEquiv(a, b, opts, channel);
  341. }
  342. if (aCollection === 'Map') { // aCollection === bCollection
  343. return mapEquiv(a, b, opts, channel);
  344. }
  345. return true;
  346. }
  347. module.exports = function deepEqual(a, b, opts) {
  348. return internalDeepEqual(a, b, opts, getSideChannel());
  349. };