From cc19d08748aa804efefe01ea99ebbc1104ee6928 Mon Sep 17 00:00:00 2001 From: Ruben Bridgewater Date: Thu, 19 Dec 2019 16:12:30 +0100 Subject: [PATCH] util: add (typed) array length to the default output MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Align the inspect output with the one used in the Chrome dev tools. A recent survey outlined that most users prefer to see the number of set and map entries. This should count as well for array sizes. The size is only added to regular arrays in case the constructor is not the default constructor. Typed arrays always indicate their size. Backport-PR-URL: https://github.com/nodejs/node/pull/31431 PR-URL: https://github.com/nodejs/node/pull/31027 Reviewed-By: Michaƫl Zasso Reviewed-By: Rich Trott Reviewed-By: Anto Aravinth --- lib/internal/util/inspect.js | 57 ++++++++---------- test/parallel/test-assert-deep.js | 8 +-- test/parallel/test-buffer-inspect.js | 2 +- test/parallel/test-fs-read-empty-buffer.js | 6 +- test/parallel/test-util-format.js | 2 +- test/parallel/test-util-inspect.js | 67 +++++++++++----------- 6 files changed, 69 insertions(+), 73 deletions(-) diff --git a/lib/internal/util/inspect.js b/lib/internal/util/inspect.js index 4133eb5341ea11..04b76077bba313 100644 --- a/lib/internal/util/inspect.js +++ b/lib/internal/util/inspect.js @@ -119,6 +119,9 @@ const setSizeGetter = uncurryThis( ObjectGetOwnPropertyDescriptor(SetPrototype, 'size').get); const mapSizeGetter = uncurryThis( ObjectGetOwnPropertyDescriptor(MapPrototype, 'size').get); +const typedArraySizeGetter = uncurryThis( + ObjectGetOwnPropertyDescriptor( + ObjectGetPrototypeOf(Uint8Array.prototype), 'length').get); let hexSlice; @@ -564,18 +567,18 @@ function addPrototypeProperties(ctx, main, obj, recurseTimes, isProto, output) { } while (++depth !== 3); } -function getPrefix(constructor, tag, fallback) { +function getPrefix(constructor, tag, fallback, size = '') { if (constructor === null) { if (tag !== '') { - return `[${fallback}: null prototype] [${tag}] `; + return `[${fallback}${size}: null prototype] [${tag}] `; } - return `[${fallback}: null prototype] `; + return `[${fallback}${size}: null prototype] `; } if (tag !== '' && constructor !== tag) { - return `${constructor} [${tag}] `; + return `${constructor}${size} [${tag}] `; } - return `${constructor} `; + return `${constructor}${size} `; } // Look up the keys of the object. @@ -760,58 +763,48 @@ function formatRaw(ctx, value, recurseTimes, typedArray) { if (value[SymbolIterator] || constructor === null) { noIterator = false; if (ArrayIsArray(value)) { - keys = getOwnNonIndexProperties(value, filter); // Only set the constructor for non ordinary ("Array [...]") arrays. - const prefix = getPrefix(constructor, tag, 'Array'); - braces = [`${prefix === 'Array ' ? '' : prefix}[`, ']']; + const prefix = (constructor !== 'Array' || tag !== '') ? + getPrefix(constructor, tag, 'Array', `(${value.length})`) : + ''; + keys = getOwnNonIndexProperties(value, filter); + braces = [`${prefix}[`, ']']; if (value.length === 0 && keys.length === 0 && protoProps === undefined) return `${braces[0]}]`; extrasType = kArrayExtrasType; formatter = formatArray; } else if (isSet(value)) { const size = setSizeGetter(value); + const prefix = getPrefix(constructor, tag, 'Set'); keys = getKeys(value, ctx.showHidden); - let prefix = ''; - if (constructor !== null) { - if (constructor === tag) - tag = ''; - prefix = getPrefix(`${constructor}`, tag, ''); - formatter = formatSet.bind(null, value, size); - } else { - prefix = getPrefix(constructor, tag, 'Set'); - formatter = formatSet.bind(null, SetPrototypeValues(value), size); - } + formatter = constructor !== null ? + formatSet.bind(null, value, size) : + formatSet.bind(null, SetPrototypeValues(value), size); if (size === 0 && keys.length === 0 && protoProps === undefined) return `${prefix}{}`; braces = [`${prefix}{`, '}']; } else if (isMap(value)) { const size = mapSizeGetter(value); + const prefix = getPrefix(constructor, tag, 'Map'); keys = getKeys(value, ctx.showHidden); - let prefix = ''; - if (constructor !== null) { - if (constructor === tag) - tag = ''; - prefix = getPrefix(`${constructor}`, tag, ''); - formatter = formatMap.bind(null, value, size); - } else { - prefix = getPrefix(constructor, tag, 'Map'); - formatter = formatMap.bind(null, MapPrototypeEntries(value), size); - } + formatter = constructor !== null ? + formatMap.bind(null, value, size) : + formatMap.bind(null, MapPrototypeEntries(value), size); if (size === 0 && keys.length === 0 && protoProps === undefined) return `${prefix}{}`; braces = [`${prefix}{`, '}']; } else if (isTypedArray(value)) { keys = getOwnNonIndexProperties(value, filter); let bound = value; - let prefix = ''; + let fallback = ''; if (constructor === null) { const constr = findTypedConstructor(value); - prefix = getPrefix(constructor, tag, constr.name); + fallback = constr.name; // Reconstruct the array information. bound = new constr(value); - } else { - prefix = getPrefix(constructor, tag); } + const size = typedArraySizeGetter(value); + const prefix = getPrefix(constructor, tag, fallback, `(${size})`); braces = [`${prefix}[`, ']']; if (value.length === 0 && keys.length === 0 && !ctx.showHidden) return `${braces[0]}]`; diff --git a/test/parallel/test-assert-deep.js b/test/parallel/test-assert-deep.js index fcc3765105f57c..914f9ecd1a3823 100644 --- a/test/parallel/test-assert-deep.js +++ b/test/parallel/test-assert-deep.js @@ -51,8 +51,8 @@ assert.throws( { code: 'ERR_ASSERTION', message: `${defaultMsgStartFull} ... Lines skipped\n\n` + - '+ Uint8Array [\n' + - '- Buffer [Uint8Array] [\n 120,\n...\n 122,\n 10\n ]' + '+ Uint8Array(4) [\n' + + '- Buffer(4) [Uint8Array] [\n 120,\n...\n 122,\n 10\n ]' } ); assert.deepEqual(arr, buf); @@ -66,7 +66,7 @@ assert.deepEqual(arr, buf); { code: 'ERR_ASSERTION', message: `${defaultMsgStartFull}\n\n` + - ' Buffer [Uint8Array] [\n' + + ' Buffer(4) [Uint8Array] [\n' + ' 120,\n' + ' 121,\n' + ' 122,\n' + @@ -86,7 +86,7 @@ assert.deepEqual(arr, buf); { code: 'ERR_ASSERTION', message: `${defaultMsgStartFull}\n\n` + - ' Uint8Array [\n' + + ' Uint8Array(4) [\n' + ' 120,\n' + ' 121,\n' + ' 122,\n' + diff --git a/test/parallel/test-buffer-inspect.js b/test/parallel/test-buffer-inspect.js index d6ecf6b7fc5c38..1e8212e876344a 100644 --- a/test/parallel/test-buffer-inspect.js +++ b/test/parallel/test-buffer-inspect.js @@ -58,7 +58,7 @@ b.inspect = undefined; b.prop = new Uint8Array(0); assert.strictEqual( util.inspect(b), - '' + '' ); b = Buffer.alloc(0); diff --git a/test/parallel/test-fs-read-empty-buffer.js b/test/parallel/test-fs-read-empty-buffer.js index 2f44b9a1843a6b..8ca08448182d4a 100644 --- a/test/parallel/test-fs-read-empty-buffer.js +++ b/test/parallel/test-fs-read-empty-buffer.js @@ -15,7 +15,7 @@ assert.throws( { code: 'ERR_INVALID_ARG_VALUE', message: 'The argument \'buffer\' is empty and cannot be written. ' + - 'Received Uint8Array []' + 'Received Uint8Array(0) []' } ); @@ -24,7 +24,7 @@ assert.throws( { code: 'ERR_INVALID_ARG_VALUE', message: 'The argument \'buffer\' is empty and cannot be written. ' + - 'Received Uint8Array []' + 'Received Uint8Array(0) []' } ); @@ -35,7 +35,7 @@ assert.throws( { code: 'ERR_INVALID_ARG_VALUE', message: 'The argument \'buffer\' is empty and cannot be written. ' + - 'Received Uint8Array []' + 'Received Uint8Array(0) []' } ); })(); diff --git a/test/parallel/test-util-format.js b/test/parallel/test-util-format.js index 9b30ca72a07816..d39df436146820 100644 --- a/test/parallel/test-util-format.js +++ b/test/parallel/test-util-format.js @@ -158,7 +158,7 @@ assert.strictEqual(util.format('%s', () => 5), '() => 5'); class Foobar extends Array { aaa = true; } assert.strictEqual( util.format('%s', new Foobar(5)), - 'Foobar [ <5 empty items>, aaa: true ]' + 'Foobar(5) [ <5 empty items>, aaa: true ]' ); // Subclassing: diff --git a/test/parallel/test-util-inspect.js b/test/parallel/test-util-inspect.js index d307d0f2dd35b8..bba497c89683c9 100644 --- a/test/parallel/test-util-inspect.js +++ b/test/parallel/test-util-inspect.js @@ -126,7 +126,7 @@ assert.strictEqual(util.inspect({ 'a': { 'b': { 'c': 2 } } }, false, 1), '{ a: { b: [Object] } }'); assert.strictEqual(util.inspect({ 'a': { 'b': ['c'] } }, false, 1), '{ a: { b: [Array] } }'); -assert.strictEqual(util.inspect(new Uint8Array(0)), 'Uint8Array []'); +assert.strictEqual(util.inspect(new Uint8Array(0)), 'Uint8Array(0) []'); assert(inspect(new Uint8Array(0), { showHidden: true }).includes('[buffer]')); assert.strictEqual( util.inspect( @@ -263,7 +263,7 @@ assert(!/Object/.test( array[1] = 97; assert.strictEqual( util.inspect(array, { showHidden: true }), - `${constructor.name} [\n` + + `${constructor.name}(${length}) [\n` + ' 65,\n' + ' 97,\n' + ` [BYTES_PER_ELEMENT]: ${constructor.BYTES_PER_ELEMENT},\n` + @@ -273,7 +273,7 @@ assert(!/Object/.test( ` [buffer]: ArrayBuffer { byteLength: ${byteLength} }\n]`); assert.strictEqual( util.inspect(array, false), - `${constructor.name} [ 65, 97 ]` + `${constructor.name}(${length}) [ 65, 97 ]` ); }); @@ -297,7 +297,7 @@ assert(!/Object/.test( array[1] = 97; assert.strictEqual( util.inspect(array, true), - `${constructor.name} [\n` + + `${constructor.name}(${length}) [\n` + ' 65,\n' + ' 97,\n' + ` [BYTES_PER_ELEMENT]: ${constructor.BYTES_PER_ELEMENT},\n` + @@ -307,7 +307,7 @@ assert(!/Object/.test( ` [buffer]: ArrayBuffer { byteLength: ${byteLength} }\n]`); assert.strictEqual( util.inspect(array, false), - `${constructor.name} [ 65, 97 ]` + `${constructor.name}(${length}) [ 65, 97 ]` ); }); @@ -397,11 +397,11 @@ assert.strictEqual( arr[49] = 'I win'; assert.strictEqual( util.inspect(arr), - "CustomArray [ <49 empty items>, 'I win' ]" + "CustomArray(50) [ <49 empty items>, 'I win' ]" ); assert.strictEqual( util.inspect(arr, { showHidden: true }), - 'CustomArray [\n' + + 'CustomArray(50) [\n' + ' <49 empty items>,\n' + " 'I win',\n" + ' [length]: 50,\n' + @@ -1291,7 +1291,7 @@ if (typeof Symbol !== 'undefined') { assert.strictEqual(util.inspect(x), 'ObjectSubclass { foo: 42 }'); assert.strictEqual(util.inspect(new ArraySubclass(1, 2, 3)), - 'ArraySubclass [ 1, 2, 3 ]'); + 'ArraySubclass(3) [ 1, 2, 3 ]'); assert.strictEqual(util.inspect(new SetSubclass([1, 2, 3])), 'SetSubclass [Set] { 1, 2, 3 }'); assert.strictEqual(util.inspect(new MapSubclass([['foo', 42]])), @@ -1387,7 +1387,7 @@ if (typeof Symbol !== 'undefined') { assert(util.inspect(x).endsWith('1 more item\n]')); assert(!util.inspect(x, { maxArrayLength: 101 }).includes('1 more item')); assert.strictEqual(util.inspect(x, { maxArrayLength: 0 }), - 'Uint8Array [ ... 101 more items ]'); + 'Uint8Array(101) [ ... 101 more items ]'); assert(!util.inspect(x, { maxArrayLength: null }).includes('1 more item')); assert(util.inspect(x, { maxArrayLength: Infinity }).endsWith(' 0, 0\n]')); } @@ -1672,7 +1672,7 @@ util.inspect(process); ' ],', ' [length]: 1', ' ]', - ' } => Uint8Array [', + ' } => Uint8Array(0) [', ' [BYTES_PER_ELEMENT]: 1,', ' [length]: 0,', ' [byteLength]: 0,', @@ -1689,7 +1689,7 @@ util.inspect(process); ' [length]: 2', ' ]', ' } => [Map Iterator] {', - ' Uint8Array [', + ' Uint8Array(0) [', ' [BYTES_PER_ELEMENT]: 1,', ' [length]: 0,', ' [byteLength]: 0,', @@ -1720,7 +1720,7 @@ util.inspect(process); ' ],', ' [length]: 1', ' ]', - ' } => Uint8Array [', + ' } => Uint8Array(0) [', ' [BYTES_PER_ELEMENT]: 1,', ' [length]: 0,', ' [byteLength]: 0,', @@ -1728,7 +1728,7 @@ util.inspect(process); ' [buffer]: ArrayBuffer { byteLength: 0, foo: true }', ' ],', ' [Set Iterator] { [ 1, 2, [length]: 2 ] } => [Map Iterator] {', - ' Uint8Array [', + ' Uint8Array(0) [', ' [BYTES_PER_ELEMENT]: 1,', ' [length]: 0,', ' [byteLength]: 0,', @@ -1756,7 +1756,7 @@ util.inspect(process); ' [length]: 2 ],', ' [size]: 1 },', ' [length]: 2 ],', - ' [length]: 1 ] } => Uint8Array [', + ' [length]: 1 ] } => Uint8Array(0) [', ' [BYTES_PER_ELEMENT]: 1,', ' [length]: 0,', ' [byteLength]: 0,', @@ -1768,7 +1768,7 @@ util.inspect(process); ' [ 1,', ' 2,', ' [length]: 2 ] } => [Map Iterator] {', - ' Uint8Array [', + ' Uint8Array(0) [', ' [BYTES_PER_ELEMENT]: 1,', ' [length]: 0,', ' [byteLength]: 0,', @@ -1946,7 +1946,7 @@ assert.strictEqual(util.inspect('"\'${a}'), "'\"\\'${a}'"); [new Set([1, 2]).entries(), '[Set Entries] { [ 1, 1 ], [ 2, 2 ] }'], [new Map([[1, 2]]).keys(), '[Map Iterator] { 1 }'], [new Date(2000), '1970-01-01T00:00:02.000Z'], - [new Uint8Array(2), 'Uint8Array [ 0, 0 ]'], + [new Uint8Array(2), 'Uint8Array(2) [ 0, 0 ]'], [new Promise((resolve) => setTimeout(resolve, 10)), 'Promise { }'], [new WeakSet(), 'WeakSet { }'], [new WeakMap(), 'WeakMap { }'], @@ -1972,23 +1972,23 @@ assert.strictEqual(util.inspect('"\'${a}'), "'\"\\'${a}'"); // Verify that having no prototype still produces nice results. [ - [[1, 3, 4], '[Array: null prototype] [ 1, 3, 4 ]'], + [[1, 3, 4], '[Array(3): null prototype] [ 1, 3, 4 ]'], [new Set([1, 2]), '[Set: null prototype] { 1, 2 }'], [new Map([[1, 2]]), '[Map: null prototype] { 1 => 2 }'], [new Promise((resolve) => setTimeout(resolve, 10)), '[Promise: null prototype] { }'], [new WeakSet(), '[WeakSet: null prototype] { }'], [new WeakMap(), '[WeakMap: null prototype] { }'], - [new Uint8Array(2), '[Uint8Array: null prototype] [ 0, 0 ]'], - [new Uint16Array(2), '[Uint16Array: null prototype] [ 0, 0 ]'], - [new Uint32Array(2), '[Uint32Array: null prototype] [ 0, 0 ]'], - [new Int8Array(2), '[Int8Array: null prototype] [ 0, 0 ]'], - [new Int16Array(2), '[Int16Array: null prototype] [ 0, 0 ]'], - [new Int32Array(2), '[Int32Array: null prototype] [ 0, 0 ]'], - [new Float32Array(2), '[Float32Array: null prototype] [ 0, 0 ]'], - [new Float64Array(2), '[Float64Array: null prototype] [ 0, 0 ]'], - [new BigInt64Array(2), '[BigInt64Array: null prototype] [ 0n, 0n ]'], - [new BigUint64Array(2), '[BigUint64Array: null prototype] [ 0n, 0n ]'], + [new Uint8Array(2), '[Uint8Array(2): null prototype] [ 0, 0 ]'], + [new Uint16Array(2), '[Uint16Array(2): null prototype] [ 0, 0 ]'], + [new Uint32Array(2), '[Uint32Array(2): null prototype] [ 0, 0 ]'], + [new Int8Array(2), '[Int8Array(2): null prototype] [ 0, 0 ]'], + [new Int16Array(2), '[Int16Array(2): null prototype] [ 0, 0 ]'], + [new Int32Array(2), '[Int32Array(2): null prototype] [ 0, 0 ]'], + [new Float32Array(2), '[Float32Array(2): null prototype] [ 0, 0 ]'], + [new Float64Array(2), '[Float64Array(2): null prototype] [ 0, 0 ]'], + [new BigInt64Array(2), '[BigInt64Array(2): null prototype] [ 0n, 0n ]'], + [new BigUint64Array(2), '[BigUint64Array(2): null prototype] [ 0n, 0n ]'], [new ArrayBuffer(16), '[ArrayBuffer: null prototype] {\n' + ' [Uint8Contents]: <00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00>,\n' + ' byteLength: undefined\n}'], @@ -2026,8 +2026,10 @@ assert.strictEqual(util.inspect('"\'${a}'), "'\"\\'${a}'"); class Foo extends base {} const value = new Foo(...input); const symbol = value[Symbol.toStringTag]; - const expected = `Foo ${symbol ? `[${symbol}] ` : ''}${rawExpected}`; - const expectedWithoutProto = `[${base.name}: null prototype] ${rawExpected}`; + const size = base.name.includes('Array') ? `(${input[0]})` : ''; + const expected = `Foo${size} ${symbol ? `[${symbol}] ` : ''}${rawExpected}`; + const expectedWithoutProto = + `[${base.name}${size}: null prototype] ${rawExpected}`; assert.strictEqual(util.inspect(value), expected); value.foo = 'bar'; assert.notStrictEqual(util.inspect(value), expected); @@ -2050,8 +2052,9 @@ assert.strictEqual(util.inspect('"\'${a}'), "'\"\\'${a}'"); assert.strictEqual(inspect(1n), '1n'); assert.strictEqual(inspect(Object(-1n)), '[BigInt: -1n]'); assert.strictEqual(inspect(Object(13n)), '[BigInt: 13n]'); -assert.strictEqual(inspect(new BigInt64Array([0n])), 'BigInt64Array [ 0n ]'); -assert.strictEqual(inspect(new BigUint64Array([0n])), 'BigUint64Array [ 0n ]'); +assert.strictEqual(inspect(new BigInt64Array([0n])), 'BigInt64Array(1) [ 0n ]'); +assert.strictEqual( + inspect(new BigUint64Array([0n])), 'BigUint64Array(1) [ 0n ]'); // Verify non-enumerable keys get escaped. { @@ -2170,7 +2173,7 @@ assert.strictEqual( Object.setPrototypeOf(obj, value); assert.strictEqual( util.inspect(obj), - 'Object <[Array: null prototype] []> { a: true }' + 'Object <[Array(0): null prototype] []> { a: true }' ); function StorageObject() {}