@@ -14,7 +14,6 @@ const {
1414 ArrayPrototypeIndexOf,
1515 ArrayPrototypeJoin,
1616 ArrayPrototypeMap,
17- ArrayPrototypePop,
1817 ArrayPrototypePush,
1918 ArrayPrototypePushApply,
2019 ArrayPrototypeSlice,
@@ -227,6 +226,7 @@ const inspectDefaultOptions = ObjectSeal({
227226 customInspect : true ,
228227 showProxy : false ,
229228 maxArrayLength : 100 ,
229+ maxObjectProperties : Infinity ,
230230 maxStringLength : 10000 ,
231231 breakLength : 80 ,
232232 compact : 3 ,
@@ -305,6 +305,7 @@ function getUserOptions(ctx, isCrossContext) {
305305 customInspect : ctx . customInspect ,
306306 showProxy : ctx . showProxy ,
307307 maxArrayLength : ctx . maxArrayLength ,
308+ maxObjectProperties : ctx . maxObjectProperties ,
308309 maxStringLength : ctx . maxStringLength ,
309310 breakLength : ctx . breakLength ,
310311 compact : ctx . compact ,
@@ -365,6 +366,7 @@ function inspect(value, opts) {
365366 customInspect : inspectDefaultOptions . customInspect ,
366367 showProxy : inspectDefaultOptions . showProxy ,
367368 maxArrayLength : inspectDefaultOptions . maxArrayLength ,
369+ maxObjectProperties : inspectDefaultOptions . maxObjectProperties ,
368370 maxStringLength : inspectDefaultOptions . maxStringLength ,
369371 breakLength : inspectDefaultOptions . breakLength ,
370372 compact : inspectDefaultOptions . compact ,
@@ -405,6 +407,7 @@ function inspect(value, opts) {
405407 }
406408 if ( ctx . colors ) ctx . stylize = stylizeWithColor ;
407409 if ( ctx . maxArrayLength === null ) ctx . maxArrayLength = Infinity ;
410+ if ( ctx . maxObjectProperties === null ) ctx . maxObjectProperties = Infinity ;
408411 if ( ctx . maxStringLength === null ) ctx . maxStringLength = Infinity ;
409412 return formatValue ( ctx , value , 0 ) ;
410413}
@@ -914,8 +917,7 @@ function getConstructorName(obj, ctx, recurseTimes, protoProps) {
914917 const { name, constructor } = wellKnownPrototypeNameAndConstructor ;
915918 if ( FunctionPrototypeSymbolHasInstance ( constructor , tmp ) ) {
916919 if ( protoProps !== undefined && firstProto !== obj ) {
917- addPrototypeProperties (
918- ctx , tmp , firstProto || tmp , recurseTimes , protoProps ) ;
920+ addPrototypeProperties ( tmp , firstProto || tmp , protoProps ) ;
919921 }
920922 return name ;
921923 }
@@ -928,8 +930,7 @@ function getConstructorName(obj, ctx, recurseTimes, protoProps) {
928930 if ( protoProps !== undefined &&
929931 ( firstProto !== obj ||
930932 ! builtInObjects . has ( descriptor . value . name ) ) ) {
931- addPrototypeProperties (
932- ctx , tmp , firstProto || tmp , recurseTimes , protoProps ) ;
933+ addPrototypeProperties ( tmp , firstProto || tmp , protoProps ) ;
933934 }
934935 return String ( descriptor . value . name ) ;
935936 }
@@ -964,10 +965,9 @@ function getConstructorName(obj, ctx, recurseTimes, protoProps) {
964965 return `${ res } <${ protoConstr } >` ;
965966}
966967
967- // This function has the side effect of adding prototype properties to the
968- // `output` argument (which is an array). This is intended to highlight user
969- // defined prototype properties.
970- function addPrototypeProperties ( ctx , main , obj , recurseTimes , output ) {
968+ // Collect user defined prototype properties. They are formatted later so a
969+ // property limit can discard them without inspecting their values.
970+ function addPrototypeProperties ( main , obj , output ) {
971971 let depth = 0 ;
972972 let keys ;
973973 let keySet ;
@@ -994,7 +994,6 @@ function addPrototypeProperties(ctx, main, obj, recurseTimes, output) {
994994 }
995995 // Get all own property names and symbols.
996996 keys = ReflectOwnKeys ( obj ) ;
997- ArrayPrototypePush ( ctx . seen , main ) ;
998997 for ( const key of keys ) {
999998 // Ignore the `constructor` property and keys that exist on layers above.
1000999 if ( key === 'constructor' ||
@@ -1006,22 +1005,41 @@ function addPrototypeProperties(ctx, main, obj, recurseTimes, output) {
10061005 if ( typeof desc . value === 'function' ) {
10071006 continue ;
10081007 }
1009- const value = formatProperty (
1010- ctx , obj , recurseTimes , key , kObjectType , desc , main ) ;
1011- if ( ctx . colors ) {
1012- // Faint!
1013- ArrayPrototypePush ( output , `\u001b[2m${ value } \u001b[22m` ) ;
1014- } else {
1015- ArrayPrototypePush ( output , value ) ;
1016- }
1008+ ArrayPrototypePush ( output , [ obj , key , desc ] ) ;
10171009 }
1018- ArrayPrototypePop ( ctx . seen ) ;
10191010 // Limit the inspection to up to three prototype layers. Using `recurseTimes`
10201011 // is not a good choice here, because it's as if the properties are declared
10211012 // on the current object from the users perspective.
10221013 } while ( ++ depth !== 3 ) ;
10231014}
10241015
1016+ function truncateProperties ( properties , limit ) {
1017+ // A NaN limit leaves `properties` untouched.
1018+ if ( ! ( properties . length > limit ) ) {
1019+ return 0 ;
1020+ }
1021+ const omitted = properties . length - limit ;
1022+ properties . length = limit ;
1023+ return omitted ;
1024+ }
1025+
1026+ // Discards properties beyond `ctx.maxObjectProperties`, retaining own
1027+ // properties before prototype properties. Property candidates have already
1028+ // been collected, so this limits formatting rather than enumeration. Returns
1029+ // the number omitted.
1030+ function limitProperties ( ctx , keys , protoProps ) {
1031+ if ( ctx . maxObjectProperties === Infinity ) {
1032+ return 0 ;
1033+ }
1034+ // Assigning to `array.length` requires an integral value.
1035+ const budget = MathTrunc ( MathMax ( 0 , ctx . maxObjectProperties ) ) ;
1036+ let omitted = truncateProperties ( keys , budget ) ;
1037+ if ( protoProps !== undefined ) {
1038+ omitted += truncateProperties ( protoProps , budget - keys . length ) ;
1039+ }
1040+ return omitted ;
1041+ }
1042+
10251043/** @type {(constructor: string, tag: string, fallback: string, size?: string) => string } */
10261044function getPrefix ( constructor , tag , fallback , size = '' ) {
10271045 if ( constructor === null ) {
@@ -1422,9 +1440,26 @@ function formatRaw(ctx, value, recurseTimes, typedArray) {
14221440 constructorName = `[${ constructorName } ]` ;
14231441 return ctx . stylize ( constructorName , 'special' ) ;
14241442 }
1443+
1444+ // This must run after type-specific key normalization and empty-value fast
1445+ // paths. It must not move into getKeys().
1446+ const remainingProperties = limitProperties ( ctx , keys , protoProps ) ;
1447+
1448+ const protoRecurseTimes = recurseTimes ;
14251449 recurseTimes += 1 ;
14261450
14271451 ctx . seen . push ( value ) ;
1452+ let formattedProtoProps ;
1453+ if ( protoProps !== undefined ) {
1454+ formattedProtoProps = new Array ( protoProps . length ) ;
1455+ for ( i = 0 ; i < protoProps . length ; i ++ ) {
1456+ const { 0 : obj , 1 : key , 2 : desc } = protoProps [ i ] ;
1457+ const formatted = formatProperty (
1458+ ctx , obj , protoRecurseTimes , key , kObjectType , desc , value ) ;
1459+ formattedProtoProps [ i ] =
1460+ ctx . colors ? `\u001b[2m${ formatted } \u001b[22m` : formatted ;
1461+ }
1462+ }
14281463 ctx . currentDepth = recurseTimes ;
14291464 let output ;
14301465 const indentationLvl = ctx . indentationLvl ;
@@ -1448,8 +1483,8 @@ function formatRaw(ctx, value, recurseTimes, typedArray) {
14481483 formatProperty ( ctx , value , recurseTimes , keys [ i ] , extrasType ) ,
14491484 ) ;
14501485 }
1451- if ( protoProps !== undefined ) {
1452- ArrayPrototypePushApply ( output , protoProps ) ;
1486+ if ( formattedProtoProps !== undefined ) {
1487+ ArrayPrototypePushApply ( output , formattedProtoProps ) ;
14531488 }
14541489 } catch ( err ) {
14551490 if ( ! isStackOverflowError ( err ) ) throw err ;
@@ -1480,9 +1515,16 @@ function formatRaw(ctx, value, recurseTimes, typedArray) {
14801515 ReflectApply ( ArrayPrototypeSplice , null , sorted ) ;
14811516 }
14821517 }
1518+ if ( remainingProperties > 0 ) {
1519+ ArrayPrototypePush (
1520+ output ,
1521+ remainingText ( remainingProperties , 'property' , 'properties' ) ,
1522+ ) ;
1523+ }
14831524
14841525 const res = reduceToSingleString (
1485- ctx , output , base , braces , extrasType , recurseTimes , value ) ;
1526+ ctx , output , base , braces , extrasType , recurseTimes , value ,
1527+ remainingProperties > 0 ? 1 : 0 ) ;
14861528 const budget = ctx . budget [ ctx . indentationLvl ] || 0 ;
14871529 const newLength = budget + res . length ;
14881530 ctx . budget [ ctx . indentationLvl ] = newLength ;
@@ -2048,12 +2090,15 @@ function formatError(err, constructor, tag, ctx, keys) {
20482090 return stack ;
20492091}
20502092
2051- function groupArrayElements ( ctx , output , value ) {
2093+ // `trailingEntries` counts entries at the end of `output` that describe omitted
2094+ // content rather than real entries. They must not take part in grouping.
2095+ function groupArrayElements ( ctx , output , value , trailingEntries ) {
20522096 let totalLength = 0 ;
20532097 let maxLength = 0 ;
20542098 let i = 0 ;
2055- let outputLength = output . length ;
2056- if ( ctx . maxArrayLength < output . length ) {
2099+ const outputLengthWithoutTrailing = output . length - trailingEntries ;
2100+ let outputLength = outputLengthWithoutTrailing ;
2101+ if ( ctx . maxArrayLength < outputLengthWithoutTrailing ) {
20572102 // This makes sure the "... n more items" part is not taken into account.
20582103 outputLength -- ;
20592104 }
@@ -2080,7 +2125,8 @@ function groupArrayElements(ctx, output, value) {
20802125 ( totalLength / actualMax > 5 || maxLength <= 6 ) ) {
20812126
20822127 const approxCharHeights = 2.5 ;
2083- const averageBias = MathSqrt ( actualMax - totalLength / output . length ) ;
2128+ const averageBias =
2129+ MathSqrt ( actualMax - totalLength / outputLengthWithoutTrailing ) ;
20842130 const biasedMax = MathMax ( actualMax - 3 - averageBias , 1 ) ;
20852131 // Dynamically check how many columns seem possible.
20862132 const columns = MathMin (
@@ -2110,7 +2156,7 @@ function groupArrayElements(ctx, output, value) {
21102156 const maxLineLength = [ ] ;
21112157 for ( let i = 0 ; i < columns ; i ++ ) {
21122158 let lineMaxLength = 0 ;
2113- for ( let j = i ; j < output . length ; j += columns ) {
2159+ for ( let j = i ; j < outputLengthWithoutTrailing ; j += columns ) {
21142160 if ( dataLen [ j ] > lineMaxLength )
21152161 lineMaxLength = dataLen [ j ] ;
21162162 }
@@ -2119,7 +2165,7 @@ function groupArrayElements(ctx, output, value) {
21192165 }
21202166 let order = StringPrototypePadStart ;
21212167 if ( value !== undefined ) {
2122- for ( let i = 0 ; i < output . length ; i ++ ) {
2168+ for ( let i = 0 ; i < outputLengthWithoutTrailing ; i ++ ) {
21232169 if ( typeof value [ i ] !== 'number' && typeof value [ i ] !== 'bigint' ) {
21242170 order = StringPrototypePadEnd ;
21252171 break ;
@@ -2150,9 +2196,12 @@ function groupArrayElements(ctx, output, value) {
21502196 }
21512197 ArrayPrototypePush ( tmp , str ) ;
21522198 }
2153- if ( ctx . maxArrayLength < output . length ) {
2199+ if ( ctx . maxArrayLength < outputLengthWithoutTrailing ) {
21542200 ArrayPrototypePush ( tmp , output [ outputLength ] ) ;
21552201 }
2202+ for ( let i = outputLengthWithoutTrailing ; i < output . length ; i ++ ) {
2203+ ArrayPrototypePush ( tmp , output [ i ] ) ;
2204+ }
21562205 output = tmp ;
21572206 }
21582207 return output ;
@@ -2192,7 +2241,8 @@ function addNumericSeparatorEnd(integerString) {
21922241 `${ result } ${ StringPrototypeSlice ( integerString , i ) } ` ;
21932242}
21942243
2195- const remainingText = ( remaining ) => `... ${ remaining } more item${ remaining > 1 ? 's' : '' } ` ;
2244+ const remainingText = ( remaining , singular = 'item' , plural = 'items' ) =>
2245+ `... ${ remaining } more ${ remaining > 1 ? plural : singular } ` ;
21962246
21972247function formatNumber ( fn , number , numericSeparator ) {
21982248 // Format -0 as '-0'. Checking `number === -0` won't distinguish 0 from -0.
@@ -2642,7 +2692,8 @@ function isBelowBreakLength(ctx, output, start, base) {
26422692}
26432693
26442694function reduceToSingleString (
2645- ctx , output , base , braces , extrasType , recurseTimes , value ) {
2695+ ctx , output , base , braces , extrasType , recurseTimes , value ,
2696+ trailingEntries = 0 ) {
26462697 if ( ctx . compact !== true ) {
26472698 if ( typeof ctx . compact === 'number' && ctx . compact >= 1 ) {
26482699 // Memorize the original output length. In case the output is grouped,
@@ -2651,7 +2702,7 @@ function reduceToSingleString(
26512702 // Group array elements together if the array contains at least six
26522703 // separate entries.
26532704 if ( extrasType === kArrayExtrasType && entries > 6 ) {
2654- output = groupArrayElements ( ctx , output , value ) ;
2705+ output = groupArrayElements ( ctx , output , value , trailingEntries ) ;
26552706 }
26562707 // `ctx.currentDepth` is set to the most inner depth of the currently
26572708 // inspected object part while `recurseTimes` is the actual current depth
0 commit comments