Skip to content

Commit 68dd666

Browse files
committed
Avoid stack size limit by moving to an iterator approach
1 parent 08779a0 commit 68dd666

File tree

2 files changed

+60
-29
lines changed

2 files changed

+60
-29
lines changed

src/validation/__tests__/OverlappingFieldsCanBeMergedRule-test.ts

+22
Original file line numberDiff line numberDiff line change
@@ -1138,4 +1138,26 @@ describe('Validate: Overlapping fields can be merged', () => {
11381138
},
11391139
]);
11401140
});
1141+
1142+
it('does not hit stack size limits', () => {
1143+
const n = 10000;
1144+
const fragments = Array.from(Array(n).keys()).reduce((fragments, next) => {
1145+
return fragments.concat(`\n
1146+
fragment X${next + 1} on Query {
1147+
...X${next}
1148+
}
1149+
`);
1150+
}, '');
1151+
1152+
const query = `
1153+
query Test {
1154+
...X${n}
1155+
}
1156+
${fragments}
1157+
fragment X0 on Query {
1158+
__typename
1159+
}
1160+
`;
1161+
expectErrors(query).toDeepEqual([])
1162+
})
11411163
});

src/validation/rules/OverlappingFieldsCanBeMergedRule.ts

+38-29
Original file line numberDiff line numberDiff line change
@@ -192,6 +192,7 @@ function findConflictsWithinSelectionSet(
192192
);
193193

194194
if (fragmentNames.length !== 0) {
195+
const discoveredFragments: Array<[string, string]> = [];
195196
// (B) Then collect conflicts between these fields and those represented by
196197
// each spread fragment name found.
197198
for (let i = 0; i < fragmentNames.length; i++) {
@@ -203,7 +204,40 @@ function findConflictsWithinSelectionSet(
203204
false,
204205
fieldMap,
205206
fragmentNames[i],
207+
discoveredFragments
206208
);
209+
210+
// (E) Then collect any conflicts between the provided collection of fields
211+
// and any fragment names found in the given fragment.
212+
while (discoveredFragments.length !== 0) {
213+
const item = discoveredFragments.pop();
214+
if (
215+
!item ||
216+
comparedFragmentPairs.has(
217+
item[1],
218+
item[0],
219+
false,
220+
)
221+
) {
222+
continue;
223+
}
224+
const [fragmentName, referencedFragmentName] = item;
225+
comparedFragmentPairs.add(
226+
referencedFragmentName,
227+
fragmentName,
228+
false,
229+
);
230+
collectConflictsBetweenFieldsAndFragment(
231+
context,
232+
conflicts,
233+
cachedFieldsAndFragmentNames,
234+
comparedFragmentPairs,
235+
false,
236+
fieldMap,
237+
fragmentName,
238+
discoveredFragments
239+
);
240+
}
207241
// (C) Then compare this fragment with all other fragments found in this
208242
// selection set to collect conflicts between fragments spread together.
209243
// This compares each item in the list of fragment names to every other
@@ -234,6 +268,7 @@ function collectConflictsBetweenFieldsAndFragment(
234268
areMutuallyExclusive: boolean,
235269
fieldMap: NodeAndDefCollection,
236270
fragmentName: string,
271+
discoveredFragments: Array<Array<string>>,
237272
): void {
238273
const fragment = context.getFragment(fragmentName);
239274
if (!fragment) {
@@ -264,35 +299,7 @@ function collectConflictsBetweenFieldsAndFragment(
264299
fieldMap2,
265300
);
266301

267-
// (E) Then collect any conflicts between the provided collection of fields
268-
// and any fragment names found in the given fragment.
269-
for (const referencedFragmentName of referencedFragmentNames) {
270-
// Memoize so two fragments are not compared for conflicts more than once.
271-
if (
272-
comparedFragmentPairs.has(
273-
referencedFragmentName,
274-
fragmentName,
275-
areMutuallyExclusive,
276-
)
277-
) {
278-
continue;
279-
}
280-
comparedFragmentPairs.add(
281-
referencedFragmentName,
282-
fragmentName,
283-
areMutuallyExclusive,
284-
);
285-
286-
collectConflictsBetweenFieldsAndFragment(
287-
context,
288-
conflicts,
289-
cachedFieldsAndFragmentNames,
290-
comparedFragmentPairs,
291-
areMutuallyExclusive,
292-
fieldMap,
293-
referencedFragmentName,
294-
);
295-
}
302+
discoveredFragments.push(...referencedFragmentNames.map(referencedFragmentName => [fragmentName, referencedFragmentName]));
296303
}
297304

298305
// Collect all conflicts found between two fragments, including via spreading in
@@ -433,6 +440,7 @@ function findConflictsBetweenSubSelectionSets(
433440
areMutuallyExclusive,
434441
fieldMap1,
435442
fragmentName2,
443+
[]
436444
);
437445
}
438446

@@ -447,6 +455,7 @@ function findConflictsBetweenSubSelectionSets(
447455
areMutuallyExclusive,
448456
fieldMap2,
449457
fragmentName1,
458+
[]
450459
);
451460
}
452461

0 commit comments

Comments
 (0)