@@ -63,115 +63,6 @@ function reasonMessage(reason: ConflictReasonMessage): string {
63
63
export function OverlappingFieldsCanBeMerged ( context : ValidationContext ) : any {
64
64
const comparedSet = new PairSet ( ) ;
65
65
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
-
175
66
return {
176
67
SelectionSet : {
177
68
// Note: we validate on the reverse traversal so deeper conflicts will be
@@ -183,7 +74,7 @@ export function OverlappingFieldsCanBeMerged(context: ValidationContext): any {
183
74
context . getParentType ( ) ,
184
75
selectionSet
185
76
) ;
186
- const conflicts = findConflicts ( false , fieldMap ) ;
77
+ const conflicts = findConflicts ( context , false , fieldMap , comparedSet ) ;
187
78
conflicts . forEach (
188
79
( [ [ responseName , reason ] , fields1 , fields2 ] ) =>
189
80
context . reportError ( new GraphQLError (
@@ -206,6 +97,132 @@ type AstAndDef = [ GraphQLCompositeType, Field, ?GraphQLFieldDefinition ];
206
97
// Map of array of those.
207
98
type AstAndDefCollection = { [ key : string ] : Array < AstAndDef > } ;
208
99
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
+
209
226
function sameArguments (
210
227
arguments1 : Array < Argument > ,
211
228
arguments2 : Array < Argument >
0 commit comments