Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

v4.x: backport util.inspect.custom #9688

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 36 additions & 9 deletions doc/api/util.md
Original file line number Diff line number Diff line change
Expand Up @@ -234,31 +234,49 @@ Predefined color codes are: `white`, `grey`, `black`, `blue`, `cyan`,
`green`, `magenta`, `red` and `yellow`.
There are also `bold`, `italic`, `underline` and `inverse` codes.

### Custom `inspect()` function on Objects
### Custom inspection functions on Objects

<!-- type=misc -->

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

```js
const util = require('util');

var obj = { name: 'nate' };
obj.inspect = function(depth) {
const obj = { name: 'nate' };
obj[util.inspect.custom] = function(depth) {
return `{${this.name}}`;
};

util.inspect(obj);
// "{nate}"
```

You may also return another Object entirely, and the returned String will be
formatted according to the returned Object. This is similar to how
`JSON.stringify()` works:
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()`.

```js
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
var obj = { foo: 'this will not show up in the inspect() output' };
const util = require('util');

const obj = { foo: 'this will not show up in the inspect() output' };
obj.inspect = function(depth) {
return { bar: 'baz' };
};
Expand All @@ -267,6 +285,14 @@ util.inspect(obj);
// "{ bar: 'baz' }"
```

### util.inspect.custom
<!-- YAML
added: REPLACEME
-->

A Symbol that can be used to declare custom inspect functions, see
[Custom inspection functions on Objects][].

## util.isArray(object)
<!-- YAML
added: v0.6.0
Expand Down Expand Up @@ -656,6 +682,7 @@ Deprecated predecessor of `console.log`.
[constructor]: https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Object/constructor
[Customizing `util.inspect` colors]: #util_customizing_util_inspect_colors
[here]: #util_customizing_util_inspect_colors
[Custom inspection functions on Objects]: #util_custom_inspection_functions_on_objects
[`Error`]: errors.html#errors_class_error
[`console.log()`]: console.html#console_console_log_data
[`console.error()`]: console.html#console_console_error_data
Expand Down
5 changes: 3 additions & 2 deletions lib/buffer.js
Original file line number Diff line number Diff line change
Expand Up @@ -469,8 +469,8 @@ Buffer.prototype.equals = function equals(b) {
};


// Inspect
Buffer.prototype.inspect = function inspect() {
// Override how buffers are presented by util.inspect().
Buffer.prototype[internalUtil.customInspectSymbol] = function inspect() {
var str = '';
var max = exports.INSPECT_MAX_BYTES;
if (this.length > 0) {
Expand All @@ -480,6 +480,7 @@ Buffer.prototype.inspect = function inspect() {
}
return '<' + this.constructor.name + ' ' + str + '>';
};
Buffer.prototype.inspect = Buffer.prototype[internalUtil.customInspectSymbol];


Buffer.prototype.compare = function compare(b) {
Expand Down
4 changes: 4 additions & 0 deletions lib/internal/util.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@ const prefix = '(node) ';

exports.getHiddenValue = binding.getHiddenValue;

// 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
// prepend the prefix to the actual message.
exports.deprecate = function(fn, msg) {
Expand Down
34 changes: 22 additions & 12 deletions lib/util.js
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,6 @@ function inspect(obj, opts) {
if (ctx.colors) ctx.stylize = stylizeWithColor;
return formatValue(ctx, obj, ctx.depth);
}
exports.inspect = inspect;


// http://en.wikipedia.org/wiki/ANSI_escape_code#graphics
Expand Down Expand Up @@ -176,6 +175,10 @@ inspect.styles = {
'regexp': 'red'
};

const customInspectSymbol = internalUtil.customInspectSymbol;

exports.inspect = inspect;
exports.inspect.custom = customInspectSymbol;

function stylizeWithColor(str, styleType) {
var style = inspect.styles[styleType];
Expand Down Expand Up @@ -243,18 +246,25 @@ function inspectPromise(p) {
function formatValue(ctx, value, recurseTimes) {
// Provide a hook for user-specified inspect functions.
// Check that value is an object with an inspect function on it
if (ctx.customInspect &&
value &&
typeof value.inspect === 'function' &&
// Filter out the util module, it's inspect function is special
value.inspect !== exports.inspect &&
// Also filter out any prototype objects using the circular check.
!(value.constructor && value.constructor.prototype === value)) {
var ret = value.inspect(recurseTimes, ctx);
if (typeof ret !== 'string') {
ret = formatValue(ctx, ret, recurseTimes);
if (ctx.customInspect && value) {
const maybeCustomInspect = value[customInspectSymbol] || value.inspect;

if (typeof maybeCustomInspect === 'function' &&
// Filter out the util module, its inspect function is special
maybeCustomInspect !== exports.inspect &&
// Also filter out any prototype objects using the circular check.
!(value.constructor && value.constructor.prototype === value)) {
let ret = maybeCustomInspect.call(value, recurseTimes, ctx);

// If the custom inspection method returned `this`, don't go into
// infinite recursion.
if (ret !== value) {
if (typeof ret !== 'string') {
ret = formatValue(ctx, ret, recurseTimes);
}
return ret;
}
}
return ret;
}

// Primitive types cannot have properties
Expand Down
3 changes: 3 additions & 0 deletions test/parallel/test-buffer-inspect.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,3 +36,6 @@ assert.doesNotThrow(function() {
assert.strictEqual(util.inspect(b), expected);
assert.strictEqual(util.inspect(s), expected);
});

b.inspect = undefined;
assert.strictEqual(util.inspect(b), expected);
101 changes: 92 additions & 9 deletions test/parallel/test-util-inspect.js
Original file line number Diff line number Diff line change
Expand Up @@ -279,19 +279,42 @@ assert.doesNotThrow(function() {

// 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 });

assert(util.inspect(subject, { showHidden: false }).indexOf('hidden') === -1);
assert(util.inspect(subject, { showHidden: true }).indexOf('hidden') !== -1);
assert(util.inspect(subject, { colors: false }).indexOf('\u001b[32m') === -1);
assert(util.inspect(subject, { colors: true }).indexOf('\u001b[32m') !== -1);
assert(util.inspect(subject, { depth: 2 }).indexOf('c: [Object]') !== -1);
assert(util.inspect(subject, { depth: 0 }).indexOf('a: [Object]') !== -1);
assert(util.inspect(subject, { depth: null }).indexOf('{ d: 0 }') !== -1);
assert.strictEqual(
util.inspect(subject, { showHidden: false }).includes('hidden'),
false
);
assert.strictEqual(
util.inspect(subject, { showHidden: true }).includes('hidden'),
true
);
assert.strictEqual(
util.inspect(subject, { colors: false }).includes('\u001b[32m'),
false
);
assert.strictEqual(
util.inspect(subject, { colors: true }).includes('\u001b[32m'),
true
);
assert.strictEqual(
util.inspect(subject, { depth: 2 }).includes('c: [Object]'),
true
);
assert.strictEqual(
util.inspect(subject, { depth: 0 }).includes('a: [Object]'),
true
);
assert.strictEqual(
util.inspect(subject, { depth: null }).includes('{ d: 0 }'),
true
);
}

{
// "customInspect" option can enable/disable calling inspect() on objects
subject = { inspect: function() { return 123; } };
const subject = { inspect: function() { return 123; } };

assert(util.inspect(subject,
{ customInspect: true }).indexOf('123') !== -1);
Expand All @@ -314,6 +337,66 @@ assert.doesNotThrow(function() {
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
);
}

{
// Returning `this` from a custom inspection function works.
assert.strictEqual(util.inspect({ a: 123, inspect() { return this; } }),
'{ a: 123, inspect: [Function: inspect] }');

const subject = { a: 123, [util.inspect.custom]() { return this; } };
assert.strictEqual(util.inspect(subject),
'{ a: 123 }');
}

// util.inspect with "colors" option should produce as many lines as without it
function test_lines(input) {
var count_lines = function(str) {
Expand Down