Skip to content

Commit

Permalink
util: add (typed) array length to the default output
Browse files Browse the repository at this point in the history
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: #31431
PR-URL: #31027
Reviewed-By: Michaël Zasso <targos@protonmail.com>
Reviewed-By: Rich Trott <rtrott@gmail.com>
Reviewed-By: Anto Aravinth <anto.aravinth.cse@gmail.com>
  • Loading branch information
BridgeAR authored and BethGriggs committed Feb 6, 2020
1 parent 19a3f8b commit d3c0f46
Show file tree
Hide file tree
Showing 6 changed files with 69 additions and 73 deletions.
57 changes: 25 additions & 32 deletions lib/internal/util/inspect.js
Expand Up @@ -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;

Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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]}]`;
Expand Down
8 changes: 4 additions & 4 deletions test/parallel/test-assert-deep.js
Expand Up @@ -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);
Expand All @@ -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' +
Expand All @@ -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' +
Expand Down
2 changes: 1 addition & 1 deletion test/parallel/test-buffer-inspect.js
Expand Up @@ -58,7 +58,7 @@ b.inspect = undefined;
b.prop = new Uint8Array(0);
assert.strictEqual(
util.inspect(b),
'<Buffer 31 32, inspect: undefined, prop: Uint8Array []>'
'<Buffer 31 32, inspect: undefined, prop: Uint8Array(0) []>'
);

b = Buffer.alloc(0);
Expand Down
6 changes: 3 additions & 3 deletions test/parallel/test-fs-read-empty-buffer.js
Expand Up @@ -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) []'
}
);

Expand All @@ -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) []'
}
);

Expand All @@ -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) []'
}
);
})();
2 changes: 1 addition & 1 deletion test/parallel/test-util-format.js
Expand Up @@ -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:
Expand Down
67 changes: 35 additions & 32 deletions test/parallel/test-util-inspect.js
Expand Up @@ -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(
Expand Down Expand Up @@ -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` +
Expand All @@ -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 ]`
);
});

Expand All @@ -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` +
Expand All @@ -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 ]`
);
});

Expand Down Expand Up @@ -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' +
Expand Down Expand Up @@ -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]])),
Expand Down Expand Up @@ -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]'));
}
Expand Down Expand Up @@ -1672,7 +1672,7 @@ util.inspect(process);
' ],',
' [length]: 1',
' ]',
' } => Uint8Array [',
' } => Uint8Array(0) [',
' [BYTES_PER_ELEMENT]: 1,',
' [length]: 0,',
' [byteLength]: 0,',
Expand All @@ -1689,7 +1689,7 @@ util.inspect(process);
' [length]: 2',
' ]',
' } => [Map Iterator] {',
' Uint8Array [',
' Uint8Array(0) [',
' [BYTES_PER_ELEMENT]: 1,',
' [length]: 0,',
' [byteLength]: 0,',
Expand Down Expand Up @@ -1720,15 +1720,15 @@ util.inspect(process);
' ],',
' [length]: 1',
' ]',
' } => Uint8Array [',
' } => Uint8Array(0) [',
' [BYTES_PER_ELEMENT]: 1,',
' [length]: 0,',
' [byteLength]: 0,',
' [byteOffset]: 0,',
' [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,',
Expand Down Expand Up @@ -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,',
Expand All @@ -1768,7 +1768,7 @@ util.inspect(process);
' [ 1,',
' 2,',
' [length]: 2 ] } => [Map Iterator] {',
' Uint8Array [',
' Uint8Array(0) [',
' [BYTES_PER_ELEMENT]: 1,',
' [length]: 0,',
' [byteLength]: 0,',
Expand Down Expand Up @@ -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 { <pending> }'],
[new WeakSet(), 'WeakSet { <items unknown> }'],
[new WeakMap(), 'WeakMap { <items unknown> }'],
Expand All @@ -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] { <pending> }'],
[new WeakSet(), '[WeakSet: null prototype] { <items unknown> }'],
[new WeakMap(), '[WeakMap: null prototype] { <items unknown> }'],
[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}'],
Expand Down Expand Up @@ -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);
Expand All @@ -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.
{
Expand Down Expand Up @@ -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() {}
Expand Down

0 comments on commit d3c0f46

Please sign in to comment.