Skip to content

Commit

Permalink
util: include reference anchor for circular structures
Browse files Browse the repository at this point in the history
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: #27685
Reviewed-By: Anna Henningsen <[email protected]>
Reviewed-By: Benjamin Gruenbaum <[email protected]>
Reviewed-By: Rich Trott <[email protected]>
Reviewed-By: Anto Aravinth <[email protected]>
Reviewed-By: James M Snell <[email protected]>
Reviewed-By: Jeremiah Senkpiel <[email protected]>
  • Loading branch information
BridgeAR committed Sep 25, 2019
1 parent 5605119 commit ced89ad
Show file tree
Hide file tree
Showing 4 changed files with 65 additions and 7 deletions.
20 changes: 18 additions & 2 deletions doc/api/util.md
Original file line number Diff line number Diff line change
Expand Up @@ -520,6 +520,24 @@ util.inspect(new Bar()); // 'Bar {}'
util.inspect(baz); // '[foo] {}'
```

Circular references are marked as `'[Circular]'`:

```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));
// {
// a: [ [Circular] ],
// b: { inner: [Circular], obj: [Circular] }
// }
```

The following example inspects all properties of the `util` object:

```js
Expand All @@ -543,8 +561,6 @@ const o = {
};
console.log(util.inspect(o, { compact: true, depth: 5, breakLength: 80 }));

// This will print

// { a:
// [ 1,
// 2,
Expand Down
24 changes: 23 additions & 1 deletion lib/internal/util/inspect.js
Original file line number Diff line number Diff line change
Expand Up @@ -574,8 +574,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))
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]', 'special');
}

return formatRaw(ctx, value, recurseTimes, typedArray);
}
Expand Down Expand Up @@ -777,6 +788,17 @@ 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) {
// Add reference always to the very beginning of the output.
if (ctx.compact !== true) {
base = base === '' ? '' : `${base}`;
} else {
braces[0] = `${braces[0]}`;
}
}
}
ctx.seen.pop();

if (ctx.sorted) {
Expand Down
3 changes: 2 additions & 1 deletion test/parallel/test-assert.js
Original file line number Diff line number Diff line change
Expand Up @@ -342,7 +342,8 @@ testAssertionMessage(/abc/gim, '/abc/gim');
testAssertionMessage({}, '{}');
testAssertionMessage([1, 2, 3], '[\n+ 1,\n+ 2,\n+ 3\n+ ]');
testAssertionMessage(function f() {}, '[Function: f]');
testAssertionMessage(circular, '{\n+ x: [Circular],\n+ y: 1\n+ }');
testAssertionMessage(circular,
'{\n+ x: [Circular],\n+ y: 1\n+ }');
testAssertionMessage({ a: undefined, b: null },
'{\n+ a: undefined,\n+ b: null\n+ }');
testAssertionMessage({ a: NaN, b: Infinity, c: -Infinity },
Expand Down
25 changes: 22 additions & 3 deletions test/parallel/test-util-inspect.js
Original file line number Diff line number Diff line change
Expand Up @@ -1049,12 +1049,29 @@ if (typeof Symbol !== 'undefined') {
{
const map = new Map();
map.set(map, 'map');
assert.strictEqual(util.inspect(map), "Map { [Circular] => 'map' }");
assert.strictEqual(inspect(map), "Map { [Circular] => 'map' }");
map.set(map, map);
assert.strictEqual(util.inspect(map), 'Map { [Circular] => [Circular] }');
assert.strictEqual(
inspect(map),
'Map { [Circular] => [Circular] }'
);
map.delete(map);
map.set('map', map);
assert.strictEqual(util.inspect(map), "Map { 'map' => [Circular] }");
assert.strictEqual(inspect(map), "Map { 'map' => [Circular] }");
}

// Test multiple circular references.
{
const obj = {};
obj.a = [obj];
obj.b = {};
obj.b.inner = obj.b;
obj.b.obj = obj;

assert.strictEqual(
inspect(obj),
'{ a: [ [Circular] ], b: { inner: [Circular], obj: [Circular] } }'
);
}

// Test Promise.
Expand Down Expand Up @@ -1253,6 +1270,8 @@ if (typeof Symbol !== 'undefined') {
assert.strictEqual(util.inspect(arr), '[ [ [ [Object] ] ] ]');
arr[0][0][0] = arr;
assert.strictEqual(util.inspect(arr), '[ [ [ [Circular] ] ] ]');
arr[0][0][0] = arr[0][0];
assert.strictEqual(util.inspect(arr), '[ [ [ [Circular] ] ] ]');
}

// Corner cases.
Expand Down

0 comments on commit ced89ad

Please sign in to comment.