@@ -326,6 +326,7 @@ function getEmptyFormatArray() {
326
326
}
327
327
328
328
function getConstructorName ( obj ) {
329
+ let firstProto ;
329
330
while ( obj ) {
330
331
const descriptor = Object . getOwnPropertyDescriptor ( obj , 'constructor' ) ;
331
332
if ( descriptor !== undefined &&
@@ -335,25 +336,35 @@ function getConstructorName(obj) {
335
336
}
336
337
337
338
obj = Object . getPrototypeOf ( obj ) ;
339
+ if ( firstProto === undefined ) {
340
+ firstProto = obj ;
341
+ }
342
+ }
343
+
344
+ if ( firstProto === null ) {
345
+ return null ;
338
346
}
347
+ // TODO(BridgeAR): Improve prototype inspection.
348
+ // We could use inspect on the prototype itself to improve the output.
339
349
340
350
return '' ;
341
351
}
342
352
343
353
function getPrefix ( constructor , tag , fallback ) {
354
+ if ( constructor === null ) {
355
+ if ( tag !== '' ) {
356
+ return `[${ fallback } : null prototype] [${ tag } ] ` ;
357
+ }
358
+ return `[${ fallback } : null prototype] ` ;
359
+ }
360
+
344
361
if ( constructor !== '' ) {
345
362
if ( tag !== '' && constructor !== tag ) {
346
363
return `${ constructor } [${ tag } ] ` ;
347
364
}
348
365
return `${ constructor } ` ;
349
366
}
350
367
351
- if ( tag !== '' )
352
- return `[${ tag } ] ` ;
353
-
354
- if ( fallback !== undefined )
355
- return `${ fallback } ` ;
356
-
357
368
return '' ;
358
369
}
359
370
@@ -427,21 +438,49 @@ function findTypedConstructor(value) {
427
438
}
428
439
}
429
440
441
+ let lazyNullPrototypeCache ;
442
+ // Creates a subclass and name
443
+ // the constructor as `${clazz} : null prototype`
444
+ function clazzWithNullPrototype ( clazz , name ) {
445
+ if ( lazyNullPrototypeCache === undefined ) {
446
+ lazyNullPrototypeCache = new Map ( ) ;
447
+ } else {
448
+ const cachedClass = lazyNullPrototypeCache . get ( clazz ) ;
449
+ if ( cachedClass !== undefined ) {
450
+ return cachedClass ;
451
+ }
452
+ }
453
+ class NullPrototype extends clazz {
454
+ get [ Symbol . toStringTag ] ( ) {
455
+ return '' ;
456
+ }
457
+ }
458
+ Object . defineProperty ( NullPrototype . prototype . constructor , 'name' ,
459
+ { value : `[${ name } : null prototype]` } ) ;
460
+ lazyNullPrototypeCache . set ( clazz , NullPrototype ) ;
461
+ return NullPrototype ;
462
+ }
463
+
430
464
function noPrototypeIterator ( ctx , value , recurseTimes ) {
431
465
let newVal ;
432
- // TODO: Create a Subclass in case there's no prototype and show
433
- // `null-prototype`.
434
466
if ( isSet ( value ) ) {
435
- const clazz = Object . getPrototypeOf ( value ) || Set ;
467
+ const clazz = Object . getPrototypeOf ( value ) ||
468
+ clazzWithNullPrototype ( Set , 'Set' ) ;
436
469
newVal = new clazz ( setValues ( value ) ) ;
437
470
} else if ( isMap ( value ) ) {
438
- const clazz = Object . getPrototypeOf ( value ) || Map ;
471
+ const clazz = Object . getPrototypeOf ( value ) ||
472
+ clazzWithNullPrototype ( Map , 'Map' ) ;
439
473
newVal = new clazz ( mapEntries ( value ) ) ;
440
474
} else if ( Array . isArray ( value ) ) {
441
- const clazz = Object . getPrototypeOf ( value ) || Array ;
475
+ const clazz = Object . getPrototypeOf ( value ) ||
476
+ clazzWithNullPrototype ( Array , 'Array' ) ;
442
477
newVal = new clazz ( value . length || 0 ) ;
443
478
} else if ( isTypedArray ( value ) ) {
444
- const clazz = findTypedConstructor ( value ) || Uint8Array ;
479
+ let clazz = Object . getPrototypeOf ( value ) ;
480
+ if ( ! clazz ) {
481
+ const constructor = findTypedConstructor ( value ) ;
482
+ clazz = clazzWithNullPrototype ( constructor , constructor . name ) ;
483
+ }
445
484
newVal = new clazz ( value ) ;
446
485
}
447
486
if ( newVal ) {
@@ -527,29 +566,32 @@ function formatRaw(ctx, value, recurseTimes) {
527
566
if ( Array . isArray ( value ) ) {
528
567
keys = getOwnNonIndexProperties ( value , filter ) ;
529
568
// Only set the constructor for non ordinary ("Array [...]") arrays.
530
- const prefix = getPrefix ( constructor , tag ) ;
569
+ const prefix = getPrefix ( constructor , tag , 'Array' ) ;
531
570
braces = [ `${ prefix === 'Array ' ? '' : prefix } [` , ']' ] ;
532
571
if ( value . length === 0 && keys . length === 0 )
533
572
return `${ braces [ 0 ] } ]` ;
534
573
extrasType = kArrayExtrasType ;
535
574
formatter = formatArray ;
536
575
} else if ( isSet ( value ) ) {
537
576
keys = getKeys ( value , ctx . showHidden ) ;
538
- const prefix = getPrefix ( constructor , tag ) ;
577
+ const prefix = getPrefix ( constructor , tag , 'Set' ) ;
539
578
if ( value . size === 0 && keys . length === 0 )
540
579
return `${ prefix } {}` ;
541
580
braces = [ `${ prefix } {` , '}' ] ;
542
581
formatter = formatSet ;
543
582
} else if ( isMap ( value ) ) {
544
583
keys = getKeys ( value , ctx . showHidden ) ;
545
- const prefix = getPrefix ( constructor , tag ) ;
584
+ const prefix = getPrefix ( constructor , tag , 'Map' ) ;
546
585
if ( value . size === 0 && keys . length === 0 )
547
586
return `${ prefix } {}` ;
548
587
braces = [ `${ prefix } {` , '}' ] ;
549
588
formatter = formatMap ;
550
589
} else if ( isTypedArray ( value ) ) {
551
590
keys = getOwnNonIndexProperties ( value , filter ) ;
552
- braces = [ `${ getPrefix ( constructor , tag ) } [` , ']' ] ;
591
+ const prefix = constructor !== null ?
592
+ getPrefix ( constructor , tag ) :
593
+ getPrefix ( constructor , tag , findTypedConstructor ( value ) . name ) ;
594
+ braces = [ `${ prefix } [` , ']' ] ;
553
595
if ( value . length === 0 && keys . length === 0 && ! ctx . showHidden )
554
596
return `${ braces [ 0 ] } ]` ;
555
597
formatter = formatTypedArray ;
@@ -575,7 +617,7 @@ function formatRaw(ctx, value, recurseTimes) {
575
617
return '[Arguments] {}' ;
576
618
braces [ 0 ] = '[Arguments] {' ;
577
619
} else if ( tag !== '' ) {
578
- braces [ 0 ] = `${ getPrefix ( constructor , tag ) } {` ;
620
+ braces [ 0 ] = `${ getPrefix ( constructor , tag , 'Object' ) } {` ;
579
621
if ( keys . length === 0 ) {
580
622
return `${ braces [ 0 ] } }` ;
581
623
}
@@ -622,13 +664,12 @@ function formatRaw(ctx, value, recurseTimes) {
622
664
base = `[${ base . slice ( 0 , stackStart ) } ]` ;
623
665
}
624
666
} else if ( isAnyArrayBuffer ( value ) ) {
625
- let prefix = getPrefix ( constructor , tag ) ;
626
- if ( prefix === '' ) {
627
- prefix = isArrayBuffer ( value ) ? 'ArrayBuffer ' : 'SharedArrayBuffer ' ;
628
- }
629
667
// Fast path for ArrayBuffer and SharedArrayBuffer.
630
668
// Can't do the same for DataView because it has a non-primitive
631
669
// .buffer property that we need to recurse for.
670
+ const arrayType = isArrayBuffer ( value ) ? 'ArrayBuffer' :
671
+ 'SharedArrayBuffer' ;
672
+ const prefix = getPrefix ( constructor , tag , arrayType ) ;
632
673
if ( keys . length === 0 )
633
674
return prefix +
634
675
`{ byteLength: ${ formatNumber ( ctx . stylize , value . byteLength ) } }` ;
@@ -693,9 +734,9 @@ function formatRaw(ctx, value, recurseTimes) {
693
734
} else if ( keys . length === 0 ) {
694
735
if ( isExternal ( value ) )
695
736
return ctx . stylize ( '[External]' , 'special' ) ;
696
- return `${ getPrefix ( constructor , tag ) } {}` ;
737
+ return `${ getPrefix ( constructor , tag , 'Object' ) } {}` ;
697
738
} else {
698
- braces [ 0 ] = `${ getPrefix ( constructor , tag ) } {` ;
739
+ braces [ 0 ] = `${ getPrefix ( constructor , tag , 'Object' ) } {` ;
699
740
}
700
741
}
701
742
}
0 commit comments