Skip to content

Commit ced89ad

Browse files
committed
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: #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]>
1 parent 5605119 commit ced89ad

File tree

4 files changed

+65
-7
lines changed

4 files changed

+65
-7
lines changed

doc/api/util.md

+18-2
Original file line numberDiff line numberDiff line change
@@ -520,6 +520,24 @@ util.inspect(new Bar()); // 'Bar {}'
520520
util.inspect(baz); // '[foo] {}'
521521
```
522522

523+
Circular references are marked as `'[Circular]'`:
524+
525+
```js
526+
const { inspect } = require('util');
527+
528+
const obj = {};
529+
obj.a = [obj];
530+
obj.b = {};
531+
obj.b.inner = obj.b;
532+
obj.b.obj = obj;
533+
534+
console.log(inspect(obj));
535+
// {
536+
// a: [ [Circular] ],
537+
// b: { inner: [Circular], obj: [Circular] }
538+
// }
539+
```
540+
523541
The following example inspects all properties of the `util` object:
524542

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

546-
// This will print
547-
548564
// { a:
549565
// [ 1,
550566
// 2,

lib/internal/util/inspect.js

+23-1
Original file line numberDiff line numberDiff line change
@@ -574,8 +574,19 @@ function formatValue(ctx, value, recurseTimes, typedArray) {
574574

575575
// Using an array here is actually better for the average case than using
576576
// a Set. `seen` will only check for the depth and will never grow too large.
577-
if (ctx.seen.includes(value))
577+
if (ctx.seen.includes(value)) {
578+
let index = 1;
579+
if (ctx.circular === undefined) {
580+
ctx.circular = new Map([[value, index]]);
581+
} else {
582+
index = ctx.circular.get(value);
583+
if (index === undefined) {
584+
index = ctx.circular.size + 1;
585+
ctx.circular.set(value, index);
586+
}
587+
}
578588
return ctx.stylize('[Circular]', 'special');
589+
}
579590

580591
return formatRaw(ctx, value, recurseTimes, typedArray);
581592
}
@@ -777,6 +788,17 @@ function formatRaw(ctx, value, recurseTimes, typedArray) {
777788
const constructorName = getCtxStyle(value, constructor, tag).slice(0, -1);
778789
return handleMaxCallStackSize(ctx, err, constructorName, indentationLvl);
779790
}
791+
if (ctx.circular !== undefined) {
792+
const index = ctx.circular.get(value);
793+
if (index !== undefined) {
794+
// Add reference always to the very beginning of the output.
795+
if (ctx.compact !== true) {
796+
base = base === '' ? '' : `${base}`;
797+
} else {
798+
braces[0] = `${braces[0]}`;
799+
}
800+
}
801+
}
780802
ctx.seen.pop();
781803

782804
if (ctx.sorted) {

test/parallel/test-assert.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -342,7 +342,8 @@ testAssertionMessage(/abc/gim, '/abc/gim');
342342
testAssertionMessage({}, '{}');
343343
testAssertionMessage([1, 2, 3], '[\n+ 1,\n+ 2,\n+ 3\n+ ]');
344344
testAssertionMessage(function f() {}, '[Function: f]');
345-
testAssertionMessage(circular, '{\n+ x: [Circular],\n+ y: 1\n+ }');
345+
testAssertionMessage(circular,
346+
'{\n+ x: [Circular],\n+ y: 1\n+ }');
346347
testAssertionMessage({ a: undefined, b: null },
347348
'{\n+ a: undefined,\n+ b: null\n+ }');
348349
testAssertionMessage({ a: NaN, b: Infinity, c: -Infinity },

test/parallel/test-util-inspect.js

+22-3
Original file line numberDiff line numberDiff line change
@@ -1049,12 +1049,29 @@ if (typeof Symbol !== 'undefined') {
10491049
{
10501050
const map = new Map();
10511051
map.set(map, 'map');
1052-
assert.strictEqual(util.inspect(map), "Map { [Circular] => 'map' }");
1052+
assert.strictEqual(inspect(map), "Map { [Circular] => 'map' }");
10531053
map.set(map, map);
1054-
assert.strictEqual(util.inspect(map), 'Map { [Circular] => [Circular] }');
1054+
assert.strictEqual(
1055+
inspect(map),
1056+
'Map { [Circular] => [Circular] }'
1057+
);
10551058
map.delete(map);
10561059
map.set('map', map);
1057-
assert.strictEqual(util.inspect(map), "Map { 'map' => [Circular] }");
1060+
assert.strictEqual(inspect(map), "Map { 'map' => [Circular] }");
1061+
}
1062+
1063+
// Test multiple circular references.
1064+
{
1065+
const obj = {};
1066+
obj.a = [obj];
1067+
obj.b = {};
1068+
obj.b.inner = obj.b;
1069+
obj.b.obj = obj;
1070+
1071+
assert.strictEqual(
1072+
inspect(obj),
1073+
'{ a: [ [Circular] ], b: { inner: [Circular], obj: [Circular] } }'
1074+
);
10581075
}
10591076

10601077
// Test Promise.
@@ -1253,6 +1270,8 @@ if (typeof Symbol !== 'undefined') {
12531270
assert.strictEqual(util.inspect(arr), '[ [ [ [Object] ] ] ]');
12541271
arr[0][0][0] = arr;
12551272
assert.strictEqual(util.inspect(arr), '[ [ [ [Circular] ] ] ]');
1273+
arr[0][0][0] = arr[0][0];
1274+
assert.strictEqual(util.inspect(arr), '[ [ [ [Circular] ] ] ]');
12561275
}
12571276

12581277
// Corner cases.

0 commit comments

Comments
 (0)