Skip to content
Prev Previous commit
Next Next commit
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: #1935
Reviewed-By: Roman Reiss <me@silverwind.io>
Reviewed-By: Jeremiah Senkpiel <fishrock123@rocketmail.com>
  • Loading branch information
monsanto authored and Fishrock123 committed Aug 11, 2015
commit ee9dd683e5aa8aafd59b8ab81d3f1eb2575e4947
31 changes: 28 additions & 3 deletions lib/util.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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().
Expand All @@ -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');
Expand All @@ -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;
Expand Down Expand Up @@ -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];
}
Expand Down
2 changes: 1 addition & 1 deletion test/parallel/test-sys.js
Original file line number Diff line number Diff line change
Expand Up @@ -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]]));

Expand Down
43 changes: 42 additions & 1 deletion test/parallel/test-util-inspect.js
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand Down Expand Up @@ -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), '{}');