Skip to content

Commit 2700388

Browse files
committed
inspect: detect circular objects
1 parent 8f2c383 commit 2700388

File tree

2 files changed

+59
-29
lines changed

2 files changed

+59
-29
lines changed

src/jsutils/__tests__/inspect-test.js

+21
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,27 @@ describe('inspect', () => {
8686
expect(inspect(map)).to.equal('{ a: true, b: null }');
8787
});
8888

89+
it('detect circular objects', () => {
90+
const obj = {};
91+
obj.self = obj;
92+
obj.deepSelf = { self: obj };
93+
94+
expect(inspect(obj)).to.equal(
95+
'{ self: [Circular], deepSelf: { self: [Circular] } }',
96+
);
97+
98+
const array = [];
99+
array[0] = array;
100+
array[1] = [array];
101+
102+
expect(inspect(array)).to.equal('[[Circular], [[Circular]]]');
103+
104+
const mixed = { array: [] };
105+
mixed.array[0] = mixed;
106+
107+
expect(inspect(mixed)).to.equal('{ array: [[Circular]] }');
108+
});
109+
89110
it('custom inspect', () => {
90111
const object = {
91112
inspect() {

src/jsutils/inspect.js

+38-29
Original file line numberDiff line numberDiff line change
@@ -16,66 +16,75 @@ const MAX_RECURSIVE_DEPTH = 2;
1616
* Used to print values in error messages.
1717
*/
1818
export default function inspect(value: mixed): string {
19-
return formatValue(value, 0);
19+
return formatValue(value, []);
2020
}
2121

22-
function formatValue(value, recurseTimes) {
22+
function formatValue(value, seenValues) {
2323
switch (typeof value) {
2424
case 'string':
2525
return JSON.stringify(value);
2626
case 'function':
2727
return value.name ? `[function ${value.name}]` : '[function]';
2828
case 'object':
29-
if (value) {
30-
const customInspectFn = getCustomFn(value);
31-
32-
if (customInspectFn) {
33-
// $FlowFixMe(>=0.90.0)
34-
const customValue = customInspectFn.call(value);
35-
36-
// check for infinite recursion
37-
if (customValue !== value) {
38-
return typeof customValue === 'string'
39-
? customValue
40-
: formatValue(customValue, recurseTimes);
41-
}
42-
} else if (Array.isArray(value)) {
43-
return formatArray(value, recurseTimes);
44-
}
45-
46-
return formatObject(value, recurseTimes);
47-
}
48-
49-
return String(value);
29+
return formatObjectValue(value, seenValues);
5030
default:
5131
return String(value);
5232
}
5333
}
5434

55-
function formatObject(object, recurseTimes) {
35+
function formatObjectValue(value, previouslySeenValues) {
36+
if (previouslySeenValues.indexOf(value) !== -1) {
37+
return '[Circular]';
38+
}
39+
const seenValues = [...previouslySeenValues, value];
40+
41+
if (value) {
42+
const customInspectFn = getCustomFn(value);
43+
44+
if (customInspectFn) {
45+
// $FlowFixMe(>=0.90.0)
46+
const customValue = customInspectFn.call(value);
47+
48+
// check for infinite recursion
49+
if (customValue !== value) {
50+
return typeof customValue === 'string'
51+
? customValue
52+
: formatValue(customValue, seenValues);
53+
}
54+
} else if (Array.isArray(value)) {
55+
return formatArray(value, seenValues);
56+
}
57+
58+
return formatObject(value, seenValues);
59+
}
60+
61+
return String(value);
62+
}
63+
64+
function formatObject(object, seenValues) {
5665
const keys = Object.keys(object);
5766
if (keys.length === 0) {
5867
return '{}';
5968
}
6069

61-
if (recurseTimes === MAX_RECURSIVE_DEPTH) {
70+
if (seenValues.length > MAX_RECURSIVE_DEPTH) {
6271
return '[' + getObjectTag(object) + ']';
6372
}
6473

6574
const properties = keys.map(key => {
66-
const value = formatValue(object[key], recurseTimes + 1);
75+
const value = formatValue(object[key], seenValues);
6776
return key + ': ' + value;
6877
});
6978

7079
return '{ ' + properties.join(', ') + ' }';
7180
}
7281

73-
function formatArray(array, recurseTimes) {
82+
function formatArray(array, seenValues) {
7483
if (array.length === 0) {
7584
return '[]';
7685
}
7786

78-
if (recurseTimes === MAX_RECURSIVE_DEPTH) {
87+
if (seenValues.length > MAX_RECURSIVE_DEPTH) {
7988
return '[Array]';
8089
}
8190

@@ -84,7 +93,7 @@ function formatArray(array, recurseTimes) {
8493
const items = [];
8594

8695
for (let i = 0; i < len; ++i) {
87-
items.push(formatValue(array[i], recurseTimes + 1));
96+
items.push(formatValue(array[i], seenValues));
8897
}
8998

9099
if (remaining === 1) {

0 commit comments

Comments
 (0)