Skip to content

Commit a5627c0

Browse files
JoviDeCroockmjmahoneyaacovCR
authored
TypeInfo & Validation for fragment-arguments (#4)
* Add typeinfo functionality as a run-up to supporting the new validation rules Co-authored-by: mjmahone <[email protected]> * Fragment arguments validation (#5) * Fragment args validation * add experimental substitution * validation suggestions (#12) * validation suggestions * remove OperationSignature from ValidationContext only used in one rule, does not need to be on context remove dependency on getVariableSignature move FRAGMENT_ARGUMENT below ARGUMENT fragment arguments do not have location defaults only variable defaults => so getDefaultValue(), which returns location defaults for use with the allowedVariableUsage helper, should never be called by getVariableUsages with respect to fragment arguments fragment arguments therefore need not add anything to the default value stack reduce diff from main these diffs crept in before Kind.FRAGMENT_ARGUMENT was separated out --------- Co-authored-by: Yaacov Rydzinski <[email protected]> * OverlappingFieldsCanBeMergedRule suggestions (#15) --------- Co-authored-by: mjmahone <[email protected]> Co-authored-by: Yaacov Rydzinski <[email protected]>
1 parent f9a1a69 commit a5627c0

21 files changed

+1539
-138
lines changed

src/utilities/TypeInfo.ts

Lines changed: 96 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,12 @@
11
import type { Maybe } from '../jsutils/Maybe.js';
22

3-
import type { ASTNode, FieldNode } from '../language/ast.js';
3+
import type {
4+
ASTNode,
5+
DocumentNode,
6+
FieldNode,
7+
FragmentDefinitionNode,
8+
VariableDefinitionNode,
9+
} from '../language/ast.js';
410
import { isNode } from '../language/ast.js';
511
import { Kind } from '../language/kinds.js';
612
import type { ASTVisitor } from '../language/visitor.js';
@@ -32,6 +38,11 @@ import type { GraphQLSchema } from '../type/schema.js';
3238

3339
import { typeFromAST } from './typeFromAST.js';
3440

41+
export interface FragmentSignature {
42+
readonly definition: FragmentDefinitionNode;
43+
readonly variableDefinitions: Map<string, VariableDefinitionNode>;
44+
}
45+
3546
/**
3647
* TypeInfo is a utility class which, given a GraphQL schema, can keep track
3748
* of the current field and type definitions at any point in a GraphQL document
@@ -47,6 +58,12 @@ export class TypeInfo {
4758
private _directive: Maybe<GraphQLDirective>;
4859
private _argument: Maybe<GraphQLArgument>;
4960
private _enumValue: Maybe<GraphQLEnumValue>;
61+
private _fragmentSignaturesByName: (
62+
fragmentName: string,
63+
) => Maybe<FragmentSignature>;
64+
65+
private _fragmentSignature: Maybe<FragmentSignature>;
66+
private _fragmentArgument: Maybe<VariableDefinitionNode>;
5067
private _getFieldDef: GetFieldDefFn;
5168

5269
constructor(
@@ -58,7 +75,10 @@ export class TypeInfo {
5875
initialType?: Maybe<GraphQLType>,
5976

6077
/** @deprecated will be removed in 17.0.0 */
61-
getFieldDefFn?: GetFieldDefFn,
78+
getFieldDefFn?: Maybe<GetFieldDefFn>,
79+
fragmentSignatures?: Maybe<
80+
(fragmentName: string) => Maybe<FragmentSignature>
81+
>,
6282
) {
6383
this._schema = schema;
6484
this._typeStack = [];
@@ -69,6 +89,9 @@ export class TypeInfo {
6989
this._directive = null;
7090
this._argument = null;
7191
this._enumValue = null;
92+
this._fragmentSignaturesByName = fragmentSignatures ?? (() => null);
93+
this._fragmentSignature = null;
94+
this._fragmentArgument = null;
7295
this._getFieldDef = getFieldDefFn ?? getFieldDef;
7396
if (initialType) {
7497
if (isInputType(initialType)) {
@@ -119,6 +142,20 @@ export class TypeInfo {
119142
return this._argument;
120143
}
121144

145+
getFragmentSignature(): Maybe<FragmentSignature> {
146+
return this._fragmentSignature;
147+
}
148+
149+
getFragmentSignatureByName(): (
150+
fragmentName: string,
151+
) => Maybe<FragmentSignature> {
152+
return this._fragmentSignaturesByName;
153+
}
154+
155+
getFragmentArgument(): Maybe<VariableDefinitionNode> {
156+
return this._fragmentArgument;
157+
}
158+
122159
getEnumValue(): Maybe<GraphQLEnumValue> {
123160
return this._enumValue;
124161
}
@@ -130,6 +167,12 @@ export class TypeInfo {
130167
// checked before continuing since TypeInfo is used as part of validation
131168
// which occurs before guarantees of schema and document validity.
132169
switch (node.kind) {
170+
case Kind.DOCUMENT: {
171+
const fragmentSignatures = getFragmentSignatures(node);
172+
this._fragmentSignaturesByName = (fragmentName: string) =>
173+
fragmentSignatures.get(fragmentName);
174+
break;
175+
}
133176
case Kind.SELECTION_SET: {
134177
const namedType: unknown = getNamedType(this.getType());
135178
this._parentTypeStack.push(
@@ -159,6 +202,12 @@ export class TypeInfo {
159202
this._typeStack.push(isObjectType(rootType) ? rootType : undefined);
160203
break;
161204
}
205+
case Kind.FRAGMENT_SPREAD: {
206+
this._fragmentSignature = this.getFragmentSignatureByName()(
207+
node.name.value,
208+
);
209+
break;
210+
}
162211
case Kind.INLINE_FRAGMENT:
163212
case Kind.FRAGMENT_DEFINITION: {
164213
const typeConditionAST = node.typeCondition;
@@ -192,6 +241,19 @@ export class TypeInfo {
192241
this._inputTypeStack.push(isInputType(argType) ? argType : undefined);
193242
break;
194243
}
244+
case Kind.FRAGMENT_ARGUMENT: {
245+
const fragmentSignature = this.getFragmentSignature();
246+
const argDef = fragmentSignature?.variableDefinitions.get(
247+
node.name.value,
248+
);
249+
this._fragmentArgument = argDef;
250+
let argType: unknown;
251+
if (argDef) {
252+
argType = typeFromAST(this._schema, argDef.type);
253+
}
254+
this._inputTypeStack.push(isInputType(argType) ? argType : undefined);
255+
break;
256+
}
195257
case Kind.LIST: {
196258
const listType: unknown = getNullableType(this.getInputType());
197259
const itemType: unknown = isListType(listType)
@@ -236,6 +298,10 @@ export class TypeInfo {
236298

237299
leave(node: ASTNode) {
238300
switch (node.kind) {
301+
case Kind.DOCUMENT:
302+
this._fragmentSignaturesByName = /* c8 ignore start */ () =>
303+
null /* c8 ignore end */;
304+
break;
239305
case Kind.SELECTION_SET:
240306
this._parentTypeStack.pop();
241307
break;
@@ -246,6 +312,9 @@ export class TypeInfo {
246312
case Kind.DIRECTIVE:
247313
this._directive = null;
248314
break;
315+
case Kind.FRAGMENT_SPREAD:
316+
this._fragmentSignature = null;
317+
break;
249318
case Kind.OPERATION_DEFINITION:
250319
case Kind.INLINE_FRAGMENT:
251320
case Kind.FRAGMENT_DEFINITION:
@@ -259,6 +328,12 @@ export class TypeInfo {
259328
this._defaultValueStack.pop();
260329
this._inputTypeStack.pop();
261330
break;
331+
case Kind.FRAGMENT_ARGUMENT: {
332+
this._fragmentArgument = null;
333+
this._defaultValueStack.pop();
334+
this._inputTypeStack.pop();
335+
break;
336+
}
262337
case Kind.LIST:
263338
case Kind.OBJECT_FIELD:
264339
this._defaultValueStack.pop();
@@ -287,6 +362,25 @@ function getFieldDef(
287362
return schema.getField(parentType, fieldNode.name.value);
288363
}
289364

365+
function getFragmentSignatures(
366+
document: DocumentNode,
367+
): Map<string, FragmentSignature> {
368+
const fragmentSignatures = new Map<string, FragmentSignature>();
369+
for (const definition of document.definitions) {
370+
if (definition.kind === Kind.FRAGMENT_DEFINITION) {
371+
const variableDefinitions = new Map<string, VariableDefinitionNode>();
372+
if (definition.variableDefinitions) {
373+
for (const varDef of definition.variableDefinitions) {
374+
variableDefinitions.set(varDef.variable.name.value, varDef);
375+
}
376+
}
377+
const signature = { definition, variableDefinitions };
378+
fragmentSignatures.set(definition.name.value, signature);
379+
}
380+
}
381+
return fragmentSignatures;
382+
}
383+
290384
/**
291385
* Creates a new visitor instance which maintains a provided TypeInfo instance
292386
* along with visiting visitor.

0 commit comments

Comments
 (0)