123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199 |
- "use strict";
- var valueToString = require("@sinonjs/commons").valueToString;
- var getClass = require("./get-class");
- var identical = require("./identical");
- var isArguments = require("./is-arguments");
- var isDate = require("./is-date");
- var isElement = require("./is-element");
- var isNaN = require("./is-nan");
- var isObject = require("./is-object");
- var isSet = require("./is-set");
- var isSubset = require("./is-subset");
- var getClassName = require("./get-class-name");
- var every = Array.prototype.every;
- var getTime = Date.prototype.getTime;
- var hasOwnProperty = Object.prototype.hasOwnProperty;
- var indexOf = Array.prototype.indexOf;
- var keys = Object.keys;
- /**
- * @name samsam.deepEqual
- * @param Object first
- * @param Object second
- *
- * Deep equal comparison. Two values are "deep equal" if:
- *
- * - They are equal, according to samsam.identical
- * - They are both date objects representing the same time
- * - They are both arrays containing elements that are all deepEqual
- * - They are objects with the same set of properties, and each property
- * in ``first`` is deepEqual to the corresponding property in ``second``
- *
- * Supports cyclic objects.
- */
- function deepEqualCyclic(first, second, match) {
- // used for cyclic comparison
- // contain already visited objects
- var objects1 = [];
- var objects2 = [];
- // contain pathes (position in the object structure)
- // of the already visited objects
- // indexes same as in objects arrays
- var paths1 = [];
- var paths2 = [];
- // contains combinations of already compared objects
- // in the manner: { "$1['ref']$2['ref']": true }
- var compared = {};
- // does the recursion for the deep equal check
- return (function deepEqual(obj1, obj2, path1, path2) {
- // If both are matchers they must be the same instance in order to be
- // considered equal If we didn't do that we would end up running one
- // matcher against the other
- if (match && match.isMatcher(obj2)) {
- if (match.isMatcher(obj1)) {
- return obj1 === obj2;
- }
- return obj2.test(obj1);
- }
- var type1 = typeof obj1;
- var type2 = typeof obj2;
- // == null also matches undefined
- if (
- obj1 === obj2 ||
- isNaN(obj1) ||
- isNaN(obj2) ||
- obj1 == null ||
- obj2 == null ||
- type1 !== "object" ||
- type2 !== "object"
- ) {
- return identical(obj1, obj2);
- }
- // Elements are only equal if identical(expected, actual)
- if (isElement(obj1) || isElement(obj2)) {
- return false;
- }
- var isDate1 = isDate(obj1);
- var isDate2 = isDate(obj2);
- if (isDate1 || isDate2) {
- if (
- !isDate1 ||
- !isDate2 ||
- getTime.call(obj1) !== getTime.call(obj2)
- ) {
- return false;
- }
- }
- if (obj1 instanceof RegExp && obj2 instanceof RegExp) {
- if (valueToString(obj1) !== valueToString(obj2)) {
- return false;
- }
- }
- if (obj1 instanceof Error && obj2 instanceof Error) {
- return obj1 === obj2;
- }
- var class1 = getClass(obj1);
- var class2 = getClass(obj2);
- var keys1 = keys(obj1);
- var keys2 = keys(obj2);
- var name1 = getClassName(obj1);
- var name2 = getClassName(obj2);
- if (isArguments(obj1) || isArguments(obj2)) {
- if (obj1.length !== obj2.length) {
- return false;
- }
- } else {
- if (
- type1 !== type2 ||
- class1 !== class2 ||
- keys1.length !== keys2.length ||
- (name1 && name2 && name1 !== name2)
- ) {
- return false;
- }
- }
- if (isSet(obj1) || isSet(obj2)) {
- if (!isSet(obj1) || !isSet(obj2) || obj1.size !== obj2.size) {
- return false;
- }
- return isSubset(obj1, obj2, deepEqual);
- }
- return every.call(keys1, function(key) {
- if (!hasOwnProperty.call(obj2, key)) {
- return false;
- }
- var value1 = obj1[key];
- var value2 = obj2[key];
- var isObject1 = isObject(value1);
- var isObject2 = isObject(value2);
- // determines, if the objects were already visited
- // (it's faster to check for isObject first, than to
- // get -1 from getIndex for non objects)
- var index1 = isObject1 ? indexOf.call(objects1, value1) : -1;
- var index2 = isObject2 ? indexOf.call(objects2, value2) : -1;
- // determines the new paths of the objects
- // - for non cyclic objects the current path will be extended
- // by current property name
- // - for cyclic objects the stored path is taken
- var newPath1 =
- index1 !== -1
- ? paths1[index1]
- : path1 + "[" + JSON.stringify(key) + "]";
- var newPath2 =
- index2 !== -1
- ? paths2[index2]
- : path2 + "[" + JSON.stringify(key) + "]";
- var combinedPath = newPath1 + newPath2;
- // stop recursion if current objects are already compared
- if (compared[combinedPath]) {
- return true;
- }
- // remember the current objects and their paths
- if (index1 === -1 && isObject1) {
- objects1.push(value1);
- paths1.push(newPath1);
- }
- if (index2 === -1 && isObject2) {
- objects2.push(value2);
- paths2.push(newPath2);
- }
- // remember that the current objects are already compared
- if (isObject1 && isObject2) {
- compared[combinedPath] = true;
- }
- // End of cyclic logic
- // neither value1 nor value2 is a cycle
- // continue with next level
- return deepEqual(value1, value2, newPath1, newPath2);
- });
- })(first, second, "$1", "$2");
- }
- deepEqualCyclic.use = function(match) {
- return function(a, b) {
- return deepEqualCyclic(a, b, match);
- };
- };
- module.exports = deepEqualCyclic;
|