@@ -448,14 +448,20 @@ function getEmptyFormatArray() {
448448 return [ ] ;
449449}
450450
451- function getConstructorName ( obj , ctx , recurseTimes ) {
451+ function getConstructorName ( obj , ctx , recurseTimes , protoProps ) {
452452 let firstProto ;
453453 const tmp = obj ;
454454 while ( obj ) {
455455 const descriptor = ObjectGetOwnPropertyDescriptor ( obj , 'constructor' ) ;
456456 if ( descriptor !== undefined &&
457457 typeof descriptor . value === 'function' &&
458458 descriptor . value . name !== '' ) {
459+ if ( protoProps !== undefined &&
460+ ! builtInObjects . has ( descriptor . value . name ) ) {
461+ const isProto = firstProto !== undefined ;
462+ addPrototypeProperties (
463+ ctx , tmp , obj , recurseTimes , isProto , protoProps ) ;
464+ }
459465 return descriptor . value . name ;
460466 }
461467
@@ -475,7 +481,8 @@ function getConstructorName(obj, ctx, recurseTimes) {
475481 return `${ res } <Complex prototype>` ;
476482 }
477483
478- const protoConstr = getConstructorName ( firstProto , ctx , recurseTimes + 1 ) ;
484+ const protoConstr = getConstructorName (
485+ firstProto , ctx , recurseTimes + 1 , protoProps ) ;
479486
480487 if ( protoConstr === null ) {
481488 return `${ res } <${ inspect ( firstProto , {
@@ -488,6 +495,68 @@ function getConstructorName(obj, ctx, recurseTimes) {
488495 return `${ res } <${ protoConstr } >` ;
489496}
490497
498+ // This function has the side effect of adding prototype properties to the
499+ // `output` argument (which is an array). This is intended to highlight user
500+ // defined prototype properties.
501+ function addPrototypeProperties ( ctx , main , obj , recurseTimes , isProto , output ) {
502+ let depth = 0 ;
503+ let keys ;
504+ let keySet ;
505+ do {
506+ if ( ! isProto ) {
507+ obj = ObjectGetPrototypeOf ( obj ) ;
508+ // Stop as soon as a null prototype is encountered.
509+ if ( obj === null ) {
510+ return ;
511+ }
512+ // Stop as soon as a built-in object type is detected.
513+ const descriptor = ObjectGetOwnPropertyDescriptor ( obj , 'constructor' ) ;
514+ if ( descriptor !== undefined &&
515+ typeof descriptor . value === 'function' &&
516+ builtInObjects . has ( descriptor . value . name ) ) {
517+ return ;
518+ }
519+ } else {
520+ isProto = false ;
521+ }
522+
523+ if ( depth === 0 ) {
524+ keySet = new Set ( ) ;
525+ } else {
526+ keys . forEach ( ( key ) => keySet . add ( key ) ) ;
527+ }
528+ // Get all own property names and symbols.
529+ keys = ObjectGetOwnPropertyNames ( obj ) ;
530+ const symbols = ObjectGetOwnPropertySymbols ( obj ) ;
531+ if ( symbols . length !== 0 ) {
532+ keys . push ( ...symbols ) ;
533+ }
534+ for ( const key of keys ) {
535+ // Ignore the `constructor` property and keys that exist on layers above.
536+ if ( key === 'constructor' ||
537+ ObjectPrototypeHasOwnProperty ( main , key ) ||
538+ ( depth !== 0 && keySet . has ( key ) ) ) {
539+ continue ;
540+ }
541+ const desc = ObjectGetOwnPropertyDescriptor ( obj , key ) ;
542+ if ( typeof desc . value === 'function' ) {
543+ continue ;
544+ }
545+ const value = formatProperty (
546+ ctx , obj , recurseTimes , key , kObjectType , desc ) ;
547+ if ( ctx . colors ) {
548+ // Faint!
549+ output . push ( `\u001b[2m${ value } \u001b[22m` ) ;
550+ } else {
551+ output . push ( value ) ;
552+ }
553+ }
554+ // Limit the inspection to up to three prototype layers. Using `recurseTimes`
555+ // is not a good choice here, because it's as if the properties are declared
556+ // on the current object from the users perspective.
557+ } while ( ++ depth !== 3 ) ;
558+ }
559+
491560function getPrefix ( constructor , tag , fallback ) {
492561 if ( constructor === null ) {
493562 if ( tag !== '' ) {
@@ -691,8 +760,17 @@ function formatValue(ctx, value, recurseTimes, typedArray) {
691760
692761function formatRaw ( ctx , value , recurseTimes , typedArray ) {
693762 let keys ;
763+ let protoProps ;
764+ if ( ctx . showHidden && ( recurseTimes <= ctx . depth || ctx . depth === null ) ) {
765+ protoProps = [ ] ;
766+ }
767+
768+ const constructor = getConstructorName ( value , ctx , recurseTimes , protoProps ) ;
769+ // Reset the variable to check for this later on.
770+ if ( protoProps !== undefined && protoProps . length === 0 ) {
771+ protoProps = undefined ;
772+ }
694773
695- const constructor = getConstructorName ( value , ctx , recurseTimes ) ;
696774 let tag = value [ SymbolToStringTag ] ;
697775 // Only list the tag in case it's non-enumerable / not an own property.
698776 // Otherwise we'd print this twice.
@@ -722,21 +800,21 @@ function formatRaw(ctx, value, recurseTimes, typedArray) {
722800 // Only set the constructor for non ordinary ("Array [...]") arrays.
723801 const prefix = getPrefix ( constructor , tag , 'Array' ) ;
724802 braces = [ `${ prefix === 'Array ' ? '' : prefix } [` , ']' ] ;
725- if ( value . length === 0 && keys . length === 0 )
803+ if ( value . length === 0 && keys . length === 0 && protoProps === undefined )
726804 return `${ braces [ 0 ] } ]` ;
727805 extrasType = kArrayExtrasType ;
728806 formatter = formatArray ;
729807 } else if ( isSet ( value ) ) {
730808 keys = getKeys ( value , ctx . showHidden ) ;
731809 const prefix = getPrefix ( constructor , tag , 'Set' ) ;
732- if ( value . size === 0 && keys . length === 0 )
810+ if ( value . size === 0 && keys . length === 0 && protoProps === undefined )
733811 return `${ prefix } {}` ;
734812 braces = [ `${ prefix } {` , '}' ] ;
735813 formatter = formatSet ;
736814 } else if ( isMap ( value ) ) {
737815 keys = getKeys ( value , ctx . showHidden ) ;
738816 const prefix = getPrefix ( constructor , tag , 'Map' ) ;
739- if ( value . size === 0 && keys . length === 0 )
817+ if ( value . size === 0 && keys . length === 0 && protoProps === undefined )
740818 return `${ prefix } {}` ;
741819 braces = [ `${ prefix } {` , '}' ] ;
742820 formatter = formatMap ;
@@ -771,12 +849,12 @@ function formatRaw(ctx, value, recurseTimes, typedArray) {
771849 } else if ( tag !== '' ) {
772850 braces [ 0 ] = `${ getPrefix ( constructor , tag , 'Object' ) } {` ;
773851 }
774- if ( keys . length === 0 ) {
852+ if ( keys . length === 0 && protoProps === undefined ) {
775853 return `${ braces [ 0 ] } }` ;
776854 }
777855 } else if ( typeof value === 'function' ) {
778856 base = getFunctionBase ( value , constructor , tag ) ;
779- if ( keys . length === 0 )
857+ if ( keys . length === 0 && protoProps === undefined )
780858 return ctx . stylize ( base , 'special' ) ;
781859 } else if ( isRegExp ( value ) ) {
782860 // Make RegExps say that they are RegExps
@@ -786,8 +864,10 @@ function formatRaw(ctx, value, recurseTimes, typedArray) {
786864 const prefix = getPrefix ( constructor , tag , 'RegExp' ) ;
787865 if ( prefix !== 'RegExp ' )
788866 base = `${ prefix } ${ base } ` ;
789- if ( keys . length === 0 || ( recurseTimes > ctx . depth && ctx . depth !== null ) )
867+ if ( ( keys . length === 0 && protoProps === undefined ) ||
868+ ( recurseTimes > ctx . depth && ctx . depth !== null ) ) {
790869 return ctx . stylize ( base , 'regexp' ) ;
870+ }
791871 } else if ( isDate ( value ) ) {
792872 // Make dates with properties first say the date
793873 base = NumberIsNaN ( DatePrototypeGetTime ( value ) ) ?
@@ -796,12 +876,12 @@ function formatRaw(ctx, value, recurseTimes, typedArray) {
796876 const prefix = getPrefix ( constructor , tag , 'Date' ) ;
797877 if ( prefix !== 'Date ' )
798878 base = `${ prefix } ${ base } ` ;
799- if ( keys . length === 0 ) {
879+ if ( keys . length === 0 && protoProps === undefined ) {
800880 return ctx . stylize ( base , 'date' ) ;
801881 }
802882 } else if ( isError ( value ) ) {
803883 base = formatError ( value , constructor , tag , ctx ) ;
804- if ( keys . length === 0 )
884+ if ( keys . length === 0 && protoProps === undefined )
805885 return base ;
806886 } else if ( isAnyArrayBuffer ( value ) ) {
807887 // Fast path for ArrayBuffer and SharedArrayBuffer.
@@ -812,7 +892,7 @@ function formatRaw(ctx, value, recurseTimes, typedArray) {
812892 const prefix = getPrefix ( constructor , tag , arrayType ) ;
813893 if ( typedArray === undefined ) {
814894 formatter = formatArrayBuffer ;
815- } else if ( keys . length === 0 ) {
895+ } else if ( keys . length === 0 && protoProps === undefined ) {
816896 return prefix +
817897 `{ byteLength: ${ formatNumber ( ctx . stylize , value . byteLength ) } }` ;
818898 }
@@ -836,7 +916,7 @@ function formatRaw(ctx, value, recurseTimes, typedArray) {
836916 formatter = formatNamespaceObject ;
837917 } else if ( isBoxedPrimitive ( value ) ) {
838918 base = getBoxedBase ( value , ctx , keys , constructor , tag ) ;
839- if ( keys . length === 0 ) {
919+ if ( keys . length === 0 && protoProps === undefined ) {
840920 return base ;
841921 }
842922 } else {
@@ -856,7 +936,7 @@ function formatRaw(ctx, value, recurseTimes, typedArray) {
856936 formatter = formatIterator ;
857937 // Handle other regular objects again.
858938 } else {
859- if ( keys . length === 0 ) {
939+ if ( keys . length === 0 && protoProps === undefined ) {
860940 if ( isExternal ( value ) )
861941 return ctx . stylize ( '[External]' , 'special' ) ;
862942 return `${ getCtxStyle ( value , constructor , tag ) } {}` ;
@@ -884,6 +964,9 @@ function formatRaw(ctx, value, recurseTimes, typedArray) {
884964 output . push (
885965 formatProperty ( ctx , value , recurseTimes , keys [ i ] , extrasType ) ) ;
886966 }
967+ if ( protoProps !== undefined ) {
968+ output . push ( ...protoProps ) ;
969+ }
887970 } catch ( err ) {
888971 const constructorName = getCtxStyle ( value , constructor , tag ) . slice ( 0 , - 1 ) ;
889972 return handleMaxCallStackSize ( ctx , err , constructorName , indentationLvl ) ;
@@ -1350,6 +1433,7 @@ function formatTypedArray(ctx, value, recurseTimes) {
13501433 }
13511434 if ( ctx . showHidden ) {
13521435 // .buffer goes last, it's not a primitive like the others.
1436+ // All besides `BYTES_PER_ELEMENT` are actually getters.
13531437 ctx . indentationLvl += 2 ;
13541438 for ( const key of [
13551439 'BYTES_PER_ELEMENT' ,
@@ -1498,10 +1582,10 @@ function formatPromise(ctx, value, recurseTimes) {
14981582 return output ;
14991583}
15001584
1501- function formatProperty ( ctx , value , recurseTimes , key , type ) {
1585+ function formatProperty ( ctx , value , recurseTimes , key , type , desc ) {
15021586 let name , str ;
15031587 let extra = ' ' ;
1504- const desc = ObjectGetOwnPropertyDescriptor ( value , key ) ||
1588+ desc = desc || ObjectGetOwnPropertyDescriptor ( value , key ) ||
15051589 { value : value [ key ] , enumerable : true } ;
15061590 if ( desc . value !== undefined ) {
15071591 const diff = ( type !== kObjectType || ctx . compact !== true ) ? 2 : 3 ;
0 commit comments