@@ -22,10 +22,9 @@ import {
22
22
} from '../type/directives.js' ;
23
23
import type { GraphQLSchema } from '../type/schema.js' ;
24
24
25
- import { substituteFragmentArguments } from '../utilities/substituteFragmentArguments.js' ;
26
25
import { typeFromAST } from '../utilities/typeFromAST.js' ;
27
26
28
- import { getDirectiveValues } from './values.js' ;
27
+ import { getArgumentValuesFromSpread , getDirectiveValues } from './values.js' ;
29
28
30
29
export interface DeferUsage {
31
30
label : string | undefined ;
@@ -35,6 +34,7 @@ export interface DeferUsage {
35
34
export interface FieldDetails {
36
35
node : FieldNode ;
37
36
deferUsage : DeferUsage | undefined ;
37
+ fragmentVariableValues ?: ObjMap < unknown > | undefined ;
38
38
}
39
39
40
40
export type FieldGroup = ReadonlyArray < FieldDetails > ;
@@ -44,10 +44,10 @@ export type GroupedFieldSet = ReadonlyMap<string, FieldGroup>;
44
44
interface CollectFieldsContext {
45
45
schema : GraphQLSchema ;
46
46
fragments : ObjMap < FragmentDefinitionNode > ;
47
- variableValues : { [ variable : string ] : unknown } ;
48
47
operation : OperationDefinitionNode ;
49
48
runtimeType : GraphQLObjectType ;
50
49
visitedFragmentNames : Set < string > ;
50
+ variableValues : { [ variable : string ] : unknown } ;
51
51
}
52
52
53
53
/**
@@ -74,8 +74,8 @@ export function collectFields(
74
74
const context : CollectFieldsContext = {
75
75
schema,
76
76
fragments,
77
- variableValues,
78
77
runtimeType,
78
+ variableValues,
79
79
operation,
80
80
visitedFragmentNames : new Set ( ) ,
81
81
} ;
@@ -85,6 +85,7 @@ export function collectFields(
85
85
operation . selectionSet ,
86
86
groupedFieldSet ,
87
87
newDeferUsages ,
88
+ variableValues ,
88
89
) ;
89
90
return { groupedFieldSet, newDeferUsages } ;
90
91
}
@@ -114,8 +115,8 @@ export function collectSubfields(
114
115
const context : CollectFieldsContext = {
115
116
schema,
116
117
fragments,
117
- variableValues,
118
118
runtimeType : returnType ,
119
+ variableValues,
119
120
operation,
120
121
visitedFragmentNames : new Set ( ) ,
121
122
} ;
@@ -130,6 +131,7 @@ export function collectSubfields(
130
131
node . selectionSet ,
131
132
subGroupedFieldSet ,
132
133
newDeferUsages ,
134
+ undefined ,
133
135
fieldDetail . deferUsage ,
134
136
) ;
135
137
}
@@ -141,31 +143,35 @@ export function collectSubfields(
141
143
} ;
142
144
}
143
145
146
+ // eslint-disable-next-line max-params
144
147
function collectFieldsImpl (
145
148
context : CollectFieldsContext ,
146
149
selectionSet : SelectionSetNode ,
147
150
groupedFieldSet : AccumulatorMap < string , FieldDetails > ,
148
151
newDeferUsages : Array < DeferUsage > ,
152
+ fragmentVariableValues ?: ObjMap < unknown > ,
149
153
deferUsage ?: DeferUsage ,
150
154
) : void {
151
155
const {
152
156
schema,
153
157
fragments,
154
- variableValues,
155
158
runtimeType,
159
+ variableValues,
156
160
operation,
157
161
visitedFragmentNames,
158
162
} = context ;
159
163
160
164
for ( const selection of selectionSet . selections ) {
161
165
switch ( selection . kind ) {
162
166
case Kind . FIELD : {
163
- if ( ! shouldIncludeNode ( variableValues , selection ) ) {
167
+ const vars = fragmentVariableValues ?? variableValues ;
168
+ if ( ! shouldIncludeNode ( vars , selection ) ) {
164
169
continue ;
165
170
}
166
171
groupedFieldSet . add ( getFieldEntryKey ( selection ) , {
167
172
node : selection ,
168
173
deferUsage,
174
+ fragmentVariableValues : fragmentVariableValues ?? undefined ,
169
175
} ) ;
170
176
break ;
171
177
}
@@ -190,6 +196,7 @@ function collectFieldsImpl(
190
196
selection . selectionSet ,
191
197
groupedFieldSet ,
192
198
newDeferUsages ,
199
+ fragmentVariableValues ,
193
200
deferUsage ,
194
201
) ;
195
202
} else {
@@ -199,6 +206,7 @@ function collectFieldsImpl(
199
206
selection . selectionSet ,
200
207
groupedFieldSet ,
201
208
newDeferUsages ,
209
+ fragmentVariableValues ,
202
210
newDeferUsage ,
203
211
) ;
204
212
}
@@ -231,27 +239,43 @@ function collectFieldsImpl(
231
239
continue ;
232
240
}
233
241
234
- const fragmentSelectionSet = substituteFragmentArguments (
235
- fragment ,
236
- selection ,
237
- ) ;
242
+ // We need to introduce a concept of shadowing:
243
+ //
244
+ // - when a fragment defines a variable that is in the parent scope but not given
245
+ // in the fragment-spread we need to look at this variable as undefined and check
246
+ // whether the definition has a defaultValue, if not remove it from the variableValues.
247
+ // - when a fragment does not define a variable we need to copy it over from the parent
248
+ // scope as that variable can still get used in spreads later on in the selectionSet.
249
+ // - when a value is passed in through the fragment-spread we need to copy over the key-value
250
+ // into our variable-values.
251
+ const fragmentArgValues = fragment . variableDefinitions
252
+ ? getArgumentValuesFromSpread (
253
+ selection ,
254
+ schema ,
255
+ fragment . variableDefinitions ,
256
+ variableValues ,
257
+ fragmentVariableValues ,
258
+ )
259
+ : undefined ;
238
260
239
261
if ( ! newDeferUsage ) {
240
262
visitedFragmentNames . add ( fragmentName ) ;
241
263
collectFieldsImpl (
242
264
context ,
243
- fragmentSelectionSet ,
265
+ fragment . selectionSet ,
244
266
groupedFieldSet ,
245
267
newDeferUsages ,
268
+ fragmentArgValues ,
246
269
deferUsage ,
247
270
) ;
248
271
} else {
249
272
newDeferUsages . push ( newDeferUsage ) ;
250
273
collectFieldsImpl (
251
274
context ,
252
- fragmentSelectionSet ,
275
+ fragment . selectionSet ,
253
276
groupedFieldSet ,
254
277
newDeferUsages ,
278
+ fragmentArgValues ,
255
279
newDeferUsage ,
256
280
) ;
257
281
}
0 commit comments