Skip to content

Commit

Permalink
[Fix] handle circular references
Browse files Browse the repository at this point in the history
 - See nodejs/node@d3aafd0
 - Fixes #19
 - Will allow tape-testing/tape#434 to be fixed
  • Loading branch information
ljharb committed Dec 1, 2019
1 parent 9859584 commit 8e04219
Show file tree
Hide file tree
Showing 3 changed files with 60 additions and 8 deletions.
3 changes: 2 additions & 1 deletion .eslintrc
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,9 @@
"eqeqeq": 1,
"func-style": [2, "declaration"],
"indent": [2, 2],
"max-params": [2, 3],
"max-params": [2, 4],
"max-statements-per-line": [2, { "max": 2 }],
"operator-linebreak": [2, "before"],
"strict": 1,
},
"globals": {
Expand Down
31 changes: 24 additions & 7 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ var $mapHas = callBound('Map.prototype.has', true);
var $mapGet = callBound('Map.prototype.get', true);
var $setHas = callBound('Set.prototype.has', true);

function deepEqual(actual, expected, options) {
function internalDeepEqual(actual, expected, options, memos) {
var opts = options || {};

// 7.1. All identical values are equivalent, as determined by ===.
Expand Down Expand Up @@ -46,8 +46,20 @@ function deepEqual(actual, expected, options) {
* corresponding key, and an identical 'prototype' property. Note: this
* accounts for both named and indexed properties on Arrays.
*/
// see https://github.com/nodejs/node/commit/d3aafd02efd3a403d646a3044adcf14e63a88d32 for memos inspiration

var actualIndex = memos.actual.indexOf(actual);
if (actualIndex !== -1) {
if (actualIndex === memos.expected.indexOf(expected)) {
return true;
}
}

memos.actual.push(actual);
memos.expected.push(expected);

// eslint-disable-next-line no-use-before-define
return objEquiv(actual, expected, opts);
return objEquiv(actual, expected, opts, memos);
}

function isUndefinedOrNull(value) {
Expand All @@ -67,7 +79,7 @@ function isBuffer(x) {
return true;
}

function objEquiv(a, b, opts) {
function objEquiv(a, b, opts, memos) {
/* eslint max-statements: [2, 100], max-lines-per-function: [2, 120], max-depth: [2, 5] */
var i, key;

Expand Down Expand Up @@ -141,7 +153,7 @@ function objEquiv(a, b, opts) {
// equivalent values for every corresponding key, and ~~~possibly expensive deep test
for (i = ka.length - 1; i >= 0; i--) {
key = ka[i];
if (!deepEqual(a[key], b[key], opts)) { return false; }
if (!internalDeepEqual(a[key], b[key], opts, memos)) { return false; }
}

var aCollection = whichCollection(a);
Expand All @@ -160,11 +172,14 @@ function objEquiv(a, b, opts) {
while ((resultA = iA.next()) && (resultB = iB.next()) && !resultA.done && !resultB.done) {
if (!$mapHas(a, resultB.value[0]) || !$mapHas(b, resultA.value[0])) { return false; }
if (resultA.value[0] === resultB.value[0]) { // optimization: keys are the same, no need to look up values
if (!deepEqual(resultA.value[1], resultB.value[1], opts)) { return false; }
if (!internalDeepEqual(resultA.value[1], resultB.value[1], opts, memos)) { return false; }
} else {
aWithBKey = $mapGet(a, resultB.value[0]);
bWithAKey = $mapGet(b, resultA.value[0]);
if (!deepEqual(resultA.value[1], bWithAKey, opts) || !deepEqual(resultB.value[1], aWithBKey, opts)) {
if (
!internalDeepEqual(resultA.value[1], bWithAKey, opts, memos)
|| !internalDeepEqual(resultB.value[1], aWithBKey, opts, memos)
) {
return false;
}
}
Expand All @@ -182,4 +197,6 @@ function objEquiv(a, b, opts) {
return true;
}

module.exports = deepEqual;
module.exports = function deepEqual(a, b, opts) {
return internalDeepEqual(a, b, opts, { actual: [], expected: [] });
};
34 changes: 34 additions & 0 deletions test/cmp.js
Original file line number Diff line number Diff line change
Expand Up @@ -625,3 +625,37 @@ test('fake arrays: extra keys will be tested', { skip: [].__proto__ !== Array.pr
t.deepEqualTest(a, [1, 1], 'fake and real array with same contents and [[Prototype]]', false, false);
t.end();
});

test('circular references', function (t) {
var b = {};
b.b = b;

var c = {};
c.b = c;

t.deepEqualTest(
b,
c,
'two self-referencing objects',
true,
true
);

var d = {};
d.a = 1;
d.b = d;

var e = {};
e.a = 1;
e.b = e.a;

t.deepEqualTest(
d,
e,
'two deeply self-referencing objects',
false,
false
);

t.end();
});

0 comments on commit 8e04219

Please sign in to comment.