Skip to content

Commit a7cb2ec

Browse files
JoviDeCroockmjmahoneyaacovCR
committed
feat: add experimental support for parsing fragment arguments (graphql#4015)
This is a rebase of graphql#3847 This implements execution of Fragment Arguments, and more specifically visiting, parsing and printing of fragment-spreads with arguments and fragment definitions with variables, as described by the spec changes in graphql/graphql-spec#1081. There are a few amendments in terms of execution and keying the fragment-spreads, these are reflected in mjmahone/graphql-spec#3 The purpose is to be able to independently review all the moving parts, the stacked PR's will contain mentions of open feedback that was present at the time. - [execution changes](JoviDeCroock#2) - [TypeInfo & validation changes](JoviDeCroock#4) - [validation changes in isolation](JoviDeCroock#5) CC @mjmahone the original author --------- Co-authored-by: mjmahone <mahoney.mattj@gmail.com> Co-authored-by: Yaacov Rydzinski <yaacovCR@gmail.com>
1 parent f58fe74 commit a7cb2ec

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

41 files changed

+2315
-303
lines changed

src/execution/__tests__/variables-test.ts

Lines changed: 400 additions & 2 deletions
Large diffs are not rendered by default.

src/execution/collectFields.ts

Lines changed: 101 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,36 @@ import type { GraphQLSchema } from '../type/schema.js';
2020

2121
import { typeFromAST } from '../utilities/typeFromAST.js';
2222

23-
import { getDirectiveValues } from './values.js';
23+
import type { GraphQLVariableSignature } from './getVariableSignature.js';
24+
import { experimentalGetArgumentValues, getDirectiveValues } from './values.js';
2425

25-
export type FieldGroup = ReadonlyArray<FieldNode>;
26+
export interface FragmentVariables {
27+
signatures: ObjMap<GraphQLVariableSignature>;
28+
values: ObjMap<unknown>;
29+
}
30+
31+
export interface FieldDetails {
32+
node: FieldNode;
33+
fragmentVariables?: FragmentVariables | undefined;
34+
}
2635

27-
export type GroupedFieldSet = Map<string, FieldGroup>;
36+
export type FieldGroup = ReadonlyArray<FieldDetails>;
37+
38+
export type GroupedFieldSet = ReadonlyMap<string, FieldGroup>;
39+
40+
export interface FragmentDetails {
41+
definition: FragmentDefinitionNode;
42+
variableSignatures?: ObjMap<GraphQLVariableSignature> | undefined;
43+
}
44+
45+
interface CollectFieldsContext {
46+
schema: GraphQLSchema;
47+
fragments: ObjMap<FragmentDetails>;
48+
variableValues: { [variable: string]: unknown };
49+
fragmentVariableValues?: FragmentVariables;
50+
runtimeType: GraphQLObjectType;
51+
visitedFragmentNames: Set<string>;
52+
}
2853

2954
/**
3055
* Given a selectionSet, collects all of the fields and returns them.
@@ -37,21 +62,21 @@ export type GroupedFieldSet = Map<string, FieldGroup>;
3762
*/
3863
export function collectFields(
3964
schema: GraphQLSchema,
40-
fragments: ObjMap<FragmentDefinitionNode>,
65+
fragments: ObjMap<FragmentDetails>,
4166
variableValues: { [variable: string]: unknown },
4267
runtimeType: GraphQLObjectType,
4368
selectionSet: SelectionSetNode,
4469
): GroupedFieldSet {
45-
const groupedFieldSet = new AccumulatorMap<string, FieldNode>();
46-
collectFieldsImpl(
70+
const groupedFieldSet = new AccumulatorMap<string, FieldDetails>();
71+
const context: CollectFieldsContext = {
4772
schema,
4873
fragments,
4974
variableValues,
5075
runtimeType,
51-
selectionSet,
52-
groupedFieldSet,
53-
new Set(),
54-
);
76+
visitedFragmentNames: new Set(),
77+
};
78+
79+
collectFieldsImpl(context, selectionSet, groupedFieldSet);
5580
return groupedFieldSet;
5681
}
5782

@@ -67,90 +92,110 @@ export function collectFields(
6792
*/
6893
export function collectSubfields(
6994
schema: GraphQLSchema,
70-
fragments: ObjMap<FragmentDefinitionNode>,
95+
fragments: ObjMap<FragmentDetails>,
7196
variableValues: { [variable: string]: unknown },
7297
returnType: GraphQLObjectType,
7398
fieldGroup: FieldGroup,
7499
): GroupedFieldSet {
75-
const subGroupedFieldSet = new AccumulatorMap<string, FieldNode>();
76-
const visitedFragmentNames = new Set<string>();
77-
for (const node of fieldGroup) {
100+
const context: CollectFieldsContext = {
101+
schema,
102+
fragments,
103+
variableValues,
104+
runtimeType: returnType,
105+
visitedFragmentNames: new Set(),
106+
};
107+
const subGroupedFieldSet = new AccumulatorMap<string, FieldDetails>();
108+
109+
for (const fieldDetail of fieldGroup) {
110+
const node = fieldDetail.node;
78111
if (node.selectionSet) {
79-
collectFieldsImpl(
80-
schema,
81-
fragments,
82-
variableValues,
83-
returnType,
84-
node.selectionSet,
85-
subGroupedFieldSet,
86-
visitedFragmentNames,
87-
);
112+
collectFieldsImpl(context, node.selectionSet, subGroupedFieldSet);
88113
}
89114
}
115+
90116
return subGroupedFieldSet;
91117
}
92118

93-
// eslint-disable-next-line max-params
94119
function collectFieldsImpl(
95-
schema: GraphQLSchema,
96-
fragments: ObjMap<FragmentDefinitionNode>,
97-
variableValues: { [variable: string]: unknown },
98-
runtimeType: GraphQLObjectType,
120+
context: CollectFieldsContext,
99121
selectionSet: SelectionSetNode,
100-
groupedFieldSet: AccumulatorMap<string, FieldNode>,
101-
visitedFragmentNames: Set<string>,
122+
groupedFieldSet: AccumulatorMap<string, FieldDetails>,
123+
fragmentVariables?: FragmentVariables,
102124
): void {
125+
const {
126+
schema,
127+
fragments,
128+
variableValues,
129+
runtimeType,
130+
visitedFragmentNames,
131+
} = context;
132+
103133
for (const selection of selectionSet.selections) {
104134
switch (selection.kind) {
105135
case Kind.FIELD: {
106-
if (!shouldIncludeNode(variableValues, selection)) {
136+
if (!shouldIncludeNode(selection, variableValues, fragmentVariables)) {
107137
continue;
108138
}
109-
groupedFieldSet.add(getFieldEntryKey(selection), selection);
139+
groupedFieldSet.add(getFieldEntryKey(selection), {
140+
node: selection,
141+
fragmentVariables,
142+
});
110143
break;
111144
}
112145
case Kind.INLINE_FRAGMENT: {
113146
if (
114-
!shouldIncludeNode(variableValues, selection) ||
147+
!shouldIncludeNode(selection, variableValues, fragmentVariables) ||
115148
!doesFragmentConditionMatch(schema, selection, runtimeType)
116149
) {
117150
continue;
118151
}
152+
119153
collectFieldsImpl(
120-
schema,
121-
fragments,
122-
variableValues,
123-
runtimeType,
154+
context,
124155
selection.selectionSet,
125156
groupedFieldSet,
126-
visitedFragmentNames,
157+
fragmentVariables,
127158
);
128159
break;
129160
}
130161
case Kind.FRAGMENT_SPREAD: {
131162
const fragName = selection.name.value;
163+
132164
if (
133165
visitedFragmentNames.has(fragName) ||
134-
!shouldIncludeNode(variableValues, selection)
166+
!shouldIncludeNode(selection, variableValues, fragmentVariables)
135167
) {
136168
continue;
137169
}
138-
visitedFragmentNames.add(fragName);
170+
139171
const fragment = fragments[fragName];
140172
if (
141173
fragment == null ||
142-
!doesFragmentConditionMatch(schema, fragment, runtimeType)
174+
!doesFragmentConditionMatch(schema, fragment.definition, runtimeType)
143175
) {
144176
continue;
145177
}
178+
179+
const fragmentVariableSignatures = fragment.variableSignatures;
180+
let newFragmentVariables: FragmentVariables | undefined;
181+
if (fragmentVariableSignatures) {
182+
newFragmentVariables = {
183+
signatures: fragmentVariableSignatures,
184+
values: experimentalGetArgumentValues(
185+
selection,
186+
Object.values(fragmentVariableSignatures),
187+
variableValues,
188+
fragmentVariables,
189+
),
190+
};
191+
}
192+
193+
visitedFragmentNames.add(fragName);
146194
collectFieldsImpl(
147-
schema,
148-
fragments,
149-
variableValues,
150-
runtimeType,
151-
fragment.selectionSet,
195+
context,
196+
fragment.definition.selectionSet,
152197
groupedFieldSet,
153-
visitedFragmentNames,
198+
newFragmentVariables,
154199
);
155200
break;
156201
}
@@ -163,10 +208,16 @@ function collectFieldsImpl(
163208
* directives, where `@skip` has higher precedence than `@include`.
164209
*/
165210
function shouldIncludeNode(
166-
variableValues: { [variable: string]: unknown },
167211
node: FragmentSpreadNode | FieldNode | InlineFragmentNode,
212+
variableValues: { [variable: string]: unknown },
213+
fragmentVariables: FragmentVariables | undefined,
168214
): boolean {
169-
const skip = getDirectiveValues(GraphQLSkipDirective, node, variableValues);
215+
const skip = getDirectiveValues(
216+
GraphQLSkipDirective,
217+
node,
218+
variableValues,
219+
fragmentVariables,
220+
);
170221
if (skip?.if === true) {
171222
return false;
172223
}
@@ -175,6 +226,7 @@ function shouldIncludeNode(
175226
GraphQLIncludeDirective,
176227
node,
177228
variableValues,
229+
fragmentVariables,
178230
);
179231
if (include?.if === false) {
180232
return false;

0 commit comments

Comments
 (0)