deep-equal.js 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287
  1. 'use strict';
  2. // Load modules
  3. const Types = require('./types');
  4. // Declare internals
  5. const internals = {
  6. mismatched: null
  7. };
  8. module.exports = function (obj, ref, options) {
  9. options = { prototype: true, ...options };
  10. return !!internals.isDeepEqual(obj, ref, options, []);
  11. };
  12. internals.isDeepEqual = function (obj, ref, options, seen) {
  13. if (obj === ref) { // Copied from Deep-eql, copyright(c) 2013 Jake Luer, jake@alogicalparadox.com, MIT Licensed, https://github.com/chaijs/deep-eql
  14. return obj !== 0 || 1 / obj === 1 / ref;
  15. }
  16. const type = typeof obj;
  17. if (type !== typeof ref) {
  18. return false;
  19. }
  20. if (type !== 'object' ||
  21. obj === null ||
  22. ref === null) {
  23. return obj !== obj && ref !== ref; // NaN
  24. }
  25. const instanceType = internals.getSharedType(obj, ref, !!options.prototype);
  26. switch (instanceType) {
  27. case Types.buffer:
  28. return Buffer.prototype.equals.call(obj, ref);
  29. case Types.promise:
  30. return obj === ref;
  31. case Types.regex:
  32. return obj.toString() === ref.toString();
  33. case internals.mismatched:
  34. return false;
  35. }
  36. for (let i = seen.length - 1; i >= 0; --i) {
  37. if (seen[i].isSame(obj, ref)) {
  38. return true; // If previous comparison failed, it would have stopped execution
  39. }
  40. }
  41. seen.push(new internals.SeenEntry(obj, ref));
  42. try {
  43. return !!internals.isDeepEqualObj(instanceType, obj, ref, options, seen);
  44. }
  45. finally {
  46. seen.pop();
  47. }
  48. };
  49. internals.getSharedType = function (obj, ref, checkPrototype) {
  50. if (checkPrototype) {
  51. if (Object.getPrototypeOf(obj) !== Object.getPrototypeOf(ref)) {
  52. return internals.mismatched;
  53. }
  54. return Types.getInternalProto(obj);
  55. }
  56. const type = Types.getInternalProto(obj);
  57. if (type !== Types.getInternalProto(ref)) {
  58. return internals.mismatched;
  59. }
  60. return type;
  61. };
  62. internals.valueOf = function (obj) {
  63. const objValueOf = obj.valueOf;
  64. if (objValueOf === undefined) {
  65. return obj;
  66. }
  67. try {
  68. return objValueOf.call(obj);
  69. }
  70. catch (err) {
  71. return err;
  72. }
  73. };
  74. internals.hasOwnEnumerableProperty = function (obj, key) {
  75. return Object.prototype.propertyIsEnumerable.call(obj, key);
  76. };
  77. internals.isSetSimpleEqual = function (obj, ref) {
  78. for (const entry of obj) {
  79. if (!ref.has(entry)) {
  80. return false;
  81. }
  82. }
  83. return true;
  84. };
  85. internals.isDeepEqualObj = function (instanceType, obj, ref, options, seen) {
  86. const { isDeepEqual, valueOf, hasOwnEnumerableProperty } = internals;
  87. const { keys, getOwnPropertySymbols } = Object;
  88. if (instanceType === Types.array) {
  89. if (options.part) {
  90. // Check if any index match any other index
  91. for (let i = 0; i < obj.length; ++i) {
  92. const objValue = obj[i];
  93. for (let j = 0; j < ref.length; ++j) {
  94. if (isDeepEqual(objValue, ref[j], options, seen)) {
  95. return true;
  96. }
  97. }
  98. }
  99. }
  100. else {
  101. if (obj.length !== ref.length) {
  102. return false;
  103. }
  104. for (let i = 0; i < obj.length; ++i) {
  105. if (!isDeepEqual(obj[i], ref[i], options, seen)) {
  106. return false;
  107. }
  108. }
  109. return true;
  110. }
  111. }
  112. else if (instanceType === Types.set) {
  113. if (obj.size !== ref.size) {
  114. return false;
  115. }
  116. if (!internals.isSetSimpleEqual(obj, ref)) {
  117. // Check for deep equality
  118. const ref2 = new Set(ref);
  119. for (const objEntry of obj) {
  120. if (ref2.delete(objEntry)) {
  121. continue;
  122. }
  123. let found = false;
  124. for (const refEntry of ref2) {
  125. if (isDeepEqual(objEntry, refEntry, options, seen)) {
  126. ref2.delete(refEntry);
  127. found = true;
  128. break;
  129. }
  130. }
  131. if (!found) {
  132. return false;
  133. }
  134. }
  135. }
  136. }
  137. else if (instanceType === Types.map) {
  138. if (obj.size !== ref.size) {
  139. return false;
  140. }
  141. for (const [key, value] of obj) {
  142. if (value === undefined && !ref.has(key)) {
  143. return false;
  144. }
  145. if (!isDeepEqual(value, ref.get(key), options, seen)) {
  146. return false;
  147. }
  148. }
  149. }
  150. else if (instanceType === Types.error) {
  151. // Always check name and message
  152. if (obj.name !== ref.name || obj.message !== ref.message) {
  153. return false;
  154. }
  155. }
  156. // Check .valueOf()
  157. const valueOfObj = valueOf(obj);
  158. const valueOfRef = valueOf(ref);
  159. if (!(obj === valueOfObj && ref === valueOfRef) &&
  160. !isDeepEqual(valueOfObj, valueOfRef, options, seen)) {
  161. return false;
  162. }
  163. // Check properties
  164. const objKeys = keys(obj);
  165. if (!options.part &&
  166. objKeys.length !== keys(ref).length) {
  167. return false;
  168. }
  169. for (let i = 0; i < objKeys.length; ++i) {
  170. const key = objKeys[i];
  171. if (!hasOwnEnumerableProperty(ref, key)) {
  172. return false;
  173. }
  174. if (!isDeepEqual(obj[key], ref[key], options, seen)) {
  175. return false;
  176. }
  177. }
  178. // Check symbols
  179. if (options.symbols) {
  180. const objSymbols = getOwnPropertySymbols(obj);
  181. const refSymbols = new Set(getOwnPropertySymbols(ref));
  182. for (let i = 0; i < objSymbols.length; ++i) {
  183. const key = objSymbols[i];
  184. if (hasOwnEnumerableProperty(obj, key)) {
  185. if (!hasOwnEnumerableProperty(ref, key)) {
  186. return false;
  187. }
  188. if (!isDeepEqual(obj[key], ref[key], options, seen)) {
  189. return false;
  190. }
  191. }
  192. else if (hasOwnEnumerableProperty(ref, key)) {
  193. return false;
  194. }
  195. refSymbols.delete(key);
  196. }
  197. for (const key of refSymbols) {
  198. if (hasOwnEnumerableProperty(ref, key)) {
  199. return false;
  200. }
  201. }
  202. }
  203. return true;
  204. };
  205. internals.SeenEntry = class {
  206. constructor(obj, ref) {
  207. this.obj = obj;
  208. this.ref = ref;
  209. }
  210. isSame(obj, ref) {
  211. return this.obj === obj && this.ref === ref;
  212. }
  213. };