diff --git a/doc/api/util.md b/doc/api/util.md index e071f1120bf74e..90a0038c66d65e 100644 --- a/doc/api/util.md +++ b/doc/api/util.md @@ -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 @@ -543,8 +561,6 @@ const o = { }; console.log(util.inspect(o, { compact: true, depth: 5, breakLength: 80 })); -// This will print - // { a: // [ 1, // 2, diff --git a/lib/internal/util/inspect.js b/lib/internal/util/inspect.js index 7ad94600b7b771..f63fafd01f89e0 100644 --- a/lib/internal/util/inspect.js +++ b/lib/internal/util/inspect.js @@ -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); } @@ -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) { diff --git a/test/parallel/test-assert.js b/test/parallel/test-assert.js index 61c8f7becead42..b85fdf2ed79b52 100644 --- a/test/parallel/test-assert.js +++ b/test/parallel/test-assert.js @@ -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 }, diff --git a/test/parallel/test-util-inspect.js b/test/parallel/test-util-inspect.js index 27179cf51cb1a7..21e9bb08e0d170 100644 --- a/test/parallel/test-util-inspect.js +++ b/test/parallel/test-util-inspect.js @@ -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. @@ -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.