Skip to content

Commit cf9be87

Browse files
committed
Factor out more closure functions
Two more functions from the overlapping-fields validator which now accept arguments rather than closing over them locally.
1 parent 3a01fac commit cf9be87

File tree

1 file changed

+127
-110
lines changed

1 file changed

+127
-110
lines changed

src/validation/rules/OverlappingFieldsCanBeMerged.js

Lines changed: 127 additions & 110 deletions
Original file line numberDiff line numberDiff line change
@@ -63,115 +63,6 @@ function reasonMessage(reason: ConflictReasonMessage): string {
6363
export function OverlappingFieldsCanBeMerged(context: ValidationContext): any {
6464
const comparedSet = new PairSet();
6565

66-
function findConflicts(
67-
parentFieldsAreMutuallyExclusive: boolean,
68-
fieldMap: AstAndDefCollection
69-
): Array<Conflict> {
70-
const conflicts = [];
71-
Object.keys(fieldMap).forEach(responseName => {
72-
const fields = fieldMap[responseName];
73-
if (fields.length > 1) {
74-
for (let i = 0; i < fields.length; i++) {
75-
for (let j = i; j < fields.length; j++) {
76-
const conflict = findConflict(
77-
parentFieldsAreMutuallyExclusive,
78-
responseName,
79-
fields[i],
80-
fields[j]
81-
);
82-
if (conflict) {
83-
conflicts.push(conflict);
84-
}
85-
}
86-
}
87-
}
88-
});
89-
return conflicts;
90-
}
91-
92-
function findConflict(
93-
parentFieldsAreMutuallyExclusive: boolean,
94-
responseName: string,
95-
field1: AstAndDef,
96-
field2: AstAndDef
97-
): ?Conflict {
98-
const [ parentType1, ast1, def1 ] = field1;
99-
const [ parentType2, ast2, def2 ] = field2;
100-
101-
// Not a pair.
102-
if (ast1 === ast2) {
103-
return;
104-
}
105-
106-
// Memoize, do not report the same issue twice.
107-
// Note: Two overlapping ASTs could be encountered both when
108-
// `parentFieldsAreMutuallyExclusive` is true and is false, which could
109-
// produce different results (when `true` being a subset of `false`).
110-
// However we do not need to include this piece of information when
111-
// memoizing since this rule visits leaf fields before their parent fields,
112-
// ensuring that `parentFieldsAreMutuallyExclusive` is `false` the first
113-
// time two overlapping fields are encountered, ensuring that the full
114-
// set of validation rules are always checked when necessary.
115-
if (comparedSet.has(ast1, ast2)) {
116-
return;
117-
}
118-
comparedSet.add(ast1, ast2);
119-
120-
// The return type for each field.
121-
const type1 = def1 && def1.type;
122-
const type2 = def2 && def2.type;
123-
124-
// If it is known that two fields could not possibly apply at the same
125-
// time, due to the parent types, then it is safe to permit them to diverge
126-
// in aliased field or arguments used as they will not present any ambiguity
127-
// by differing.
128-
// It is known that two parent types could never overlap if they are
129-
// different Object types. Interface or Union types might overlap - if not
130-
// in the current state of the schema, then perhaps in some future version,
131-
// thus may not safely diverge.
132-
const fieldsAreMutuallyExclusive =
133-
parentFieldsAreMutuallyExclusive ||
134-
parentType1 !== parentType2 &&
135-
parentType1 instanceof GraphQLObjectType &&
136-
parentType2 instanceof GraphQLObjectType;
137-
138-
if (!fieldsAreMutuallyExclusive) {
139-
// Two aliases must refer to the same field.
140-
const name1 = ast1.name.value;
141-
const name2 = ast2.name.value;
142-
if (name1 !== name2) {
143-
return [
144-
[ responseName, `${name1} and ${name2} are different fields` ],
145-
[ ast1 ],
146-
[ ast2 ]
147-
];
148-
}
149-
150-
// Two field calls must have the same arguments.
151-
if (!sameArguments(ast1.arguments || [], ast2.arguments || [])) {
152-
return [
153-
[ responseName, 'they have differing arguments' ],
154-
[ ast1 ],
155-
[ ast2 ]
156-
];
157-
}
158-
}
159-
160-
if (type1 && type2 && doTypesConflict(type1, type2)) {
161-
return [
162-
[ responseName, `they return conflicting types ${type1} and ${type2}` ],
163-
[ ast1 ],
164-
[ ast2 ]
165-
];
166-
}
167-
168-
const subfieldMap = getSubfieldMap(context, ast1, type1, ast2, type2);
169-
if (subfieldMap) {
170-
const conflicts = findConflicts(fieldsAreMutuallyExclusive, subfieldMap);
171-
return subfieldConflicts(conflicts, responseName, ast1, ast2);
172-
}
173-
}
174-
17566
return {
17667
SelectionSet: {
17768
// Note: we validate on the reverse traversal so deeper conflicts will be
@@ -183,7 +74,7 @@ export function OverlappingFieldsCanBeMerged(context: ValidationContext): any {
18374
context.getParentType(),
18475
selectionSet
18576
);
186-
const conflicts = findConflicts(false, fieldMap);
77+
const conflicts = findConflicts(context, false, fieldMap, comparedSet);
18778
conflicts.forEach(
18879
([ [ responseName, reason ], fields1, fields2 ]) =>
18980
context.reportError(new GraphQLError(
@@ -206,6 +97,132 @@ type AstAndDef = [ GraphQLCompositeType, Field, ?GraphQLFieldDefinition ];
20697
// Map of array of those.
20798
type AstAndDefCollection = { [key: string]: Array<AstAndDef> };
20899

100+
/**
101+
* Find all Conflicts within a collection of fields.
102+
*/
103+
function findConflicts(
104+
context: ValidationContext,
105+
parentFieldsAreMutuallyExclusive: boolean,
106+
fieldMap: AstAndDefCollection,
107+
comparedSet: PairSet
108+
): Array<Conflict> {
109+
const conflicts = [];
110+
Object.keys(fieldMap).forEach(responseName => {
111+
const fields = fieldMap[responseName];
112+
if (fields.length > 1) {
113+
for (let i = 0; i < fields.length; i++) {
114+
for (let j = i; j < fields.length; j++) {
115+
const conflict = findConflict(
116+
context,
117+
parentFieldsAreMutuallyExclusive,
118+
responseName,
119+
fields[i],
120+
fields[j],
121+
comparedSet
122+
);
123+
if (conflict) {
124+
conflicts.push(conflict);
125+
}
126+
}
127+
}
128+
}
129+
});
130+
return conflicts;
131+
}
132+
133+
/**
134+
* Determines if there is a conflict between two particular fields.
135+
*/
136+
function findConflict(
137+
context: ValidationContext,
138+
parentFieldsAreMutuallyExclusive: boolean,
139+
responseName: string,
140+
field1: AstAndDef,
141+
field2: AstAndDef,
142+
comparedSet: PairSet
143+
): ?Conflict {
144+
const [ parentType1, ast1, def1 ] = field1;
145+
const [ parentType2, ast2, def2 ] = field2;
146+
147+
// Not a pair.
148+
if (ast1 === ast2) {
149+
return;
150+
}
151+
152+
// Memoize, do not report the same issue twice.
153+
// Note: Two overlapping ASTs could be encountered both when
154+
// `parentFieldsAreMutuallyExclusive` is true and is false, which could
155+
// produce different results (when `true` being a subset of `false`).
156+
// However we do not need to include this piece of information when
157+
// memoizing since this rule visits leaf fields before their parent fields,
158+
// ensuring that `parentFieldsAreMutuallyExclusive` is `false` the first
159+
// time two overlapping fields are encountered, ensuring that the full
160+
// set of validation rules are always checked when necessary.
161+
if (comparedSet.has(ast1, ast2)) {
162+
return;
163+
}
164+
comparedSet.add(ast1, ast2);
165+
166+
// The return type for each field.
167+
const type1 = def1 && def1.type;
168+
const type2 = def2 && def2.type;
169+
170+
// If it is known that two fields could not possibly apply at the same
171+
// time, due to the parent types, then it is safe to permit them to diverge
172+
// in aliased field or arguments used as they will not present any ambiguity
173+
// by differing.
174+
// It is known that two parent types could never overlap if they are
175+
// different Object types. Interface or Union types might overlap - if not
176+
// in the current state of the schema, then perhaps in some future version,
177+
// thus may not safely diverge.
178+
const fieldsAreMutuallyExclusive =
179+
parentFieldsAreMutuallyExclusive ||
180+
parentType1 !== parentType2 &&
181+
parentType1 instanceof GraphQLObjectType &&
182+
parentType2 instanceof GraphQLObjectType;
183+
184+
if (!fieldsAreMutuallyExclusive) {
185+
// Two aliases must refer to the same field.
186+
const name1 = ast1.name.value;
187+
const name2 = ast2.name.value;
188+
if (name1 !== name2) {
189+
return [
190+
[ responseName, `${name1} and ${name2} are different fields` ],
191+
[ ast1 ],
192+
[ ast2 ]
193+
];
194+
}
195+
196+
// Two field calls must have the same arguments.
197+
if (!sameArguments(ast1.arguments || [], ast2.arguments || [])) {
198+
return [
199+
[ responseName, 'they have differing arguments' ],
200+
[ ast1 ],
201+
[ ast2 ]
202+
];
203+
}
204+
}
205+
206+
if (type1 && type2 && doTypesConflict(type1, type2)) {
207+
return [
208+
[ responseName, `they return conflicting types ${type1} and ${type2}` ],
209+
[ ast1 ],
210+
[ ast2 ]
211+
];
212+
}
213+
214+
const subfieldMap = getSubfieldMap(context, ast1, type1, ast2, type2);
215+
if (subfieldMap) {
216+
const conflicts = findConflicts(
217+
context,
218+
fieldsAreMutuallyExclusive,
219+
subfieldMap,
220+
comparedSet
221+
);
222+
return subfieldConflicts(conflicts, responseName, ast1, ast2);
223+
}
224+
}
225+
209226
function sameArguments(
210227
arguments1: Array<Argument>,
211228
arguments2: Array<Argument>

0 commit comments

Comments
 (0)