Skip to content

Commit 6828fbb

Browse files
BridgeARaddaleax
authored andcommitted
util: group array elements together
When using `util.inspect()` with `compact` mode set to a number, all array entries exceeding 6 are going to be grouped together into logical parts. PR-URL: #26269 Reviewed-By: James M Snell <[email protected]> Reviewed-By: Anna Henningsen <[email protected]>
1 parent 4500ed8 commit 6828fbb

File tree

3 files changed

+341
-36
lines changed

3 files changed

+341
-36
lines changed

‎doc/api/util.md‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -451,9 +451,9 @@ changes:
451451
to be displayed on a new line. It will also add new lines to text that is
452452
longer than `breakLength`. If set to a number, the most `n` inner elements
453453
are united on a single line as long as all properties fit into
454-
`breakLength`. Note that no text will be reduced below 16 characters, no
455-
matter the `breakLength` size. For more information, see the example below.
456-
**Default:**`true`.
454+
`breakLength`. Short array elements are also grouped together. Note that no
455+
text will be reduced below 16 characters, no matter the `breakLength` size.
456+
For more information, see the example below. **Default:**`true`.
457457
*`sorted`{boolean|Function} If set to `true` or a function, all properties
458458
of an object, and `Set` and `Map` entries are sorted in the resulting
459459
string. If set to `true` the [default sort][] is used. If set to a function,

‎lib/internal/util/inspect.js‎

Lines changed: 145 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -785,8 +785,35 @@ function formatRaw(ctx, value, recurseTimes, typedArray){
785785
}
786786
}
787787

788-
constcombine=typeofctx.compact==='number'&&
789-
ctx.currentDepth-recurseTimes<ctx.compact;
788+
letcombine=false;
789+
if(typeofctx.compact==='number'){
790+
// Memorize the original output length. In case the the output is grouped,
791+
// prevent lining up the entries on a single line.
792+
constentries=output.length;
793+
// Group array elements together if the array contains at least six separate
794+
// entries.
795+
if(extrasType===kArrayExtrasType&&output.length>6){
796+
output=groupArrayElements(ctx,output);
797+
}
798+
// `ctx.currentDepth` is set to the most inner depth of the currently
799+
// inspected object part while `recurseTimes` is the actual current depth
800+
// that is inspected.
801+
//
802+
// Example:
803+
//
804+
// const a ={first: [ 1, 2, 3 ], second:{inner: [ 1, 2, 3 ] }}
805+
//
806+
// The deepest depth of `a` is 2 (a.second.inner) and `a.first` has a max
807+
// depth of 1.
808+
//
809+
// Consolidate all entries of the local most inner depth up to
810+
// `ctx.compact`, as long as the properties are smaller than
811+
// `ctx.breakLength`.
812+
if(ctx.currentDepth-recurseTimes<ctx.compact&&
813+
entries===output.length){
814+
combine=true;
815+
}
816+
}
790817

791818
constres=reduceToSingleString(ctx,output,base,braces,combine);
792819
constbudget=ctx.budget[ctx.indentationLvl]||0;
@@ -805,6 +832,83 @@ function formatRaw(ctx, value, recurseTimes, typedArray){
805832
returnres;
806833
}
807834

835+
functiongroupArrayElements(ctx,output){
836+
lettotalLength=0;
837+
letmaxLength=0;
838+
leti=0;
839+
constdataLen=newArray(output.length);
840+
// Calculate the total length of all output entries and the individual max
841+
// entries length of all output entries. We have to remove colors first,
842+
// otherwise the length would not be calculated properly.
843+
for(;i<output.length;i++){
844+
constlen=ctx.colors ? removeColors(output[i]).length : output[i].length;
845+
dataLen[i]=len;
846+
totalLength+=len;
847+
if(maxLength<len)
848+
maxLength=len;
849+
}
850+
// Add two to `maxLength` as we add a single whitespace character plus a comma
851+
// in-between two entries.
852+
constactualMax=maxLength+2;
853+
// Check if at least three entries fit next to each other and prevent grouping
854+
// of arrays that contains entries of very different length (i.e., if a single
855+
// entry is longer than 1/5 of all other entries combined). Otherwise the
856+
// space in-between small entries would be enormous.
857+
if(actualMax*3+ctx.indentationLvl<ctx.breakLength&&
858+
(totalLength/maxLength>5||maxLength<=6)){
859+
860+
constapproxCharHeights=2.5;
861+
constbias=1;
862+
// Dynamically check how many columns seem possible.
863+
constcolumns=Math.min(
864+
// Ideally a square should be drawn. We expect a character to be about 2.5
865+
// times as high as wide. This is the area formula to calculate a square
866+
// which contains n rectangles of size `actualMax * approxCharHeights`.
867+
// Divide that by `actualMax` to receive the correct number of columns.
868+
// The added bias slightly increases the columns for short entries.
869+
Math.round(
870+
Math.sqrt(
871+
approxCharHeights*(actualMax-bias)*output.length
872+
)/(actualMax-bias)
873+
),
874+
// Limit array grouping for small `compact` modes as the user requested
875+
// minimal grouping.
876+
ctx.compact*3,
877+
// Limit the columns to a maximum of ten.
878+
10
879+
);
880+
// Return with the original output if no grouping should happen.
881+
if(columns<=1){
882+
returnoutput;
883+
}
884+
// Calculate the maximum length of all entries that are visible in the first
885+
// column of the group.
886+
consttmp=[];
887+
letfirstLineMaxLength=dataLen[0];
888+
for(i=columns;i<dataLen.length;i+=columns){
889+
if(dataLen[i]>firstLineMaxLength)
890+
firstLineMaxLength=dataLen[i];
891+
}
892+
// Each iteration creates a single line of grouped entries.
893+
for(i=0;i<output.length;i+=columns){
894+
// Calculate extra color padding in case it's active. This has to be done
895+
// line by line as some lines might contain more colors than others.
896+
letcolorPadding=output[i].length-dataLen[i];
897+
// Add padding to the first column of the output.
898+
letstr=output[i].padStart(firstLineMaxLength+colorPadding,' ');
899+
// The last lines may contain less entries than columns.
900+
constmax=Math.min(i+columns,output.length);
901+
for(varj=i+1;j<max;j++){
902+
colorPadding=output[j].length-dataLen[j];
903+
str+=`, ${output[j].padStart(maxLength+colorPadding,' ')}`;
904+
}
905+
tmp.push(str);
906+
}
907+
output=tmp;
908+
}
909+
returnoutput;
910+
}
911+
808912
functionhandleMaxCallStackSize(ctx,err,constructor,tag,indentationLvl){
809913
if(isStackOverflowError(err)){
810914
ctx.seen.pop();
@@ -1196,50 +1300,58 @@ function formatProperty(ctx, value, recurseTimes, key, type){
11961300
return`${name}:${extra}${str}`;
11971301
}
11981302

1303+
functionisBelowBreakLength(ctx,output,start){
1304+
// Each entry is separated by at least a comma. Thus, we start with a total
1305+
// length of at least `output.length`. In addition, some cases have a
1306+
// whitespace in-between each other that is added to the total as well.
1307+
lettotalLength=output.length+start;
1308+
if(totalLength+output.length>ctx.breakLength)
1309+
returnfalse;
1310+
for(vari=0;i<output.length;i++){
1311+
if(ctx.colors){
1312+
totalLength+=removeColors(output[i]).length;
1313+
}else{
1314+
totalLength+=output[i].length;
1315+
}
1316+
if(totalLength>ctx.breakLength){
1317+
returnfalse;
1318+
}
1319+
}
1320+
returntrue;
1321+
}
1322+
11991323
functionreduceToSingleString(ctx,output,base,braces,combine=false){
1200-
constbreakLength=ctx.breakLength;
1201-
leti=0;
12021324
if(ctx.compact!==true){
12031325
if(combine){
1204-
consttotalLength=output.reduce((sum,cur)=>sum+cur.length,0);
1205-
if(totalLength+output.length*2<breakLength){
1206-
letres=`${base ? `${base} ` : ''}${braces[0]} `;
1207-
for(;i<output.length-1;i++){
1208-
res+=`${output[i]}, `;
1209-
}
1210-
res+=`${output[i]}${braces[1]}`;
1211-
returnres;
1326+
// Line up all entries on a single line in case the entries do not exceed
1327+
// `breakLength`. Add 10 as constant to start next to all other factors
1328+
// that may reduce `breakLength`.
1329+
conststart=output.length+ctx.indentationLvl+
1330+
braces[0].length+base.length+10;
1331+
if(isBelowBreakLength(ctx,output,start)){
1332+
return`${base ? `${base} ` : ''}${braces[0]}${join(output,', ')} `+
1333+
braces[1];
12121334
}
12131335
}
1336+
// Line up each entry on an individual line.
12141337
constindentation=`\n${' '.repeat(ctx.indentationLvl)}`;
1215-
letres=`${base ? `${base} ` : ''}${braces[0]}${indentation} `;
1216-
for(;i<output.length-1;i++){
1217-
res+=`${output[i]},${indentation} `;
1218-
}
1219-
res+=`${output[i]}${indentation}${braces[1]}`;
1220-
returnres;
1338+
return`${base ? `${base} ` : ''}${braces[0]}${indentation} `+
1339+
`${join(output,`,${indentation} `)}${indentation}${braces[1]}`;
12211340
}
1222-
if(output.length*2<=breakLength){
1223-
letlength=0;
1224-
for(;i<output.length&&length<=breakLength;i++){
1225-
if(ctx.colors){
1226-
length+=removeColors(output[i]).length+1;
1227-
}else{
1228-
length+=output[i].length+1;
1229-
}
1230-
}
1231-
if(length<=breakLength)
1232-
return`${braces[0]}${base ? ` ${base}` : ''}${join(output,', ')} `+
1233-
braces[1];
1341+
// Line up all entries on a single line in case the entries do not exceed
1342+
// `breakLength`.
1343+
if(isBelowBreakLength(ctx,output,0)){
1344+
return`${braces[0]}${base ? ` ${base}` : ''}${join(output,', ')} `+
1345+
braces[1];
12341346
}
1347+
constindentation=' '.repeat(ctx.indentationLvl);
12351348
// If the opening "brace" is too large, like in the case of "Set{",
12361349
// we need to force the first item to be on the next line or the
12371350
// items will not line up correctly.
1238-
constindentation=' '.repeat(ctx.indentationLvl);
12391351
constln=base===''&&braces[0].length===1 ?
12401352
' ' : `${base ? ` ${base}` : ''}\n${indentation} `;
1241-
conststr=join(output,`,\n${indentation} `);
1242-
return`${braces[0]}${ln}${str}${braces[1]}`;
1353+
// Line up each entry on an individual line.
1354+
return`${braces[0]}${ln}${join(output,`,\n${indentation} `)}${braces[1]}`;
12431355
}
12441356

12451357
module.exports={

0 commit comments

Comments
(0)