@@ -326,6 +326,7 @@ function getEmptyFormatArray() {
326326}
327327
328328function getConstructorName ( obj ) {
329+ let firstProto ;
329330 while ( obj ) {
330331 const descriptor = Object . getOwnPropertyDescriptor ( obj , 'constructor' ) ;
331332 if ( descriptor !== undefined &&
@@ -335,25 +336,35 @@ function getConstructorName(obj) {
335336 }
336337
337338 obj = Object . getPrototypeOf ( obj ) ;
339+ if ( firstProto === undefined ) {
340+ firstProto = obj ;
341+ }
342+ }
343+
344+ if ( firstProto === null ) {
345+ return null ;
338346 }
347+ // TODO(BridgeAR): Improve prototype inspection.
348+ // We could use inspect on the prototype itself to improve the output.
339349
340350 return '' ;
341351}
342352
343353function 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+
344361 if ( constructor !== '' ) {
345362 if ( tag !== '' && constructor !== tag ) {
346363 return `${ constructor } [${ tag } ] ` ;
347364 }
348365 return `${ constructor } ` ;
349366 }
350367
351- if ( tag !== '' )
352- return `[${ tag } ] ` ;
353-
354- if ( fallback !== undefined )
355- return `${ fallback } ` ;
356-
357368 return '' ;
358369}
359370
@@ -427,21 +438,49 @@ function findTypedConstructor(value) {
427438 }
428439}
429440
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+
430464function noPrototypeIterator ( ctx , value , recurseTimes ) {
431465 let newVal ;
432- // TODO: Create a Subclass in case there's no prototype and show
433- // `null-prototype`.
434466 if ( isSet ( value ) ) {
435- const clazz = Object . getPrototypeOf ( value ) || Set ;
467+ const clazz = Object . getPrototypeOf ( value ) ||
468+ clazzWithNullPrototype ( Set , 'Set' ) ;
436469 newVal = new clazz ( setValues ( value ) ) ;
437470 } else if ( isMap ( value ) ) {
438- const clazz = Object . getPrototypeOf ( value ) || Map ;
471+ const clazz = Object . getPrototypeOf ( value ) ||
472+ clazzWithNullPrototype ( Map , 'Map' ) ;
439473 newVal = new clazz ( mapEntries ( value ) ) ;
440474 } else if ( Array . isArray ( value ) ) {
441- const clazz = Object . getPrototypeOf ( value ) || Array ;
475+ const clazz = Object . getPrototypeOf ( value ) ||
476+ clazzWithNullPrototype ( Array , 'Array' ) ;
442477 newVal = new clazz ( value . length || 0 ) ;
443478 } 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+ }
445484 newVal = new clazz ( value ) ;
446485 }
447486 if ( newVal ) {
@@ -527,29 +566,32 @@ function formatRaw(ctx, value, recurseTimes) {
527566 if ( Array . isArray ( value ) ) {
528567 keys = getOwnNonIndexProperties ( value , filter ) ;
529568 // Only set the constructor for non ordinary ("Array [...]") arrays.
530- const prefix = getPrefix ( constructor , tag ) ;
569+ const prefix = getPrefix ( constructor , tag , 'Array' ) ;
531570 braces = [ `${ prefix === 'Array ' ? '' : prefix } [` , ']' ] ;
532571 if ( value . length === 0 && keys . length === 0 )
533572 return `${ braces [ 0 ] } ]` ;
534573 extrasType = kArrayExtrasType ;
535574 formatter = formatArray ;
536575 } else if ( isSet ( value ) ) {
537576 keys = getKeys ( value , ctx . showHidden ) ;
538- const prefix = getPrefix ( constructor , tag ) ;
577+ const prefix = getPrefix ( constructor , tag , 'Set' ) ;
539578 if ( value . size === 0 && keys . length === 0 )
540579 return `${ prefix } {}` ;
541580 braces = [ `${ prefix } {` , '}' ] ;
542581 formatter = formatSet ;
543582 } else if ( isMap ( value ) ) {
544583 keys = getKeys ( value , ctx . showHidden ) ;
545- const prefix = getPrefix ( constructor , tag ) ;
584+ const prefix = getPrefix ( constructor , tag , 'Map' ) ;
546585 if ( value . size === 0 && keys . length === 0 )
547586 return `${ prefix } {}` ;
548587 braces = [ `${ prefix } {` , '}' ] ;
549588 formatter = formatMap ;
550589 } else if ( isTypedArray ( value ) ) {
551590 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 } [` , ']' ] ;
553595 if ( value . length === 0 && keys . length === 0 && ! ctx . showHidden )
554596 return `${ braces [ 0 ] } ]` ;
555597 formatter = formatTypedArray ;
@@ -575,7 +617,7 @@ function formatRaw(ctx, value, recurseTimes) {
575617 return '[Arguments] {}' ;
576618 braces [ 0 ] = '[Arguments] {' ;
577619 } else if ( tag !== '' ) {
578- braces [ 0 ] = `${ getPrefix ( constructor , tag ) } {` ;
620+ braces [ 0 ] = `${ getPrefix ( constructor , tag , 'Object' ) } {` ;
579621 if ( keys . length === 0 ) {
580622 return `${ braces [ 0 ] } }` ;
581623 }
@@ -622,13 +664,12 @@ function formatRaw(ctx, value, recurseTimes) {
622664 base = `[${ base . slice ( 0 , stackStart ) } ]` ;
623665 }
624666 } else if ( isAnyArrayBuffer ( value ) ) {
625- let prefix = getPrefix ( constructor , tag ) ;
626- if ( prefix === '' ) {
627- prefix = isArrayBuffer ( value ) ? 'ArrayBuffer ' : 'SharedArrayBuffer ' ;
628- }
629667 // Fast path for ArrayBuffer and SharedArrayBuffer.
630668 // Can't do the same for DataView because it has a non-primitive
631669 // .buffer property that we need to recurse for.
670+ const arrayType = isArrayBuffer ( value ) ? 'ArrayBuffer' :
671+ 'SharedArrayBuffer' ;
672+ const prefix = getPrefix ( constructor , tag , arrayType ) ;
632673 if ( keys . length === 0 )
633674 return prefix +
634675 `{ byteLength: ${ formatNumber ( ctx . stylize , value . byteLength ) } }` ;
@@ -693,9 +734,9 @@ function formatRaw(ctx, value, recurseTimes) {
693734 } else if ( keys . length === 0 ) {
694735 if ( isExternal ( value ) )
695736 return ctx . stylize ( '[External]' , 'special' ) ;
696- return `${ getPrefix ( constructor , tag ) } {}` ;
737+ return `${ getPrefix ( constructor , tag , 'Object' ) } {}` ;
697738 } else {
698- braces [ 0 ] = `${ getPrefix ( constructor , tag ) } {` ;
739+ braces [ 0 ] = `${ getPrefix ( constructor , tag , 'Object' ) } {` ;
699740 }
700741 }
701742 }
0 commit comments