Skip to content

Commit 15f7181

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

File tree

2 files changed

+54
-27
lines changed

2 files changed

+54
-27
lines changed

Diff for: src/validation/__tests__/OverlappingFieldsCanBeMergedRule-test.ts

+24
Original file line numberDiff line numberDiff line change
@@ -1138,4 +1138,28 @@ 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(
1145+
(acc, next) =>
1146+
acc.concat(`\n
1147+
fragment X${next + 1} on Query {
1148+
...X${next}
1149+
}
1150+
`),
1151+
'',
1152+
);
1153+
1154+
const query = `
1155+
query Test {
1156+
...X${n}
1157+
}
1158+
${fragments}
1159+
fragment X0 on Query {
1160+
__typename
1161+
}
1162+
`;
1163+
expectErrors(query).toDeepEqual([]);
1164+
});
11411165
});

Diff for: src/validation/rules/OverlappingFieldsCanBeMergedRule.ts

+30-27
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,29 @@ 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 (!item || comparedFragmentPairs.has(item[1], item[0], false)) {
215+
continue;
216+
}
217+
const [fragmentName, referencedFragmentName] = item;
218+
comparedFragmentPairs.add(referencedFragmentName, fragmentName, false);
219+
collectConflictsBetweenFieldsAndFragment(
220+
context,
221+
conflicts,
222+
cachedFieldsAndFragmentNames,
223+
comparedFragmentPairs,
224+
false,
225+
fieldMap,
226+
fragmentName,
227+
discoveredFragments,
228+
);
229+
}
207230
// (C) Then compare this fragment with all other fragments found in this
208231
// selection set to collect conflicts between fragments spread together.
209232
// This compares each item in the list of fragment names to every other
@@ -234,6 +257,7 @@ function collectConflictsBetweenFieldsAndFragment(
234257
areMutuallyExclusive: boolean,
235258
fieldMap: NodeAndDefCollection,
236259
fragmentName: string,
260+
discoveredFragments: Array<Array<string>>,
237261
): void {
238262
const fragment = context.getFragment(fragmentName);
239263
if (!fragment) {
@@ -264,35 +288,12 @@ function collectConflictsBetweenFieldsAndFragment(
264288
fieldMap2,
265289
);
266290

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,
291+
discoveredFragments.push(
292+
...referencedFragmentNames.map((referencedFragmentName) => [
282293
fragmentName,
283-
areMutuallyExclusive,
284-
);
285-
286-
collectConflictsBetweenFieldsAndFragment(
287-
context,
288-
conflicts,
289-
cachedFieldsAndFragmentNames,
290-
comparedFragmentPairs,
291-
areMutuallyExclusive,
292-
fieldMap,
293294
referencedFragmentName,
294-
);
295-
}
295+
]),
296+
);
296297
}
297298

298299
// Collect all conflicts found between two fragments, including via spreading in
@@ -433,6 +434,7 @@ function findConflictsBetweenSubSelectionSets(
433434
areMutuallyExclusive,
434435
fieldMap1,
435436
fragmentName2,
437+
[],
436438
);
437439
}
438440

@@ -447,6 +449,7 @@ function findConflictsBetweenSubSelectionSets(
447449
areMutuallyExclusive,
448450
fieldMap2,
449451
fragmentName1,
452+
[],
450453
);
451454
}
452455

0 commit comments

Comments
 (0)