@@ -15,14 +15,36 @@ export function MaxIntrospectionDepthRule(
15
15
* Counts the depth of list fields in "__Type" recursively and
16
16
* returns `true` if the limit has been reached.
17
17
*/
18
- function checkDepth ( node : ASTNode , depth : number = 0 ) : boolean {
18
+ function checkDepth (
19
+ node : ASTNode ,
20
+ visitedFragments : {
21
+ [ fragmentName : string ] : true | undefined ;
22
+ } = Object . create ( null ) ,
23
+ depth : number = 0 ,
24
+ ) : boolean {
19
25
if ( node . kind === Kind . FRAGMENT_SPREAD ) {
20
- const fragment = context . getFragment ( node . name . value ) ;
26
+ const fragmentName = node . name . value ;
27
+ if ( visitedFragments [ fragmentName ] ) {
28
+ // Fragment cycles are handled by `NoFragmentCyclesRule`.
29
+ return false ;
30
+ }
31
+ const fragment = context . getFragment ( fragmentName ) ;
21
32
if ( ! fragment ) {
22
- // missing fragments checks are handled by the `KnownFragmentNamesRule`
33
+ // Missing fragments checks are handled by the `KnownFragmentNamesRule`.
23
34
return false ;
24
35
}
25
- return checkDepth ( fragment , depth ) ;
36
+
37
+ // Rather than following an immutable programming pattern which has
38
+ // significant memory and garbage collection overhead, we've opted to
39
+ // take a mutable approach for efficiency's sake. Importantly visiting a
40
+ // fragment twice is fine, so long as you don't do one visit inside the
41
+ // other.
42
+ try {
43
+ visitedFragments [ fragmentName ] = true ;
44
+ return checkDepth ( fragment , visitedFragments , depth ) ;
45
+ } finally {
46
+ visitedFragments [ fragmentName ] = undefined ;
47
+ }
26
48
}
27
49
28
50
if (
@@ -43,7 +65,7 @@ export function MaxIntrospectionDepthRule(
43
65
// handles fields and inline fragments
44
66
if ( 'selectionSet' in node && node . selectionSet ) {
45
67
for ( const child of node . selectionSet . selections ) {
46
- if ( checkDepth ( child , depth ) ) {
68
+ if ( checkDepth ( child , visitedFragments , depth ) ) {
47
69
return true ;
48
70
}
49
71
}
0 commit comments