Skip to content

Commit

Permalink
util: display constructor when inspecting objects
Browse files Browse the repository at this point in the history
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: nodejs#1935
Reviewed-By: Roman Reiss <[email protected]>
Reviewed-By: Jeremiah Senkpiel <[email protected]>
  • Loading branch information
monsanto authored and Fishrock123 committed Aug 11, 2015
1 parent 581850b commit ee9dd68
Show file tree
Hide file tree
Showing 3 changed files with 71 additions and 5 deletions.
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), '{}');

0 comments on commit ee9dd68

Please sign in to comment.