Skip to content

Commit 5b12367

Browse files
committed
Add fragment arguments to AST and related utility functions
1 parent e08993a commit 5b12367

File tree

8 files changed

+117
-19
lines changed

8 files changed

+117
-19
lines changed

src/language/__tests__/parser-test.ts

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -363,11 +363,20 @@ describe('Parser', () => {
363363
expect('loc' in result).to.equal(false);
364364
});
365365

366-
it('Legacy: allows parsing fragment defined variables', () => {
366+
it('allows parsing fragment defined variables', () => {
367367
const document = 'fragment a($v: Boolean = false) on t { f(v: $v) }';
368368

369369
expect(() =>
370-
parse(document, { allowLegacyFragmentVariables: true }),
370+
parse(document, { allowFragmentArguments: true }),
371+
).to.not.throw();
372+
expect(() => parse(document)).to.throw('Syntax Error');
373+
});
374+
375+
it('allows parsing fragment spread arguments', () => {
376+
const document = 'fragment a on t { ...b(v: $v) }';
377+
378+
expect(() =>
379+
parse(document, { allowFragmentArguments: true }),
371380
).to.not.throw();
372381
expect(() => parse(document)).to.throw('Syntax Error');
373382
});

src/language/__tests__/printer-test.ts

Lines changed: 32 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -113,10 +113,10 @@ describe('Printer: Query document', () => {
113113
);
114114
});
115115

116-
it('Legacy: prints fragment with variable directives', () => {
116+
it('prints fragment with variable directives', () => {
117117
const queryASTWithVariableDirective = parse(
118118
'fragment Foo($foo: TestType @test) on TestType @testDirective { id }',
119-
{ allowLegacyFragmentVariables: true },
119+
{ allowFragmentArguments: true },
120120
);
121121
expect(print(queryASTWithVariableDirective)).to.equal(dedent`
122122
fragment Foo($foo: TestType @test) on TestType @testDirective {
@@ -125,14 +125,14 @@ describe('Printer: Query document', () => {
125125
`);
126126
});
127127

128-
it('Legacy: correctly prints fragment defined variables', () => {
128+
it('correctly prints fragment defined variables', () => {
129129
const fragmentWithVariable = parse(
130130
`
131131
fragment Foo($a: ComplexType, $b: Boolean = false) on TestType {
132132
id
133133
}
134134
`,
135-
{ allowLegacyFragmentVariables: true },
135+
{ allowFragmentArguments: true },
136136
);
137137
expect(print(fragmentWithVariable)).to.equal(dedent`
138138
fragment Foo($a: ComplexType, $b: Boolean = false) on TestType {
@@ -141,6 +141,34 @@ describe('Printer: Query document', () => {
141141
`);
142142
});
143143

144+
it('prints fragment spread with arguments', () => {
145+
const queryASTWithVariableDirective = parse(
146+
'fragment Foo on TestType { ...Bar(a: {x: $x}, b: true) }',
147+
{ allowFragmentArguments: true },
148+
);
149+
expect(print(queryASTWithVariableDirective)).to.equal(dedent`
150+
fragment Foo on TestType {
151+
...Bar(a: {x: $x}, b: true)
152+
}
153+
`);
154+
});
155+
156+
it('prints fragment spread with multi-line arguments', () => {
157+
const queryASTWithVariableDirective = parse(
158+
'fragment Foo on TestType { ...Bar(a: {x: $x, y: $y, z: $z, xy: $xy}, b: true, c: "a long string extending arguments over max length") }',
159+
{ allowFragmentArguments: true },
160+
);
161+
expect(print(queryASTWithVariableDirective)).to.equal(dedent`
162+
fragment Foo on TestType {
163+
...Bar(
164+
a: {x: $x, y: $y, z: $z, xy: $xy}
165+
b: true
166+
c: "a long string extending arguments over max length"
167+
)
168+
}
169+
`);
170+
});
171+
144172
it('prints kitchen sink without altering ast', () => {
145173
const ast = parse(kitchenSinkQuery, { noLocation: true });
146174

src/language/__tests__/visitor-test.ts

Lines changed: 46 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -462,10 +462,10 @@ describe('Visitor', () => {
462462
]);
463463
});
464464

465-
it('Legacy: visits variables defined in fragments', () => {
465+
it('visits variables defined in fragments', () => {
466466
const ast = parse('fragment a($v: Boolean = false) on t { f }', {
467467
noLocation: true,
468-
allowLegacyFragmentVariables: true,
468+
allowFragmentArguments: true,
469469
});
470470
const visited: Array<any> = [];
471471

@@ -512,6 +512,50 @@ describe('Visitor', () => {
512512
]);
513513
});
514514

515+
it('visits arguments on fragment spreads', () => {
516+
const ast = parse('fragment a on t { ...s(v: false) }', {
517+
noLocation: true,
518+
allowFragmentArguments: true,
519+
});
520+
const visited: Array<any> = [];
521+
522+
visit(ast, {
523+
enter(node) {
524+
checkVisitorFnArgs(ast, arguments);
525+
visited.push(['enter', node.kind, getValue(node)]);
526+
},
527+
leave(node) {
528+
checkVisitorFnArgs(ast, arguments);
529+
visited.push(['leave', node.kind, getValue(node)]);
530+
},
531+
});
532+
533+
expect(visited).to.deep.equal([
534+
['enter', 'Document', undefined],
535+
['enter', 'FragmentDefinition', undefined],
536+
['enter', 'Name', 'a'],
537+
['leave', 'Name', 'a'],
538+
['enter', 'NamedType', undefined],
539+
['enter', 'Name', 't'],
540+
['leave', 'Name', 't'],
541+
['leave', 'NamedType', undefined],
542+
['enter', 'SelectionSet', undefined],
543+
['enter', 'FragmentSpread', undefined],
544+
['enter', 'Name', 's'],
545+
['leave', 'Name', 's'],
546+
['enter', 'Argument', { kind: 'BooleanValue', value: false }],
547+
['enter', 'Name', 'v'],
548+
['leave', 'Name', 'v'],
549+
['enter', 'BooleanValue', false],
550+
['leave', 'BooleanValue', false],
551+
['leave', 'Argument', { kind: 'BooleanValue', value: false }],
552+
['leave', 'FragmentSpread', undefined],
553+
['leave', 'SelectionSet', undefined],
554+
['leave', 'FragmentDefinition', undefined],
555+
['leave', 'Document', undefined],
556+
]);
557+
});
558+
515559
it('visits kitchen sink', () => {
516560
const ast = parse(kitchenSinkQuery);
517561
const visited: Array<any> = [];

src/language/ast.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -318,6 +318,7 @@ export interface FragmentSpreadNode {
318318
readonly kind: 'FragmentSpread';
319319
readonly loc?: Location;
320320
readonly name: NameNode;
321+
readonly arguments?: ReadonlyArray<ArgumentNode>;
321322
readonly directives?: ReadonlyArray<DirectiveNode>;
322323
}
323324

@@ -333,7 +334,6 @@ export interface FragmentDefinitionNode {
333334
readonly kind: 'FragmentDefinition';
334335
readonly loc?: Location;
335336
readonly name: NameNode;
336-
/** @deprecated variableDefinitions will be removed in v17.0.0 */
337337
readonly variableDefinitions?: ReadonlyArray<VariableDefinitionNode>;
338338
readonly typeCondition: NamedTypeNode;
339339
readonly directives?: ReadonlyArray<DirectiveNode>;

src/language/parser.ts

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -82,20 +82,22 @@ export interface ParseOptions {
8282
noLocation?: boolean;
8383

8484
/**
85-
* @deprecated will be removed in the v17.0.0
86-
*
8785
* If enabled, the parser will understand and parse variable definitions
8886
* contained in a fragment definition. They'll be represented in the
8987
* `variableDefinitions` field of the FragmentDefinitionNode.
9088
*
89+
* Additionally, the parser will understand arguments passed to fragment
90+
* spreads. They'll be represented in the `arguments` field of the
91+
* FragmentSpreadNode.
92+
*
9193
* The syntax is identical to normal, query-defined variables. For example:
9294
*
9395
* fragment A($var: Boolean = false) on T {
9496
* ...
9597
* }
9698
*
9799
*/
98-
allowLegacyFragmentVariables?: boolean;
100+
allowFragmentArguments?: boolean;
99101
}
100102

101103
/**
@@ -437,7 +439,7 @@ export class Parser {
437439
/**
438440
* Corresponds to both FragmentSpread and InlineFragment in the spec.
439441
*
440-
* FragmentSpread : ... FragmentName Directives?
442+
* FragmentSpread : ... FragmentName Arguments? Directives?
441443
*
442444
* InlineFragment : ... TypeCondition? Directives? SelectionSet
443445
*/
@@ -447,6 +449,14 @@ export class Parser {
447449

448450
const hasTypeCondition = this.expectOptionalKeyword('on');
449451
if (!hasTypeCondition && this.peek(TokenKind.NAME)) {
452+
if (this._options?.allowFragmentArguments === true) {
453+
return this.node<FragmentSpreadNode>(start, {
454+
kind: Kind.FRAGMENT_SPREAD,
455+
name: this.parseFragmentName(),
456+
arguments: this.parseArguments(false),
457+
directives: this.parseDirectives(false),
458+
});
459+
}
450460
return this.node<FragmentSpreadNode>(start, {
451461
kind: Kind.FRAGMENT_SPREAD,
452462
name: this.parseFragmentName(),
@@ -473,7 +483,7 @@ export class Parser {
473483
// Legacy support for defining variables within fragments changes
474484
// the grammar of FragmentDefinition:
475485
// - fragment FragmentName VariableDefinitions? on TypeCondition Directives? SelectionSet
476-
if (this._options?.allowLegacyFragmentVariables === true) {
486+
if (this._options?.allowFragmentArguments === true) {
477487
return this.node<FragmentDefinitionNode>(start, {
478488
kind: Kind.FRAGMENT_DEFINITION,
479489
name: this.parseFragmentName(),

src/language/printer.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -71,8 +71,16 @@ const printDocASTReducer: ASTReducer<string> = {
7171
// Fragments
7272

7373
FragmentSpread: {
74-
leave: ({ name, directives }) =>
75-
'...' + name + wrap(' ', join(directives, ' ')),
74+
leave: ({ name, arguments: args, directives }) => {
75+
const prefix = '...' + name;
76+
let argsLine = prefix + wrap('(', join(args, ', '), ')');
77+
78+
if (argsLine.length > MAX_LINE_LENGTH) {
79+
argsLine = prefix + wrap('(\n', indent(join(args, '\n')), '\n)');
80+
}
81+
82+
return argsLine + wrap(' ', join(directives, ' '));
83+
},
7684
},
7785

7886
InlineFragment: {

src/language/visitor.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -92,11 +92,10 @@ const QueryDocumentKeys = {
9292
Field: ['alias', 'name', 'arguments', 'directives', 'selectionSet'],
9393
Argument: ['name', 'value'],
9494

95-
FragmentSpread: ['name', 'directives'],
95+
FragmentSpread: ['name', 'arguments', 'directives'],
9696
InlineFragment: ['typeCondition', 'directives', 'selectionSet'],
9797
FragmentDefinition: [
9898
'name',
99-
// Note: fragment variable definitions are deprecated and will removed in v17.0.0
10099
'variableDefinitions',
101100
'typeCondition',
102101
'directives',

src/utilities/buildASTSchema.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ export function buildSchema(
9999
): GraphQLSchema {
100100
const document = parse(source, {
101101
noLocation: options?.noLocation,
102-
allowLegacyFragmentVariables: options?.allowLegacyFragmentVariables,
102+
allowFragmentArguments: options?.allowFragmentArguments,
103103
});
104104

105105
return buildASTSchema(document, {

0 commit comments

Comments
 (0)