Skip to content

Commit eef8d97

Browse files
committed
[Validation] Factor out and memoize recursively referenced fragments.
This adds a new method to the validation context which when given an Operation definition, returns a list of all Fragment definitions recursively referenced via fragment spreads. This new method is then used to ensure no fragments are unused. The implementation of this method is the same in principle as the one which used to be inline in the validation rule, but has been unfolded from recursion to use a while loop.
1 parent 568dc52 commit eef8d97

File tree

2 files changed

+42
-18
lines changed

2 files changed

+42
-18
lines changed

src/validation/rules/NoUnusedFragments.js

Lines changed: 11 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -23,34 +23,27 @@ export function unusedFragMessage(fragName: any): string {
2323
* within operations, or spread within other fragments spread within operations.
2424
*/
2525
export function NoUnusedFragments(context: ValidationContext): any {
26-
var spreadsWithinOperation = [];
26+
var operationDefs = [];
2727
var fragmentDefs = [];
2828

2929
return {
3030
OperationDefinition(node) {
31-
spreadsWithinOperation.push(context.getFragmentSpreads(node));
31+
operationDefs.push(node);
3232
return false;
3333
},
34-
FragmentDefinition(def) {
35-
fragmentDefs.push(def);
34+
FragmentDefinition(node) {
35+
fragmentDefs.push(node);
3636
return false;
3737
},
3838
Document: {
3939
leave() {
40-
var fragmentNameUsed = {};
41-
var reduceSpreadFragments = function (spreads) {
42-
spreads.forEach(spread => {
43-
const fragName = spread.name.value;
44-
if (fragmentNameUsed[fragName] !== true) {
45-
fragmentNameUsed[fragName] = true;
46-
const fragment = context.getFragment(fragName);
47-
if (fragment) {
48-
reduceSpreadFragments(context.getFragmentSpreads(fragment));
49-
}
50-
}
51-
});
52-
};
53-
spreadsWithinOperation.forEach(reduceSpreadFragments);
40+
const fragmentNameUsed = Object.create(null);
41+
operationDefs.forEach(operation => {
42+
context.getRecursivelyReferencedFragments(operation).forEach(
43+
fragment => { fragmentNameUsed[fragment.name.value] = true; }
44+
);
45+
});
46+
5447
var errors = fragmentDefs
5548
.filter(def => fragmentNameUsed[def.name.value] !== true)
5649
.map(def => new GraphQLError(

src/validation/validate.js

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -187,12 +187,15 @@ export class ValidationContext {
187187
_typeInfo: TypeInfo;
188188
_fragments: {[name: string]: FragmentDefinition};
189189
_fragmentSpreads: Map<HasSelectionSet, Array<FragmentSpread>>;
190+
_recursivelyReferencedFragments:
191+
Map<OperationDefinition, Array<FragmentDefinition>>;
190192

191193
constructor(schema: GraphQLSchema, ast: Document, typeInfo: TypeInfo) {
192194
this._schema = schema;
193195
this._ast = ast;
194196
this._typeInfo = typeInfo;
195197
this._fragmentSpreads = new Map();
198+
this._recursivelyReferencedFragments = new Map();
196199
}
197200

198201
getSchema(): GraphQLSchema {
@@ -227,6 +230,34 @@ export class ValidationContext {
227230
return spreads;
228231
}
229232

233+
getRecursivelyReferencedFragments(
234+
operation: OperationDefinition
235+
): Array<FragmentDefinition> {
236+
let fragments = this._recursivelyReferencedFragments.get(operation);
237+
if (!fragments) {
238+
fragments = [];
239+
const collectedNames = Object.create(null);
240+
const nodesToVisit: Array<HasSelectionSet> = [ operation ];
241+
while (nodesToVisit.length !== 0) {
242+
const node = nodesToVisit.pop();
243+
const spreads = this.getFragmentSpreads(node);
244+
for (let i = 0; i < spreads.length; i++) {
245+
const fragName = spreads[i].name.value;
246+
if (collectedNames[fragName] !== true) {
247+
collectedNames[fragName] = true;
248+
const fragment = this.getFragment(fragName);
249+
if (fragment) {
250+
fragments.push(fragment);
251+
nodesToVisit.push(fragment);
252+
}
253+
}
254+
}
255+
}
256+
this._recursivelyReferencedFragments.set(operation, fragments);
257+
}
258+
return fragments;
259+
}
260+
230261
getType(): ?GraphQLOutputType {
231262
return this._typeInfo.getType();
232263
}

0 commit comments

Comments
 (0)