util: allow symbol-based custom inspection methods
Add a `util.inspect.custom` Symbol which can be used to customize `util.inspect()` output. Providing `obj[util.inspect.custom]` works like providing `obj.inspect`, except that the former allows avoiding name clashes with other `inspect()` methods. Fixes: https://github.com/nodejs/node/issues/8071 PR-URL: https://github.com/nodejs/node/pull/8174 Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Michaël Zasso <mic.besace@gmail.com>
This commit is contained in:
parent
6e50fc7637
commit
59714cb7b3
@ -275,18 +275,19 @@ The predefined color codes are: `white`, `grey`, `black`, `blue`, `cyan`,
|
|||||||
Color styling uses ANSI control codes that may not be supported on all
|
Color styling uses ANSI control codes that may not be supported on all
|
||||||
terminals.
|
terminals.
|
||||||
|
|
||||||
### Custom `inspect()` function on Objects
|
### Custom inspection functions on Objects
|
||||||
|
|
||||||
<!-- type=misc -->
|
<!-- type=misc -->
|
||||||
|
|
||||||
Objects may also define their own `inspect(depth, opts)` function that
|
Objects may also define their own `[util.inspect.custom](depth, opts)`
|
||||||
`util.inspect()` will invoke and use the result of when inspecting the object:
|
(or, equivalently `inspect(depth, opts)`) function that `util.inspect()` will
|
||||||
|
invoke and use the result of when inspecting the object:
|
||||||
|
|
||||||
```js
|
```js
|
||||||
const util = require('util');
|
const util = require('util');
|
||||||
|
|
||||||
const obj = { name: 'nate' };
|
const obj = { name: 'nate' };
|
||||||
obj.inspect = function(depth) {
|
obj[util.inspect.custom] = function(depth) {
|
||||||
return `{${this.name}}`;
|
return `{${this.name}}`;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -294,13 +295,28 @@ util.inspect(obj);
|
|||||||
// "{nate}"
|
// "{nate}"
|
||||||
```
|
```
|
||||||
|
|
||||||
Custom `inspect(depth, opts)` functions typically return a string but may
|
Custom `[util.inspect.custom](depth, opts)` functions typically return a string
|
||||||
return a value of any type that will be formatted accordingly by
|
but may return a value of any type that will be formatted accordingly by
|
||||||
`util.inspect()`.
|
`util.inspect()`.
|
||||||
|
|
||||||
```js
|
```js
|
||||||
const util = require('util');
|
const util = require('util');
|
||||||
|
|
||||||
|
const obj = { foo: 'this will not show up in the inspect() output' };
|
||||||
|
obj[util.inspect.custom] = function(depth) {
|
||||||
|
return { bar: 'baz' };
|
||||||
|
};
|
||||||
|
|
||||||
|
util.inspect(obj);
|
||||||
|
// "{ bar: 'baz' }"
|
||||||
|
```
|
||||||
|
|
||||||
|
A custom inspection method can alternatively be provided by exposing
|
||||||
|
an `inspect(depth, opts)` method on the object:
|
||||||
|
|
||||||
|
```js
|
||||||
|
const util = require('util');
|
||||||
|
|
||||||
const obj = { foo: 'this will not show up in the inspect() output' };
|
const obj = { foo: 'this will not show up in the inspect() output' };
|
||||||
obj.inspect = function(depth) {
|
obj.inspect = function(depth) {
|
||||||
return { bar: 'baz' };
|
return { bar: 'baz' };
|
||||||
@ -330,6 +346,14 @@ util.inspect.defaultOptions.maxArrayLength = null;
|
|||||||
console.log(arr); // logs the full array
|
console.log(arr); // logs the full array
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### util.inspect.custom
|
||||||
|
<!-- YAML
|
||||||
|
added: REPLACEME
|
||||||
|
-->
|
||||||
|
|
||||||
|
A Symbol that can be used to declare custom inspect functions, see
|
||||||
|
[Custom inspection functions on Objects][].
|
||||||
|
|
||||||
## Deprecated APIs
|
## Deprecated APIs
|
||||||
|
|
||||||
The following APIs have been deprecated and should no longer be used. Existing
|
The following APIs have been deprecated and should no longer be used. Existing
|
||||||
@ -807,6 +831,7 @@ similar built-in functionality through [`Object.assign()`].
|
|||||||
[semantically incompatible]: https://github.com/nodejs/node/issues/4179
|
[semantically incompatible]: https://github.com/nodejs/node/issues/4179
|
||||||
[`util.inspect()`]: #util_util_inspect_object_options
|
[`util.inspect()`]: #util_util_inspect_object_options
|
||||||
[Customizing `util.inspect` colors]: #util_customizing_util_inspect_colors
|
[Customizing `util.inspect` colors]: #util_customizing_util_inspect_colors
|
||||||
|
[Custom inspection functions on Objects]: #util_custom_inspection_functions_on_objects
|
||||||
[`Error`]: errors.html#errors_class_error
|
[`Error`]: errors.html#errors_class_error
|
||||||
[`console.log()`]: console.html#console_console_log_data
|
[`console.log()`]: console.html#console_console_log_data
|
||||||
[`console.error()`]: console.html#console_console_error_data
|
[`console.error()`]: console.html#console_console_error_data
|
||||||
|
@ -500,8 +500,8 @@ Buffer.prototype.equals = function equals(b) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
// Inspect
|
// Override how buffers are presented by util.inspect().
|
||||||
Buffer.prototype.inspect = function inspect() {
|
Buffer.prototype[internalUtil.inspectSymbol] = function inspect() {
|
||||||
var str = '';
|
var str = '';
|
||||||
var max = exports.INSPECT_MAX_BYTES;
|
var max = exports.INSPECT_MAX_BYTES;
|
||||||
if (this.length > 0) {
|
if (this.length > 0) {
|
||||||
@ -511,6 +511,7 @@ Buffer.prototype.inspect = function inspect() {
|
|||||||
}
|
}
|
||||||
return '<' + this.constructor.name + ' ' + str + '>';
|
return '<' + this.constructor.name + ' ' + str + '>';
|
||||||
};
|
};
|
||||||
|
Buffer.prototype.inspect = Buffer.prototype[internalUtil.inspectSymbol];
|
||||||
|
|
||||||
Buffer.prototype.compare = function compare(target,
|
Buffer.prototype.compare = function compare(target,
|
||||||
start,
|
start,
|
||||||
|
@ -9,6 +9,10 @@ const kDecoratedPrivateSymbolIndex = binding['decorated_private_symbol'];
|
|||||||
exports.getHiddenValue = binding.getHiddenValue;
|
exports.getHiddenValue = binding.getHiddenValue;
|
||||||
exports.setHiddenValue = binding.setHiddenValue;
|
exports.setHiddenValue = binding.setHiddenValue;
|
||||||
|
|
||||||
|
// The `buffer` module uses this. Defining it here instead of in the public
|
||||||
|
// `util` module makes it accessible without having to `require('util')` there.
|
||||||
|
exports.customInspectSymbol = Symbol('util.inspect.custom');
|
||||||
|
|
||||||
// All the internal deprecations have to use this function only, as this will
|
// All the internal deprecations have to use this function only, as this will
|
||||||
// prepend the prefix to the actual message.
|
// prepend the prefix to the actual message.
|
||||||
exports.deprecate = function(fn, msg) {
|
exports.deprecate = function(fn, msg) {
|
||||||
|
27
lib/util.js
27
lib/util.js
@ -242,7 +242,10 @@ inspect.styles = {
|
|||||||
'regexp': 'red'
|
'regexp': 'red'
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const customInspectSymbol = internalUtil.customInspectSymbol;
|
||||||
|
|
||||||
exports.inspect = inspect;
|
exports.inspect = inspect;
|
||||||
|
exports.inspect.custom = customInspectSymbol;
|
||||||
|
|
||||||
function stylizeWithColor(str, styleType) {
|
function stylizeWithColor(str, styleType) {
|
||||||
var style = inspect.styles[styleType];
|
var style = inspect.styles[styleType];
|
||||||
@ -350,18 +353,20 @@ function formatValue(ctx, value, recurseTimes) {
|
|||||||
|
|
||||||
// Provide a hook for user-specified inspect functions.
|
// Provide a hook for user-specified inspect functions.
|
||||||
// Check that value is an object with an inspect function on it
|
// Check that value is an object with an inspect function on it
|
||||||
if (ctx.customInspect &&
|
if (ctx.customInspect && value) {
|
||||||
value &&
|
const maybeCustomInspect = value[customInspectSymbol] || value.inspect;
|
||||||
typeof value.inspect === 'function' &&
|
|
||||||
// Filter out the util module, it's inspect function is special
|
if (typeof maybeCustomInspect === 'function' &&
|
||||||
value.inspect !== exports.inspect &&
|
// Filter out the util module, its inspect function is special
|
||||||
// Also filter out any prototype objects using the circular check.
|
maybeCustomInspect !== exports.inspect &&
|
||||||
!(value.constructor && value.constructor.prototype === value)) {
|
// Also filter out any prototype objects using the circular check.
|
||||||
var ret = value.inspect(recurseTimes, ctx);
|
!(value.constructor && value.constructor.prototype === value)) {
|
||||||
if (typeof ret !== 'string') {
|
let ret = maybeCustomInspect.call(value, recurseTimes, ctx);
|
||||||
ret = formatValue(ctx, ret, recurseTimes);
|
if (typeof ret !== 'string') {
|
||||||
|
ret = formatValue(ctx, ret, recurseTimes);
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
}
|
}
|
||||||
return ret;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Primitive types cannot have properties
|
// Primitive types cannot have properties
|
||||||
|
@ -451,7 +451,7 @@ assert.doesNotThrow(function() {
|
|||||||
|
|
||||||
// new API, accepts an "options" object
|
// new API, accepts an "options" object
|
||||||
{
|
{
|
||||||
let subject = { foo: 'bar', hello: 31, a: { b: { c: { d: 0 } } } };
|
const subject = { foo: 'bar', hello: 31, a: { b: { c: { d: 0 } } } };
|
||||||
Object.defineProperty(subject, 'hidden', { enumerable: false, value: null });
|
Object.defineProperty(subject, 'hidden', { enumerable: false, value: null });
|
||||||
|
|
||||||
assert.strictEqual(
|
assert.strictEqual(
|
||||||
@ -482,9 +482,11 @@ assert.doesNotThrow(function() {
|
|||||||
util.inspect(subject, { depth: null }).includes('{ d: 0 }'),
|
util.inspect(subject, { depth: null }).includes('{ d: 0 }'),
|
||||||
true
|
true
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
// "customInspect" option can enable/disable calling inspect() on objects
|
// "customInspect" option can enable/disable calling inspect() on objects
|
||||||
subject = { inspect: function() { return 123; } };
|
const subject = { inspect: function() { return 123; } };
|
||||||
|
|
||||||
assert.strictEqual(
|
assert.strictEqual(
|
||||||
util.inspect(subject, { customInspect: true }).includes('123'),
|
util.inspect(subject, { customInspect: true }).includes('123'),
|
||||||
@ -515,6 +517,56 @@ assert.doesNotThrow(function() {
|
|||||||
util.inspect(subject, { customInspectOptions: true });
|
util.inspect(subject, { customInspectOptions: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
// "customInspect" option can enable/disable calling [util.inspect.custom]()
|
||||||
|
const subject = { [util.inspect.custom]: function() { return 123; } };
|
||||||
|
|
||||||
|
assert.strictEqual(
|
||||||
|
util.inspect(subject, { customInspect: true }).includes('123'),
|
||||||
|
true
|
||||||
|
);
|
||||||
|
assert.strictEqual(
|
||||||
|
util.inspect(subject, { customInspect: false }).includes('123'),
|
||||||
|
false
|
||||||
|
);
|
||||||
|
|
||||||
|
// a custom [util.inspect.custom]() should be able to return other Objects
|
||||||
|
subject[util.inspect.custom] = function() { return { foo: 'bar' }; };
|
||||||
|
|
||||||
|
assert.strictEqual(util.inspect(subject), '{ foo: \'bar\' }');
|
||||||
|
|
||||||
|
subject[util.inspect.custom] = function(depth, opts) {
|
||||||
|
assert.strictEqual(opts.customInspectOptions, true);
|
||||||
|
};
|
||||||
|
|
||||||
|
util.inspect(subject, { customInspectOptions: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
// [util.inspect.custom] takes precedence over inspect
|
||||||
|
const subject = {
|
||||||
|
[util.inspect.custom]() { return 123; },
|
||||||
|
inspect() { return 456; }
|
||||||
|
};
|
||||||
|
|
||||||
|
assert.strictEqual(
|
||||||
|
util.inspect(subject, { customInspect: true }).includes('123'),
|
||||||
|
true
|
||||||
|
);
|
||||||
|
assert.strictEqual(
|
||||||
|
util.inspect(subject, { customInspect: false }).includes('123'),
|
||||||
|
false
|
||||||
|
);
|
||||||
|
assert.strictEqual(
|
||||||
|
util.inspect(subject, { customInspect: true }).includes('456'),
|
||||||
|
false
|
||||||
|
);
|
||||||
|
assert.strictEqual(
|
||||||
|
util.inspect(subject, { customInspect: false }).includes('456'),
|
||||||
|
false
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// util.inspect with "colors" option should produce as many lines as without it
|
// util.inspect with "colors" option should produce as many lines as without it
|
||||||
function test_lines(input) {
|
function test_lines(input) {
|
||||||
var count_lines = function(str) {
|
var count_lines = function(str) {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user