From f5ff88b3cbf14d024ea75fcc44e5d715ae7b99b4 Mon Sep 17 00:00:00 2001 From: Ruben Bridgewater Date: Sat, 11 Dec 2021 12:49:45 +0100 Subject: [PATCH] util: pass through the inspect function to custom inspect functions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This allows to use more portable custom inspect functions. Fixes: https://github.com/nodejs/node/issues/35956 Signed-off-by: Ruben Bridgewater PR-URL: https://github.com/nodejs/node/pull/41019 Reviewed-By: Michaƫl Zasso --- doc/api/util.md | 35 ++++++++++++++++++++---------- lib/internal/util/inspect.js | 9 ++++++-- test/parallel/test-util-inspect.js | 7 +++--- 3 files changed, 35 insertions(+), 16 deletions(-) diff --git a/doc/api/util.md b/doc/api/util.md index 3b4078b6dc8a42..a779df4758eed4 100644 --- a/doc/api/util.md +++ b/doc/api/util.md @@ -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`. @@ -874,10 +874,18 @@ ignored, if not supported. + + 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'); @@ -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'); } @@ -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} >`; } } @@ -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'); @@ -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) { @@ -954,7 +967,7 @@ class Password { return 'xxxxxxxx'; } - [inspect]() { + [customInspectSymbol](depth, inspectOptions, inspect) { return `Password <${this.toString()}>`; } } diff --git a/lib/internal/util/inspect.js b/lib/internal/util/inspect.js index a4db785042965f..367675b92f31f2 100644 --- a/lib/internal/util/inspect.js +++ b/lib/internal/util/inspect.js @@ -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) { @@ -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 && diff --git a/test/parallel/test-util-inspect.js b/test/parallel/test-util-inspect.js index aeb6359fe331b7..3ffaf656487395 100644 --- a/test/parallel/test-util-inspect.js +++ b/test/parallel/test-util-inspect.js @@ -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 @@ -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); }) }; });