@@ -22,10 +22,9 @@ import {
2222} from '../type/directives.js' ;
2323import type { GraphQLSchema } from '../type/schema.js' ;
2424
25- import { substituteFragmentArguments } from '../utilities/substituteFragmentArguments.js' ;
2625import { typeFromAST } from '../utilities/typeFromAST.js' ;
2726
28- import { getDirectiveValues } from './values.js' ;
27+ import { getArgumentValuesFromSpread , getDirectiveValues } from './values.js' ;
2928
3029export interface DeferUsage {
3130 label : string | undefined ;
@@ -35,15 +34,16 @@ export interface DeferUsage {
3534export interface FieldDetails {
3635 node : FieldNode ;
3736 deferUsage : DeferUsage | undefined ;
37+ fragmentVariableValues ?: ObjMap < unknown > | undefined ;
3838}
3939
4040interface CollectFieldsContext {
4141 schema : GraphQLSchema ;
4242 fragments : ObjMap < FragmentDefinitionNode > ;
43- variableValues : { [ variable : string ] : unknown } ;
4443 operation : OperationDefinitionNode ;
4544 runtimeType : GraphQLObjectType ;
4645 visitedFragmentNames : Set < string > ;
46+ variableValues : { [ variable : string ] : unknown } ;
4747}
4848
4949/**
@@ -66,13 +66,18 @@ export function collectFields(
6666 const context : CollectFieldsContext = {
6767 schema,
6868 fragments,
69- variableValues,
7069 runtimeType,
70+ variableValues,
7171 operation,
7272 visitedFragmentNames : new Set ( ) ,
7373 } ;
7474
75- collectFieldsImpl ( context , operation . selectionSet , groupedFieldSet ) ;
75+ collectFieldsImpl (
76+ context ,
77+ operation . selectionSet ,
78+ groupedFieldSet ,
79+ variableValues ,
80+ ) ;
7681 return groupedFieldSet ;
7782}
7883
@@ -98,8 +103,8 @@ export function collectSubfields(
98103 const context : CollectFieldsContext = {
99104 schema,
100105 fragments,
101- variableValues,
102106 runtimeType : returnType ,
107+ variableValues,
103108 operation,
104109 visitedFragmentNames : new Set ( ) ,
105110 } ;
@@ -112,6 +117,7 @@ export function collectSubfields(
112117 context ,
113118 node . selectionSet ,
114119 subGroupedFieldSet ,
120+ undefined ,
115121 fieldDetail . deferUsage ,
116122 ) ;
117123 }
@@ -120,31 +126,35 @@ export function collectSubfields(
120126 return subGroupedFieldSet ;
121127}
122128
129+ // eslint-disable-next-line max-params
123130function collectFieldsImpl (
124131 context : CollectFieldsContext ,
125132 selectionSet : SelectionSetNode ,
126133 groupedFieldSet : AccumulatorMap < string , FieldDetails > ,
134+ fragmentVariableValues ?: ObjMap < unknown > ,
127135 parentDeferUsage ?: DeferUsage ,
128136 deferUsage ?: DeferUsage ,
129137) : void {
130138 const {
131139 schema,
132140 fragments,
133- variableValues,
134141 runtimeType,
142+ variableValues,
135143 operation,
136144 visitedFragmentNames,
137145 } = context ;
138146
139147 for ( const selection of selectionSet . selections ) {
140148 switch ( selection . kind ) {
141149 case Kind . FIELD : {
142- if ( ! shouldIncludeNode ( variableValues , selection ) ) {
150+ const vars = fragmentVariableValues ?? variableValues ;
151+ if ( ! shouldIncludeNode ( vars , selection ) ) {
143152 continue ;
144153 }
145154 groupedFieldSet . add ( getFieldEntryKey ( selection ) , {
146155 node : selection ,
147156 deferUsage : deferUsage ?? parentDeferUsage ,
157+ fragmentVariableValues : fragmentVariableValues ?? undefined ,
148158 } ) ;
149159 break ;
150160 }
@@ -167,6 +177,7 @@ function collectFieldsImpl(
167177 context ,
168178 selection . selectionSet ,
169179 groupedFieldSet ,
180+ fragmentVariableValues ,
170181 parentDeferUsage ,
171182 newDeferUsage ?? deferUsage ,
172183 ) ;
@@ -203,18 +214,34 @@ function collectFieldsImpl(
203214 visitedFragmentNames . add ( fragmentName ) ;
204215 }
205216
206- const fragmentSelectionSet = substituteFragmentArguments (
207- fragment ,
208- selection ,
209- ) ;
217+ // We need to introduce a concept of shadowing:
218+ //
219+ // - when a fragment defines a variable that is in the parent scope but not given
220+ // in the fragment-spread we need to look at this variable as undefined and check
221+ // whether the definition has a defaultValue, if not remove it from the variableValues.
222+ // - when a fragment does not define a variable we need to copy it over from the parent
223+ // scope as that variable can still get used in spreads later on in the selectionSet.
224+ // - when a value is passed in through the fragment-spread we need to copy over the key-value
225+ // into our variable-values.
226+ const fragmentArgValues = fragment . variableDefinitions
227+ ? getArgumentValuesFromSpread (
228+ selection ,
229+ schema ,
230+ fragment . variableDefinitions ,
231+ variableValues ,
232+ fragmentVariableValues ,
233+ )
234+ : undefined ;
210235
211236 collectFieldsImpl (
212237 context ,
213- fragmentSelectionSet ,
238+ fragment . selectionSet ,
214239 groupedFieldSet ,
240+ fragmentArgValues ,
215241 parentDeferUsage ,
216242 newDeferUsage ?? deferUsage ,
217243 ) ;
244+
218245 break ;
219246 }
220247 }
0 commit comments