Skip to content

Commit 7ae985e

Browse files
committed
util: add maxItemLength option to truncate iterable entries
1 parent 5629a7c commit 7ae985e

File tree

3 files changed

+127
-22
lines changed

3 files changed

+127
-22
lines changed

doc/api/util.md

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -487,7 +487,7 @@ added: v0.3.0
487487
changes:
488488
- version: REPLACEME
489489
pr-url: https://github.com/nodejs/node/pull/43576
490-
description: add support for `maxArrayLength` when inspecting `Set` and `Map`.
490+
description: The `maxItemLength` option is supported now.
491491
- version:
492492
- v17.3.0
493493
- v16.14.0
@@ -588,11 +588,15 @@ changes:
588588
**Default:** `true`.
589589
* `showProxy` {boolean} If `true`, `Proxy` inspection includes
590590
the [`target` and `handler`][] objects. **Default:** `false`.
591-
* `maxArrayLength` {integer} Specifies the maximum number of `Array`,
592-
[`TypedArray`][], [`Map`][], [`Set`][], [`WeakMap`][],
591+
* `maxItemLength` {integer} Specifies the maximum number of iterable items
592+
such as [`Array`][], [`TypedArray`][], [`Map`][], [`Set`][], [`WeakMap`][],
593593
and [`WeakSet`][] elements to include when formatting.
594594
Set to `null` or `Infinity` to show all elements. Set to `0` or
595595
negative to show no elements. **Default:** `100`.
596+
* `maxArrayLength` {integer} Specifies the maximum number of `Array`,
597+
[`TypedArray`][], [`WeakMap`][], and [`WeakSet`][] elements to include when
598+
formatting. Set to `null` or `Infinity` to show all elements. Set to `0` or
599+
negative to show no elements. **Default:** `100`.
596600
* `maxStringLength` {integer} Specifies the maximum number of characters to
597601
include when formatting. Set to `null` or `Infinity` to show all elements.
598602
Set to `0` or negative to show no characters. **Default:** `10000`.
@@ -722,7 +726,7 @@ console.log(util.inspect(o, { compact: false, depth: 5, breakLength: 80 }));
722726
```
723727

724728
The `showHidden` option allows [`WeakMap`][] and [`WeakSet`][] entries to be
725-
inspected. If there are more entries than `maxArrayLength`, there is no
729+
inspected. If there are more entries than `maxItemLength`, there is no
726730
guarantee which entries are displayed. That means retrieving the same
727731
[`WeakSet`][] entries twice may result in different output. Furthermore, entries
728732
with no remaining strong references may be garbage collected at any time.
@@ -1004,7 +1008,7 @@ const util = require('node:util');
10041008
const arr = Array(101).fill(0);
10051009

10061010
console.log(arr); // Logs the truncated array
1007-
util.inspect.defaultOptions.maxArrayLength = null;
1011+
util.inspect.defaultOptions.maxItemLength = null;
10081012
console.log(arr); // logs the full array
10091013
```
10101014

lib/internal/util/inspect.js

Lines changed: 40 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -159,20 +159,31 @@ const isUndetectableObject = (v) => typeof v === 'undefined' && v !== undefined;
159159

160160
// These options must stay in sync with `getUserOptions`. So if any option will
161161
// be added or removed, `getUserOptions` must also be updated accordingly.
162-
const inspectDefaultOptions = ObjectSeal({
162+
const inspectDefaultOptions = {
163163
showHidden: false,
164164
depth: 2,
165165
colors: false,
166166
customInspect: true,
167167
showProxy: false,
168-
maxArrayLength: 100,
168+
maxItemLength: 100,
169169
maxStringLength: 10000,
170170
breakLength: 80,
171171
compact: 3,
172172
sorted: false,
173173
getters: false,
174174
numericSeparator: false,
175+
};
176+
ObjectDefineProperty(inspectDefaultOptions, 'maxArrayLength', {
177+
__proto__: null,
178+
get() {
179+
return inspectDefaultOptions.maxItemLength;
180+
},
181+
set(val) {
182+
inspectDefaultOptions.maxItemLength = val;
183+
},
184+
enumerable: true,
175185
});
186+
ObjectSeal(inspectDefaultOptions);
176187

177188
const kObjectType = 0;
178189
const kArrayType = 1;
@@ -242,6 +253,7 @@ function getUserOptions(ctx, isCrossContext) {
242253
customInspect: ctx.customInspect,
243254
showProxy: ctx.showProxy,
244255
maxArrayLength: ctx.maxArrayLength,
256+
maxItemLength: ctx.maxItemLength,
245257
maxStringLength: ctx.maxStringLength,
246258
breakLength: ctx.breakLength,
247259
compact: ctx.compact,
@@ -302,14 +314,25 @@ function inspect(value, opts) {
302314
colors: inspectDefaultOptions.colors,
303315
customInspect: inspectDefaultOptions.customInspect,
304316
showProxy: inspectDefaultOptions.showProxy,
305-
maxArrayLength: inspectDefaultOptions.maxArrayLength,
317+
maxItemLength: inspectDefaultOptions.maxItemLength,
306318
maxStringLength: inspectDefaultOptions.maxStringLength,
307319
breakLength: inspectDefaultOptions.breakLength,
308320
compact: inspectDefaultOptions.compact,
309321
sorted: inspectDefaultOptions.sorted,
310322
getters: inspectDefaultOptions.getters,
311323
numericSeparator: inspectDefaultOptions.numericSeparator,
312324
};
325+
ObjectDefineProperty(ctx, 'maxArrayLength', {
326+
__proto__: null,
327+
get() {
328+
return ctx.maxItemLength;
329+
},
330+
set(val) {
331+
ctx.maxItemLength = val;
332+
},
333+
enumerable: true,
334+
});
335+
313336
if (arguments.length > 1) {
314337
// Legacy...
315338
if (arguments.length > 2) {
@@ -342,7 +365,7 @@ function inspect(value, opts) {
342365
}
343366
}
344367
if (ctx.colors) ctx.stylize = stylizeWithColor;
345-
if (ctx.maxArrayLength === null) ctx.maxArrayLength = Infinity;
368+
if (ctx.maxItemLength === null) ctx.maxItemLength = Infinity;
346369
if (ctx.maxStringLength === null) ctx.maxStringLength = Infinity;
347370
return formatValue(ctx, value, 0);
348371
}
@@ -1345,7 +1368,7 @@ function groupArrayElements(ctx, output, value) {
13451368
let maxLength = 0;
13461369
let i = 0;
13471370
let outputLength = output.length;
1348-
if (ctx.maxArrayLength < output.length) {
1371+
if (ctx.maxItemLength < output.length) {
13491372
// This makes sure the "... n more items" part is not taken into account.
13501373
outputLength--;
13511374
}
@@ -1442,7 +1465,7 @@ function groupArrayElements(ctx, output, value) {
14421465
}
14431466
ArrayPrototypePush(tmp, str);
14441467
}
1445-
if (ctx.maxArrayLength < output.length) {
1468+
if (ctx.maxItemLength < output.length) {
14461469
ArrayPrototypePush(tmp, output[outputLength]);
14471470
}
14481471
output = tmp;
@@ -1631,17 +1654,17 @@ function formatArrayBuffer(ctx, value) {
16311654
hexSlice = uncurryThis(require('buffer').Buffer.prototype.hexSlice);
16321655
let str = StringPrototypeTrim(RegExpPrototypeSymbolReplace(
16331656
/(.{2})/g,
1634-
hexSlice(buffer, 0, MathMin(ctx.maxArrayLength, buffer.length)),
1657+
hexSlice(buffer, 0, MathMin(ctx.maxItemLength, buffer.length)),
16351658
'$1 '));
1636-
const remaining = buffer.length - ctx.maxArrayLength;
1659+
const remaining = buffer.length - ctx.maxItemLength;
16371660
if (remaining > 0)
16381661
str += ` ... ${remaining} more byte${remaining > 1 ? 's' : ''}`;
16391662
return [`${ctx.stylize('[Uint8Contents]', 'special')}: <${str}>`];
16401663
}
16411664

16421665
function formatArray(ctx, value, recurseTimes) {
16431666
const valLen = value.length;
1644-
const len = MathMin(MathMax(0, ctx.maxArrayLength), valLen);
1667+
const len = MathMin(MathMax(0, ctx.maxItemLength), valLen);
16451668

16461669
const remaining = valLen - len;
16471670
const output = [];
@@ -1658,7 +1681,7 @@ function formatArray(ctx, value, recurseTimes) {
16581681
}
16591682

16601683
function formatTypedArray(value, length, ctx, ignored, recurseTimes) {
1661-
const maxLength = MathMin(MathMax(0, ctx.maxArrayLength), length);
1684+
const maxLength = MathMin(MathMax(0, ctx.maxItemLength), length);
16621685
const remaining = value.length - maxLength;
16631686
const output = new Array(maxLength);
16641687
const elementFormatter = value.length > 0 && typeof value[0] === 'number' ?
@@ -1691,7 +1714,7 @@ function formatTypedArray(value, length, ctx, ignored, recurseTimes) {
16911714

16921715
function formatSet(value, ctx, ignored, recurseTimes) {
16931716
const length = value.size;
1694-
const maxLength = MathMin(MathMax(0, ctx.maxArrayLength), length);
1717+
const maxLength = MathMin(MathMax(0, ctx.maxItemLength), length);
16951718
const remaining = length - maxLength;
16961719
const output = [];
16971720
ctx.indentationLvl += 2;
@@ -1710,7 +1733,7 @@ function formatSet(value, ctx, ignored, recurseTimes) {
17101733

17111734
function formatMap(value, ctx, ignored, recurseTimes) {
17121735
const length = value.size;
1713-
const maxLength = MathMin(MathMax(0, ctx.maxArrayLength), length);
1736+
const maxLength = MathMin(MathMax(0, ctx.maxItemLength), length);
17141737
const remaining = length - maxLength;
17151738
const output = [];
17161739
ctx.indentationLvl += 2;
@@ -1730,8 +1753,8 @@ function formatMap(value, ctx, ignored, recurseTimes) {
17301753
}
17311754

17321755
function formatSetIterInner(ctx, recurseTimes, entries, state) {
1733-
const maxArrayLength = MathMax(ctx.maxArrayLength, 0);
1734-
const maxLength = MathMin(maxArrayLength, entries.length);
1756+
const maxItemLength = MathMax(ctx.maxItemLength, 0);
1757+
const maxLength = MathMin(maxItemLength, entries.length);
17351758
const output = new Array(maxLength);
17361759
ctx.indentationLvl += 2;
17371760
for (let i = 0; i < maxLength; i++) {
@@ -1752,11 +1775,11 @@ function formatSetIterInner(ctx, recurseTimes, entries, state) {
17521775
}
17531776

17541777
function formatMapIterInner(ctx, recurseTimes, entries, state) {
1755-
const maxArrayLength = MathMax(ctx.maxArrayLength, 0);
1778+
const maxItemLength = MathMax(ctx.maxItemLength, 0);
17561779
// Entries exist as [key1, val1, key2, val2, ...]
17571780
const len = entries.length / 2;
1758-
const remaining = len - maxArrayLength;
1759-
const maxLength = MathMin(maxArrayLength, len);
1781+
const remaining = len - maxItemLength;
1782+
const maxLength = MathMin(maxItemLength, len);
17601783
let output = new Array(maxLength);
17611784
let i = 0;
17621785
ctx.indentationLvl += 2;

test/parallel/test-util-inspect.js

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -218,9 +218,15 @@ assert.doesNotMatch(
218218
assert.strictEqual(util.inspect(ab, { showHidden: true, maxArrayLength: 2 }),
219219
'ArrayBuffer { [Uint8Contents]' +
220220
': <00 00 ... 1 more byte>, byteLength: 3 }');
221+
assert.strictEqual(util.inspect(ab, { showHidden: true, maxItemLength: 2 }),
222+
'ArrayBuffer { [Uint8Contents]' +
223+
': <00 00 ... 1 more byte>, byteLength: 3 }');
221224
assert.strictEqual(util.inspect(ab, { showHidden: true, maxArrayLength: 1 }),
222225
'ArrayBuffer { [Uint8Contents]' +
223226
': <00 ... 2 more bytes>, byteLength: 3 }');
227+
assert.strictEqual(util.inspect(ab, { showHidden: true, maxItemLength: 1 }),
228+
'ArrayBuffer { [Uint8Contents]' +
229+
': <00 ... 2 more bytes>, byteLength: 3 }');
224230
}
225231

226232
// Now do the same checks but from a different context.
@@ -571,10 +577,17 @@ assert.strictEqual(util.inspect(-5e-324), '-5e-324');
571577
util.inspect(a, { maxArrayLength: 4 }),
572578
"[ 'foo', <1 empty item>, 'baz', <97 empty items>, ... 1 more item ]"
573579
);
580+
assert.strictEqual(
581+
util.inspect(a, { maxItemLength: 4 }),
582+
"[ 'foo', <1 empty item>, 'baz', <97 empty items>, ... 1 more item ]"
583+
);
574584
// test 4 special case
575585
assert.strictEqual(util.inspect(a, {
576586
maxArrayLength: 2
577587
}), "[ 'foo', <1 empty item>, ... 99 more items ]");
588+
assert.strictEqual(util.inspect(a, {
589+
maxItemLength: 2
590+
}), "[ 'foo', <1 empty item>, ... 99 more items ]");
578591
}
579592

580593
// Test for Array constructor in different context.
@@ -1172,6 +1185,7 @@ if (typeof Symbol !== 'undefined') {
11721185
assert.strictEqual(util.inspect(new Set()), 'Set(0) {}');
11731186
assert.strictEqual(util.inspect(new Set([1, 2, 3])), 'Set(3) { 1, 2, 3 }');
11741187
assert.strictEqual(util.inspect(new Set([1, 2, 3]), { maxArrayLength: 1 }), 'Set(3) { 1, ... 2 more items }');
1188+
assert.strictEqual(util.inspect(new Set([1, 2, 3]), { maxItemLength: 1 }), 'Set(3) { 1, ... 2 more items }');
11751189
const set = new Set(['foo']);
11761190
set.bar = 42;
11771191
assert.strictEqual(
@@ -1194,6 +1208,8 @@ if (typeof Symbol !== 'undefined') {
11941208
"Map(3) { 1 => 'a', 2 => 'b', 3 => 'c' }");
11951209
assert.strictEqual(util.inspect(new Map([[1, 'a'], [2, 'b'], [3, 'c']]), { maxArrayLength: 1 }),
11961210
"Map(3) { 1 => 'a', ... 2 more items }");
1211+
assert.strictEqual(util.inspect(new Map([[1, 'a'], [2, 'b'], [3, 'c']]), { maxItemLength: 1 }),
1212+
"Map(3) { 1 => 'a', ... 2 more items }");
11971213
const map = new Map([['foo', null]]);
11981214
map.bar = 42;
11991215
assert.strictEqual(util.inspect(map, true),
@@ -1280,6 +1296,8 @@ if (typeof Symbol !== 'undefined') {
12801296
map.set('A', 'B!');
12811297
assert.strictEqual(util.inspect(map.entries(), { maxArrayLength: 1 }),
12821298
"[Map Entries] { [ 'foo', 'bar' ], ... 1 more item }");
1299+
assert.strictEqual(util.inspect(map.entries(), { maxItemLength: 1 }),
1300+
"[Map Entries] { [ 'foo', 'bar' ], ... 1 more item }");
12831301
// Make sure the iterator doesn't get consumed.
12841302
const keys = map.keys();
12851303
assert.strictEqual(util.inspect(keys), "[Map Iterator] { 'foo', 'A' }");
@@ -1288,6 +1306,9 @@ if (typeof Symbol !== 'undefined') {
12881306
assert.strictEqual(
12891307
util.inspect(keys, { maxArrayLength: 0 }),
12901308
'[Map Iterator] { ... 2 more items, extra: true }');
1309+
assert.strictEqual(
1310+
util.inspect(keys, { maxItemLength: 0 }),
1311+
'[Map Iterator] { ... 2 more items, extra: true }');
12911312
}
12921313

12931314
// Test Set iterators.
@@ -1311,6 +1332,9 @@ if (typeof Symbol !== 'undefined') {
13111332
assert.strictEqual(
13121333
util.inspect(keys, { maxArrayLength: 1 }),
13131334
'[Set Iterator] { 1, ... 1 more item, extra: true }');
1335+
assert.strictEqual(
1336+
util.inspect(keys, { maxItemLength: 1 }),
1337+
'[Set Iterator] { 1, ... 1 more item, extra: true }');
13141338
}
13151339

13161340
// Minimal inspection should still return as much information as possible about
@@ -1511,6 +1535,39 @@ if (typeof Symbol !== 'undefined') {
15111535
assert(util.inspect(x, { maxArrayLength: Infinity }).endsWith(' 0, 0\n]'));
15121536
}
15131537

1538+
// maxItemLength
1539+
{
1540+
const x = new Array(101).fill();
1541+
assert(util.inspect(x).endsWith('1 more item\n]'));
1542+
assert(!util.inspect(x, { maxItemLength: 101 }).endsWith('1 more item\n]'));
1543+
assert.strictEqual(
1544+
util.inspect(x, { maxItemLength: -1 }),
1545+
'[ ... 101 more items ]'
1546+
);
1547+
assert.strictEqual(util.inspect(x, { maxItemLength: 0 }),
1548+
'[ ... 101 more items ]');
1549+
}
1550+
1551+
{
1552+
const x = Array(101);
1553+
assert.strictEqual(util.inspect(x, { maxItemLength: 0 }),
1554+
'[ ... 101 more items ]');
1555+
assert(!util.inspect(x, { maxItemLength: null }).endsWith('1 more item\n]'));
1556+
assert(!util.inspect(
1557+
x, { maxItemLength: Infinity }
1558+
).endsWith('1 more item ]'));
1559+
}
1560+
1561+
{
1562+
const x = new Uint8Array(101);
1563+
assert(util.inspect(x).endsWith('1 more item\n]'));
1564+
assert(!util.inspect(x, { maxItemLength: 101 }).includes('1 more item'));
1565+
assert.strictEqual(util.inspect(x, { maxItemLength: 0 }),
1566+
'Uint8Array(101) [ ... 101 more items ]');
1567+
assert(!util.inspect(x, { maxItemLength: null }).includes('1 more item'));
1568+
assert(util.inspect(x, { maxItemLength: Infinity }).endsWith(' 0, 0\n]'));
1569+
}
1570+
15141571
{
15151572
const obj = { foo: 'abc', bar: 'xyz' };
15161573
const oneLine = util.inspect(obj, { breakLength: Infinity });
@@ -1922,6 +1979,9 @@ util.inspect(process);
19221979
out = util.inspect(weakMap, { maxArrayLength: 0, showHidden: true });
19231980
expect = 'WeakMap { ... 2 more items }';
19241981
assert.strictEqual(out, expect);
1982+
out = util.inspect(weakMap, { maxItemLength: 0, showHidden: true });
1983+
expect = 'WeakMap { ... 2 more items }';
1984+
assert.strictEqual(out, expect);
19251985

19261986
weakMap.extra = true;
19271987
out = util.inspect(weakMap, { maxArrayLength: 1, showHidden: true });
@@ -1931,6 +1991,14 @@ util.inspect(process);
19311991
'extra: true }';
19321992
assert(out === expect || out === expectAlt,
19331993
`Found: "${out}"\nrather than: "${expect}"\nor: "${expectAlt}"`);
1994+
weakMap.extra = true;
1995+
out = util.inspect(weakMap, { maxItemLength: 1, showHidden: true });
1996+
// It is not possible to determine the output reliable.
1997+
expect = 'WeakMap { [ [length]: 0 ] => {}, ... 1 more item, extra: true }';
1998+
expectAlt = 'WeakMap { {} => [ [length]: 0 ], ... 1 more item, ' +
1999+
'extra: true }';
2000+
assert(out === expect || out === expectAlt,
2001+
`Found: "${out}"\nrather than: "${expect}"\nor: "${expectAlt}"`);
19342002

19352003
// Test WeakSet
19362004
arr.push(1);
@@ -1946,12 +2014,22 @@ util.inspect(process);
19462014
out = util.inspect(weakSet, { maxArrayLength: -2, showHidden: true });
19472015
expect = 'WeakSet { ... 2 more items }';
19482016
assert.strictEqual(out, expect);
2017+
out = util.inspect(weakSet, { maxItemLength: -2, showHidden: true });
2018+
expect = 'WeakSet { ... 2 more items }';
2019+
assert.strictEqual(out, expect);
19492020

19502021
weakSet.extra = true;
19512022
out = util.inspect(weakSet, { maxArrayLength: 1, showHidden: true });
19522023
// It is not possible to determine the output reliable.
19532024
expect = 'WeakSet { {}, ... 1 more item, extra: true }';
19542025
expectAlt = 'WeakSet { [ 1, [length]: 1 ], ... 1 more item, extra: true }';
2026+
assert(out === expect || out === expectAlt,
2027+
`Found: "${out}"\nrather than: "${expect}"\nor: "${expectAlt}"`);
2028+
weakSet.extra = true;
2029+
out = util.inspect(weakSet, { maxItemLength: 1, showHidden: true });
2030+
// It is not possible to determine the output reliable.
2031+
expect = 'WeakSet { {}, ... 1 more item, extra: true }';
2032+
expectAlt = 'WeakSet { [ 1, [length]: 1 ], ... 1 more item, extra: true }';
19552033
assert(out === expect || out === expectAlt,
19562034
`Found: "${out}"\nrather than: "${expect}"\nor: "${expectAlt}"`);
19572035
// Keep references to the WeakMap entries, otherwise they could be GCed too

0 commit comments

Comments
 (0)