util: include reference anchor for circular structures
This adds a reference anchor to circular structures when using `util.inspect`. That way it's possible to identify with what object the circular reference corresponds too. PR-URL: https://github.com/nodejs/node/pull/27685 Reviewed-By: Anna Henningsen <anna@addaleax.net> Reviewed-By: Benjamin Gruenbaum <benjamingr@gmail.com> Reviewed-By: Rich Trott <rtrott@gmail.com> Reviewed-By: Anto Aravinth <anto.aravinth.cse@gmail.com> Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Jeremiah Senkpiel <fishrock123@rocketmail.com>
This commit is contained in:
parent
5518664d41
commit
9f71dbc334
@ -392,6 +392,9 @@ stream.write('With ES6');
|
||||
<!-- YAML
|
||||
added: v0.3.0
|
||||
changes:
|
||||
- version: REPLACEME
|
||||
pr-url: https://github.com/nodejs/node/pull/27685
|
||||
description: Circular references now include a marker to the reference.
|
||||
- version: v12.0.0
|
||||
pr-url: https://github.com/nodejs/node/pull/27109
|
||||
description: The `compact` options default is changed to `3` and the
|
||||
@ -514,6 +517,24 @@ util.inspect(new Bar()); // 'Bar {}'
|
||||
util.inspect(baz); // '[foo] {}'
|
||||
```
|
||||
|
||||
Circular references point to their anchor by using a reference index:
|
||||
|
||||
```js
|
||||
const { inspect } = require('util');
|
||||
|
||||
const obj = {};
|
||||
obj.a = [obj];
|
||||
obj.b = {};
|
||||
obj.b.inner = obj.b;
|
||||
obj.b.obj = obj;
|
||||
|
||||
console.log(inspect(obj));
|
||||
// <ref *1> {
|
||||
// a: [ [Circular *1] ],
|
||||
// b: <ref *2> { inner: [Circular *2], obj: [Circular *1] }
|
||||
// }
|
||||
```
|
||||
|
||||
The following example inspects all properties of the `util` object:
|
||||
|
||||
```js
|
||||
@ -537,8 +558,6 @@ const o = {
|
||||
};
|
||||
console.log(util.inspect(o, { compact: true, depth: 5, breakLength: 80 }));
|
||||
|
||||
// This will print
|
||||
|
||||
// { a:
|
||||
// [ 1,
|
||||
// 2,
|
||||
|
@ -563,8 +563,19 @@ function formatValue(ctx, value, recurseTimes, typedArray) {
|
||||
|
||||
// Using an array here is actually better for the average case than using
|
||||
// a Set. `seen` will only check for the depth and will never grow too large.
|
||||
if (ctx.seen.includes(value))
|
||||
return ctx.stylize('[Circular]', 'special');
|
||||
if (ctx.seen.includes(value)) {
|
||||
let index = 1;
|
||||
if (ctx.circular === undefined) {
|
||||
ctx.circular = new Map([[value, index]]);
|
||||
} else {
|
||||
index = ctx.circular.get(value);
|
||||
if (index === undefined) {
|
||||
index = ctx.circular.size + 1;
|
||||
ctx.circular.set(value, index);
|
||||
}
|
||||
}
|
||||
return ctx.stylize(`[Circular *${index}]`, 'special');
|
||||
}
|
||||
|
||||
return formatRaw(ctx, value, recurseTimes, typedArray);
|
||||
}
|
||||
@ -766,6 +777,18 @@ function formatRaw(ctx, value, recurseTimes, typedArray) {
|
||||
const constructorName = getCtxStyle(value, constructor, tag).slice(0, -1);
|
||||
return handleMaxCallStackSize(ctx, err, constructorName, indentationLvl);
|
||||
}
|
||||
if (ctx.circular !== undefined) {
|
||||
const index = ctx.circular.get(value);
|
||||
if (index !== undefined) {
|
||||
const reference = ctx.stylize(`<ref *${index}>`, 'special');
|
||||
// Add reference always to the very beginning of the output.
|
||||
if (ctx.compact !== true) {
|
||||
base = base === '' ? reference : `${reference} ${base}`;
|
||||
} else {
|
||||
braces[0] = `${reference} ${braces[0]}`;
|
||||
}
|
||||
}
|
||||
}
|
||||
ctx.seen.pop();
|
||||
|
||||
if (ctx.sorted) {
|
||||
|
@ -298,7 +298,8 @@ testAssertionMessage({}, '{}');
|
||||
testAssertionMessage([1, 2, 3], '[\n+ 1,\n+ 2,\n+ 3\n+ ]');
|
||||
testAssertionMessage(function f() {}, '[Function: f]');
|
||||
testAssertionMessage(function() {}, '[Function (anonymous)]');
|
||||
testAssertionMessage(circular, '{\n+ x: [Circular],\n+ y: 1\n+ }');
|
||||
testAssertionMessage(circular,
|
||||
'<ref *1> {\n+ x: [Circular *1],\n+ y: 1\n+ }');
|
||||
testAssertionMessage({ a: undefined, b: null },
|
||||
'{\n+ a: undefined,\n+ b: null\n+ }');
|
||||
testAssertionMessage({ a: NaN, b: Infinity, c: -Infinity },
|
||||
|
@ -195,10 +195,10 @@ assert.strictEqual(
|
||||
'{\n' +
|
||||
' foo: \'bar\',\n' +
|
||||
' foobar: 1,\n' +
|
||||
' func: [Function: func] {\n' +
|
||||
' func: <ref *1> [Function: func] {\n' +
|
||||
' [length]: 0,\n' +
|
||||
' [name]: \'func\',\n' +
|
||||
' [prototype]: func { [constructor]: [Circular] }\n' +
|
||||
' [prototype]: func { [constructor]: [Circular *1] }\n' +
|
||||
' }\n' +
|
||||
'}');
|
||||
assert.strictEqual(
|
||||
@ -208,10 +208,10 @@ assert.strictEqual(
|
||||
' foobar: 1,\n' +
|
||||
' func: [\n' +
|
||||
' {\n' +
|
||||
' a: [Function: a] {\n' +
|
||||
' a: <ref *1> [Function: a] {\n' +
|
||||
' [length]: 0,\n' +
|
||||
' [name]: \'a\',\n' +
|
||||
' [prototype]: a { [constructor]: [Circular] }\n' +
|
||||
' [prototype]: a { [constructor]: [Circular *1] }\n' +
|
||||
' }\n' +
|
||||
' },\n' +
|
||||
' [length]: 1\n' +
|
||||
@ -223,10 +223,10 @@ assert.strictEqual(
|
||||
' foo: \'bar\',\n' +
|
||||
' foobar: {\n' +
|
||||
' foo: \'bar\',\n' +
|
||||
' func: [Function: func] {\n' +
|
||||
' func: <ref *1> [Function: func] {\n' +
|
||||
' [length]: 0,\n' +
|
||||
' [name]: \'func\',\n' +
|
||||
' [prototype]: func { [constructor]: [Circular] }\n' +
|
||||
' [prototype]: func { [constructor]: [Circular *1] }\n' +
|
||||
' }\n' +
|
||||
' }\n' +
|
||||
'}');
|
||||
@ -235,18 +235,18 @@ assert.strictEqual(
|
||||
'{\n' +
|
||||
' foo: \'bar\',\n' +
|
||||
' foobar: 1,\n' +
|
||||
' func: [Function: func] {\n' +
|
||||
' func: <ref *1> [Function: func] {\n' +
|
||||
' [length]: 0,\n' +
|
||||
' [name]: \'func\',\n' +
|
||||
' [prototype]: func { [constructor]: [Circular] }\n' +
|
||||
' [prototype]: func { [constructor]: [Circular *1] }\n' +
|
||||
' }\n' +
|
||||
'} {\n' +
|
||||
' foo: \'bar\',\n' +
|
||||
' foobar: 1,\n' +
|
||||
' func: [Function: func] {\n' +
|
||||
' func: <ref *1> [Function: func] {\n' +
|
||||
' [length]: 0,\n' +
|
||||
' [name]: \'func\',\n' +
|
||||
' [prototype]: func { [constructor]: [Circular] }\n' +
|
||||
' [prototype]: func { [constructor]: [Circular *1] }\n' +
|
||||
' }\n' +
|
||||
'}');
|
||||
assert.strictEqual(
|
||||
@ -254,10 +254,10 @@ assert.strictEqual(
|
||||
'{\n' +
|
||||
' foo: \'bar\',\n' +
|
||||
' foobar: 1,\n' +
|
||||
' func: [Function: func] {\n' +
|
||||
' func: <ref *1> [Function: func] {\n' +
|
||||
' [length]: 0,\n' +
|
||||
' [name]: \'func\',\n' +
|
||||
' [prototype]: func { [constructor]: [Circular] }\n' +
|
||||
' [prototype]: func { [constructor]: [Circular *1] }\n' +
|
||||
' }\n' +
|
||||
'} %o');
|
||||
|
||||
|
@ -338,7 +338,7 @@ assert.strictEqual(
|
||||
|
||||
const value = {};
|
||||
value.a = value;
|
||||
assert.strictEqual(util.inspect(value), '{ a: [Circular] }');
|
||||
assert.strictEqual(util.inspect(value), '<ref *1> { a: [Circular *1] }');
|
||||
}
|
||||
|
||||
// Array with dynamic properties.
|
||||
@ -993,7 +993,7 @@ if (typeof Symbol !== 'undefined') {
|
||||
{
|
||||
const set = new Set();
|
||||
set.add(set);
|
||||
assert.strictEqual(util.inspect(set), 'Set { [Circular] }');
|
||||
assert.strictEqual(util.inspect(set), '<ref *1> Set { [Circular *1] }');
|
||||
}
|
||||
|
||||
// Test Map.
|
||||
@ -1011,12 +1011,32 @@ if (typeof Symbol !== 'undefined') {
|
||||
{
|
||||
const map = new Map();
|
||||
map.set(map, 'map');
|
||||
assert.strictEqual(util.inspect(map), "Map { [Circular] => 'map' }");
|
||||
assert.strictEqual(inspect(map), "<ref *1> Map { [Circular *1] => 'map' }");
|
||||
map.set(map, map);
|
||||
assert.strictEqual(util.inspect(map), 'Map { [Circular] => [Circular] }');
|
||||
assert.strictEqual(
|
||||
inspect(map),
|
||||
'<ref *1> Map { [Circular *1] => [Circular *1] }'
|
||||
);
|
||||
map.delete(map);
|
||||
map.set('map', map);
|
||||
assert.strictEqual(util.inspect(map), "Map { 'map' => [Circular] }");
|
||||
assert.strictEqual(inspect(map), "<ref *1> Map { 'map' => [Circular *1] }");
|
||||
}
|
||||
|
||||
// Test multiple circular references.
|
||||
{
|
||||
const obj = {};
|
||||
obj.a = [obj];
|
||||
obj.b = {};
|
||||
obj.b.inner = obj.b;
|
||||
obj.b.obj = obj;
|
||||
|
||||
assert.strictEqual(
|
||||
inspect(obj),
|
||||
'<ref *1> {\n' +
|
||||
' a: [ [Circular *1] ],\n' +
|
||||
' b: <ref *2> { inner: [Circular *2], obj: [Circular *1] }\n' +
|
||||
'}'
|
||||
);
|
||||
}
|
||||
|
||||
// Test Promise.
|
||||
@ -1214,7 +1234,9 @@ if (typeof Symbol !== 'undefined') {
|
||||
arr[0][0][0] = { a: 2 };
|
||||
assert.strictEqual(util.inspect(arr), '[ [ [ [Object] ] ] ]');
|
||||
arr[0][0][0] = arr;
|
||||
assert.strictEqual(util.inspect(arr), '[ [ [ [Circular] ] ] ]');
|
||||
assert.strictEqual(util.inspect(arr), '<ref *1> [ [ [ [Circular *1] ] ] ]');
|
||||
arr[0][0][0] = arr[0][0];
|
||||
assert.strictEqual(util.inspect(arr), '[ [ <ref *1> [ [Circular *1] ] ] ]');
|
||||
}
|
||||
|
||||
// Corner cases.
|
||||
@ -1608,7 +1630,7 @@ util.inspect(process);
|
||||
' 2,',
|
||||
' [length]: 2',
|
||||
' ]',
|
||||
' } => [Map Iterator] {',
|
||||
' } => <ref *1> [Map Iterator] {',
|
||||
' Uint8Array [',
|
||||
' [BYTES_PER_ELEMENT]: 1,',
|
||||
' [length]: 0,',
|
||||
@ -1619,7 +1641,7 @@ util.inspect(process);
|
||||
' foo: true',
|
||||
' }',
|
||||
' ],',
|
||||
' [Circular]',
|
||||
' [Circular *1]',
|
||||
' },',
|
||||
' [size]: 2',
|
||||
'}'
|
||||
@ -1647,7 +1669,7 @@ util.inspect(process);
|
||||
' [byteOffset]: 0,',
|
||||
' [buffer]: ArrayBuffer { byteLength: 0, foo: true }',
|
||||
' ],',
|
||||
' [Set Iterator] { [ 1, 2, [length]: 2 ] } => [Map Iterator] {',
|
||||
' [Set Iterator] { [ 1, 2, [length]: 2 ] } => <ref *1> [Map Iterator] {',
|
||||
' Uint8Array [',
|
||||
' [BYTES_PER_ELEMENT]: 1,',
|
||||
' [length]: 0,',
|
||||
@ -1655,7 +1677,7 @@ util.inspect(process);
|
||||
' [byteOffset]: 0,',
|
||||
' [buffer]: ArrayBuffer { byteLength: 0, foo: true }',
|
||||
' ],',
|
||||
' [Circular]',
|
||||
' [Circular *1]',
|
||||
' },',
|
||||
' [size]: 2',
|
||||
'}'
|
||||
@ -1687,7 +1709,7 @@ util.inspect(process);
|
||||
' [Set Iterator] {',
|
||||
' [ 1,',
|
||||
' 2,',
|
||||
' [length]: 2 ] } => [Map Iterator] {',
|
||||
' [length]: 2 ] } => <ref *1> [Map Iterator] {',
|
||||
' Uint8Array [',
|
||||
' [BYTES_PER_ELEMENT]: 1,',
|
||||
' [length]: 0,',
|
||||
@ -1696,7 +1718,7 @@ util.inspect(process);
|
||||
' [buffer]: ArrayBuffer {',
|
||||
' byteLength: 0,',
|
||||
' foo: true } ],',
|
||||
' [Circular] },',
|
||||
' [Circular *1] },',
|
||||
' [size]: 2 }'
|
||||
].join('\n');
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user