diff --git a/doc/api/util.md b/doc/api/util.md index 7c9b06d2c807d3..0b1f87582ccfd5 100644 --- a/doc/api/util.md +++ b/doc/api/util.md @@ -487,7 +487,7 @@ added: v0.3.0 changes: - version: REPLACEME pr-url: https://github.com/nodejs/node/pull/43576 - description: add support for `maxArrayLength` when inspecting `Set` and `Map`. + description: The `maxItemLength` option is supported now. - version: - v17.3.0 - v16.14.0 @@ -588,11 +588,15 @@ changes: **Default:** `true`. * `showProxy` {boolean} If `true`, `Proxy` inspection includes the [`target` and `handler`][] objects. **Default:** `false`. - * `maxArrayLength` {integer} Specifies the maximum number of `Array`, - [`TypedArray`][], [`Map`][], [`Set`][], [`WeakMap`][], + * `maxItemLength` {integer} Specifies the maximum number of iterable items + such as `Array`, [`TypedArray`][], [`Map`][], [`Set`][], [`WeakMap`][], and [`WeakSet`][] elements to include when formatting. Set to `null` or `Infinity` to show all elements. Set to `0` or negative to show no elements. **Default:** `100`. + * `maxArrayLength` {integer} Specifies the maximum number of `Array`, + [`TypedArray`][], [`WeakMap`][], and [`WeakSet`][] elements to include when + formatting. Set to `null` or `Infinity` to show all elements. Set to `0` or + negative to show no elements. **Default:** `100`. * `maxStringLength` {integer} Specifies the maximum number of characters to include when formatting. Set to `null` or `Infinity` to show all elements. 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 })); ``` The `showHidden` option allows [`WeakMap`][] and [`WeakSet`][] entries to be -inspected. If there are more entries than `maxArrayLength`, there is no +inspected. If there are more entries than `maxItemLength`, there is no guarantee which entries are displayed. That means retrieving the same [`WeakSet`][] entries twice may result in different output. Furthermore, entries with no remaining strong references may be garbage collected at any time. @@ -1004,7 +1008,7 @@ const util = require('node:util'); const arr = Array(101).fill(0); console.log(arr); // Logs the truncated array -util.inspect.defaultOptions.maxArrayLength = null; +util.inspect.defaultOptions.maxItemLength = null; console.log(arr); // logs the full array ``` diff --git a/lib/internal/util/inspect.js b/lib/internal/util/inspect.js index 8112faeb257f3c..98c2d17e020d0e 100644 --- a/lib/internal/util/inspect.js +++ b/lib/internal/util/inspect.js @@ -159,20 +159,31 @@ const isUndetectableObject = (v) => typeof v === 'undefined' && v !== undefined; // These options must stay in sync with `getUserOptions`. So if any option will // be added or removed, `getUserOptions` must also be updated accordingly. -const inspectDefaultOptions = ObjectSeal({ +const inspectDefaultOptions = { showHidden: false, depth: 2, colors: false, customInspect: true, showProxy: false, - maxArrayLength: 100, + maxItemLength: 100, maxStringLength: 10000, breakLength: 80, compact: 3, sorted: false, getters: false, numericSeparator: false, +}; +ObjectDefineProperty(inspectDefaultOptions, 'maxArrayLength', { + __proto__: null, + get() { + return inspectDefaultOptions.maxItemLength; + }, + set(val) { + inspectDefaultOptions.maxItemLength = val; + }, + enumerable: true, }); +ObjectSeal(inspectDefaultOptions); const kObjectType = 0; const kArrayType = 1; @@ -242,6 +253,7 @@ function getUserOptions(ctx, isCrossContext) { customInspect: ctx.customInspect, showProxy: ctx.showProxy, maxArrayLength: ctx.maxArrayLength, + maxItemLength: ctx.maxItemLength, maxStringLength: ctx.maxStringLength, breakLength: ctx.breakLength, compact: ctx.compact, @@ -302,7 +314,7 @@ function inspect(value, opts) { colors: inspectDefaultOptions.colors, customInspect: inspectDefaultOptions.customInspect, showProxy: inspectDefaultOptions.showProxy, - maxArrayLength: inspectDefaultOptions.maxArrayLength, + maxItemLength: inspectDefaultOptions.maxItemLength, maxStringLength: inspectDefaultOptions.maxStringLength, breakLength: inspectDefaultOptions.breakLength, compact: inspectDefaultOptions.compact, @@ -310,6 +322,17 @@ function inspect(value, opts) { getters: inspectDefaultOptions.getters, numericSeparator: inspectDefaultOptions.numericSeparator, }; + ObjectDefineProperty(ctx, 'maxArrayLength', { + __proto__: null, + get() { + return ctx.maxItemLength; + }, + set(val) { + ctx.maxItemLength = val; + }, + enumerable: true, + }); + if (arguments.length > 1) { // Legacy... if (arguments.length > 2) { @@ -342,7 +365,7 @@ function inspect(value, opts) { } } if (ctx.colors) ctx.stylize = stylizeWithColor; - if (ctx.maxArrayLength === null) ctx.maxArrayLength = Infinity; + if (ctx.maxItemLength === null) ctx.maxItemLength = Infinity; if (ctx.maxStringLength === null) ctx.maxStringLength = Infinity; return formatValue(ctx, value, 0); } @@ -1345,7 +1368,7 @@ function groupArrayElements(ctx, output, value) { let maxLength = 0; let i = 0; let outputLength = output.length; - if (ctx.maxArrayLength < output.length) { + if (ctx.maxItemLength < output.length) { // This makes sure the "... n more items" part is not taken into account. outputLength--; } @@ -1442,7 +1465,7 @@ function groupArrayElements(ctx, output, value) { } ArrayPrototypePush(tmp, str); } - if (ctx.maxArrayLength < output.length) { + if (ctx.maxItemLength < output.length) { ArrayPrototypePush(tmp, output[outputLength]); } output = tmp; @@ -1631,9 +1654,9 @@ function formatArrayBuffer(ctx, value) { hexSlice = uncurryThis(require('buffer').Buffer.prototype.hexSlice); let str = StringPrototypeTrim(RegExpPrototypeSymbolReplace( /(.{2})/g, - hexSlice(buffer, 0, MathMin(ctx.maxArrayLength, buffer.length)), + hexSlice(buffer, 0, MathMin(ctx.maxItemLength, buffer.length)), '$1 ')); - const remaining = buffer.length - ctx.maxArrayLength; + const remaining = buffer.length - ctx.maxItemLength; if (remaining > 0) str += ` ... ${remaining} more byte${remaining > 1 ? 's' : ''}`; return [`${ctx.stylize('[Uint8Contents]', 'special')}: <${str}>`]; @@ -1641,7 +1664,7 @@ function formatArrayBuffer(ctx, value) { function formatArray(ctx, value, recurseTimes) { const valLen = value.length; - const len = MathMin(MathMax(0, ctx.maxArrayLength), valLen); + const len = MathMin(MathMax(0, ctx.maxItemLength), valLen); const remaining = valLen - len; const output = []; @@ -1658,7 +1681,7 @@ function formatArray(ctx, value, recurseTimes) { } function formatTypedArray(value, length, ctx, ignored, recurseTimes) { - const maxLength = MathMin(MathMax(0, ctx.maxArrayLength), length); + const maxLength = MathMin(MathMax(0, ctx.maxItemLength), length); const remaining = value.length - maxLength; const output = new Array(maxLength); const elementFormatter = value.length > 0 && typeof value[0] === 'number' ? @@ -1691,7 +1714,7 @@ function formatTypedArray(value, length, ctx, ignored, recurseTimes) { function formatSet(value, ctx, ignored, recurseTimes) { const length = value.size; - const maxLength = MathMin(MathMax(0, ctx.maxArrayLength), length); + const maxLength = MathMin(MathMax(0, ctx.maxItemLength), length); const remaining = length - maxLength; const output = []; ctx.indentationLvl += 2; @@ -1710,7 +1733,7 @@ function formatSet(value, ctx, ignored, recurseTimes) { function formatMap(value, ctx, ignored, recurseTimes) { const length = value.size; - const maxLength = MathMin(MathMax(0, ctx.maxArrayLength), length); + const maxLength = MathMin(MathMax(0, ctx.maxItemLength), length); const remaining = length - maxLength; const output = []; ctx.indentationLvl += 2; @@ -1730,8 +1753,8 @@ function formatMap(value, ctx, ignored, recurseTimes) { } function formatSetIterInner(ctx, recurseTimes, entries, state) { - const maxArrayLength = MathMax(ctx.maxArrayLength, 0); - const maxLength = MathMin(maxArrayLength, entries.length); + const maxItemLength = MathMax(ctx.maxItemLength, 0); + const maxLength = MathMin(maxItemLength, entries.length); const output = new Array(maxLength); ctx.indentationLvl += 2; for (let i = 0; i < maxLength; i++) { @@ -1752,11 +1775,11 @@ function formatSetIterInner(ctx, recurseTimes, entries, state) { } function formatMapIterInner(ctx, recurseTimes, entries, state) { - const maxArrayLength = MathMax(ctx.maxArrayLength, 0); + const maxItemLength = MathMax(ctx.maxItemLength, 0); // Entries exist as [key1, val1, key2, val2, ...] const len = entries.length / 2; - const remaining = len - maxArrayLength; - const maxLength = MathMin(maxArrayLength, len); + const remaining = len - maxItemLength; + const maxLength = MathMin(maxItemLength, len); let output = new Array(maxLength); let i = 0; ctx.indentationLvl += 2; diff --git a/test/parallel/test-util-inspect.js b/test/parallel/test-util-inspect.js index d0a17849e3d31e..764e1396c98f56 100644 --- a/test/parallel/test-util-inspect.js +++ b/test/parallel/test-util-inspect.js @@ -218,9 +218,15 @@ assert.doesNotMatch( assert.strictEqual(util.inspect(ab, { showHidden: true, maxArrayLength: 2 }), 'ArrayBuffer { [Uint8Contents]' + ': <00 00 ... 1 more byte>, byteLength: 3 }'); + assert.strictEqual(util.inspect(ab, { showHidden: true, maxItemLength: 2 }), + 'ArrayBuffer { [Uint8Contents]' + + ': <00 00 ... 1 more byte>, byteLength: 3 }'); assert.strictEqual(util.inspect(ab, { showHidden: true, maxArrayLength: 1 }), 'ArrayBuffer { [Uint8Contents]' + ': <00 ... 2 more bytes>, byteLength: 3 }'); + assert.strictEqual(util.inspect(ab, { showHidden: true, maxItemLength: 1 }), + 'ArrayBuffer { [Uint8Contents]' + + ': <00 ... 2 more bytes>, byteLength: 3 }'); } // Now do the same checks but from a different context. @@ -571,10 +577,17 @@ assert.strictEqual(util.inspect(-5e-324), '-5e-324'); util.inspect(a, { maxArrayLength: 4 }), "[ 'foo', <1 empty item>, 'baz', <97 empty items>, ... 1 more item ]" ); + assert.strictEqual( + util.inspect(a, { maxItemLength: 4 }), + "[ 'foo', <1 empty item>, 'baz', <97 empty items>, ... 1 more item ]" + ); // test 4 special case assert.strictEqual(util.inspect(a, { maxArrayLength: 2 }), "[ 'foo', <1 empty item>, ... 99 more items ]"); + assert.strictEqual(util.inspect(a, { + maxItemLength: 2 + }), "[ 'foo', <1 empty item>, ... 99 more items ]"); } // Test for Array constructor in different context. @@ -1172,6 +1185,7 @@ if (typeof Symbol !== 'undefined') { assert.strictEqual(util.inspect(new Set()), 'Set(0) {}'); assert.strictEqual(util.inspect(new Set([1, 2, 3])), 'Set(3) { 1, 2, 3 }'); assert.strictEqual(util.inspect(new Set([1, 2, 3]), { maxArrayLength: 1 }), 'Set(3) { 1, ... 2 more items }'); + assert.strictEqual(util.inspect(new Set([1, 2, 3]), { maxItemLength: 1 }), 'Set(3) { 1, ... 2 more items }'); const set = new Set(['foo']); set.bar = 42; assert.strictEqual( @@ -1194,6 +1208,8 @@ if (typeof Symbol !== 'undefined') { "Map(3) { 1 => 'a', 2 => 'b', 3 => 'c' }"); assert.strictEqual(util.inspect(new Map([[1, 'a'], [2, 'b'], [3, 'c']]), { maxArrayLength: 1 }), "Map(3) { 1 => 'a', ... 2 more items }"); + assert.strictEqual(util.inspect(new Map([[1, 'a'], [2, 'b'], [3, 'c']]), { maxItemLength: 1 }), + "Map(3) { 1 => 'a', ... 2 more items }"); const map = new Map([['foo', null]]); map.bar = 42; assert.strictEqual(util.inspect(map, true), @@ -1280,6 +1296,8 @@ if (typeof Symbol !== 'undefined') { map.set('A', 'B!'); assert.strictEqual(util.inspect(map.entries(), { maxArrayLength: 1 }), "[Map Entries] { [ 'foo', 'bar' ], ... 1 more item }"); + assert.strictEqual(util.inspect(map.entries(), { maxItemLength: 1 }), + "[Map Entries] { [ 'foo', 'bar' ], ... 1 more item }"); // Make sure the iterator doesn't get consumed. const keys = map.keys(); assert.strictEqual(util.inspect(keys), "[Map Iterator] { 'foo', 'A' }"); @@ -1288,6 +1306,9 @@ if (typeof Symbol !== 'undefined') { assert.strictEqual( util.inspect(keys, { maxArrayLength: 0 }), '[Map Iterator] { ... 2 more items, extra: true }'); + assert.strictEqual( + util.inspect(keys, { maxItemLength: 0 }), + '[Map Iterator] { ... 2 more items, extra: true }'); } // Test Set iterators. @@ -1311,6 +1332,9 @@ if (typeof Symbol !== 'undefined') { assert.strictEqual( util.inspect(keys, { maxArrayLength: 1 }), '[Set Iterator] { 1, ... 1 more item, extra: true }'); + assert.strictEqual( + util.inspect(keys, { maxItemLength: 1 }), + '[Set Iterator] { 1, ... 1 more item, extra: true }'); } // Minimal inspection should still return as much information as possible about @@ -1511,6 +1535,39 @@ if (typeof Symbol !== 'undefined') { assert(util.inspect(x, { maxArrayLength: Infinity }).endsWith(' 0, 0\n]')); } +// maxItemLength +{ + const x = new Array(101).fill(); + assert(util.inspect(x).endsWith('1 more item\n]')); + assert(!util.inspect(x, { maxItemLength: 101 }).endsWith('1 more item\n]')); + assert.strictEqual( + util.inspect(x, { maxItemLength: -1 }), + '[ ... 101 more items ]' + ); + assert.strictEqual(util.inspect(x, { maxItemLength: 0 }), + '[ ... 101 more items ]'); +} + +{ + const x = Array(101); + assert.strictEqual(util.inspect(x, { maxItemLength: 0 }), + '[ ... 101 more items ]'); + assert(!util.inspect(x, { maxItemLength: null }).endsWith('1 more item\n]')); + assert(!util.inspect( + x, { maxItemLength: Infinity } + ).endsWith('1 more item ]')); +} + +{ + const x = new Uint8Array(101); + assert(util.inspect(x).endsWith('1 more item\n]')); + assert(!util.inspect(x, { maxItemLength: 101 }).includes('1 more item')); + assert.strictEqual(util.inspect(x, { maxItemLength: 0 }), + 'Uint8Array(101) [ ... 101 more items ]'); + assert(!util.inspect(x, { maxItemLength: null }).includes('1 more item')); + assert(util.inspect(x, { maxItemLength: Infinity }).endsWith(' 0, 0\n]')); +} + { const obj = { foo: 'abc', bar: 'xyz' }; const oneLine = util.inspect(obj, { breakLength: Infinity }); @@ -1922,6 +1979,9 @@ util.inspect(process); out = util.inspect(weakMap, { maxArrayLength: 0, showHidden: true }); expect = 'WeakMap { ... 2 more items }'; assert.strictEqual(out, expect); + out = util.inspect(weakMap, { maxItemLength: 0, showHidden: true }); + expect = 'WeakMap { ... 2 more items }'; + assert.strictEqual(out, expect); weakMap.extra = true; out = util.inspect(weakMap, { maxArrayLength: 1, showHidden: true }); @@ -1931,6 +1991,14 @@ util.inspect(process); 'extra: true }'; assert(out === expect || out === expectAlt, `Found: "${out}"\nrather than: "${expect}"\nor: "${expectAlt}"`); + weakMap.extra = true; + out = util.inspect(weakMap, { maxItemLength: 1, showHidden: true }); + // It is not possible to determine the output reliable. + expect = 'WeakMap { [ [length]: 0 ] => {}, ... 1 more item, extra: true }'; + expectAlt = 'WeakMap { {} => [ [length]: 0 ], ... 1 more item, ' + + 'extra: true }'; + assert(out === expect || out === expectAlt, + `Found: "${out}"\nrather than: "${expect}"\nor: "${expectAlt}"`); // Test WeakSet arr.push(1); @@ -1946,12 +2014,22 @@ util.inspect(process); out = util.inspect(weakSet, { maxArrayLength: -2, showHidden: true }); expect = 'WeakSet { ... 2 more items }'; assert.strictEqual(out, expect); + out = util.inspect(weakSet, { maxItemLength: -2, showHidden: true }); + expect = 'WeakSet { ... 2 more items }'; + assert.strictEqual(out, expect); weakSet.extra = true; out = util.inspect(weakSet, { maxArrayLength: 1, showHidden: true }); // It is not possible to determine the output reliable. expect = 'WeakSet { {}, ... 1 more item, extra: true }'; expectAlt = 'WeakSet { [ 1, [length]: 1 ], ... 1 more item, extra: true }'; + assert(out === expect || out === expectAlt, + `Found: "${out}"\nrather than: "${expect}"\nor: "${expectAlt}"`); + weakSet.extra = true; + out = util.inspect(weakSet, { maxItemLength: 1, showHidden: true }); + // It is not possible to determine the output reliable. + expect = 'WeakSet { {}, ... 1 more item, extra: true }'; + expectAlt = 'WeakSet { [ 1, [length]: 1 ], ... 1 more item, extra: true }'; assert(out === expect || out === expectAlt, `Found: "${out}"\nrather than: "${expect}"\nor: "${expectAlt}"`); // Keep references to the WeakMap entries, otherwise they could be GCed too