Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

util: display constructor name when inspecting objects #1935

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If there is no constructor this just returns undefined I guess..?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes.

I modified this so we explicitly return null instead of relying on the implicit undefined.


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;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure why it is nulled out here and below?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Null constructors aren't displayed. We explicitly null out for Array and Object so {} continues to display as {} (as opposed to Object {}) and [] continues to display as [] (as opposed to Array []). The reader should assume that if there is no constructor name that it is the default for the brace type. If we didn't do this the output would get pretty noisy for large object graphs, and we would have to modify a bunch of tests.

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), '{}');