Skip to content

Commit bf7ac08

Browse files
monsantochrisdickinson
authored andcommitted
util: add Map and Set inspection support
PR-URL: #1471 Reviewed-By: Chris Dickinson <[email protected]>
1 parent 5178f93 commit bf7ac08

File tree

2 files changed

+172
-13
lines changed

2 files changed

+172
-13
lines changed

lib/util.js

+107-13
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
'use strict';
22

33
const uv = process.binding('uv');
4+
const Debug = require('vm').runInDebugContext('Debug');
45

56
const formatRegExp = /%[sdj%]/g;
67
exports.format = function(f) {
@@ -192,6 +193,14 @@ function arrayToHash(array) {
192193
}
193194

194195

196+
function inspectPromise(p) {
197+
var mirror = Debug.MakeMirror(p, true);
198+
if (!mirror.isPromise())
199+
return null;
200+
return {status: mirror.status(), value: mirror.promiseValue().value_};
201+
}
202+
203+
195204
function formatValue(ctx, value, recurseTimes) {
196205
// Provide a hook for user-specified inspect functions.
197206
// Check that value is an object with an inspect function on it
@@ -276,14 +285,43 @@ function formatValue(ctx, value, recurseTimes) {
276285
}
277286
}
278287

279-
var base = '', array = false, braces = ['{', '}'];
288+
var base = '', empty = false, braces, formatter;
280289

281-
// Make Array say that they are Array
282290
if (Array.isArray(value)) {
283-
array = true;
284291
braces = ['[', ']'];
292+
empty = value.length === 0;
293+
formatter = formatArray;
294+
} else if (value instanceof Set) {
295+
braces = ['Set {', '}'];
296+
// With `showHidden`, `length` will display as a hidden property for
297+
// arrays. For consistency's sake, do the same for `size`, even though this
298+
// property isn't selected by Object.getOwnPropertyNames().
299+
if (ctx.showHidden)
300+
keys.unshift('size');
301+
empty = value.size === 0;
302+
formatter = formatSet;
303+
} else if (value instanceof Map) {
304+
braces = ['Map {', '}'];
305+
// Ditto.
306+
if (ctx.showHidden)
307+
keys.unshift('size');
308+
empty = value.size === 0;
309+
formatter = formatMap;
310+
} else {
311+
// Only create a mirror if the object superficially looks like a Promise.
312+
var promiseInternals = value instanceof Promise && inspectPromise(value);
313+
if (promiseInternals) {
314+
braces = ['Promise {', '}'];
315+
formatter = formatPromise;
316+
} else {
317+
braces = ['{', '}'];
318+
empty = true; // No other data than keys.
319+
formatter = formatObject;
320+
}
285321
}
286322

323+
empty = empty === true && keys.length === 0;
324+
287325
// Make functions say that they are functions
288326
if (typeof value === 'function') {
289327
var n = value.name ? ': ' + value.name : '';
@@ -323,7 +361,7 @@ function formatValue(ctx, value, recurseTimes) {
323361
base = ' ' + '[Boolean: ' + formatted + ']';
324362
}
325363

326-
if (keys.length === 0 && (!array || value.length === 0)) {
364+
if (empty === true) {
327365
return braces[0] + base + braces[1];
328366
}
329367

@@ -337,14 +375,7 @@ function formatValue(ctx, value, recurseTimes) {
337375

338376
ctx.seen.push(value);
339377

340-
var output;
341-
if (array) {
342-
output = formatArray(ctx, value, recurseTimes, visibleKeys, keys);
343-
} else {
344-
output = keys.map(function(key) {
345-
return formatProperty(ctx, value, recurseTimes, visibleKeys, key, array);
346-
});
347-
}
378+
var output = formatter(ctx, value, recurseTimes, visibleKeys, keys);
348379

349380
ctx.seen.pop();
350381

@@ -397,6 +428,13 @@ function formatError(value) {
397428
}
398429

399430

431+
function formatObject(ctx, value, recurseTimes, visibleKeys, keys) {
432+
return keys.map(function(key) {
433+
return formatProperty(ctx, value, recurseTimes, visibleKeys, key, false);
434+
});
435+
}
436+
437+
400438
function formatArray(ctx, value, recurseTimes, visibleKeys, keys) {
401439
var output = [];
402440
for (var i = 0, l = value.length; i < l; ++i) {
@@ -417,6 +455,59 @@ function formatArray(ctx, value, recurseTimes, visibleKeys, keys) {
417455
}
418456

419457

458+
function formatSet(ctx, value, recurseTimes, visibleKeys, keys) {
459+
var output = [];
460+
value.forEach(function(v) {
461+
var nextRecurseTimes = recurseTimes === null ? null : recurseTimes - 1;
462+
var str = formatValue(ctx, v, nextRecurseTimes);
463+
output.push(str);
464+
});
465+
keys.forEach(function(key) {
466+
output.push(formatProperty(ctx, value, recurseTimes, visibleKeys,
467+
key, false));
468+
});
469+
return output;
470+
}
471+
472+
473+
function formatMap(ctx, value, recurseTimes, visibleKeys, keys) {
474+
var output = [];
475+
value.forEach(function(v, k) {
476+
var nextRecurseTimes = recurseTimes === null ? null : recurseTimes - 1;
477+
var str = formatValue(ctx, k, nextRecurseTimes);
478+
str += ' => ';
479+
str += formatValue(ctx, v, nextRecurseTimes);
480+
output.push(str);
481+
});
482+
keys.forEach(function(key) {
483+
output.push(formatProperty(ctx, value, recurseTimes, visibleKeys,
484+
key, false));
485+
});
486+
return output;
487+
}
488+
489+
function formatPromise(ctx, value, recurseTimes, visibleKeys, keys) {
490+
var output = [];
491+
var internals = inspectPromise(value);
492+
if (internals.status === 'pending') {
493+
output.push('<pending>');
494+
} else {
495+
var nextRecurseTimes = recurseTimes === null ? null : recurseTimes - 1;
496+
var str = formatValue(ctx, internals.value, nextRecurseTimes);
497+
if (internals.status === 'rejected') {
498+
output.push('<rejected> ' + str);
499+
} else {
500+
output.push(str);
501+
}
502+
}
503+
keys.forEach(function(key) {
504+
output.push(formatProperty(ctx, value, recurseTimes, visibleKeys,
505+
key, false));
506+
});
507+
return output;
508+
}
509+
510+
420511
function formatProperty(ctx, value, recurseTimes, visibleKeys, key, array) {
421512
var name, str, desc;
422513
desc = Object.getOwnPropertyDescriptor(value, key) || { value: value[key] };
@@ -488,7 +579,10 @@ function reduceToSingleString(output, base, braces) {
488579

489580
if (length > 60) {
490581
return braces[0] +
491-
(base === '' ? '' : base + '\n ') +
582+
// If the opening "brace" is too large, like in the case of "Set {",
583+
// we need to force the first item to be on the next line or the
584+
// items will not line up correctly.
585+
(base === '' && braces[0].length === 1 ? '' : base + '\n ') +
492586
' ' +
493587
output.join(',\n ') +
494588
' ' +

test/parallel/test-util-inspect.js

+65
Original file line numberDiff line numberDiff line change
@@ -235,3 +235,68 @@ if (typeof Symbol !== 'undefined') {
235235
assert.equal(util.inspect(subject, options), '[ 1, 2, 3, [length]: 3, [Symbol(symbol)]: 42 ]');
236236

237237
}
238+
239+
// test Set
240+
assert.equal(util.inspect(new Set), 'Set {}');
241+
assert.equal(util.inspect(new Set([1, 2, 3])), 'Set { 1, 2, 3 }');
242+
var set = new Set(['foo']);
243+
set.bar = 42;
244+
assert.equal(util.inspect(set, true), 'Set { \'foo\', [size]: 1, bar: 42 }');
245+
246+
// test Map
247+
assert.equal(util.inspect(new Map), 'Map {}');
248+
assert.equal(util.inspect(new Map([[1, 'a'], [2, 'b'], [3, 'c']])),
249+
'Map { 1 => \'a\', 2 => \'b\', 3 => \'c\' }');
250+
var map = new Map([['foo', null]]);
251+
map.bar = 42;
252+
assert.equal(util.inspect(map, true),
253+
'Map { \'foo\' => null, [size]: 1, bar: 42 }');
254+
255+
// test Promise
256+
assert.equal(util.inspect(Promise.resolve(3)), 'Promise { 3 }');
257+
assert.equal(util.inspect(Promise.reject(3)), 'Promise { <rejected> 3 }');
258+
assert.equal(util.inspect(new Promise(function() {})), 'Promise { <pending> }');
259+
var promise = Promise.resolve('foo');
260+
promise.bar = 42;
261+
assert.equal(util.inspect(promise), 'Promise { \'foo\', bar: 42 }');
262+
263+
// Make sure it doesn't choke on polyfills. Unlike Set/Map, there is no standard
264+
// interface to synchronously inspect a Promise, so our techniques only work on
265+
// a bonafide native Promise.
266+
var oldPromise = Promise;
267+
global.Promise = function() { this.bar = 42; };
268+
assert.equal(util.inspect(new Promise), '{ bar: 42 }');
269+
global.Promise = oldPromise;
270+
271+
272+
// Test alignment of items in container
273+
// Assumes that the first numeric character is the start of an item.
274+
275+
function checkAlignment(container) {
276+
var lines = util.inspect(container).split('\n');
277+
var pos;
278+
lines.forEach(function(line) {
279+
var npos = line.search(/\d/);
280+
if (npos !== -1) {
281+
if (pos !== undefined)
282+
assert.equal(pos, npos, 'container items not aligned');
283+
pos = npos;
284+
}
285+
});
286+
}
287+
288+
var big_array = [];
289+
for (var i = 0; i < 100; i++) {
290+
big_array.push(i);
291+
}
292+
293+
checkAlignment(big_array);
294+
checkAlignment(function() {
295+
var obj = {};
296+
big_array.forEach(function(v) {
297+
obj[v] = null;
298+
});
299+
return obj;
300+
}());
301+
checkAlignment(new Set(big_array));
302+
checkAlignment(new Map(big_array.map(function (y) { return [y, null] })));

0 commit comments

Comments
 (0)