util: display constructor when inspecting objects

This commit modifies util.inspect(obj) to additionally show the name of
the function that constructed the object. This often reveals useful
information about the object's prototype. In other words, instead of

> new Cls
{}

we have

> new Cls
Cls {}

This also works with exotic objects:

> class ArrayCls extends Array {}
> new ArrayCls(1, 2, 3)
ArrayCls [ 1, 2, 3 ]

The names of "trivial" constructors like Object and Array are not shown,
unless there is a mismatch between the object representation and the
prototype:

> Object.create([])
Array {}

This feature is inspired by browser devtools.

PR-URL: https://github.com/nodejs/io.js/pull/1935
Reviewed-By: Roman Reiss <me@silverwind.io>
Reviewed-By: Jeremiah Senkpiel <fishrock123@rocketmail.com>
This commit is contained in:
Christopher Monsanto 2015-06-10 04:25:04 -04:00
parent 6ad99ac1ef
commit 7d14dd9b5e
3 changed files with 71 additions and 5 deletions

View File

@ -167,6 +167,22 @@ function arrayToHash(array) {
}
function getConstructorOf(obj) {
while (obj) {
var descriptor = Object.getOwnPropertyDescriptor(obj, 'constructor');
if (descriptor !== undefined &&
typeof descriptor.value === 'function' &&
descriptor.value.name !== '') {
return descriptor.value;
}
obj = Object.getPrototypeOf(obj);
}
return null;
}
function inspectPromise(p) {
Debug = Debug || require('vm').runInDebugContext('Debug');
var mirror = Debug.MakeMirror(p, true);
@ -260,14 +276,17 @@ function formatValue(ctx, value, recurseTimes) {
}
}
var constructor = getConstructorOf(value);
var base = '', empty = false, braces, formatter;
if (Array.isArray(value)) {
if (constructor === Array)
constructor = null;
braces = ['[', ']'];
empty = value.length === 0;
formatter = formatArray;
} else if (value instanceof Set) {
braces = ['Set {', '}'];
braces = ['{', '}'];
// With `showHidden`, `length` will display as a hidden property for
// arrays. For consistency's sake, do the same for `size`, even though this
// property isn't selected by Object.getOwnPropertyNames().
@ -276,7 +295,7 @@ function formatValue(ctx, value, recurseTimes) {
empty = value.size === 0;
formatter = formatSet;
} else if (value instanceof Map) {
braces = ['Map {', '}'];
braces = ['{', '}'];
// Ditto.
if (ctx.showHidden)
keys.unshift('size');
@ -286,9 +305,11 @@ function formatValue(ctx, value, recurseTimes) {
// Only create a mirror if the object superficially looks like a Promise.
var promiseInternals = value instanceof Promise && inspectPromise(value);
if (promiseInternals) {
braces = ['Promise {', '}'];
braces = ['{', '}'];
formatter = formatPromise;
} else {
if (constructor === Object)
constructor = null;
braces = ['{', '}'];
empty = true; // No other data than keys.
formatter = formatObject;
@ -336,6 +357,10 @@ function formatValue(ctx, value, recurseTimes) {
base = ' ' + '[Boolean: ' + formatted + ']';
}
// Add constructor name if available
if (base === '' && constructor)
braces[0] = constructor.name + ' ' + braces[0];
if (empty === true) {
return braces[0] + base + braces[1];
}

View File

@ -17,7 +17,7 @@ assert.equal(new Date('2010-02-14T12:48:40+01:00').toString(),
assert.equal("'\\n\\u0001'", common.inspect('\n\u0001'));
assert.equal('[]', common.inspect([]));
assert.equal('{}', common.inspect(Object.create([])));
assert.equal('Array {}', common.inspect(Object.create([])));
assert.equal('[ 1, 2 ]', common.inspect([1, 2]));
assert.equal('[ 1, [ 2, 3 ] ]', common.inspect([1, [2, 3]]));

View File

@ -61,7 +61,7 @@ assert.ok(ex.indexOf('[message]') != -1);
// GH-1941
// should not throw:
assert.equal(util.inspect(Object.create(Date.prototype)), '{}');
assert.equal(util.inspect(Object.create(Date.prototype)), 'Date {}');
// GH-1944
assert.doesNotThrow(function() {
@ -306,3 +306,44 @@ checkAlignment(function() {
}());
checkAlignment(new Set(big_array));
checkAlignment(new Map(big_array.map(function(y) { return [y, null]; })));
// Test display of constructors
class ObjectSubclass {}
class ArraySubclass extends Array {}
class SetSubclass extends Set {}
class MapSubclass extends Map {}
class PromiseSubclass extends Promise {}
var x = new ObjectSubclass();
x.foo = 42;
assert.equal(util.inspect(x),
'ObjectSubclass { foo: 42 }');
assert.equal(util.inspect(new ArraySubclass(1, 2, 3)),
'ArraySubclass [ 1, 2, 3 ]');
assert.equal(util.inspect(new SetSubclass([1, 2, 3])),
'SetSubclass { 1, 2, 3 }');
assert.equal(util.inspect(new MapSubclass([['foo', 42]])),
'MapSubclass { \'foo\' => 42 }');
assert.equal(util.inspect(new PromiseSubclass(function() {})),
'PromiseSubclass { <pending> }');
// Corner cases.
var x = { constructor: 42 };
assert.equal(util.inspect(x), '{ constructor: 42 }');
var x = {};
Object.defineProperty(x, 'constructor', {
get: function() {
throw new Error('should not access constructor');
},
enumerable: true
});
assert.equal(util.inspect(x), '{ constructor: [Getter] }');
var x = new (function() {});
assert.equal(util.inspect(x), '{}');
var x = Object.create(null);
assert.equal(util.inspect(x), '{}');