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
|
<!-- YAML
|
||||||
added: v0.3.0
|
added: v0.3.0
|
||||||
changes:
|
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
|
- version: v12.0.0
|
||||||
pr-url: https://github.com/nodejs/node/pull/27109
|
pr-url: https://github.com/nodejs/node/pull/27109
|
||||||
description: The `compact` options default is changed to `3` and the
|
description: The `compact` options default is changed to `3` and the
|
||||||
@ -514,6 +517,24 @@ util.inspect(new Bar()); // 'Bar {}'
|
|||||||
util.inspect(baz); // '[foo] {}'
|
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:
|
The following example inspects all properties of the `util` object:
|
||||||
|
|
||||||
```js
|
```js
|
||||||
@ -537,8 +558,6 @@ const o = {
|
|||||||
};
|
};
|
||||||
console.log(util.inspect(o, { compact: true, depth: 5, breakLength: 80 }));
|
console.log(util.inspect(o, { compact: true, depth: 5, breakLength: 80 }));
|
||||||
|
|
||||||
// This will print
|
|
||||||
|
|
||||||
// { a:
|
// { a:
|
||||||
// [ 1,
|
// [ 1,
|
||||||
// 2,
|
// 2,
|
||||||
|
@ -563,8 +563,19 @@ function formatValue(ctx, value, recurseTimes, typedArray) {
|
|||||||
|
|
||||||
// Using an array here is actually better for the average case than using
|
// 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.
|
// a Set. `seen` will only check for the depth and will never grow too large.
|
||||||
if (ctx.seen.includes(value))
|
if (ctx.seen.includes(value)) {
|
||||||
return ctx.stylize('[Circular]', 'special');
|
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);
|
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);
|
const constructorName = getCtxStyle(value, constructor, tag).slice(0, -1);
|
||||||
return handleMaxCallStackSize(ctx, err, constructorName, indentationLvl);
|
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();
|
ctx.seen.pop();
|
||||||
|
|
||||||
if (ctx.sorted) {
|
if (ctx.sorted) {
|
||||||
|
@ -298,7 +298,8 @@ testAssertionMessage({}, '{}');
|
|||||||
testAssertionMessage([1, 2, 3], '[\n+ 1,\n+ 2,\n+ 3\n+ ]');
|
testAssertionMessage([1, 2, 3], '[\n+ 1,\n+ 2,\n+ 3\n+ ]');
|
||||||
testAssertionMessage(function f() {}, '[Function: f]');
|
testAssertionMessage(function f() {}, '[Function: f]');
|
||||||
testAssertionMessage(function() {}, '[Function (anonymous)]');
|
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 },
|
testAssertionMessage({ a: undefined, b: null },
|
||||||
'{\n+ a: undefined,\n+ b: null\n+ }');
|
'{\n+ a: undefined,\n+ b: null\n+ }');
|
||||||
testAssertionMessage({ a: NaN, b: Infinity, c: -Infinity },
|
testAssertionMessage({ a: NaN, b: Infinity, c: -Infinity },
|
||||||
|
@ -195,10 +195,10 @@ assert.strictEqual(
|
|||||||
'{\n' +
|
'{\n' +
|
||||||
' foo: \'bar\',\n' +
|
' foo: \'bar\',\n' +
|
||||||
' foobar: 1,\n' +
|
' foobar: 1,\n' +
|
||||||
' func: [Function: func] {\n' +
|
' func: <ref *1> [Function: func] {\n' +
|
||||||
' [length]: 0,\n' +
|
' [length]: 0,\n' +
|
||||||
' [name]: \'func\',\n' +
|
' [name]: \'func\',\n' +
|
||||||
' [prototype]: func { [constructor]: [Circular] }\n' +
|
' [prototype]: func { [constructor]: [Circular *1] }\n' +
|
||||||
' }\n' +
|
' }\n' +
|
||||||
'}');
|
'}');
|
||||||
assert.strictEqual(
|
assert.strictEqual(
|
||||||
@ -208,10 +208,10 @@ assert.strictEqual(
|
|||||||
' foobar: 1,\n' +
|
' foobar: 1,\n' +
|
||||||
' func: [\n' +
|
' func: [\n' +
|
||||||
' {\n' +
|
' {\n' +
|
||||||
' a: [Function: a] {\n' +
|
' a: <ref *1> [Function: a] {\n' +
|
||||||
' [length]: 0,\n' +
|
' [length]: 0,\n' +
|
||||||
' [name]: \'a\',\n' +
|
' [name]: \'a\',\n' +
|
||||||
' [prototype]: a { [constructor]: [Circular] }\n' +
|
' [prototype]: a { [constructor]: [Circular *1] }\n' +
|
||||||
' }\n' +
|
' }\n' +
|
||||||
' },\n' +
|
' },\n' +
|
||||||
' [length]: 1\n' +
|
' [length]: 1\n' +
|
||||||
@ -223,10 +223,10 @@ assert.strictEqual(
|
|||||||
' foo: \'bar\',\n' +
|
' foo: \'bar\',\n' +
|
||||||
' foobar: {\n' +
|
' foobar: {\n' +
|
||||||
' foo: \'bar\',\n' +
|
' foo: \'bar\',\n' +
|
||||||
' func: [Function: func] {\n' +
|
' func: <ref *1> [Function: func] {\n' +
|
||||||
' [length]: 0,\n' +
|
' [length]: 0,\n' +
|
||||||
' [name]: \'func\',\n' +
|
' [name]: \'func\',\n' +
|
||||||
' [prototype]: func { [constructor]: [Circular] }\n' +
|
' [prototype]: func { [constructor]: [Circular *1] }\n' +
|
||||||
' }\n' +
|
' }\n' +
|
||||||
' }\n' +
|
' }\n' +
|
||||||
'}');
|
'}');
|
||||||
@ -235,18 +235,18 @@ assert.strictEqual(
|
|||||||
'{\n' +
|
'{\n' +
|
||||||
' foo: \'bar\',\n' +
|
' foo: \'bar\',\n' +
|
||||||
' foobar: 1,\n' +
|
' foobar: 1,\n' +
|
||||||
' func: [Function: func] {\n' +
|
' func: <ref *1> [Function: func] {\n' +
|
||||||
' [length]: 0,\n' +
|
' [length]: 0,\n' +
|
||||||
' [name]: \'func\',\n' +
|
' [name]: \'func\',\n' +
|
||||||
' [prototype]: func { [constructor]: [Circular] }\n' +
|
' [prototype]: func { [constructor]: [Circular *1] }\n' +
|
||||||
' }\n' +
|
' }\n' +
|
||||||
'} {\n' +
|
'} {\n' +
|
||||||
' foo: \'bar\',\n' +
|
' foo: \'bar\',\n' +
|
||||||
' foobar: 1,\n' +
|
' foobar: 1,\n' +
|
||||||
' func: [Function: func] {\n' +
|
' func: <ref *1> [Function: func] {\n' +
|
||||||
' [length]: 0,\n' +
|
' [length]: 0,\n' +
|
||||||
' [name]: \'func\',\n' +
|
' [name]: \'func\',\n' +
|
||||||
' [prototype]: func { [constructor]: [Circular] }\n' +
|
' [prototype]: func { [constructor]: [Circular *1] }\n' +
|
||||||
' }\n' +
|
' }\n' +
|
||||||
'}');
|
'}');
|
||||||
assert.strictEqual(
|
assert.strictEqual(
|
||||||
@ -254,10 +254,10 @@ assert.strictEqual(
|
|||||||
'{\n' +
|
'{\n' +
|
||||||
' foo: \'bar\',\n' +
|
' foo: \'bar\',\n' +
|
||||||
' foobar: 1,\n' +
|
' foobar: 1,\n' +
|
||||||
' func: [Function: func] {\n' +
|
' func: <ref *1> [Function: func] {\n' +
|
||||||
' [length]: 0,\n' +
|
' [length]: 0,\n' +
|
||||||
' [name]: \'func\',\n' +
|
' [name]: \'func\',\n' +
|
||||||
' [prototype]: func { [constructor]: [Circular] }\n' +
|
' [prototype]: func { [constructor]: [Circular *1] }\n' +
|
||||||
' }\n' +
|
' }\n' +
|
||||||
'} %o');
|
'} %o');
|
||||||
|
|
||||||
|
@ -338,7 +338,7 @@ assert.strictEqual(
|
|||||||
|
|
||||||
const value = {};
|
const value = {};
|
||||||
value.a = 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.
|
// Array with dynamic properties.
|
||||||
@ -993,7 +993,7 @@ if (typeof Symbol !== 'undefined') {
|
|||||||
{
|
{
|
||||||
const set = new Set();
|
const set = new Set();
|
||||||
set.add(set);
|
set.add(set);
|
||||||
assert.strictEqual(util.inspect(set), 'Set { [Circular] }');
|
assert.strictEqual(util.inspect(set), '<ref *1> Set { [Circular *1] }');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Test Map.
|
// Test Map.
|
||||||
@ -1011,12 +1011,32 @@ if (typeof Symbol !== 'undefined') {
|
|||||||
{
|
{
|
||||||
const map = new Map();
|
const map = new Map();
|
||||||
map.set(map, '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);
|
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.delete(map);
|
||||||
map.set('map', 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.
|
// Test Promise.
|
||||||
@ -1214,7 +1234,9 @@ if (typeof Symbol !== 'undefined') {
|
|||||||
arr[0][0][0] = { a: 2 };
|
arr[0][0][0] = { a: 2 };
|
||||||
assert.strictEqual(util.inspect(arr), '[ [ [ [Object] ] ] ]');
|
assert.strictEqual(util.inspect(arr), '[ [ [ [Object] ] ] ]');
|
||||||
arr[0][0][0] = arr;
|
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.
|
// Corner cases.
|
||||||
@ -1608,7 +1630,7 @@ util.inspect(process);
|
|||||||
' 2,',
|
' 2,',
|
||||||
' [length]: 2',
|
' [length]: 2',
|
||||||
' ]',
|
' ]',
|
||||||
' } => [Map Iterator] {',
|
' } => <ref *1> [Map Iterator] {',
|
||||||
' Uint8Array [',
|
' Uint8Array [',
|
||||||
' [BYTES_PER_ELEMENT]: 1,',
|
' [BYTES_PER_ELEMENT]: 1,',
|
||||||
' [length]: 0,',
|
' [length]: 0,',
|
||||||
@ -1619,7 +1641,7 @@ util.inspect(process);
|
|||||||
' foo: true',
|
' foo: true',
|
||||||
' }',
|
' }',
|
||||||
' ],',
|
' ],',
|
||||||
' [Circular]',
|
' [Circular *1]',
|
||||||
' },',
|
' },',
|
||||||
' [size]: 2',
|
' [size]: 2',
|
||||||
'}'
|
'}'
|
||||||
@ -1647,7 +1669,7 @@ util.inspect(process);
|
|||||||
' [byteOffset]: 0,',
|
' [byteOffset]: 0,',
|
||||||
' [buffer]: ArrayBuffer { byteLength: 0, foo: true }',
|
' [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 [',
|
' Uint8Array [',
|
||||||
' [BYTES_PER_ELEMENT]: 1,',
|
' [BYTES_PER_ELEMENT]: 1,',
|
||||||
' [length]: 0,',
|
' [length]: 0,',
|
||||||
@ -1655,7 +1677,7 @@ util.inspect(process);
|
|||||||
' [byteOffset]: 0,',
|
' [byteOffset]: 0,',
|
||||||
' [buffer]: ArrayBuffer { byteLength: 0, foo: true }',
|
' [buffer]: ArrayBuffer { byteLength: 0, foo: true }',
|
||||||
' ],',
|
' ],',
|
||||||
' [Circular]',
|
' [Circular *1]',
|
||||||
' },',
|
' },',
|
||||||
' [size]: 2',
|
' [size]: 2',
|
||||||
'}'
|
'}'
|
||||||
@ -1687,7 +1709,7 @@ util.inspect(process);
|
|||||||
' [Set Iterator] {',
|
' [Set Iterator] {',
|
||||||
' [ 1,',
|
' [ 1,',
|
||||||
' 2,',
|
' 2,',
|
||||||
' [length]: 2 ] } => [Map Iterator] {',
|
' [length]: 2 ] } => <ref *1> [Map Iterator] {',
|
||||||
' Uint8Array [',
|
' Uint8Array [',
|
||||||
' [BYTES_PER_ELEMENT]: 1,',
|
' [BYTES_PER_ELEMENT]: 1,',
|
||||||
' [length]: 0,',
|
' [length]: 0,',
|
||||||
@ -1696,7 +1718,7 @@ util.inspect(process);
|
|||||||
' [buffer]: ArrayBuffer {',
|
' [buffer]: ArrayBuffer {',
|
||||||
' byteLength: 0,',
|
' byteLength: 0,',
|
||||||
' foo: true } ],',
|
' foo: true } ],',
|
||||||
' [Circular] },',
|
' [Circular *1] },',
|
||||||
' [size]: 2 }'
|
' [size]: 2 }'
|
||||||
].join('\n');
|
].join('\n');
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user