@@ -33,11 +33,6 @@ const errors = require('internal/errors');
33
33
34
34
const assert = module . exports = ok ;
35
35
36
- // At present only the three keys mentioned above are used and
37
- // understood by the spec. Implementations or sub modules can pass
38
- // other keys to the AssertionError's constructor - they will be
39
- // ignored.
40
-
41
36
// All of the following functions must throw an AssertionError
42
37
// when a corresponding condition is not met, with a message that
43
38
// may be undefined if not provided. All assertion methods provide
@@ -112,9 +107,9 @@ function areSimilarRegExps(a, b) {
112
107
return a . source === b . source && a . flags === b . flags ;
113
108
}
114
109
115
- // For small buffers it's faster to compare the buffer in a loop.
116
- // The c++ barrier takes the advantage of the faster compare otherwise.
117
- // 300 was the number after which compare became faster.
110
+ // For small buffers it's faster to compare the buffer in a loop. The c++
111
+ // barrier including the Buffer.from operation takes the advantage of the faster
112
+ // compare otherwise. 300 was the number after which compare became faster.
118
113
function areSimilarTypedArrays ( a , b ) {
119
114
const len = a . byteLength ;
120
115
if ( len !== b . byteLength ) {
@@ -221,21 +216,15 @@ function looseDeepEqual(actual, expected) {
221
216
return false ;
222
217
}
223
218
if ( util . isDate ( actual ) && util . isDate ( expected ) ) {
224
- if ( actual . getTime ( ) !== expected . getTime ( ) ) {
225
- return false ;
226
- }
227
- return true ;
219
+ return actual . getTime ( ) === expected . getTime ( ) ;
228
220
}
229
221
if ( util . isRegExp ( actual ) && util . isRegExp ( expected ) ) {
230
- if ( ! areSimilarRegExps ( actual , expected ) ) {
231
- return false ;
232
- }
233
- return true ;
222
+ return areSimilarRegExps ( actual , expected ) ;
234
223
}
235
224
const actualTag = objectToString ( actual ) ;
236
225
const expectedTag = objectToString ( expected ) ;
237
226
if ( actualTag === expectedTag ) {
238
- if ( ! isFloatTypedArrayTag ( actualTag ) && ! isObjectOrArrayTag ( actualTag ) &&
227
+ if ( ! isObjectOrArrayTag ( actualTag ) && ! isFloatTypedArrayTag ( actualTag ) &&
239
228
ArrayBuffer . isView ( actual ) ) {
240
229
return areSimilarTypedArrays ( actual , expected ) ;
241
230
}
@@ -314,29 +303,38 @@ function innerDeepEqual(actual, expected, strict, memos) {
314
303
return areEq ;
315
304
}
316
305
317
- function setHasSimilarElement ( set , val1 , usedEntries , strict , memo ) {
318
- if ( set . has ( val1 ) ) {
319
- if ( usedEntries !== null )
320
- usedEntries . add ( val1 ) ;
321
- return true ;
322
- }
323
-
324
- // In strict mode the only things which can match a primitive or a function
325
- // will already be detected by set.has(val1).
326
- if ( strict && ( typeof val1 !== 'object' || val1 === null ) )
327
- return false ;
328
-
329
- // Otherwise go looking.
306
+ function setHasEqualElement ( set , val1 , strict , memo ) {
307
+ // Go looking.
330
308
for ( const val2 of set ) {
331
- if ( ! usedEntries . has ( val2 ) && innerDeepEqual ( val1 , val2 , strict , memo ) ) {
332
- usedEntries . add ( val2 ) ;
309
+ if ( innerDeepEqual ( val1 , val2 , strict , memo ) ) {
310
+ // Remove the matching element to make sure we do not check that again.
311
+ set . delete ( val2 ) ;
333
312
return true ;
334
313
}
335
314
}
336
315
337
316
return false ;
338
317
}
339
318
319
+ // Note: we actually run this multiple times for each loose key!
320
+ // This is done to prevent slowing down the average case.
321
+ function setHasLoosePrim ( a , b , val ) {
322
+ const altValues = findLooseMatchingPrimitives ( val ) ;
323
+ if ( altValues === undefined )
324
+ return false ;
325
+
326
+ var matches = 1 ;
327
+ for ( var i = 0 ; i < altValues . length ; i ++ ) {
328
+ if ( b . has ( altValues [ i ] ) ) {
329
+ matches -- ;
330
+ }
331
+ if ( a . has ( altValues [ i ] ) ) {
332
+ matches ++ ;
333
+ }
334
+ }
335
+ return matches === 0 ;
336
+ }
337
+
340
338
function setEquiv ( a , b , strict , memo ) {
341
339
// This code currently returns false for this pair of sets:
342
340
// assert.deepEqual(new Set(['1', 1]), new Set([1]))
@@ -348,59 +346,124 @@ function setEquiv(a, b, strict, memo) {
348
346
if ( a . size !== b . size )
349
347
return false ;
350
348
351
- // This is a set of the entries in b which have been consumed in our pairwise
352
- // comparison .
353
- //
349
+ // This is a lazily initiated Set of entries which have to be compared
350
+ // pairwise .
351
+ var set = null ;
354
352
// When the sets contain only value types (eg, lots of numbers), and we're in
355
- // strict mode, we don't need to match off the entries in a pairwise way. In
356
- // that case this initialization is done lazily to avoid the allocation &
357
- // bookkeeping cost. Unfortunately, we can't get away with that in non-strict
358
- // mode.
359
- let usedEntries = strict === true ? null : new Set ( ) ;
360
-
361
- for ( const val1 of a ) {
362
- if ( usedEntries === null && typeof val1 === 'object' )
363
- usedEntries = new Set ( ) ;
364
-
365
- // If the value doesn't exist in the second set by reference, and its an
366
- // object or an array we'll need to go hunting for something thats
367
- // deep-equal to it. Note that this is O(n^2) complexity, and will get
368
- // slower if large, very similar sets / maps are nested inside.
369
- // Unfortunately there's no real way around this.
370
- if ( ! setHasSimilarElement ( b , val1 , usedEntries , strict , memo ) )
353
+ // strict mode or if all entries strictly match, we don't need to match the
354
+ // entries in a pairwise way. In that case this initialization is done lazily
355
+ // to avoid the allocation & bookkeeping cost.
356
+ for ( const val of a ) {
357
+ // Note: Checking for the objects first improves the performance for object
358
+ // heavy sets but it is a minor slow down for primitives. As they are fast
359
+ // to check this improves the worst case scenario instead.
360
+ if ( typeof val === 'object' && val !== null ) {
361
+ if ( set === null ) {
362
+ set = new Set ( ) ;
363
+ }
364
+ // If the specified value doesn't exist in the second set its an not null
365
+ // object (or non strict only: a not matching primitive) we'll need to go
366
+ // hunting for something thats deep-(strict-)equal to it. To make this
367
+ // O(n log n) complexity we have to copy these values in a new set first.
368
+ set . add ( val ) ;
369
+ } else if ( ! b . has ( val ) && ( strict || ! setHasLoosePrim ( a , b , val ) ) ) {
371
370
return false ;
371
+ }
372
+ }
373
+
374
+ if ( set !== null ) {
375
+ for ( const val of b ) {
376
+ // In non-strict-mode we have to check if a primitive value is already
377
+ // matching and only if it's not, go hunting for it.
378
+ if ( typeof val === 'object' && val !== null ) {
379
+ if ( ! setHasEqualElement ( set , val , strict , memo ) )
380
+ return false ;
381
+ } else if ( ! a . has ( val ) && ( strict || ! setHasLoosePrim ( b , a , val ) ) ) {
382
+ return false ;
383
+ }
384
+ }
372
385
}
373
386
374
387
return true ;
375
388
}
376
389
377
- function mapHasSimilarEntry ( map , key1 , item1 , usedEntries , strict , memo ) {
378
- // To be able to handle cases like:
379
- // Map([[1, 'a'], ['1', 'b']]) vs Map([['1', 'a'], [1, 'b']])
380
- // or:
381
- // Map([[{}, 'a'], [{}, 'b']]) vs Map([[{}, 'b'], [{}, 'a']])
382
- // ... we need to consider *all* matching keys, not just the first we find.
390
+ function findLooseMatchingPrimitives ( prim ) {
391
+ var values , number ;
392
+ switch ( typeof prim ) {
393
+ case 'number' :
394
+ values = [ '' + prim ] ;
395
+ if ( prim === 1 || prim === 0 )
396
+ values . push ( Boolean ( prim ) ) ;
397
+ return values ;
398
+ case 'string' :
399
+ number = + prim ;
400
+ if ( '' + number === prim ) {
401
+ values = [ number ] ;
402
+ if ( number === 1 || number === 0 )
403
+ values . push ( Boolean ( number ) ) ;
404
+ }
405
+ return values ;
406
+ case 'undefined' :
407
+ return [ null ] ;
408
+ case 'object' : // Only pass in null as object!
409
+ return [ undefined ] ;
410
+ case 'boolean' :
411
+ number = + prim ;
412
+ return [ number , '' + number ] ;
413
+ }
414
+ }
383
415
384
- // This check is not strictly necessary. The loop performs this check, but
385
- // doing it here improves performance of the common case when reference-equal
386
- // keys exist (which includes all primitive-valued keys).
387
- if ( map . has ( key1 ) && innerDeepEqual ( item1 , map . get ( key1 ) , strict , memo ) ) {
388
- if ( usedEntries !== null )
389
- usedEntries . add ( key1 ) ;
390
- return true ;
416
+ // This is a ugly but relatively fast way to determine if a loose equal entry
417
+ // actually has a correspondent matching entry. Otherwise checking for such
418
+ // values would be way more expensive (O(n^2)).
419
+ // Note: we actually run this multiple times for each loose key!
420
+ // This is done to prevent slowing down the average case.
421
+ function mapHasLoosePrim ( a , b , key1 , memo , item1 , item2 ) {
422
+ const altKeys = findLooseMatchingPrimitives ( key1 ) ;
423
+ if ( altKeys === undefined )
424
+ return false ;
425
+
426
+ const setA = new Set ( ) ;
427
+ const setB = new Set ( ) ;
428
+
429
+ var keyCount = 1 ;
430
+
431
+ setA . add ( item1 ) ;
432
+ if ( b . has ( key1 ) ) {
433
+ keyCount -- ;
434
+ setB . add ( item2 ) ;
391
435
}
392
436
393
- if ( strict && ( typeof key1 !== 'object' || key1 === null ) )
437
+ for ( var i = 0 ; i < altKeys . length ; i ++ ) {
438
+ const key2 = altKeys [ i ] ;
439
+ if ( a . has ( key2 ) ) {
440
+ keyCount ++ ;
441
+ setA . add ( a . get ( key2 ) ) ;
442
+ }
443
+ if ( b . has ( key2 ) ) {
444
+ keyCount -- ;
445
+ setB . add ( b . get ( key2 ) ) ;
446
+ }
447
+ }
448
+ if ( keyCount !== 0 || setA . size !== setB . size )
394
449
return false ;
395
450
396
- for ( const [ key2 , item2 ] of map ) {
397
- // The first part is checked above.
398
- if ( key2 === key1 || usedEntries . has ( key2 ) )
399
- continue ;
451
+ for ( const val of setA ) {
452
+ if ( ! setHasEqualElement ( setB , val , false , memo ) )
453
+ return false ;
454
+ }
455
+
456
+ return true ;
457
+ }
400
458
459
+ function mapHasEqualEntry ( set , map , key1 , item1 , strict , memo ) {
460
+ // To be able to handle cases like:
461
+ // Map([[{}, 'a'], [{}, 'b']]) vs Map([[{}, 'b'], [{}, 'a']])
462
+ // ... we need to consider *all* matching keys, not just the first we find.
463
+ for ( const key2 of set ) {
401
464
if ( innerDeepEqual ( key1 , key2 , strict , memo ) &&
402
- innerDeepEqual ( item1 , item2 , strict , memo ) ) {
403
- usedEntries . add ( key2 ) ;
465
+ innerDeepEqual ( item1 , map . get ( key2 ) , strict , memo ) ) {
466
+ set . delete ( key2 ) ;
404
467
return true ;
405
468
}
406
469
}
@@ -411,21 +474,45 @@ function mapHasSimilarEntry(map, key1, item1, usedEntries, strict, memo) {
411
474
function mapEquiv ( a , b , strict , memo ) {
412
475
// Caveat: In non-strict mode, this implementation does not handle cases
413
476
// where maps contain two equivalent-but-not-reference-equal keys.
414
- //
415
- // For example, maps like this are currently considered not equivalent:
416
477
if ( a . size !== b . size )
417
478
return false ;
418
479
419
- let usedEntries = strict === true ? null : new Set ( ) ;
420
-
421
- for ( const [ key , item ] of a ) {
422
- if ( usedEntries === null && typeof key === 'object' )
423
- usedEntries = new Set ( ) ;
424
-
425
- // Just like setEquiv above, this hunt makes this function O(n^2) when
426
- // using objects and lists as keys
427
- if ( ! mapHasSimilarEntry ( b , key , item , usedEntries , strict , memo ) )
480
+ var set = null ;
481
+
482
+ for ( const [ key , item1 ] of a ) {
483
+ // By directly retrieving the value we prevent another b.has(key) check in
484
+ // almost all possible cases.
485
+ const item2 = b . get ( key ) ;
486
+ if ( item2 === undefined ) {
487
+ // Just like setEquiv above but in addition we have to make sure the
488
+ // values are also equal.
489
+ if ( typeof key === 'object' && key !== null ) {
490
+ if ( set === null ) {
491
+ set = new Set ( ) ;
492
+ }
493
+ set . add ( key ) ;
494
+ // Note: we do not have to pass memo in this case as at least one item
495
+ // is undefined.
496
+ } else if ( ( ! innerDeepEqual ( item1 , item2 , strict ) || ! b . has ( key ) ) &&
497
+ ( strict || ! mapHasLoosePrim ( a , b , key , memo , item1 ) ) ) {
498
+ return false ;
499
+ }
500
+ } else if ( ! innerDeepEqual ( item1 , item2 , strict , memo ) &&
501
+ ( strict || ! mapHasLoosePrim ( a , b , key , memo , item1 , item2 ) ) ) {
428
502
return false ;
503
+ }
504
+ }
505
+
506
+ if ( set !== null ) {
507
+ for ( const [ key , item ] of b ) {
508
+ if ( typeof key === 'object' && key !== null ) {
509
+ if ( ! mapHasEqualEntry ( set , a , key , item , strict , memo ) )
510
+ return false ;
511
+ } else if ( ! a . has ( key ) &&
512
+ ( strict || ! mapHasLoosePrim ( b , a , key , memo , item ) ) ) {
513
+ return false ;
514
+ }
515
+ }
429
516
}
430
517
431
518
return true ;
@@ -437,12 +524,10 @@ function objEquiv(a, b, strict, keys, memos) {
437
524
if ( isSet ( a ) ) {
438
525
if ( ! isSet ( b ) || ! setEquiv ( a , b , strict , memos ) )
439
526
return false ;
440
- } else if ( isSet ( b ) ) {
441
- return false ;
442
527
} else if ( isMap ( a ) ) {
443
528
if ( ! isMap ( b ) || ! mapEquiv ( a , b , strict , memos ) )
444
529
return false ;
445
- } else if ( isMap ( b ) ) {
530
+ } else if ( isSet ( b ) || isMap ( b ) ) {
446
531
return false ;
447
532
}
448
533
0 commit comments