21
21
'use strict';
22
22
23
23
const {
24
+ ArrayBufferIsView,
25
+ ArrayBufferPrototypeGetByteLength,
24
26
ArrayFrom,
25
27
ArrayIsArray,
26
28
ArrayPrototypeIndexOf,
27
29
ArrayPrototypeJoin,
28
30
ArrayPrototypePush,
29
31
ArrayPrototypeSlice,
32
+ DataViewPrototypeGetBuffer,
33
+ DataViewPrototypeGetByteLength,
34
+ DataViewPrototypeGetByteOffset,
30
35
Error,
31
36
FunctionPrototypeCall,
32
- MapPrototypeDelete,
33
37
MapPrototypeGet,
38
+ MapPrototypeGetSize,
34
39
MapPrototypeHas,
35
- MapPrototypeSet,
36
40
NumberIsNaN,
37
41
ObjectAssign,
38
42
ObjectIs,
39
43
ObjectKeys,
40
44
ObjectPrototypeIsPrototypeOf,
45
+ ObjectPrototypeToString,
41
46
ReflectApply,
42
47
ReflectHas,
43
48
ReflectOwnKeys,
44
49
RegExpPrototypeExec,
50
+ SafeArrayIterator,
45
51
SafeMap,
46
52
SafeSet,
47
53
SafeWeakSet,
54
+ SetPrototypeGetSize,
48
55
String,
49
56
StringPrototypeIndexOf,
50
57
StringPrototypeSlice,
51
58
StringPrototypeSplit,
52
59
SymbolIterator,
60
+ TypedArrayPrototypeGetLength,
61
+ Uint8Array,
53
62
} = primordials;
54
63
55
64
const {
@@ -65,6 +74,8 @@ const AssertionError = require('internal/assert/assertion_error');
65
74
const { inspect } = require('internal/util/inspect');
66
75
const { Buffer } = require('buffer');
67
76
const {
77
+ isArrayBuffer,
78
+ isDataView,
68
79
isKeyObject,
69
80
isPromise,
70
81
isRegExp,
@@ -73,6 +84,8 @@ const {
73
84
isDate,
74
85
isWeakSet,
75
86
isWeakMap,
87
+ isSharedArrayBuffer,
88
+ isAnyArrayBuffer,
76
89
} = require('internal/util/types');
77
90
const { isError, deprecate, emitExperimentalWarning } = require('internal/util');
78
91
const { innerOk } = require('internal/assert/utils');
@@ -369,9 +382,161 @@ function isSpecial(obj) {
369
382
}
370
383
371
384
const typesToCallDeepStrictEqualWith = [
372
- isKeyObject, isWeakSet, isWeakMap, Buffer.isBuffer,
385
+ isKeyObject, isWeakSet, isWeakMap, Buffer.isBuffer, isSharedArrayBuffer,
373
386
];
374
387
388
+ function compareMaps(actual, expected, comparedObjects) {
389
+ if (MapPrototypeGetSize(actual) !== MapPrototypeGetSize(expected)) {
390
+ return false;
391
+ }
392
+ const safeIterator = FunctionPrototypeCall(SafeMap.prototype[SymbolIterator], actual);
393
+
394
+ comparedObjects ??= new SafeWeakSet();
395
+
396
+ for (const { 0: key, 1: val } of safeIterator) {
397
+ if (!MapPrototypeHas(expected, key)) {
398
+ return false;
399
+ }
400
+ if (!compareBranch(val, MapPrototypeGet(expected, key), comparedObjects)) {
401
+ return false;
402
+ }
403
+ }
404
+ return true;
405
+ }
406
+
407
+ function partiallyCompareArrayBuffersOrViews(actual, expected) {
408
+ let actualView, expectedView, expectedViewLength;
409
+
410
+ if (!ArrayBufferIsView(actual)) {
411
+ let actualViewLength;
412
+
413
+ if (isArrayBuffer(actual) && isArrayBuffer(expected)) {
414
+ actualViewLength = ArrayBufferPrototypeGetByteLength(actual);
415
+ expectedViewLength = ArrayBufferPrototypeGetByteLength(expected);
416
+ } else if (isSharedArrayBuffer(actual) && isSharedArrayBuffer(expected)) {
417
+ actualViewLength = actual.byteLength;
418
+ expectedViewLength = expected.byteLength;
419
+ } else {
420
+ // Cannot compare ArrayBuffers with SharedArrayBuffers
421
+ return false;
422
+ }
423
+
424
+ if (expectedViewLength > actualViewLength) {
425
+ return false;
426
+ }
427
+ actualView = new Uint8Array(actual);
428
+ expectedView = new Uint8Array(expected);
429
+
430
+ } else if (isDataView(actual)) {
431
+ if (!isDataView(expected)) {
432
+ return false;
433
+ }
434
+ const actualByteLength = DataViewPrototypeGetByteLength(actual);
435
+ expectedViewLength = DataViewPrototypeGetByteLength(expected);
436
+ if (expectedViewLength > actualByteLength) {
437
+ return false;
438
+ }
439
+
440
+ actualView = new Uint8Array(
441
+ DataViewPrototypeGetBuffer(actual),
442
+ DataViewPrototypeGetByteOffset(actual),
443
+ actualByteLength,
444
+ );
445
+ expectedView = new Uint8Array(
446
+ DataViewPrototypeGetBuffer(expected),
447
+ DataViewPrototypeGetByteOffset(expected),
448
+ expectedViewLength,
449
+ );
450
+ } else {
451
+ if (ObjectPrototypeToString(actual) !== ObjectPrototypeToString(expected)) {
452
+ return false;
453
+ }
454
+ actualView = actual;
455
+ expectedView = expected;
456
+ expectedViewLength = TypedArrayPrototypeGetLength(expected);
457
+
458
+ if (expectedViewLength > TypedArrayPrototypeGetLength(actual)) {
459
+ return false;
460
+ }
461
+ }
462
+
463
+ for (let i = 0; i < expectedViewLength; i++) {
464
+ if (actualView[i] !== expectedView[i]) {
465
+ return false;
466
+ }
467
+ }
468
+
469
+ return true;
470
+ }
471
+
472
+ function partiallyCompareSets(actual, expected, comparedObjects) {
473
+ if (SetPrototypeGetSize(expected) > SetPrototypeGetSize(actual)) {
474
+ return false; // `expected` can't be a subset if it has more elements
475
+ }
476
+
477
+ if (isDeepEqual === undefined) lazyLoadComparison();
478
+
479
+ const actualArray = ArrayFrom(FunctionPrototypeCall(SafeSet.prototype[SymbolIterator], actual));
480
+ const expectedIterator = FunctionPrototypeCall(SafeSet.prototype[SymbolIterator], expected);
481
+ const usedIndices = new SafeSet();
482
+
483
+ expectedIteration: for (const expectedItem of expectedIterator) {
484
+ for (let actualIdx = 0; actualIdx < actualArray.length; actualIdx++) {
485
+ if (!usedIndices.has(actualIdx) && isDeepStrictEqual(actualArray[actualIdx], expectedItem)) {
486
+ usedIndices.add(actualIdx);
487
+ continue expectedIteration;
488
+ }
489
+ }
490
+ return false;
491
+ }
492
+
493
+ return true;
494
+ }
495
+
496
+ function partiallyCompareArrays(actual, expected, comparedObjects) {
497
+ if (expected.length > actual.length) {
498
+ return false;
499
+ }
500
+
501
+ if (isDeepEqual === undefined) lazyLoadComparison();
502
+
503
+ // Create a map to count occurrences of each element in the expected array
504
+ const expectedCounts = new SafeMap();
505
+ for (const expectedItem of expected) {
506
+ let found = false;
507
+ for (const { 0: key, 1: count } of expectedCounts) {
508
+ if (isDeepStrictEqual(key, expectedItem)) {
509
+ expectedCounts.set(key, count + 1);
510
+ found = true;
511
+ break;
512
+ }
513
+ }
514
+ if (!found) {
515
+ expectedCounts.set(expectedItem, 1);
516
+ }
517
+ }
518
+
519
+ const safeActual = new SafeArrayIterator(actual);
520
+
521
+ // Create a map to count occurrences of relevant elements in the actual array
522
+ for (const actualItem of safeActual) {
523
+ for (const { 0: key, 1: count } of expectedCounts) {
524
+ if (isDeepStrictEqual(key, actualItem)) {
525
+ if (count === 1) {
526
+ expectedCounts.delete(key);
527
+ } else {
528
+ expectedCounts.set(key, count - 1);
529
+ }
530
+ break;
531
+ }
532
+ }
533
+ }
534
+
535
+ const { size } = expectedCounts;
536
+ expectedCounts.clear();
537
+ return size === 0;
538
+ }
539
+
375
540
/**
376
541
* Compares two objects or values recursively to check if they are equal.
377
542
* @param {any} actual - The actual value to compare.
@@ -388,22 +553,16 @@ function compareBranch(
388
553
) {
389
554
// Check for Map object equality
390
555
if (isMap(actual) && isMap(expected)) {
391
- if (actual.size !== expected.size) {
392
- return false;
393
- }
394
- const safeIterator = FunctionPrototypeCall(SafeMap.prototype[SymbolIterator], actual);
395
-
396
- comparedObjects ??= new SafeWeakSet();
556
+ return compareMaps(actual, expected, comparedObjects);
557
+ }
397
558
398
- for (const { 0: key, 1: val } of safeIterator) {
399
- if (!MapPrototypeHas(expected, key)) {
400
- return false;
401
- }
402
- if (!compareBranch(val, MapPrototypeGet(expected, key), comparedObjects)) {
403
- return false;
404
- }
405
- }
406
- return true;
559
+ if (
560
+ ArrayBufferIsView(actual) ||
561
+ isAnyArrayBuffer(actual) ||
562
+ ArrayBufferIsView(expected) ||
563
+ isAnyArrayBuffer(expected)
564
+ ) {
565
+ return partiallyCompareArrayBuffersOrViews(actual, expected);
407
566
}
408
567
409
568
for (const type of typesToCallDeepStrictEqualWith) {
@@ -415,68 +574,12 @@ function compareBranch(
415
574
416
575
// Check for Set object equality
417
576
if (isSet(actual) && isSet(expected)) {
418
- if (expected.size > actual.size) {
419
- return false; // `expected` can't be a subset if it has more elements
420
- }
421
-
422
- if (isDeepEqual === undefined) lazyLoadComparison();
423
-
424
- const actualArray = ArrayFrom(FunctionPrototypeCall(SafeSet.prototype[SymbolIterator], actual));
425
- const expectedIterator = FunctionPrototypeCall(SafeSet.prototype[SymbolIterator], expected);
426
- const usedIndices = new SafeSet();
427
-
428
- expectedIteration: for (const expectedItem of expectedIterator) {
429
- for (let actualIdx = 0; actualIdx < actualArray.length; actualIdx++) {
430
- if (!usedIndices.has(actualIdx) && isDeepStrictEqual(actualArray[actualIdx], expectedItem)) {
431
- usedIndices.add(actualIdx);
432
- continue expectedIteration;
433
- }
434
- }
435
- return false;
436
- }
437
-
438
- return true;
577
+ return partiallyCompareSets(actual, expected, comparedObjects);
439
578
}
440
579
441
580
// Check if expected array is a subset of actual array
442
581
if (ArrayIsArray(actual) && ArrayIsArray(expected)) {
443
- if (expected.length > actual.length) {
444
- return false;
445
- }
446
-
447
- if (isDeepEqual === undefined) lazyLoadComparison();
448
-
449
- // Create a map to count occurrences of each element in the expected array
450
- const expectedCounts = new SafeMap();
451
- for (const expectedItem of expected) {
452
- let found = false;
453
- for (const { 0: key, 1: count } of expectedCounts) {
454
- if (isDeepStrictEqual(key, expectedItem)) {
455
- MapPrototypeSet(expectedCounts, key, count + 1);
456
- found = true;
457
- break;
458
- }
459
- }
460
- if (!found) {
461
- MapPrototypeSet(expectedCounts, expectedItem, 1);
462
- }
463
- }
464
-
465
- // Create a map to count occurrences of relevant elements in the actual array
466
- for (const actualItem of actual) {
467
- for (const { 0: key, 1: count } of expectedCounts) {
468
- if (isDeepStrictEqual(key, actualItem)) {
469
- if (count === 1) {
470
- MapPrototypeDelete(expectedCounts, key);
471
- } else {
472
- MapPrototypeSet(expectedCounts, key, count - 1);
473
- }
474
- break;
475
- }
476
- }
477
- }
478
-
479
- return !expectedCounts.size;
582
+ return partiallyCompareArrays(actual, expected, comparedObjects);
480
583
}
481
584
482
585
// Comparison done when at least one of the values is not an object
0 commit comments