From 5690c87eb299a8e3c41b31b173374de760502de3 Mon Sep 17 00:00:00 2001 From: Yarchik Date: Thu, 25 Jun 2026 19:31:56 +0100 Subject: [PATCH] fix: compare own enumerable properties on Date, RegExp, and boxed primitives Date, RegExp, and boxed-primitive (String/Number/Boolean) instances were compared only by their internal value (valueOf/toString), so two such objects with differing own enumerable data properties were reported as deeply equal. This contradicts the documented rule that all own and inherited enumerable properties are considered (only Error is exempt) and diverges from node's util.isDeepStrictEqual. AND the type-specific value check with the existing objectEqual own-key comparison so extra own properties are taken into account while valueOf, -0/NaN, and the Error special-case semantics are preserved. --- index.js | 10 +++++++--- test/index.js | 40 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 47 insertions(+), 3 deletions(-) diff --git a/index.js b/index.js index 8e5e333..40d0dc8 100644 --- a/index.js +++ b/index.js @@ -215,8 +215,10 @@ function extensiveDeepEqualByType(leftHandOperand, rightHandOperand, leftHandTyp case 'Number': case 'Boolean': case 'Date': - // If these types are their instance types (e.g. `new Number`) then re-deepEqual against their values - return deepEqual(leftHandOperand.valueOf(), rightHandOperand.valueOf()); + // If these types are their instance types (e.g. `new Number`) then re-deepEqual against their values. + // Own enumerable properties are still compared so two instances carrying differing data are not equal. + return deepEqual(leftHandOperand.valueOf(), rightHandOperand.valueOf()) && + objectEqual(leftHandOperand, rightHandOperand, options); case 'Promise': case 'Symbol': case 'function': @@ -238,7 +240,9 @@ function extensiveDeepEqualByType(leftHandOperand, rightHandOperand, leftHandTyp case 'Array': return iterableEqual(leftHandOperand, rightHandOperand, options); case 'RegExp': - return regexpEqual(leftHandOperand, rightHandOperand); + // Own enumerable properties are still compared so two regexes carrying differing data are not equal. + return regexpEqual(leftHandOperand, rightHandOperand) && + objectEqual(leftHandOperand, rightHandOperand, options); case 'Generator': return generatorEqual(leftHandOperand, rightHandOperand, options); case 'DataView': diff --git a/test/index.js b/test/index.js index c6d4c33..a50b715 100644 --- a/test/index.js +++ b/test/index.js @@ -30,6 +30,14 @@ describe('Generic', function () { assert(eql('x', 'y') === false, 'eql("x", "y") === false'); }); + it('returns false for instances with different own enumerable properties', function () { + var strA = new String('x'); + strA.foo = 1; + var strB = new String('x'); + strB.foo = 2; + assert(eql(strA, strB) === false, 'eql(new String("x") {foo:1}, new String("x") {foo:2}) === false'); + }); + }); describe('booleans', function () { @@ -63,6 +71,14 @@ describe('Generic', function () { assert(eql(true, Boolean(false)) === false, 'eql(true, Boolean(false)) === false'); }); + it('returns false for instances with different own enumerable properties', function () { + var boolA = new Boolean(true); + boolA.foo = 1; + var boolB = new Boolean(true); + boolB.foo = 2; + assert(eql(boolA, boolB) === false, 'eql(new Boolean(true) {foo:1}, new Boolean(true) {foo:2}) === false'); + }); + }); describe('null', function () { @@ -129,6 +145,14 @@ describe('Generic', function () { 'eql(new Number(-Infinity), new Number(+Infinity)) === false'); }); + it('returns false for instances with different own enumerable properties', function () { + var numA = new Number(1); + numA.foo = 1; + var numB = new Number(1); + numB.foo = 2; + assert(eql(numA, numB) === false, 'eql(new Number(1) {foo:1}, new Number(1) {foo:2}) === false'); + }); + }); describe('dates', function () { @@ -148,6 +172,14 @@ describe('Generic', function () { 'eql(dateA, new Date(dateA.getTime() + 1)) === false'); }); + it('returns false given two dates with different own enumerable properties', function () { + var dateA = new Date(0); + dateA.foo = 1; + var dateB = new Date(0); + dateB.foo = 2; + assert(eql(dateA, dateB) === false, 'eql(new Date(0) {foo:1}, new Date(0) {foo:2}) === false'); + }); + }); describe('regexp', function () { @@ -166,6 +198,14 @@ describe('Generic', function () { assert(eql(/^/m, /^/i) === false, 'eql(/^/m, /^/i) === false'); }); + it('returns false given two regexes with different own enumerable properties', function () { + var regexpA = /x/; + regexpA.foo = 1; + var regexpB = /x/; + regexpB.foo = 2; + assert(eql(regexpA, regexpB) === false, 'eql(/x/ {foo:1}, /x/ {foo:2}) === false'); + }); + }); describe('empty types', function () {