Skip to content

Commit 40bf7ce

Browse files
committed
Add fragment arguments to AST and related utility functions
1 parent 5aadd61 commit 40bf7ce

File tree

7 files changed

+70
-15
lines changed

7 files changed

+70
-15
lines changed

src/language/__tests__/parser-test.ts

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

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

597597
expect(() =>
598-
parse(document, { allowLegacyFragmentVariables: true }),
598+
parse(document, { allowFragmentArguments: true }),
599+
).to.not.throw();
600+
expect(() => parse(document)).to.throw('Syntax Error');
601+
});
602+
603+
it('allows parsing fragment spread arguments', () => {
604+
const document = 'fragment a on t { ...b(v: $v) }';
605+
606+
expect(() =>
607+
parse(document, { allowFragmentArguments: true }),
599608
).to.not.throw();
600609
expect(() => parse(document)).to.throw('Syntax Error');
601610
});

src/language/__tests__/printer-test.ts

Lines changed: 32 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -110,10 +110,10 @@ describe('Printer: Query document', () => {
110110
`);
111111
});
112112

113-
it('Legacy: prints fragment with variable directives', () => {
113+
it('prints fragment with variable directives', () => {
114114
const queryASTWithVariableDirective = parse(
115115
'fragment Foo($foo: TestType @test) on TestType @testDirective { id }',
116-
{ allowLegacyFragmentVariables: true },
116+
{ allowFragmentArguments: true },
117117
);
118118
expect(print(queryASTWithVariableDirective)).to.equal(dedent`
119119
fragment Foo($foo: TestType @test) on TestType @testDirective {
@@ -122,14 +122,14 @@ describe('Printer: Query document', () => {
122122
`);
123123
});
124124

125-
it('Legacy: correctly prints fragment defined variables', () => {
125+
it('correctly prints fragment defined variables', () => {
126126
const fragmentWithVariable = parse(
127127
`
128128
fragment Foo($a: ComplexType, $b: Boolean = false) on TestType {
129129
id
130130
}
131131
`,
132-
{ allowLegacyFragmentVariables: true },
132+
{ allowFragmentArguments: true },
133133
);
134134
expect(print(fragmentWithVariable)).to.equal(dedent`
135135
fragment Foo($a: ComplexType, $b: Boolean = false) on TestType {
@@ -138,6 +138,34 @@ describe('Printer: Query document', () => {
138138
`);
139139
});
140140

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

src/language/__tests__/visitor-test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -455,10 +455,10 @@ describe('Visitor', () => {
455455
]);
456456
});
457457

458-
it('Legacy: visits variables defined in fragments', () => {
458+
it('visits variables defined in fragments', () => {
459459
const ast = parse('fragment a($v: Boolean = false) on t { f }', {
460460
noLocation: true,
461-
allowLegacyFragmentVariables: true,
461+
allowFragmentArguments: true,
462462
});
463463
const visited: Array<any> = [];
464464

src/language/ast.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -429,6 +429,7 @@ export interface FragmentSpreadNode {
429429
readonly loc?: Location | undefined;
430430
readonly name: NameNode;
431431
readonly directives?: ReadonlyArray<DirectiveNode> | undefined;
432+
readonly arguments?: ReadonlyArray<ArgumentNode> | undefined;
432433
}
433434

434435
export interface InlineFragmentNode {
@@ -443,7 +444,6 @@ export interface FragmentDefinitionNode {
443444
readonly kind: Kind.FRAGMENT_DEFINITION;
444445
readonly loc?: Location | undefined;
445446
readonly name: NameNode;
446-
/** @deprecated variableDefinitions will be removed in v17.0.0 */
447447
readonly variableDefinitions?:
448448
| ReadonlyArray<VariableDefinitionNode>
449449
| undefined;

src/language/parser.ts

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -83,12 +83,14 @@ export interface ParseOptions {
8383
noLocation?: boolean | undefined;
8484

8585
/**
86-
* @deprecated will be removed in the v17.0.0
87-
*
8886
* If enabled, the parser will understand and parse variable definitions
8987
* contained in a fragment definition. They'll be represented in the
9088
* `variableDefinitions` field of the FragmentDefinitionNode.
9189
*
90+
* Additionally, the parser will understand arguments passed to fragment
91+
* spreads. They'll be represented in the `arguments` field of the
92+
* FragmentSpreadNode.
93+
*
9294
* The syntax is identical to normal, query-defined variables. For example:
9395
*
9496
* ```graphql
@@ -539,7 +541,7 @@ export class Parser {
539541
/**
540542
* Corresponds to both FragmentSpread and InlineFragment in the spec.
541543
*
542-
* FragmentSpread : ... FragmentName Directives?
544+
* FragmentSpread : ... FragmentName Arguments? Directives?
543545
*
544546
* InlineFragment : ... TypeCondition? Directives? SelectionSet
545547
*/
@@ -549,6 +551,14 @@ export class Parser {
549551

550552
const hasTypeCondition = this.expectOptionalKeyword('on');
551553
if (!hasTypeCondition && this.peek(TokenKind.NAME)) {
554+
if (this._options?.allowFragmentArguments === true) {
555+
return this.node<FragmentSpreadNode>(start, {
556+
kind: Kind.FRAGMENT_SPREAD,
557+
name: this.parseFragmentName(),
558+
arguments: this.parseArguments(false),
559+
directives: this.parseDirectives(false),
560+
});
561+
}
552562
return this.node<FragmentSpreadNode>(start, {
553563
kind: Kind.FRAGMENT_SPREAD,
554564
name: this.parseFragmentName(),

src/language/printer.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -105,8 +105,16 @@ 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+
116+
return argsLine + wrap(' ', join(directives, ' '));
117+
},
110118
},
111119

112120
InlineFragment: {

src/utilities/buildASTSchema.ts

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

9999
return buildASTSchema(document, {

0 commit comments

Comments
 (0)