Skip to content

Commit 7606bdb

Browse files
monsantorvagg
authored andcommitted
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 <[email protected]> Reviewed-By: Jeremiah Senkpiel <[email protected]>
1 parent 94b765f commit 7606bdb

File tree

3 files changed

+71
-5
lines changed

3 files changed

+71
-5
lines changed

lib/util.js

+28-3
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,22 @@ function arrayToHash(array) {
167167
}
168168

169169

170+
function getConstructorOf(obj) {
171+
while (obj) {
172+
var descriptor = Object.getOwnPropertyDescriptor(obj, 'constructor');
173+
if (descriptor !== undefined &&
174+
typeof descriptor.value === 'function' &&
175+
descriptor.value.name !== '') {
176+
return descriptor.value;
177+
}
178+
179+
obj = Object.getPrototypeOf(obj);
180+
}
181+
182+
return null;
183+
}
184+
185+
170186
function inspectPromise(p) {
171187
Debug = Debug || require('vm').runInDebugContext('Debug');
172188
var mirror = Debug.MakeMirror(p, true);
@@ -260,14 +276,17 @@ function formatValue(ctx, value, recurseTimes) {
260276
}
261277
}
262278

279+
var constructor = getConstructorOf(value);
263280
var base = '', empty = false, braces, formatter;
264281

265282
if (Array.isArray(value)) {
283+
if (constructor === Array)
284+
constructor = null;
266285
braces = ['[', ']'];
267286
empty = value.length === 0;
268287
formatter = formatArray;
269288
} else if (value instanceof Set) {
270-
braces = ['Set {', '}'];
289+
braces = ['{', '}'];
271290
// With `showHidden`, `length` will display as a hidden property for
272291
// arrays. For consistency's sake, do the same for `size`, even though this
273292
// property isn't selected by Object.getOwnPropertyNames().
@@ -276,7 +295,7 @@ function formatValue(ctx, value, recurseTimes) {
276295
empty = value.size === 0;
277296
formatter = formatSet;
278297
} else if (value instanceof Map) {
279-
braces = ['Map {', '}'];
298+
braces = ['{', '}'];
280299
// Ditto.
281300
if (ctx.showHidden)
282301
keys.unshift('size');
@@ -286,9 +305,11 @@ function formatValue(ctx, value, recurseTimes) {
286305
// Only create a mirror if the object superficially looks like a Promise.
287306
var promiseInternals = value instanceof Promise && inspectPromise(value);
288307
if (promiseInternals) {
289-
braces = ['Promise {', '}'];
308+
braces = ['{', '}'];
290309
formatter = formatPromise;
291310
} else {
311+
if (constructor === Object)
312+
constructor = null;
292313
braces = ['{', '}'];
293314
empty = true; // No other data than keys.
294315
formatter = formatObject;
@@ -336,6 +357,10 @@ function formatValue(ctx, value, recurseTimes) {
336357
base = ' ' + '[Boolean: ' + formatted + ']';
337358
}
338359

360+
// Add constructor name if available
361+
if (base === '' && constructor)
362+
braces[0] = constructor.name + ' ' + braces[0];
363+
339364
if (empty === true) {
340365
return braces[0] + base + braces[1];
341366
}

test/parallel/test-sys.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ assert.equal(new Date('2010-02-14T12:48:40+01:00').toString(),
1717
assert.equal("'\\n\\u0001'", common.inspect('\n\u0001'));
1818

1919
assert.equal('[]', common.inspect([]));
20-
assert.equal('{}', common.inspect(Object.create([])));
20+
assert.equal('Array {}', common.inspect(Object.create([])));
2121
assert.equal('[ 1, 2 ]', common.inspect([1, 2]));
2222
assert.equal('[ 1, [ 2, 3 ] ]', common.inspect([1, [2, 3]]));
2323

test/parallel/test-util-inspect.js

+42-1
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ assert.ok(ex.indexOf('[message]') != -1);
6161

6262
// GH-1941
6363
// should not throw:
64-
assert.equal(util.inspect(Object.create(Date.prototype)), '{}');
64+
assert.equal(util.inspect(Object.create(Date.prototype)), 'Date {}');
6565

6666
// GH-1944
6767
assert.doesNotThrow(function() {
@@ -306,3 +306,44 @@ checkAlignment(function() {
306306
}());
307307
checkAlignment(new Set(big_array));
308308
checkAlignment(new Map(big_array.map(function(y) { return [y, null]; })));
309+
310+
311+
// Test display of constructors
312+
313+
class ObjectSubclass {}
314+
class ArraySubclass extends Array {}
315+
class SetSubclass extends Set {}
316+
class MapSubclass extends Map {}
317+
class PromiseSubclass extends Promise {}
318+
319+
var x = new ObjectSubclass();
320+
x.foo = 42;
321+
assert.equal(util.inspect(x),
322+
'ObjectSubclass { foo: 42 }');
323+
assert.equal(util.inspect(new ArraySubclass(1, 2, 3)),
324+
'ArraySubclass [ 1, 2, 3 ]');
325+
assert.equal(util.inspect(new SetSubclass([1, 2, 3])),
326+
'SetSubclass { 1, 2, 3 }');
327+
assert.equal(util.inspect(new MapSubclass([['foo', 42]])),
328+
'MapSubclass { \'foo\' => 42 }');
329+
assert.equal(util.inspect(new PromiseSubclass(function() {})),
330+
'PromiseSubclass { <pending> }');
331+
332+
// Corner cases.
333+
var x = { constructor: 42 };
334+
assert.equal(util.inspect(x), '{ constructor: 42 }');
335+
336+
var x = {};
337+
Object.defineProperty(x, 'constructor', {
338+
get: function() {
339+
throw new Error('should not access constructor');
340+
},
341+
enumerable: true
342+
});
343+
assert.equal(util.inspect(x), '{ constructor: [Getter] }');
344+
345+
var x = new (function() {});
346+
assert.equal(util.inspect(x), '{}');
347+
348+
var x = Object.create(null);
349+
assert.equal(util.inspect(x), '{}');

0 commit comments

Comments
 (0)