@@ -450,14 +450,20 @@ function getEmptyFormatArray() {
450450 return [ ] ;
451451}
452452
453- function getConstructorName ( obj , ctx , recurseTimes ) {
453+ function getConstructorName ( obj , ctx , recurseTimes , protoProps ) {
454454 let firstProto ;
455455 const tmp = obj ;
456456 while ( obj ) {
457457 const descriptor = ObjectGetOwnPropertyDescriptor ( obj , 'constructor' ) ;
458458 if ( descriptor !== undefined &&
459459 typeof descriptor . value === 'function' &&
460460 descriptor . value . name !== '' ) {
461+ if ( protoProps !== undefined &&
462+ ! builtInObjects . has ( descriptor . value . name ) ) {
463+ const isProto = firstProto !== undefined ;
464+ addPrototypeProperties (
465+ ctx , tmp , obj , recurseTimes , isProto , protoProps ) ;
466+ }
461467 return descriptor . value . name ;
462468 }
463469
@@ -477,7 +483,8 @@ function getConstructorName(obj, ctx, recurseTimes) {
477483 return `${ res } <Complex prototype>` ;
478484 }
479485
480- const protoConstr = getConstructorName ( firstProto , ctx , recurseTimes + 1 ) ;
486+ const protoConstr = getConstructorName (
487+ firstProto , ctx , recurseTimes + 1 , protoProps ) ;
481488
482489 if ( protoConstr === null ) {
483490 return `${ res } <${ inspect ( firstProto , {
@@ -490,6 +497,68 @@ function getConstructorName(obj, ctx, recurseTimes) {
490497 return `${ res } <${ protoConstr } >` ;
491498}
492499
500+ // This function has the side effect of adding prototype properties to the
501+ // `output` argument (which is an array). This is intended to highlight user
502+ // defined prototype properties.
503+ function addPrototypeProperties ( ctx , main , obj , recurseTimes , isProto , output ) {
504+ let depth = 0 ;
505+ let keys ;
506+ let keySet ;
507+ do {
508+ if ( ! isProto ) {
509+ obj = ObjectGetPrototypeOf ( obj ) ;
510+ // Stop as soon as a null prototype is encountered.
511+ if ( obj === null ) {
512+ return ;
513+ }
514+ // Stop as soon as a built-in object type is detected.
515+ const descriptor = ObjectGetOwnPropertyDescriptor ( obj , 'constructor' ) ;
516+ if ( descriptor !== undefined &&
517+ typeof descriptor . value === 'function' &&
518+ builtInObjects . has ( descriptor . value . name ) ) {
519+ return ;
520+ }
521+ } else {
522+ isProto = false ;
523+ }
524+
525+ if ( depth === 0 ) {
526+ keySet = new Set ( ) ;
527+ } else {
528+ keys . forEach ( ( key ) => keySet . add ( key ) ) ;
529+ }
530+ // Get all own property names and symbols.
531+ keys = ObjectGetOwnPropertyNames ( obj ) ;
532+ const symbols = ObjectGetOwnPropertySymbols ( obj ) ;
533+ if ( symbols . length !== 0 ) {
534+ keys . push ( ...symbols ) ;
535+ }
536+ for ( const key of keys ) {
537+ // Ignore the `constructor` property and keys that exist on layers above.
538+ if ( key === 'constructor' ||
539+ ObjectPrototypeHasOwnProperty ( main , key ) ||
540+ ( depth !== 0 && keySet . has ( key ) ) ) {
541+ continue ;
542+ }
543+ const desc = ObjectGetOwnPropertyDescriptor ( obj , key ) ;
544+ if ( typeof desc . value === 'function' ) {
545+ continue ;
546+ }
547+ const value = formatProperty (
548+ ctx , obj , recurseTimes , key , kObjectType , desc ) ;
549+ if ( ctx . colors ) {
550+ // Faint!
551+ output . push ( `\u001b[2m${ value } \u001b[22m` ) ;
552+ } else {
553+ output . push ( value ) ;
554+ }
555+ }
556+ // Limit the inspection to up to three prototype layers. Using `recurseTimes`
557+ // is not a good choice here, because it's as if the properties are declared
558+ // on the current object from the users perspective.
559+ } while ( ++ depth !== 3 ) ;
560+ }
561+
493562function getPrefix ( constructor , tag , fallback ) {
494563 if ( constructor === null ) {
495564 if ( tag !== '' ) {
@@ -693,8 +762,17 @@ function formatValue(ctx, value, recurseTimes, typedArray) {
693762
694763function formatRaw ( ctx , value , recurseTimes , typedArray ) {
695764 let keys ;
765+ let protoProps ;
766+ if ( ctx . showHidden && ( recurseTimes <= ctx . depth || ctx . depth === null ) ) {
767+ protoProps = [ ] ;
768+ }
769+
770+ const constructor = getConstructorName ( value , ctx , recurseTimes , protoProps ) ;
771+ // Reset the variable to check for this later on.
772+ if ( protoProps !== undefined && protoProps . length === 0 ) {
773+ protoProps = undefined ;
774+ }
696775
697- const constructor = getConstructorName ( value , ctx , recurseTimes ) ;
698776 let tag = value [ SymbolToStringTag ] ;
699777 // Only list the tag in case it's non-enumerable / not an own property.
700778 // Otherwise we'd print this twice.
@@ -724,21 +802,21 @@ function formatRaw(ctx, value, recurseTimes, typedArray) {
724802 // Only set the constructor for non ordinary ("Array [...]") arrays.
725803 const prefix = getPrefix ( constructor , tag , 'Array' ) ;
726804 braces = [ `${ prefix === 'Array ' ? '' : prefix } [` , ']' ] ;
727- if ( value . length === 0 && keys . length === 0 )
805+ if ( value . length === 0 && keys . length === 0 && protoProps === undefined )
728806 return `${ braces [ 0 ] } ]` ;
729807 extrasType = kArrayExtrasType ;
730808 formatter = formatArray ;
731809 } else if ( isSet ( value ) ) {
732810 keys = getKeys ( value , ctx . showHidden ) ;
733811 const prefix = getPrefix ( constructor , tag , 'Set' ) ;
734- if ( value . size === 0 && keys . length === 0 )
812+ if ( value . size === 0 && keys . length === 0 && protoProps === undefined )
735813 return `${ prefix } {}` ;
736814 braces = [ `${ prefix } {` , '}' ] ;
737815 formatter = formatSet ;
738816 } else if ( isMap ( value ) ) {
739817 keys = getKeys ( value , ctx . showHidden ) ;
740818 const prefix = getPrefix ( constructor , tag , 'Map' ) ;
741- if ( value . size === 0 && keys . length === 0 )
819+ if ( value . size === 0 && keys . length === 0 && protoProps === undefined )
742820 return `${ prefix } {}` ;
743821 braces = [ `${ prefix } {` , '}' ] ;
744822 formatter = formatMap ;
@@ -773,12 +851,12 @@ function formatRaw(ctx, value, recurseTimes, typedArray) {
773851 } else if ( tag !== '' ) {
774852 braces [ 0 ] = `${ getPrefix ( constructor , tag , 'Object' ) } {` ;
775853 }
776- if ( keys . length === 0 ) {
854+ if ( keys . length === 0 && protoProps === undefined ) {
777855 return `${ braces [ 0 ] } }` ;
778856 }
779857 } else if ( typeof value === 'function' ) {
780858 base = getFunctionBase ( value , constructor , tag ) ;
781- if ( keys . length === 0 )
859+ if ( keys . length === 0 && protoProps === undefined )
782860 return ctx . stylize ( base , 'special' ) ;
783861 } else if ( isRegExp ( value ) ) {
784862 // Make RegExps say that they are RegExps
@@ -788,8 +866,10 @@ function formatRaw(ctx, value, recurseTimes, typedArray) {
788866 const prefix = getPrefix ( constructor , tag , 'RegExp' ) ;
789867 if ( prefix !== 'RegExp ' )
790868 base = `${ prefix } ${ base } ` ;
791- if ( keys . length === 0 || ( recurseTimes > ctx . depth && ctx . depth !== null ) )
869+ if ( ( keys . length === 0 && protoProps === undefined ) ||
870+ ( recurseTimes > ctx . depth && ctx . depth !== null ) ) {
792871 return ctx . stylize ( base , 'regexp' ) ;
872+ }
793873 } else if ( isDate ( value ) ) {
794874 // Make dates with properties first say the date
795875 base = NumberIsNaN ( DatePrototypeGetTime ( value ) ) ?
@@ -798,12 +878,12 @@ function formatRaw(ctx, value, recurseTimes, typedArray) {
798878 const prefix = getPrefix ( constructor , tag , 'Date' ) ;
799879 if ( prefix !== 'Date ' )
800880 base = `${ prefix } ${ base } ` ;
801- if ( keys . length === 0 ) {
881+ if ( keys . length === 0 && protoProps === undefined ) {
802882 return ctx . stylize ( base , 'date' ) ;
803883 }
804884 } else if ( isError ( value ) ) {
805885 base = formatError ( value , constructor , tag , ctx ) ;
806- if ( keys . length === 0 )
886+ if ( keys . length === 0 && protoProps === undefined )
807887 return base ;
808888 } else if ( isAnyArrayBuffer ( value ) ) {
809889 // Fast path for ArrayBuffer and SharedArrayBuffer.
@@ -814,7 +894,7 @@ function formatRaw(ctx, value, recurseTimes, typedArray) {
814894 const prefix = getPrefix ( constructor , tag , arrayType ) ;
815895 if ( typedArray === undefined ) {
816896 formatter = formatArrayBuffer ;
817- } else if ( keys . length === 0 ) {
897+ } else if ( keys . length === 0 && protoProps === undefined ) {
818898 return prefix +
819899 `{ byteLength: ${ formatNumber ( ctx . stylize , value . byteLength ) } }` ;
820900 }
@@ -838,7 +918,7 @@ function formatRaw(ctx, value, recurseTimes, typedArray) {
838918 formatter = formatNamespaceObject ;
839919 } else if ( isBoxedPrimitive ( value ) ) {
840920 base = getBoxedBase ( value , ctx , keys , constructor , tag ) ;
841- if ( keys . length === 0 ) {
921+ if ( keys . length === 0 && protoProps === undefined ) {
842922 return base ;
843923 }
844924 } else {
@@ -858,7 +938,7 @@ function formatRaw(ctx, value, recurseTimes, typedArray) {
858938 formatter = formatIterator ;
859939 // Handle other regular objects again.
860940 } else {
861- if ( keys . length === 0 ) {
941+ if ( keys . length === 0 && protoProps === undefined ) {
862942 if ( isExternal ( value ) )
863943 return ctx . stylize ( '[External]' , 'special' ) ;
864944 return `${ getCtxStyle ( value , constructor , tag ) } {}` ;
@@ -886,6 +966,9 @@ function formatRaw(ctx, value, recurseTimes, typedArray) {
886966 output . push (
887967 formatProperty ( ctx , value , recurseTimes , keys [ i ] , extrasType ) ) ;
888968 }
969+ if ( protoProps !== undefined ) {
970+ output . push ( ...protoProps ) ;
971+ }
889972 } catch ( err ) {
890973 const constructorName = getCtxStyle ( value , constructor , tag ) . slice ( 0 , - 1 ) ;
891974 return handleMaxCallStackSize ( ctx , err , constructorName , indentationLvl ) ;
@@ -1349,6 +1432,7 @@ function formatTypedArray(ctx, value, recurseTimes) {
13491432 }
13501433 if ( ctx . showHidden ) {
13511434 // .buffer goes last, it's not a primitive like the others.
1435+ // All besides `BYTES_PER_ELEMENT` are actually getters.
13521436 ctx . indentationLvl += 2 ;
13531437 for ( const key of [
13541438 'BYTES_PER_ELEMENT' ,
@@ -1497,10 +1581,10 @@ function formatPromise(ctx, value, recurseTimes) {
14971581 return output ;
14981582}
14991583
1500- function formatProperty ( ctx , value , recurseTimes , key , type ) {
1584+ function formatProperty ( ctx , value , recurseTimes , key , type , desc ) {
15011585 let name , str ;
15021586 let extra = ' ' ;
1503- const desc = ObjectGetOwnPropertyDescriptor ( value , key ) ||
1587+ desc = desc || ObjectGetOwnPropertyDescriptor ( value , key ) ||
15041588 { value : value [ key ] , enumerable : true } ;
15051589 if ( desc . value !== undefined ) {
15061590 const diff = ( type !== kObjectType || ctx . compact !== true ) ? 2 : 3 ;
0 commit comments