Skip to content

Commit

Permalink
util: pass through the inspect function to custom inspect functions
Browse files Browse the repository at this point in the history
This allows to use more portable custom inspect functions.

Fixes: #35956

Signed-off-by: Ruben Bridgewater <ruben@bridgewater.de>

PR-URL: #41019
Reviewed-By: Michaël Zasso <targos@protonmail.com>
  • Loading branch information
BridgeAR authored and danielleadams committed Dec 13, 2021
1 parent 1fc6fd6 commit f5ff88b
Show file tree
Hide file tree
Showing 3 changed files with 35 additions and 16 deletions.
35 changes: 24 additions & 11 deletions doc/api/util.md
Original file line number Diff line number Diff line change
Expand Up @@ -579,7 +579,7 @@ changes:
codes. Colors are customizable. See [Customizing `util.inspect` colors][].
**Default:** `false`.
* `customInspect` {boolean} If `false`,
`[util.inspect.custom](depth, opts)` functions are not invoked.
`[util.inspect.custom](depth, opts, inspect)` functions are not invoked.
**Default:** `true`.
* `showProxy` {boolean} If `true`, `Proxy` inspection includes
the [`target` and `handler`][] objects. **Default:** `false`.
Expand Down Expand Up @@ -874,10 +874,18 @@ ignored, if not supported.

<!-- type=misc -->

<!-- YAML
added: v0.1.97
changes:
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/41019
description: The inspect argument is added for more interoperability.
-->

Objects may also define their own
[`[util.inspect.custom](depth, opts)`][util.inspect.custom] function,
[`[util.inspect.custom](depth, opts, inspect)`][util.inspect.custom] function,
which `util.inspect()` will invoke and use the result of when inspecting
the object:
the object.

```js
const util = require('util');
Expand All @@ -887,7 +895,7 @@ class Box {
this.value = value;
}

[util.inspect.custom](depth, options) {
[util.inspect.custom](depth, options, inspect) {
if (depth < 0) {
return options.stylize('[Box]', 'special');
}
Expand All @@ -898,8 +906,8 @@ class Box {

// Five space padding because that's the size of "Box< ".
const padding = ' '.repeat(5);
const inner = util.inspect(this.value, newOptions)
.replace(/\n/g, `\n${padding}`);
const inner = inspect(this.value, newOptions)
.replace(/\n/g, `\n${padding}`);
return `${options.stylize('Box', 'special')}< ${inner} >`;
}
}
Expand All @@ -910,9 +918,9 @@ util.inspect(box);
// Returns: "Box< true >"
```

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

```js
const util = require('util');
Expand Down Expand Up @@ -942,8 +950,13 @@ In addition to being accessible through `util.inspect.custom`, this
symbol is [registered globally][global symbol registry] and can be
accessed in any environment as `Symbol.for('nodejs.util.inspect.custom')`.

Using this allows code to be written in a portable fashion, so that the custom
inspect function is used in an Node.js environment and ignored in the browser.
The `util.inspect()` function itself is passed as third argument to the custom
inspect function to allow further portability.

```js
const inspect = Symbol.for('nodejs.util.inspect.custom');
const customInspectSymbol = Symbol.for('nodejs.util.inspect.custom');

class Password {
constructor(value) {
Expand All @@ -954,7 +967,7 @@ class Password {
return 'xxxxxxxx';
}

[inspect]() {
[customInspectSymbol](depth, inspectOptions, inspect) {
return `Password <${this.toString()}>`;
}
}
Expand Down
9 changes: 7 additions & 2 deletions lib/internal/util/inspect.js
Original file line number Diff line number Diff line change
Expand Up @@ -778,7 +778,12 @@ function formatValue(ctx, value, recurseTimes, typedArray) {
const isCrossContext =
proxy !== undefined || !(context instanceof Object);
const ret = FunctionPrototypeCall(
maybeCustom, context, depth, getUserOptions(ctx, isCrossContext));
maybeCustom,
context,
depth,
getUserOptions(ctx, isCrossContext),
inspect
);
// If the custom inspection method returned `this`, don't go into
// infinite recursion.
if (ret !== context) {
Expand Down Expand Up @@ -1143,7 +1148,7 @@ function getClassBase(value, constructor, tag) {

function getFunctionBase(value, constructor, tag) {
const stringified = FunctionPrototypeToString(value);
if (stringified.slice(0, 5) === 'class' && stringified.endsWith('}')) {
if (stringified.startsWith('class') && stringified.endsWith('}')) {
const slice = stringified.slice(5, -1);
const bracketIndex = slice.indexOf('{');
if (bracketIndex !== -1 &&
Expand Down
7 changes: 4 additions & 3 deletions test/parallel/test-util-inspect.js
Original file line number Diff line number Diff line change
Expand Up @@ -974,7 +974,7 @@ util.inspect({ hasOwnProperty: null });

assert.strictEqual(util.inspect(subject), "{ foo: 'bar' }");

subject[util.inspect.custom] = common.mustCall((depth, opts) => {
subject[util.inspect.custom] = common.mustCall((depth, opts, inspect) => {
const clone = { ...opts };
// This might change at some point but for now we keep the stylize function.
// The function should either be documented or an alternative should be
Expand All @@ -984,12 +984,13 @@ util.inspect({ hasOwnProperty: null });
assert.strictEqual(opts.budget, undefined);
assert.strictEqual(opts.indentationLvl, undefined);
assert.strictEqual(opts.showHidden, false);
assert.strictEqual(inspect, util.inspect);
assert.deepStrictEqual(
new Set(Object.keys(util.inspect.defaultOptions).concat(['stylize'])),
new Set(Object.keys(inspect.defaultOptions).concat(['stylize'])),
new Set(Object.keys(opts))
);
opts.showHidden = true;
return { [util.inspect.custom]: common.mustCall((depth, opts2) => {
return { [inspect.custom]: common.mustCall((depth, opts2) => {
assert.deepStrictEqual(clone, opts2);
}) };
});
Expand Down

0 comments on commit f5ff88b

Please sign in to comment.