Skip to content

Commit 59714cb

Browse files
committed
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: #8071 PR-URL: #8174 Reviewed-By: James M Snell <[email protected]> Reviewed-By: Michaël Zasso <[email protected]>
1 parent 6e50fc7 commit 59714cb

File tree

5 files changed

+108
-21
lines changed

5 files changed

+108
-21
lines changed

doc/api/util.md

+31-6
Original file line numberDiff line numberDiff line change
@@ -275,32 +275,48 @@ The predefined color codes are: `white`, `grey`, `black`, `blue`, `cyan`,
275275
Color styling uses ANSI control codes that may not be supported on all
276276
terminals.
277277

278-
### Custom `inspect()` function on Objects
278+
### Custom inspection functions on Objects
279279

280280
<!-- type=misc -->
281281

282-
Objects may also define their own `inspect(depth, opts)` function that
283-
`util.inspect()` will invoke and use the result of when inspecting the object:
282+
Objects may also define their own `[util.inspect.custom](depth, opts)`
283+
(or, equivalently `inspect(depth, opts)`) function that `util.inspect()` will
284+
invoke and use the result of when inspecting the object:
284285

285286
```js
286287
const util = require('util');
287288

288289
const obj = { name: 'nate' };
289-
obj.inspect = function(depth) {
290+
obj[util.inspect.custom] = function(depth) {
290291
return `{${this.name}}`;
291292
};
292293

293294
util.inspect(obj);
294295
// "{nate}"
295296
```
296297

297-
Custom `inspect(depth, opts)` functions typically return a string but may
298-
return a value of any type that will be formatted accordingly by
298+
Custom `[util.inspect.custom](depth, opts)` functions typically return a string
299+
but may return a value of any type that will be formatted accordingly by
299300
`util.inspect()`.
300301

301302
```js
302303
const util = require('util');
303304

305+
const obj = { foo: 'this will not show up in the inspect() output' };
306+
obj[util.inspect.custom] = function(depth) {
307+
return { bar: 'baz' };
308+
};
309+
310+
util.inspect(obj);
311+
// "{ bar: 'baz' }"
312+
```
313+
314+
A custom inspection method can alternatively be provided by exposing
315+
an `inspect(depth, opts)` method on the object:
316+
317+
```js
318+
const util = require('util');
319+
304320
const obj = { foo: 'this will not show up in the inspect() output' };
305321
obj.inspect = function(depth) {
306322
return { bar: 'baz' };
@@ -330,6 +346,14 @@ util.inspect.defaultOptions.maxArrayLength = null;
330346
console.log(arr); // logs the full array
331347
```
332348

349+
### util.inspect.custom
350+
<!-- YAML
351+
added: REPLACEME
352+
-->
353+
354+
A Symbol that can be used to declare custom inspect functions, see
355+
[Custom inspection functions on Objects][].
356+
333357
## Deprecated APIs
334358

335359
The following APIs have been deprecated and should no longer be used. Existing
@@ -807,6 +831,7 @@ similar built-in functionality through [`Object.assign()`].
807831
[semantically incompatible]: https://github.com/nodejs/node/issues/4179
808832
[`util.inspect()`]: #util_util_inspect_object_options
809833
[Customizing `util.inspect` colors]: #util_customizing_util_inspect_colors
834+
[Custom inspection functions on Objects]: #util_custom_inspection_functions_on_objects
810835
[`Error`]: errors.html#errors_class_error
811836
[`console.log()`]: console.html#console_console_log_data
812837
[`console.error()`]: console.html#console_console_error_data

lib/buffer.js

+3-2
Original file line numberDiff line numberDiff line change
@@ -500,8 +500,8 @@ Buffer.prototype.equals = function equals(b) {
500500
};
501501

502502

503-
// Inspect
504-
Buffer.prototype.inspect = function inspect() {
503+
// Override how buffers are presented by util.inspect().
504+
Buffer.prototype[internalUtil.inspectSymbol] = function inspect() {
505505
var str = '';
506506
var max = exports.INSPECT_MAX_BYTES;
507507
if (this.length > 0) {
@@ -511,6 +511,7 @@ Buffer.prototype.inspect = function inspect() {
511511
}
512512
return '<' + this.constructor.name + ' ' + str + '>';
513513
};
514+
Buffer.prototype.inspect = Buffer.prototype[internalUtil.inspectSymbol];
514515

515516
Buffer.prototype.compare = function compare(target,
516517
start,

lib/internal/util.js

+4
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,10 @@ const kDecoratedPrivateSymbolIndex = binding['decorated_private_symbol'];
99
exports.getHiddenValue = binding.getHiddenValue;
1010
exports.setHiddenValue = binding.setHiddenValue;
1111

12+
// The `buffer` module uses this. Defining it here instead of in the public
13+
// `util` module makes it accessible without having to `require('util')` there.
14+
exports.customInspectSymbol = Symbol('util.inspect.custom');
15+
1216
// All the internal deprecations have to use this function only, as this will
1317
// prepend the prefix to the actual message.
1418
exports.deprecate = function(fn, msg) {

lib/util.js

+16-11
Original file line numberDiff line numberDiff line change
@@ -242,7 +242,10 @@ inspect.styles = {
242242
'regexp': 'red'
243243
};
244244

245+
const customInspectSymbol = internalUtil.customInspectSymbol;
246+
245247
exports.inspect = inspect;
248+
exports.inspect.custom = customInspectSymbol;
246249

247250
function stylizeWithColor(str, styleType) {
248251
var style = inspect.styles[styleType];
@@ -350,18 +353,20 @@ function formatValue(ctx, value, recurseTimes) {
350353

351354
// Provide a hook for user-specified inspect functions.
352355
// Check that value is an object with an inspect function on it
353-
if (ctx.customInspect &&
354-
value &&
355-
typeof value.inspect === 'function' &&
356-
// Filter out the util module, it's inspect function is special
357-
value.inspect !== exports.inspect &&
358-
// Also filter out any prototype objects using the circular check.
359-
!(value.constructor && value.constructor.prototype === value)) {
360-
var ret = value.inspect(recurseTimes, ctx);
361-
if (typeof ret !== 'string') {
362-
ret = formatValue(ctx, ret, recurseTimes);
356+
if (ctx.customInspect && value) {
357+
const maybeCustomInspect = value[customInspectSymbol] || value.inspect;
358+
359+
if (typeof maybeCustomInspect === 'function' &&
360+
// Filter out the util module, its inspect function is special
361+
maybeCustomInspect !== exports.inspect &&
362+
// Also filter out any prototype objects using the circular check.
363+
!(value.constructor && value.constructor.prototype === value)) {
364+
let ret = maybeCustomInspect.call(value, recurseTimes, ctx);
365+
if (typeof ret !== 'string') {
366+
ret = formatValue(ctx, ret, recurseTimes);
367+
}
368+
return ret;
363369
}
364-
return ret;
365370
}
366371

367372
// Primitive types cannot have properties

test/parallel/test-util-inspect.js

+54-2
Original file line numberDiff line numberDiff line change
@@ -451,7 +451,7 @@ assert.doesNotThrow(function() {
451451

452452
// new API, accepts an "options" object
453453
{
454-
let subject = { foo: 'bar', hello: 31, a: { b: { c: { d: 0 } } } };
454+
const subject = { foo: 'bar', hello: 31, a: { b: { c: { d: 0 } } } };
455455
Object.defineProperty(subject, 'hidden', { enumerable: false, value: null });
456456

457457
assert.strictEqual(
@@ -482,9 +482,11 @@ assert.doesNotThrow(function() {
482482
util.inspect(subject, { depth: null }).includes('{ d: 0 }'),
483483
true
484484
);
485+
}
485486

487+
{
486488
// "customInspect" option can enable/disable calling inspect() on objects
487-
subject = { inspect: function() { return 123; } };
489+
const subject = { inspect: function() { return 123; } };
488490

489491
assert.strictEqual(
490492
util.inspect(subject, { customInspect: true }).includes('123'),
@@ -515,6 +517,56 @@ assert.doesNotThrow(function() {
515517
util.inspect(subject, { customInspectOptions: true });
516518
}
517519

520+
{
521+
// "customInspect" option can enable/disable calling [util.inspect.custom]()
522+
const subject = { [util.inspect.custom]: function() { return 123; } };
523+
524+
assert.strictEqual(
525+
util.inspect(subject, { customInspect: true }).includes('123'),
526+
true
527+
);
528+
assert.strictEqual(
529+
util.inspect(subject, { customInspect: false }).includes('123'),
530+
false
531+
);
532+
533+
// a custom [util.inspect.custom]() should be able to return other Objects
534+
subject[util.inspect.custom] = function() { return { foo: 'bar' }; };
535+
536+
assert.strictEqual(util.inspect(subject), '{ foo: \'bar\' }');
537+
538+
subject[util.inspect.custom] = function(depth, opts) {
539+
assert.strictEqual(opts.customInspectOptions, true);
540+
};
541+
542+
util.inspect(subject, { customInspectOptions: true });
543+
}
544+
545+
{
546+
// [util.inspect.custom] takes precedence over inspect
547+
const subject = {
548+
[util.inspect.custom]() { return 123; },
549+
inspect() { return 456; }
550+
};
551+
552+
assert.strictEqual(
553+
util.inspect(subject, { customInspect: true }).includes('123'),
554+
true
555+
);
556+
assert.strictEqual(
557+
util.inspect(subject, { customInspect: false }).includes('123'),
558+
false
559+
);
560+
assert.strictEqual(
561+
util.inspect(subject, { customInspect: true }).includes('456'),
562+
false
563+
);
564+
assert.strictEqual(
565+
util.inspect(subject, { customInspect: false }).includes('456'),
566+
false
567+
);
568+
}
569+
518570
// util.inspect with "colors" option should produce as many lines as without it
519571
function test_lines(input) {
520572
var count_lines = function(str) {

0 commit comments

Comments
 (0)