From 7606bdb89720b65af32073c6470393d158fe580a Mon Sep 17 00:00:00 2001 From: Christopher Monsanto Date: Wed, 10 Jun 2015 04:25:04 -0400 Subject: [PATCH] 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 Reviewed-By: Jeremiah Senkpiel --- lib/util.js | 31 ++++++++++++++++++--- test/parallel/test-sys.js | 2 +- test/parallel/test-util-inspect.js | 43 +++++++++++++++++++++++++++++- 3 files changed, 71 insertions(+), 5 deletions(-) diff --git a/lib/util.js b/lib/util.js index 136a66a6c1c0e8..c5d7bea7db352d 100644 --- a/lib/util.js +++ b/lib/util.js @@ -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]; } diff --git a/test/parallel/test-sys.js b/test/parallel/test-sys.js index bbc8c092002af6..9367e55c687f2f 100644 --- a/test/parallel/test-sys.js +++ b/test/parallel/test-sys.js @@ -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]])); diff --git a/test/parallel/test-util-inspect.js b/test/parallel/test-util-inspect.js index f583005ce96f75..fcde1c74c3984e 100644 --- a/test/parallel/test-util-inspect.js +++ b/test/parallel/test-util-inspect.js @@ -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 { }'); + +// 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), '{}');