@@ -453,14 +453,20 @@ function getEmptyFormatArray() {
453453 return [ ] ;
454454}
455455
456- function getConstructorName ( obj , ctx , recurseTimes ) {
456+ function getConstructorName ( obj , ctx , recurseTimes , protoProps ) {
457457 let firstProto ;
458458 const tmp = obj ;
459459 while ( obj ) {
460460 const descriptor = ObjectGetOwnPropertyDescriptor ( obj , 'constructor' ) ;
461461 if ( descriptor !== undefined &&
462462 typeof descriptor . value === 'function' &&
463463 descriptor . value . name !== '' ) {
464+ if ( protoProps !== undefined &&
465+ ! builtInObjects . has ( descriptor . value . name ) ) {
466+ const isProto = firstProto !== undefined ;
467+ addPrototypeProperties (
468+ ctx , tmp , obj , recurseTimes , isProto , protoProps ) ;
469+ }
464470 return descriptor . value . name ;
465471 }
466472
@@ -480,7 +486,8 @@ function getConstructorName(obj, ctx, recurseTimes) {
480486 return `${ res } <Complex prototype>` ;
481487 }
482488
483- const protoConstr = getConstructorName ( firstProto , ctx , recurseTimes + 1 ) ;
489+ const protoConstr = getConstructorName (
490+ firstProto , ctx , recurseTimes + 1 , protoProps ) ;
484491
485492 if ( protoConstr === null ) {
486493 return `${ res } <${ inspect ( firstProto , {
@@ -493,6 +500,68 @@ function getConstructorName(obj, ctx, recurseTimes) {
493500 return `${ res } <${ protoConstr } >` ;
494501}
495502
503+ // This function has the side effect of adding prototype properties to the
504+ // `output` argument (which is an array). This is intended to highlight user
505+ // defined prototype properties.
506+ function addPrototypeProperties ( ctx , main , obj , recurseTimes , isProto , output ) {
507+ let depth = 0 ;
508+ let keys ;
509+ let keySet ;
510+ do {
511+ if ( ! isProto ) {
512+ obj = ObjectGetPrototypeOf ( obj ) ;
513+ // Stop as soon as a null prototype is encountered.
514+ if ( obj === null ) {
515+ return ;
516+ }
517+ // Stop as soon as a built-in object type is detected.
518+ const descriptor = ObjectGetOwnPropertyDescriptor ( obj , 'constructor' ) ;
519+ if ( descriptor !== undefined &&
520+ typeof descriptor . value === 'function' &&
521+ builtInObjects . has ( descriptor . value . name ) ) {
522+ return ;
523+ }
524+ } else {
525+ isProto = false ;
526+ }
527+
528+ if ( depth === 0 ) {
529+ keySet = new Set ( ) ;
530+ } else {
531+ keys . forEach ( ( key ) => keySet . add ( key ) ) ;
532+ }
533+ // Get all own property names and symbols.
534+ keys = ObjectGetOwnPropertyNames ( obj ) ;
535+ const symbols = ObjectGetOwnPropertySymbols ( obj ) ;
536+ if ( symbols . length !== 0 ) {
537+ keys . push ( ...symbols ) ;
538+ }
539+ for ( const key of keys ) {
540+ // Ignore the `constructor` property and keys that exist on layers above.
541+ if ( key === 'constructor' ||
542+ ObjectPrototypeHasOwnProperty ( main , key ) ||
543+ ( depth !== 0 && keySet . has ( key ) ) ) {
544+ continue ;
545+ }
546+ const desc = ObjectGetOwnPropertyDescriptor ( obj , key ) ;
547+ if ( typeof desc . value === 'function' ) {
548+ continue ;
549+ }
550+ const value = formatProperty (
551+ ctx , obj , recurseTimes , key , kObjectType , desc ) ;
552+ if ( ctx . colors ) {
553+ // Faint!
554+ output . push ( `\u001b[2m${ value } \u001b[22m` ) ;
555+ } else {
556+ output . push ( value ) ;
557+ }
558+ }
559+ // Limit the inspection to up to three prototype layers. Using `recurseTimes`
560+ // is not a good choice here, because it's as if the properties are declared
561+ // on the current object from the users perspective.
562+ } while ( ++ depth !== 3 ) ;
563+ }
564+
496565function getPrefix ( constructor , tag , fallback ) {
497566 if ( constructor === null ) {
498567 if ( tag !== '' ) {
@@ -696,8 +765,17 @@ function formatValue(ctx, value, recurseTimes, typedArray) {
696765
697766function formatRaw ( ctx , value , recurseTimes , typedArray ) {
698767 let keys ;
768+ let protoProps ;
769+ if ( ctx . showHidden && ( recurseTimes <= ctx . depth || ctx . depth === null ) ) {
770+ protoProps = [ ] ;
771+ }
772+
773+ const constructor = getConstructorName ( value , ctx , recurseTimes , protoProps ) ;
774+ // Reset the variable to check for this later on.
775+ if ( protoProps !== undefined && protoProps . length === 0 ) {
776+ protoProps = undefined ;
777+ }
699778
700- const constructor = getConstructorName ( value , ctx , recurseTimes ) ;
701779 let tag = value [ SymbolToStringTag ] ;
702780 // Only list the tag in case it's non-enumerable / not an own property.
703781 // Otherwise we'd print this twice.
@@ -727,21 +805,21 @@ function formatRaw(ctx, value, recurseTimes, typedArray) {
727805 // Only set the constructor for non ordinary ("Array [...]") arrays.
728806 const prefix = getPrefix ( constructor , tag , 'Array' ) ;
729807 braces = [ `${ prefix === 'Array ' ? '' : prefix } [` , ']' ] ;
730- if ( value . length === 0 && keys . length === 0 )
808+ if ( value . length === 0 && keys . length === 0 && protoProps === undefined )
731809 return `${ braces [ 0 ] } ]` ;
732810 extrasType = kArrayExtrasType ;
733811 formatter = formatArray ;
734812 } else if ( isSet ( value ) ) {
735813 keys = getKeys ( value , ctx . showHidden ) ;
736814 const prefix = getPrefix ( constructor , tag , 'Set' ) ;
737- if ( value . size === 0 && keys . length === 0 )
815+ if ( value . size === 0 && keys . length === 0 && protoProps === undefined )
738816 return `${ prefix } {}` ;
739817 braces = [ `${ prefix } {` , '}' ] ;
740818 formatter = formatSet ;
741819 } else if ( isMap ( value ) ) {
742820 keys = getKeys ( value , ctx . showHidden ) ;
743821 const prefix = getPrefix ( constructor , tag , 'Map' ) ;
744- if ( value . size === 0 && keys . length === 0 )
822+ if ( value . size === 0 && keys . length === 0 && protoProps === undefined )
745823 return `${ prefix } {}` ;
746824 braces = [ `${ prefix } {` , '}' ] ;
747825 formatter = formatMap ;
@@ -776,12 +854,12 @@ function formatRaw(ctx, value, recurseTimes, typedArray) {
776854 } else if ( tag !== '' ) {
777855 braces [ 0 ] = `${ getPrefix ( constructor , tag , 'Object' ) } {` ;
778856 }
779- if ( keys . length === 0 ) {
857+ if ( keys . length === 0 && protoProps === undefined ) {
780858 return `${ braces [ 0 ] } }` ;
781859 }
782860 } else if ( typeof value === 'function' ) {
783861 base = getFunctionBase ( value , constructor , tag ) ;
784- if ( keys . length === 0 )
862+ if ( keys . length === 0 && protoProps === undefined )
785863 return ctx . stylize ( base , 'special' ) ;
786864 } else if ( isRegExp ( value ) ) {
787865 // Make RegExps say that they are RegExps
@@ -791,8 +869,10 @@ function formatRaw(ctx, value, recurseTimes, typedArray) {
791869 const prefix = getPrefix ( constructor , tag , 'RegExp' ) ;
792870 if ( prefix !== 'RegExp ' )
793871 base = `${ prefix } ${ base } ` ;
794- if ( keys . length === 0 || ( recurseTimes > ctx . depth && ctx . depth !== null ) )
872+ if ( ( keys . length === 0 && protoProps === undefined ) ||
873+ ( recurseTimes > ctx . depth && ctx . depth !== null ) ) {
795874 return ctx . stylize ( base , 'regexp' ) ;
875+ }
796876 } else if ( isDate ( value ) ) {
797877 // Make dates with properties first say the date
798878 base = NumberIsNaN ( DatePrototypeGetTime ( value ) ) ?
@@ -801,12 +881,12 @@ function formatRaw(ctx, value, recurseTimes, typedArray) {
801881 const prefix = getPrefix ( constructor , tag , 'Date' ) ;
802882 if ( prefix !== 'Date ' )
803883 base = `${ prefix } ${ base } ` ;
804- if ( keys . length === 0 ) {
884+ if ( keys . length === 0 && protoProps === undefined ) {
805885 return ctx . stylize ( base , 'date' ) ;
806886 }
807887 } else if ( isError ( value ) ) {
808888 base = formatError ( value , constructor , tag , ctx ) ;
809- if ( keys . length === 0 )
889+ if ( keys . length === 0 && protoProps === undefined )
810890 return base ;
811891 } else if ( isAnyArrayBuffer ( value ) ) {
812892 // Fast path for ArrayBuffer and SharedArrayBuffer.
@@ -817,7 +897,7 @@ function formatRaw(ctx, value, recurseTimes, typedArray) {
817897 const prefix = getPrefix ( constructor , tag , arrayType ) ;
818898 if ( typedArray === undefined ) {
819899 formatter = formatArrayBuffer ;
820- } else if ( keys . length === 0 ) {
900+ } else if ( keys . length === 0 && protoProps === undefined ) {
821901 return prefix +
822902 `{ byteLength: ${ formatNumber ( ctx . stylize , value . byteLength ) } }` ;
823903 }
@@ -841,7 +921,7 @@ function formatRaw(ctx, value, recurseTimes, typedArray) {
841921 formatter = formatNamespaceObject ;
842922 } else if ( isBoxedPrimitive ( value ) ) {
843923 base = getBoxedBase ( value , ctx , keys , constructor , tag ) ;
844- if ( keys . length === 0 ) {
924+ if ( keys . length === 0 && protoProps === undefined ) {
845925 return base ;
846926 }
847927 } else {
@@ -861,7 +941,7 @@ function formatRaw(ctx, value, recurseTimes, typedArray) {
861941 formatter = formatIterator ;
862942 // Handle other regular objects again.
863943 } else {
864- if ( keys . length === 0 ) {
944+ if ( keys . length === 0 && protoProps === undefined ) {
865945 if ( isExternal ( value ) )
866946 return ctx . stylize ( '[External]' , 'special' ) ;
867947 return `${ getCtxStyle ( value , constructor , tag ) } {}` ;
@@ -889,6 +969,9 @@ function formatRaw(ctx, value, recurseTimes, typedArray) {
889969 output . push (
890970 formatProperty ( ctx , value , recurseTimes , keys [ i ] , extrasType ) ) ;
891971 }
972+ if ( protoProps !== undefined ) {
973+ output . push ( ...protoProps ) ;
974+ }
892975 } catch ( err ) {
893976 const constructorName = getCtxStyle ( value , constructor , tag ) . slice ( 0 , - 1 ) ;
894977 return handleMaxCallStackSize ( ctx , err , constructorName , indentationLvl ) ;
@@ -1355,6 +1438,7 @@ function formatTypedArray(ctx, value, recurseTimes) {
13551438 }
13561439 if ( ctx . showHidden ) {
13571440 // .buffer goes last, it's not a primitive like the others.
1441+ // All besides `BYTES_PER_ELEMENT` are actually getters.
13581442 ctx . indentationLvl += 2 ;
13591443 for ( const key of [
13601444 'BYTES_PER_ELEMENT' ,
@@ -1503,10 +1587,10 @@ function formatPromise(ctx, value, recurseTimes) {
15031587 return output ;
15041588}
15051589
1506- function formatProperty ( ctx , value , recurseTimes , key , type ) {
1590+ function formatProperty ( ctx , value , recurseTimes , key , type , desc ) {
15071591 let name , str ;
15081592 let extra = ' ' ;
1509- const desc = ObjectGetOwnPropertyDescriptor ( value , key ) ||
1593+ desc = desc || ObjectGetOwnPropertyDescriptor ( value , key ) ||
15101594 { value : value [ key ] , enumerable : true } ;
15111595 if ( desc . value !== undefined ) {
15121596 const diff = ( type !== kObjectType || ctx . compact !== true ) ? 2 : 3 ;
0 commit comments