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 <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>
1 parent 5605119 commit ced89ad

4 files changed

Lines changed: 65 additions & 7 deletions

File tree

‎doc/api/util.md‎

Lines changed: 18 additions & 2 deletions
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+
constobj= {};
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‎

Lines changed: 23 additions & 1 deletion
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+
letindex=1;
579+
if(ctx.circular===undefined){
580+
ctx.circular=newMap([[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
returnctx.stylize('[Circular]','special');
589+
}
579590

580591
returnformatRaw(ctx,value,recurseTimes,typedArray);
581592
}
@@ -777,6 +788,17 @@ function formatRaw(ctx, value, recurseTimes, typedArray) {
777788
constconstructorName=getCtxStyle(value,constructor,tag).slice(0,-1);
778789
returnhandleMaxCallStackSize(ctx,err,constructorName,indentationLvl);
779790
}
791+
if(ctx.circular!==undefined){
792+
constindex=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‎

Lines changed: 2 additions & 1 deletion
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(functionf(){},'[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‎

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1049,12 +1049,29 @@ if (typeof Symbol !== 'undefined') {
10491049
{
10501050
constmap=newMap();
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+
constobj={};
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)