From a6998085d27412b57c06a29142960bb0e4d35b92 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. 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 | 61 +++++++++----------- 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, 71 insertions(+), 75 deletions(-) diff --git a/lib/internal/util/inspect.js b/lib/internal/util/inspect.js index 5f07afe65fbd6a..cda8839eb17633 100644 --- a/lib/internal/util/inspect.js +++ b/lib/internal/util/inspect.js @@ -117,6 +117,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; @@ -562,18 +565,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. @@ -758,58 +761,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', `(${size})`); keys = getKeys(value, ctx.showHidden); - let prefix = ''; - if (constructor !== null) { - if (constructor === tag) - tag = ''; - prefix = getPrefix(`${constructor}(${size})`, tag, ''); - formatter = formatSet.bind(null, value, size); - } else { - prefix = getPrefix(constructor, tag, `Set(${size})`); - formatter = formatSet.bind(null, SetPrototypeValues(value), size); - } + formatter = constructor !== null ? + formatSet.bind(null, value) : + formatSet.bind(null, SetPrototypeValues(value)); 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', `(${size})`); keys = getKeys(value, ctx.showHidden); - let prefix = ''; - if (constructor !== null) { - if (constructor === tag) - tag = ''; - prefix = getPrefix(`${constructor}(${size})`, tag, ''); - formatter = formatMap.bind(null, value, size); - } else { - prefix = getPrefix(constructor, tag, `Map(${size})`); - formatter = formatMap.bind(null, MapPrototypeEntries(value), size); - } + formatter = constructor !== null ? + formatMap.bind(null, value) : + formatMap.bind(null, MapPrototypeEntries(value)); 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]}]`; @@ -1425,7 +1418,7 @@ function formatTypedArray(value, ctx, ignored, recurseTimes) { return output; } -function formatSet(value, size, ctx, ignored, recurseTimes) { +function formatSet(value, ctx, ignored, recurseTimes) { const output = []; ctx.indentationLvl += 2; for (const v of value) { @@ -1435,7 +1428,7 @@ function formatSet(value, size, ctx, ignored, recurseTimes) { return output; } -function formatMap(value, size, ctx, ignored, recurseTimes) { +function formatMap(value, ctx, ignored, recurseTimes) { const output = []; ctx.indentationLvl += 2; for (const [k, v] of value) { diff --git a/test/parallel/test-assert-deep.js b/test/parallel/test-assert-deep.js index f98ab14bffc12d..649a1846917a5a 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 e07ec6d6a34c2f..0599fda1d63456 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 e206e94c0f1c48..06d9e9f2e1bdd7 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' + @@ -1300,7 +1300,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(3) [Set] { 1, 2, 3 }'); assert.strictEqual(util.inspect(new MapSubclass([['foo', 42]])), @@ -1396,7 +1396,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]')); } @@ -1680,7 +1680,7 @@ util.inspect(process); ' ],', ' [length]: 1', ' ]', - ' } => Uint8Array [', + ' } => Uint8Array(0) [', ' [BYTES_PER_ELEMENT]: 1,', ' [length]: 0,', ' [byteLength]: 0,', @@ -1697,7 +1697,7 @@ util.inspect(process); ' [length]: 2', ' ]', ' } => [Map Iterator] {', - ' Uint8Array [', + ' Uint8Array(0) [', ' [BYTES_PER_ELEMENT]: 1,', ' [length]: 0,', ' [byteLength]: 0,', @@ -1727,7 +1727,7 @@ util.inspect(process); ' ],', ' [length]: 1', ' ]', - ' } => Uint8Array [', + ' } => Uint8Array(0) [', ' [BYTES_PER_ELEMENT]: 1,', ' [length]: 0,', ' [byteLength]: 0,', @@ -1735,7 +1735,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,', @@ -1761,7 +1761,7 @@ util.inspect(process); ' 2,', ' [length]: 2 ] },', ' [length]: 2 ],', - ' [length]: 1 ] } => Uint8Array [', + ' [length]: 1 ] } => Uint8Array(0) [', ' [BYTES_PER_ELEMENT]: 1,', ' [length]: 0,', ' [byteLength]: 0,', @@ -1773,7 +1773,7 @@ util.inspect(process); ' [ 1,', ' 2,', ' [length]: 2 ] } => [Map Iterator] {', - ' Uint8Array [', + ' Uint8Array(0) [', ' [BYTES_PER_ELEMENT]: 1,', ' [length]: 0,', ' [byteLength]: 0,', @@ -1950,7 +1950,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 { }'], @@ -1976,23 +1976,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(2): null prototype] { 1, 2 }'], [new Map([[1, 2]]), '[Map(1): 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}'], @@ -2030,8 +2030,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); @@ -2054,8 +2056,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. { @@ -2174,7 +2177,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() {}