Skip to content

Commit 34a3591

Browse files
committed
util: improve typed array formatting
Pretty-print typed arrays like regular arrays. Speeds up formatting by almost 300% because it no longer stringifies the array indices. Pretty-print ArrayBuffer and DataView as well by including byteLength, byteOffset and buffer properties in the stringified representation. PR-URL: #3793 Reviewed-By: Trevor Norris <trev.norris@gmail.com>
1 parent 8b57b31 commit 34a3591

File tree

2 files changed

+121
-9
lines changed

2 files changed

+121
-9
lines changed

lib/util.js

+68-9
Original file line numberDiff line numberDiff line change
@@ -284,10 +284,17 @@ function formatValue(ctx, value, recurseTimes) {
284284
formatted = formatPrimitiveNoColor(ctx, raw);
285285
return ctx.stylize('[Boolean: ' + formatted + ']', 'boolean');
286286
}
287+
// Fast path for ArrayBuffer. Can't do the same for DataView because it
288+
// has a non-primitive .buffer property that we need to recurse for.
289+
if (value instanceof ArrayBuffer) {
290+
return `${getConstructorOf(value).name}` +
291+
` { byteLength: ${formatNumber(ctx, value.byteLength)} }`;
292+
}
287293
}
288294

289295
var constructor = getConstructorOf(value);
290-
var base = '', empty = false, braces, formatter;
296+
var base = '', empty = false, braces;
297+
var formatter = formatObject;
291298

292299
if (Array.isArray(value)) {
293300
// We can't use `constructor === Array` because this could
@@ -314,6 +321,28 @@ function formatValue(ctx, value, recurseTimes) {
314321
keys.unshift('size');
315322
empty = value.size === 0;
316323
formatter = formatMap;
324+
} else if (value instanceof ArrayBuffer) {
325+
braces = ['{', '}'];
326+
keys.unshift('byteLength');
327+
visibleKeys.byteLength = true;
328+
} else if (value instanceof DataView) {
329+
braces = ['{', '}'];
330+
// .buffer goes last, it's not a primitive like the others.
331+
keys.unshift('byteLength', 'byteOffset', 'buffer');
332+
visibleKeys.byteLength = true;
333+
visibleKeys.byteOffset = true;
334+
visibleKeys.buffer = true;
335+
} else if (isTypedArray(value)) {
336+
braces = ['[', ']'];
337+
formatter = formatTypedArray;
338+
if (ctx.showHidden) {
339+
// .buffer goes last, it's not a primitive like the others.
340+
keys.unshift('BYTES_PER_ELEMENT',
341+
'length',
342+
'byteLength',
343+
'byteOffset',
344+
'buffer');
345+
}
317346
} else {
318347
// Only create a mirror if the object superficially looks like a Promise.
319348
var promiseInternals = value instanceof Promise && inspectPromise(value);
@@ -336,7 +365,6 @@ function formatValue(ctx, value, recurseTimes) {
336365
constructor = null;
337366
braces = ['{', '}'];
338367
empty = true; // No other data than keys.
339-
formatter = formatObject;
340368
}
341369
}
342370
}
@@ -408,6 +436,15 @@ function formatValue(ctx, value, recurseTimes) {
408436
}
409437

410438

439+
function formatNumber(ctx, value) {
440+
// Format -0 as '-0'. Strict equality won't distinguish 0 from -0,
441+
// so instead we use the fact that 1 / -0 < 0 whereas 1 / 0 > 0 .
442+
if (value === 0 && 1 / value < 0)
443+
return ctx.stylize('-0', 'number');
444+
return ctx.stylize('' + value, 'number');
445+
}
446+
447+
411448
function formatPrimitive(ctx, value) {
412449
if (value === undefined)
413450
return ctx.stylize('undefined', 'undefined');
@@ -424,13 +461,8 @@ function formatPrimitive(ctx, value) {
424461
.replace(/\\"/g, '"') + '\'';
425462
return ctx.stylize(simple, 'string');
426463
}
427-
if (type === 'number') {
428-
// Format -0 as '-0'. Strict equality won't distinguish 0 from -0,
429-
// so instead we use the fact that 1 / -0 < 0 whereas 1 / 0 > 0 .
430-
if (value === 0 && 1 / value < 0)
431-
return ctx.stylize('-0', 'number');
432-
return ctx.stylize('' + value, 'number');
433-
}
464+
if (type === 'number')
465+
return formatNumber(ctx, value);
434466
if (type === 'boolean')
435467
return ctx.stylize('' + value, 'boolean');
436468
// es6 symbol primitive
@@ -480,6 +512,20 @@ function formatArray(ctx, value, recurseTimes, visibleKeys, keys) {
480512
}
481513

482514

515+
function formatTypedArray(ctx, value, recurseTimes, visibleKeys, keys) {
516+
var output = new Array(value.length);
517+
for (var i = 0, l = value.length; i < l; ++i)
518+
output[i] = formatNumber(ctx, value[i]);
519+
for (const key of keys) {
520+
if (typeof key === 'symbol' || !key.match(/^\d+$/)) {
521+
output.push(
522+
formatProperty(ctx, value, recurseTimes, visibleKeys, key, true));
523+
}
524+
}
525+
return output;
526+
}
527+
528+
483529
function formatSet(ctx, value, recurseTimes, visibleKeys, keys) {
484530
var output = [];
485531
value.forEach(function(v) {
@@ -626,6 +672,19 @@ function reduceToSingleString(output, base, braces) {
626672
}
627673

628674

675+
function isTypedArray(value) {
676+
return value instanceof Float32Array ||
677+
value instanceof Float64Array ||
678+
value instanceof Int16Array ||
679+
value instanceof Int32Array ||
680+
value instanceof Int8Array ||
681+
value instanceof Uint16Array ||
682+
value instanceof Uint32Array ||
683+
value instanceof Uint8Array ||
684+
value instanceof Uint8ClampedArray;
685+
}
686+
687+
629688
// NOTE: These type checking functions intentionally don't use `instanceof`
630689
// because it is fragile and can be easily faked with `Object.create()`.
631690
exports.isArray = Array.isArray;

test/parallel/test-util-inspect.js

+53
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,59 @@ assert.equal(util.inspect(Object.create({},
4141
'{ visible: 1 }'
4242
);
4343

44+
for (const showHidden of [true, false]) {
45+
const ab = new ArrayBuffer(4);
46+
const dv = new DataView(ab, 1, 2);
47+
assert.equal(util.inspect(ab, showHidden), 'ArrayBuffer { byteLength: 4 }');
48+
assert.equal(util.inspect(new DataView(ab, 1, 2), showHidden),
49+
'DataView {\n' +
50+
' byteLength: 2,\n' +
51+
' byteOffset: 1,\n' +
52+
' buffer: ArrayBuffer { byteLength: 4 } }');
53+
assert.equal(util.inspect(ab, showHidden), 'ArrayBuffer { byteLength: 4 }');
54+
assert.equal(util.inspect(dv, showHidden),
55+
'DataView {\n' +
56+
' byteLength: 2,\n' +
57+
' byteOffset: 1,\n' +
58+
' buffer: ArrayBuffer { byteLength: 4 } }');
59+
ab.x = 42;
60+
dv.y = 1337;
61+
assert.equal(util.inspect(ab, showHidden),
62+
'ArrayBuffer { byteLength: 4, x: 42 }');
63+
assert.equal(util.inspect(dv, showHidden),
64+
'DataView {\n' +
65+
' byteLength: 2,\n' +
66+
' byteOffset: 1,\n' +
67+
' buffer: ArrayBuffer { byteLength: 4, x: 42 },\n' +
68+
' y: 1337 }');
69+
}
70+
71+
[ Float32Array,
72+
Float64Array,
73+
Int16Array,
74+
Int32Array,
75+
Int8Array,
76+
Uint16Array,
77+
Uint32Array,
78+
Uint8Array,
79+
Uint8ClampedArray ].forEach(constructor => {
80+
const length = 2;
81+
const byteLength = length * constructor.BYTES_PER_ELEMENT;
82+
const array = new constructor(new ArrayBuffer(byteLength), 0, length);
83+
array[0] = 65;
84+
array[1] = 97;
85+
assert.equal(util.inspect(array, true),
86+
`${constructor.name} [\n` +
87+
` 65,\n` +
88+
` 97,\n` +
89+
` [BYTES_PER_ELEMENT]: ${constructor.BYTES_PER_ELEMENT},\n` +
90+
` [length]: ${length},\n` +
91+
` [byteLength]: ${byteLength},\n` +
92+
` [byteOffset]: 0,\n` +
93+
` [buffer]: ArrayBuffer { byteLength: ${byteLength} } ]`);
94+
assert.equal(util.inspect(array, false), `${constructor.name} [ 65, 97 ]`);
95+
});
96+
4497
// Due to the hash seed randomization it's not deterministic the order that
4598
// the following ways this hash is displayed.
4699
// See http://codereview.chromium.org/9124004/

0 commit comments

Comments
 (0)