Skip to content

Commit 94fef5d

Browse files
committed
Make FragmentArgumentDefinition a unique AST node to see downstream effects
1 parent 90d048d commit 94fef5d

File tree

6 files changed

+106
-46
lines changed

6 files changed

+106
-46
lines changed

src/language/__tests__/visitor-test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -477,7 +477,7 @@ describe('Visitor', () => {
477477
['enter', 'FragmentDefinition', undefined],
478478
['enter', 'Name', 'a'],
479479
['leave', 'Name', 'a'],
480-
['enter', 'VariableDefinition', undefined],
480+
['enter', 'FragmentArgumentDefinition', undefined],
481481
['enter', 'Variable', undefined],
482482
['enter', 'Name', 'v'],
483483
['leave', 'Name', 'v'],
@@ -488,7 +488,7 @@ describe('Visitor', () => {
488488
['leave', 'NamedType', undefined],
489489
['enter', 'BooleanValue', false],
490490
['leave', 'BooleanValue', false],
491-
['leave', 'VariableDefinition', undefined],
491+
['leave', 'FragmentArgumentDefinition', undefined],
492492
['enter', 'NamedType', undefined],
493493
['enter', 'Name', 't'],
494494
['leave', 'Name', 't'],

src/language/ast.ts

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,7 @@ export type ASTNode =
149149
| FragmentSpreadNode
150150
| InlineFragmentNode
151151
| FragmentDefinitionNode
152+
| FragmentArgumentDefinitionNode
152153
| IntValueNode
153154
| FloatValueNode
154155
| StringValueNode
@@ -227,16 +228,22 @@ export const QueryDocumentKeys: {
227228
NonNullAssertion: ['nullabilityAssertion'],
228229
ErrorBoundary: ['nullabilityAssertion'],
229230

230-
FragmentSpread: ['name', 'directives'],
231+
FragmentSpread: ['name', 'arguments', 'directives'],
231232
InlineFragment: ['typeCondition', 'directives', 'selectionSet'],
232233
FragmentDefinition: [
233234
'name',
234-
// Note: fragment variable definitions are deprecated and will removed in v17.0.0
235-
'variableDefinitions',
235+
'arguments',
236236
'typeCondition',
237237
'directives',
238238
'selectionSet',
239239
],
240+
FragmentArgumentDefinition: [
241+
'description',
242+
'variable',
243+
'type',
244+
'defaultValue',
245+
'directives',
246+
],
240247

241248
IntValue: [],
242249
FloatValue: [],
@@ -428,6 +435,7 @@ export interface FragmentSpreadNode {
428435
readonly kind: Kind.FRAGMENT_SPREAD;
429436
readonly loc?: Location | undefined;
430437
readonly name: NameNode;
438+
readonly arguments?: ReadonlyArray<ArgumentNode> | undefined;
431439
readonly directives?: ReadonlyArray<DirectiveNode> | undefined;
432440
}
433441

@@ -443,15 +451,24 @@ export interface FragmentDefinitionNode {
443451
readonly kind: Kind.FRAGMENT_DEFINITION;
444452
readonly loc?: Location | undefined;
445453
readonly name: NameNode;
446-
/** @deprecated variableDefinitions will be removed in v17.0.0 */
447-
readonly variableDefinitions?:
448-
| ReadonlyArray<VariableDefinitionNode>
454+
readonly arguments?:
455+
| ReadonlyArray<FragmentArgumentDefinitionNode>
449456
| undefined;
450457
readonly typeCondition: NamedTypeNode;
451458
readonly directives?: ReadonlyArray<DirectiveNode> | undefined;
452459
readonly selectionSet: SelectionSetNode;
453460
}
454461

462+
export interface FragmentArgumentDefinitionNode {
463+
readonly kind: Kind.FRAGMENT_ARGUMENT_DEFINITION;
464+
readonly loc?: Location | undefined;
465+
readonly description?: StringValueNode | undefined;
466+
readonly variable: VariableNode;
467+
readonly type: TypeNode;
468+
readonly defaultValue?: ConstValueNode | undefined;
469+
readonly directives?: ReadonlyArray<ConstDirectiveNode> | undefined;
470+
}
471+
455472
/** Values */
456473

457474
export type ValueNode =

src/language/kinds.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ export enum Kind {
2222
FRAGMENT_SPREAD = 'FragmentSpread',
2323
INLINE_FRAGMENT = 'InlineFragment',
2424
FRAGMENT_DEFINITION = 'FragmentDefinition',
25+
FRAGMENT_ARGUMENT_DEFINITION = 'FragmentArgumentDefinition',
2526

2627
/** Values */
2728
VARIABLE = 'Variable',

src/language/parser.ts

Lines changed: 56 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import type {
2424
FieldDefinitionNode,
2525
FieldNode,
2626
FloatValueNode,
27+
FragmentArgumentDefinitionNode,
2728
FragmentDefinitionNode,
2829
FragmentSpreadNode,
2930
InlineFragmentNode,
@@ -91,23 +92,6 @@ export interface ParseOptions {
9192
*/
9293
maxTokens?: number | undefined;
9394

94-
/**
95-
* @deprecated will be removed in the v17.0.0
96-
*
97-
* If enabled, the parser will understand and parse variable definitions
98-
* contained in a fragment definition. They'll be represented in the
99-
* `variableDefinitions` field of the FragmentDefinitionNode.
100-
*
101-
* The syntax is identical to normal, query-defined variables. For example:
102-
*
103-
* ```graphql
104-
* fragment A($var: Boolean = false) on T {
105-
* ...
106-
* }
107-
* ```
108-
*/
109-
allowLegacyFragmentVariables?: boolean | undefined;
110-
11195
/**
11296
* EXPERIMENTAL:
11397
*
@@ -550,7 +534,7 @@ export class Parser {
550534
/**
551535
* Corresponds to both FragmentSpread and InlineFragment in the spec.
552536
*
553-
* FragmentSpread : ... FragmentName Directives?
537+
* FragmentSpread : ... FragmentName Arguments? Directives?
554538
*
555539
* InlineFragment : ... TypeCondition? Directives? SelectionSet
556540
*/
@@ -560,9 +544,18 @@ export class Parser {
560544

561545
const hasTypeCondition = this.expectOptionalKeyword('on');
562546
if (!hasTypeCondition && this.peek(TokenKind.NAME)) {
547+
const name = this.parseFragmentName();
548+
if (this.peek(TokenKind.PAREN_L)) {
549+
return this.node<FragmentSpreadNode>(start, {
550+
kind: Kind.FRAGMENT_SPREAD,
551+
name,
552+
arguments: this.parseArguments(false),
553+
directives: this.parseDirectives(false),
554+
});
555+
}
563556
return this.node<FragmentSpreadNode>(start, {
564557
kind: Kind.FRAGMENT_SPREAD,
565-
name: this.parseFragmentName(),
558+
name,
566559
directives: this.parseDirectives(false),
567560
});
568561
}
@@ -576,29 +569,17 @@ export class Parser {
576569

577570
/**
578571
* FragmentDefinition :
579-
* - fragment FragmentName on TypeCondition Directives? SelectionSet
572+
* - fragment FragmentName FragmentArgumentsDefinition? on TypeCondition Directives? SelectionSet
580573
*
581574
* TypeCondition : NamedType
582575
*/
583576
parseFragmentDefinition(): FragmentDefinitionNode {
584577
const start = this._lexer.token;
585578
this.expectKeyword('fragment');
586-
// Legacy support for defining variables within fragments changes
587-
// the grammar of FragmentDefinition:
588-
// - fragment FragmentName VariableDefinitions? on TypeCondition Directives? SelectionSet
589-
if (this._options.allowLegacyFragmentVariables === true) {
590-
return this.node<FragmentDefinitionNode>(start, {
591-
kind: Kind.FRAGMENT_DEFINITION,
592-
name: this.parseFragmentName(),
593-
variableDefinitions: this.parseVariableDefinitions(),
594-
typeCondition: (this.expectKeyword('on'), this.parseNamedType()),
595-
directives: this.parseDirectives(false),
596-
selectionSet: this.parseSelectionSet(),
597-
});
598-
}
599579
return this.node<FragmentDefinitionNode>(start, {
600580
kind: Kind.FRAGMENT_DEFINITION,
601581
name: this.parseFragmentName(),
582+
arguments: this.parseFragmentArgumentDefs(),
602583
typeCondition: (this.expectKeyword('on'), this.parseNamedType()),
603584
directives: this.parseDirectives(false),
604585
selectionSet: this.parseSelectionSet(),
@@ -615,6 +596,48 @@ export class Parser {
615596
return this.parseName();
616597
}
617598

599+
/**
600+
* FragmentArgumentsDefinition : ( FragmentArgumentDefinition+ )
601+
*/
602+
parseFragmentArgumentDefs(): Array<FragmentArgumentDefinitionNode> {
603+
return this.optionalMany(
604+
TokenKind.PAREN_L,
605+
this.parseFragmentArgumentDef,
606+
TokenKind.PAREN_R,
607+
);
608+
}
609+
610+
/**
611+
* FragmentArgumentDefinition :
612+
* - Description? Variable : Type DefaultValue? Directives[Const]?
613+
*
614+
* Note: identical to InputValueDefinition, EXCEPT Name always begins
615+
* with $, so we need to parse a Variable out instead of a plain Name.
616+
*
617+
* Note: identical to VariableDefinition, EXCEPT we allow Description.
618+
* Fragments are re-used, and their arguments may need documentation.
619+
*/
620+
parseFragmentArgumentDef(): FragmentArgumentDefinitionNode {
621+
const start = this._lexer.token;
622+
const description = this.parseDescription();
623+
const variable = this.parseVariable();
624+
this.expectToken(TokenKind.COLON);
625+
const type = this.parseTypeReference();
626+
let defaultValue;
627+
if (this.expectOptionalToken(TokenKind.EQUALS)) {
628+
defaultValue = this.parseConstValueLiteral();
629+
}
630+
const directives = this.parseConstDirectives();
631+
return this.node<FragmentArgumentDefinitionNode>(start, {
632+
kind: Kind.FRAGMENT_ARGUMENT_DEFINITION,
633+
description,
634+
variable,
635+
type,
636+
defaultValue,
637+
directives,
638+
});
639+
}
640+
618641
// Implements the parsing rules in the Values section.
619642

620643
/**

src/language/printer.ts

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -105,8 +105,15 @@ const printDocASTReducer: ASTReducer<string> = {
105105
// Fragments
106106

107107
FragmentSpread: {
108-
leave: ({ name, directives }) =>
109-
'...' + name + wrap(' ', join(directives, ' ')),
108+
leave: ({ name, arguments: args, directives }) => {
109+
const prefix = '...' + name;
110+
let argsLine = prefix + wrap('(', join(args, ', '), ')');
111+
112+
if (argsLine.length > MAX_LINE_LENGTH) {
113+
argsLine = prefix + wrap('(\n', indent(join(args, '\n')), '\n)');
114+
}
115+
return argsLine + wrap(' ', join(directives, ' '));
116+
},
110117
},
111118

112119
InlineFragment: {
@@ -126,17 +133,30 @@ const printDocASTReducer: ASTReducer<string> = {
126133
leave: ({
127134
name,
128135
typeCondition,
129-
variableDefinitions,
136+
arguments: args,
130137
directives,
131138
selectionSet,
132139
}) =>
133140
// Note: fragment variable definitions are experimental and may be changed
134141
// or removed in the future.
135-
`fragment ${name}${wrap('(', join(variableDefinitions, ', '), ')')} ` +
142+
`fragment ${name}${wrap('(', join(args, ', '), ')')} ` +
136143
`on ${typeCondition} ${wrap('', join(directives, ' '), ' ')}` +
137144
selectionSet,
138145
},
139146

147+
FragmentArgumentDefinition: {
148+
leave: ({ description, variable, type, defaultValue, directives }) =>
149+
wrap('', description, '\n') +
150+
join(
151+
[
152+
variable + ': ' + type,
153+
wrap('= ', defaultValue),
154+
join(directives, ' '),
155+
],
156+
' ',
157+
),
158+
},
159+
140160
// Value
141161

142162
IntValue: { leave: ({ value }) => value },

src/utilities/buildASTSchema.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,6 @@ export function buildSchema(
9393
): GraphQLSchema {
9494
const document = parse(source, {
9595
noLocation: options?.noLocation,
96-
allowLegacyFragmentVariables: options?.allowLegacyFragmentVariables,
9796
});
9897

9998
return buildASTSchema(document, {

0 commit comments

Comments
 (0)