2323// UTILITY
2424const compare = process . binding ( 'buffer' ) . compare ;
2525const util = require ( 'util' ) ;
26+ const objectToString = require ( 'internal/util' ) . objectToString ;
2627const Buffer = require ( 'buffer' ) . Buffer ;
27- const pToString = ( obj ) => Object . prototype . toString . call ( obj ) ;
2828
2929// The assert module provides functions that throw
3030// AssertionError's when particular conditions are not met. The
@@ -136,117 +136,177 @@ assert.deepStrictEqual = function deepStrictEqual(actual, expected, message) {
136136 }
137137} ;
138138
139+ function areSimilarRegExps ( a , b ) {
140+ return a . source === b . source && a . flags === b . flags ;
141+ }
142+
143+ function areSimilarTypedArrays ( a , b ) {
144+ return compare ( Buffer . from ( a . buffer ,
145+ a . byteOffset ,
146+ a . byteLength ) ,
147+ Buffer . from ( b . buffer ,
148+ b . byteOffset ,
149+ b . byteLength ) ) === 0 ;
150+ }
151+
152+ function isNullOrNonObj ( object ) {
153+ return object === null || typeof object !== 'object' ;
154+ }
155+
156+ function isFloatTypedArrayTag ( tag ) {
157+ return tag === '[object Float32Array]' || tag === '[object Float64Array]' ;
158+ }
159+
160+ function isArguments ( tag ) {
161+ return tag === '[object Arguments]' ;
162+ }
163+
139164function _deepEqual ( actual , expected , strict , memos ) {
140165 // All identical values are equivalent, as determined by ===.
141166 if ( actual === expected ) {
142167 return true ;
168+ }
143169
144- // If both values are instances of buffers, equivalence is
145- // determined by comparing the values and ensuring the result
146- // === 0.
147- } else if ( actual instanceof Buffer && expected instanceof Buffer ) {
148- return compare ( actual , expected ) === 0 ;
149-
150- // If the expected value is a Date object, the actual value is
151- // equivalent if it is also a Date object that refers to the same time.
152- } else if ( util . isDate ( actual ) && util . isDate ( expected ) ) {
153- return actual . getTime ( ) === expected . getTime ( ) ;
154-
155- // If the expected value is a RegExp object, the actual value is
156- // equivalent if it is also a RegExp object with the same source and
157- // properties (`global`, `multiline`, `lastIndex`, `ignoreCase`).
158- } else if ( util . isRegExp ( actual ) && util . isRegExp ( expected ) ) {
159- return actual . source === expected . source &&
160- actual . global === expected . global &&
161- actual . multiline === expected . multiline &&
162- actual . lastIndex === expected . lastIndex &&
163- actual . ignoreCase === expected . ignoreCase ;
164-
165- // If both values are primitives, equivalence is determined by
166- // == or, if checking for strict equivalence, ===.
167- } else if ( ( actual === null || typeof actual !== 'object' ) &&
168- ( expected === null || typeof expected !== 'object' ) ) {
170+ // For primitives / functions
171+ // (determined by typeof value !== 'object'),
172+ // or null, equivalence is determined by === or ==.
173+ if ( isNullOrNonObj ( actual ) && isNullOrNonObj ( expected ) ) {
169174 return strict ? actual === expected : actual == expected ;
175+ }
170176
171- // If both values are instances of typed arrays, wrap their underlying
172- // ArrayBuffers in a Buffer to increase performance.
173- // This optimization requires the arrays to have the same type as checked by
174- // Object.prototype.toString (pToString). Never perform binary
175- // comparisons for Float*Arrays, though, since +0 === -0 is true despite the
176- // two values' bit patterns not being identical.
177- } else if ( ArrayBuffer . isView ( actual ) && ArrayBuffer . isView ( expected ) &&
178- pToString ( actual ) === pToString ( expected ) &&
179- ! ( actual instanceof Float32Array ||
180- actual instanceof Float64Array ) ) {
181- return compare ( Buffer . from ( actual . buffer ,
182- actual . byteOffset ,
183- actual . byteLength ) ,
184- Buffer . from ( expected . buffer ,
185- expected . byteOffset ,
186- expected . byteLength ) ) === 0 ;
187-
188- // For all other Object pairs, including Array objects, equivalence is
189- // determined by having the same number of owned properties (as verified
190- // with Object.prototype.hasOwnProperty.call), the same set of keys
191- // (although not necessarily the same order), equivalent values for every
192- // corresponding key, and an identical 'prototype' property. Note: this
193- // accounts for both named and indexed properties on Arrays.
194- } else {
195- memos = memos || { actual : [ ] , expected : [ ] } ;
177+ // If they bypass the previous check, then at least
178+ // one of them must be an non-null object.
179+ // If the other one is null or undefined, they must not be equal.
180+ if ( actual === null || actual === undefined ||
181+ expected === null || expected === undefined )
182+ return false ;
196183
197- const actualIndex = memos . actual . indexOf ( actual ) ;
198- if ( actualIndex !== - 1 ) {
199- if ( actualIndex === memos . expected . indexOf ( expected ) ) {
200- return true ;
201- }
184+ // Notes: Type tags are historical [[Class]] properties that can be set by
185+ // FunctionTemplate::SetClassName() in C++ or Symbol.toStringTag in JS
186+ // and retrieved using Object.prototype.toString.call(obj) in JS
187+ // See https://tc39.github.io/ecma262/#sec-object.prototype.tostring
188+ // for a list of tags pre-defined in the spec.
189+ // There are some unspecified tags in the wild too (e.g. typed array tags).
190+ // Since tags can be altered, they only serve fast failures
191+ const actualTag = objectToString ( actual ) ;
192+ const expectedTag = objectToString ( expected ) ;
193+
194+ // Passing null or undefined to Object.getPrototypeOf() will throw
195+ // so this must done after previous checks.
196+ // For strict comparison, objects should have
197+ // a) The same prototypes.
198+ // b) The same built-in type tags
199+ if ( strict ) {
200+ if ( Object . getPrototypeOf ( actual ) !== Object . getPrototypeOf ( expected ) ) {
201+ return false ;
202202 }
203+ }
203204
204- memos . actual . push ( actual ) ;
205- memos . expected . push ( expected ) ;
205+ // Do fast checks for builtin types.
206+ // If they don't match, they must not be equal.
207+ // If they match, return true for non-strict comparison.
208+ // For strict comparison we need to exam further.
206209
207- return objEquiv ( actual , expected , strict , memos ) ;
210+ // If both values are Date objects,
211+ // check if the time underneath are equal first.
212+ if ( util . isDate ( actual ) && util . isDate ( expected ) ) {
213+ if ( actual . getTime ( ) !== expected . getTime ( ) ) {
214+ return false ;
215+ } else if ( ! strict ) {
216+ return true ; // Skip further checks for non-strict comparison.
217+ }
208218 }
209- }
210219
211- function isArguments ( object ) {
212- return Object . prototype . toString . call ( object ) === '[object Arguments]' ;
213- }
220+ // If both values are RegExp, check if they have
221+ // the same source and flags first
222+ if ( util . isRegExp ( actual ) && util . isRegExp ( expected ) ) {
223+ if ( ! areSimilarRegExps ( actual , expected ) ) {
224+ return false ;
225+ } else if ( ! strict ) {
226+ return true ; // Skip further checks for non-strict comparison.
227+ }
228+ }
214229
215- function objEquiv ( a , b , strict , actualVisitedObjects ) {
216- if ( a === null || a === undefined || b === null || b === undefined )
230+ // Ensure reflexivity of deepEqual with `arguments` objects.
231+ // See https://github.com/nodejs/node-v0.x-archive/pull/7178
232+ if ( isArguments ( actualTag ) !== isArguments ( expectedTag ) ) {
217233 return false ;
234+ }
235+
236+ // Check typed arrays and buffers by comparing the content in their
237+ // underlying ArrayBuffer. This optimization requires that it's
238+ // reasonable to interpret their underlying memory in the same way,
239+ // which is checked by comparing their type tags.
240+ // (e.g. a Uint8Array and a Uint16Array with the same memory content
241+ // could still be different because they will be interpreted differently)
242+ // Never perform binary comparisons for Float*Arrays, though,
243+ // since e.g. +0 === -0 is true despite the two values' bit patterns
244+ // not being identical.
245+ if ( ArrayBuffer . isView ( actual ) && ArrayBuffer . isView ( expected ) &&
246+ actualTag === expectedTag && ! isFloatTypedArrayTag ( actualTag ) ) {
247+ if ( ! areSimilarTypedArrays ( actual , expected ) ) {
248+ return false ;
249+ } else if ( ! strict ) {
250+ return true ; // Skip further checks for non-strict comparison.
251+ }
252+
253+ // Buffer.compare returns true, so actual.length === expected.length
254+ // if they both only contain numeric keys, we don't need to exam further
255+ if ( Object . keys ( actual ) . length === actual . length &&
256+ Object . keys ( expected ) . length === expected . length ) {
257+ return true ;
258+ }
259+ }
260+
261+ // For all other Object pairs, including Array objects,
262+ // equivalence is determined by having:
263+ // a) The same number of owned enumerable properties
264+ // b) The same set of keys/indexes (although not necessarily the same order)
265+ // c) Equivalent values for every corresponding key/index
266+ // Note: this accounts for both named and indexed properties on Arrays.
267+
268+ // Use memos to handle cycles.
269+ memos = memos || { actual : [ ] , expected : [ ] } ;
270+ const actualIndex = memos . actual . indexOf ( actual ) ;
271+ if ( actualIndex !== - 1 ) {
272+ if ( actualIndex === memos . expected . indexOf ( expected ) ) {
273+ return true ;
274+ }
275+ }
276+ memos . actual . push ( actual ) ;
277+ memos . expected . push ( expected ) ;
218278
219- // If one is a primitive, the other must be the same.
279+ return objEquiv ( actual , expected , strict , memos ) ;
280+ }
281+
282+ function objEquiv ( a , b , strict , actualVisitedObjects ) {
283+ // If one of them is a primitive, the other must be the same.
220284 if ( util . isPrimitive ( a ) || util . isPrimitive ( b ) )
221285 return a === b ;
222- if ( strict && Object . getPrototypeOf ( a ) !== Object . getPrototypeOf ( b ) )
223- return false ;
224- const aIsArgs = isArguments ( a ) ;
225- const bIsArgs = isArguments ( b ) ;
226- if ( ( aIsArgs && ! bIsArgs ) || ( ! aIsArgs && bIsArgs ) )
227- return false ;
228- const ka = Object . keys ( a ) ;
229- const kb = Object . keys ( b ) ;
286+
287+ const aKeys = Object . keys ( a ) ;
288+ const bKeys = Object . keys ( b ) ;
230289 var key , i ;
231290
232- // The pair must have the same number of owned properties (keys
233- // incorporates hasOwnProperty).
234- if ( ka . length !== kb . length )
291+ // The pair must have the same number of owned properties
292+ // (keys incorporates hasOwnProperty).
293+ if ( aKeys . length !== bKeys . length )
235294 return false ;
236295
237296 // The pair must have the same set of keys (although not
238297 // necessarily in the same order).
239- ka . sort ( ) ;
240- kb . sort ( ) ;
298+ aKeys . sort ( ) ;
299+ bKeys . sort ( ) ;
241300 // Cheap key test:
242- for ( i = ka . length - 1 ; i >= 0 ; i -- ) {
243- if ( ka [ i ] !== kb [ i ] )
301+ for ( i = aKeys . length - 1 ; i >= 0 ; i -- ) {
302+ if ( aKeys [ i ] !== bKeys [ i ] )
244303 return false ;
245304 }
305+
246306 // The pair must have equivalent values for every corresponding key.
247307 // Possibly expensive deep test:
248- for ( i = ka . length - 1 ; i >= 0 ; i -- ) {
249- key = ka [ i ] ;
308+ for ( i = aKeys . length - 1 ; i >= 0 ; i -- ) {
309+ key = aKeys [ i ] ;
250310 if ( ! _deepEqual ( a [ key ] , b [ key ] , strict , actualVisitedObjects ) )
251311 return false ;
252312 }
@@ -269,7 +329,6 @@ function notDeepStrictEqual(actual, expected, message) {
269329 }
270330}
271331
272-
273332// The strict equality assertion tests strict equality, as determined by ===.
274333// assert.strictEqual(actual, expected, message_opt);
275334
@@ -295,7 +354,7 @@ function expectedException(actual, expected) {
295354 return false ;
296355 }
297356
298- if ( Object . prototype . toString . call ( expected ) === '[object RegExp]' ) {
357+ if ( objectToString ( expected ) === '[object RegExp]' ) {
299358 return expected . test ( actual ) ;
300359 }
301360
0 commit comments