Skip to content

Commit 4590519

Browse files
author
Daniel Nagy
committed
Add fragment spread arguments syntax
1 parent 540f336 commit 4590519

File tree

8 files changed

+239
-3
lines changed

8 files changed

+239
-3
lines changed

src/language/__tests__/parser-test.js

+122
Original file line numberDiff line numberDiff line change
@@ -372,6 +372,128 @@ describe('Parser', () => {
372372
expect(() => parse(document)).to.throw('Syntax Error');
373373
});
374374

375+
it('Legacy: allows parsing fragment spread arguments if `allowLegacyFragmentVariables` is enabled', () => {
376+
const fragmentSpread = '{ ...Foo(v: $v) }';
377+
378+
expect(() =>
379+
parse(fragmentSpread, { allowLegacyFragmentVariables: true }),
380+
).to.not.throw();
381+
expect(() => parse(fragmentSpread)).to.throw('Syntax Error');
382+
});
383+
384+
it('Legacy: does not parse arguments on inline fragments', () => {
385+
const inlineFragment = '{ ... on Foo(v: $v) { id } }';
386+
expect(() =>
387+
parse(inlineFragment, { allowLegacyFragmentVariables: true }),
388+
).to.throw('Syntax Error');
389+
});
390+
391+
it('Legacy: parses fragment spread arguments', () => {
392+
const document = parse('{ ...Foo(v: $v) }', {
393+
allowLegacyFragmentVariables: true,
394+
});
395+
396+
expect(toJSONDeep(document)).to.have.deep.nested.property(
397+
'definitions[0].selectionSet.selections[0]',
398+
{
399+
arguments: [
400+
{
401+
kind: Kind.ARGUMENT,
402+
loc: { end: 14, start: 9 },
403+
name: {
404+
kind: Kind.NAME,
405+
loc: { end: 10, start: 9 },
406+
value: 'v',
407+
},
408+
value: {
409+
kind: Kind.VARIABLE,
410+
loc: { end: 14, start: 12 },
411+
name: {
412+
kind: Kind.NAME,
413+
loc: { end: 14, start: 13 },
414+
value: 'v',
415+
},
416+
},
417+
},
418+
],
419+
directives: [],
420+
kind: Kind.FRAGMENT_SPREAD,
421+
loc: { end: 15, start: 2 },
422+
name: {
423+
kind: Kind.NAME,
424+
loc: { end: 8, start: 5 },
425+
value: 'Foo',
426+
},
427+
},
428+
);
429+
});
430+
431+
it('Legacy: parses fragment spread arguments and directives', () => {
432+
const document = parse('{ ...Foo(v: $v) @skip(if: true) }', {
433+
allowLegacyFragmentVariables: true,
434+
});
435+
436+
expect(toJSONDeep(document)).to.have.deep.nested.property(
437+
'definitions[0].selectionSet.selections[0]',
438+
{
439+
arguments: [
440+
{
441+
kind: Kind.ARGUMENT,
442+
loc: { end: 14, start: 9 },
443+
name: {
444+
kind: Kind.NAME,
445+
loc: { end: 10, start: 9 },
446+
value: 'v',
447+
},
448+
value: {
449+
kind: Kind.VARIABLE,
450+
loc: { end: 14, start: 12 },
451+
name: {
452+
kind: Kind.NAME,
453+
loc: { end: 14, start: 13 },
454+
value: 'v',
455+
},
456+
},
457+
},
458+
],
459+
directives: [
460+
{
461+
arguments: [
462+
{
463+
kind: Kind.ARGUMENT,
464+
loc: { end: 30, start: 22 },
465+
name: {
466+
kind: Kind.NAME,
467+
loc: { end: 24, start: 22 },
468+
value: 'if',
469+
},
470+
value: {
471+
kind: Kind.BOOLEAN,
472+
loc: { end: 30, start: 26 },
473+
value: true,
474+
},
475+
},
476+
],
477+
kind: Kind.DIRECTIVE,
478+
loc: { end: 31, start: 16 },
479+
name: {
480+
kind: Kind.NAME,
481+
loc: { end: 21, start: 17 },
482+
value: 'skip',
483+
},
484+
},
485+
],
486+
kind: Kind.FRAGMENT_SPREAD,
487+
loc: { end: 31, start: 2 },
488+
name: {
489+
kind: Kind.NAME,
490+
loc: { end: 8, start: 5 },
491+
value: 'Foo',
492+
},
493+
},
494+
);
495+
});
496+
375497
it('contains location information that only stringifies start/end', () => {
376498
const result = parse('{ id }');
377499

src/language/__tests__/printer-test.js

+43
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,49 @@ describe('Printer: Query document', () => {
145145
`);
146146
});
147147

148+
it('Legacy: correctly prints fragment spread arguments', () => {
149+
const parsed = parse('{ ...Foo(bar: $bar) }', {
150+
allowLegacyFragmentVariables: true,
151+
});
152+
153+
expect(print(parsed)).to.equal(dedent`
154+
{
155+
...Foo(bar: $bar)
156+
}
157+
`);
158+
});
159+
160+
it('Legacy: correctly prints fragment spread arguments that exceed the max line length', () => {
161+
const parsed = parse(
162+
'{ ...Dogs(breads: ["Boston Terrier", "Poodle", "Beagle"], likesPets: true, likesToys: true) }',
163+
{
164+
allowLegacyFragmentVariables: true,
165+
},
166+
);
167+
168+
expect(print(parsed)).to.equal(dedent`
169+
{
170+
...Dogs(
171+
breads: ["Boston Terrier", "Poodle", "Beagle"]
172+
likesPets: true
173+
likesToys: true
174+
)
175+
}
176+
`);
177+
});
178+
179+
it('Legacy: correctly prints fragment spread arguments with directives', () => {
180+
const parsed = parse('{ ...Foo(bar: $bar) @skip(if: $isBaz) }', {
181+
allowLegacyFragmentVariables: true,
182+
});
183+
184+
expect(print(parsed)).to.equal(dedent`
185+
{
186+
...Foo(bar: $bar) @skip(if: $isBaz)
187+
}
188+
`);
189+
});
190+
148191
it('prints kitchen sink', () => {
149192
const printed = print(parse(kitchenSinkQuery));
150193

src/language/__tests__/visitor-test.js

+47
Original file line numberDiff line numberDiff line change
@@ -470,6 +470,53 @@ describe('Visitor', () => {
470470
]);
471471
});
472472

473+
it('Legacy: visits fragment spread arguments', () => {
474+
const ast = parse('{ ...Foo(v: $v) }', {
475+
noLocation: true,
476+
allowLegacyFragmentVariables: true,
477+
});
478+
479+
const visited = [];
480+
481+
visit(ast, {
482+
enter(node) {
483+
checkVisitorFnArgs(ast, arguments);
484+
visited.push(['enter', node.kind, getValue(node)]);
485+
},
486+
leave(node) {
487+
checkVisitorFnArgs(ast, arguments);
488+
visited.push(['leave', node.kind, getValue(node)]);
489+
},
490+
});
491+
492+
const argumentNode = {
493+
kind: Kind.VARIABLE,
494+
name: { kind: Kind.NAME, loc: undefined, value: 'v' },
495+
loc: undefined,
496+
};
497+
498+
expect(visited).to.deep.equal([
499+
['enter', Kind.DOCUMENT, undefined],
500+
['enter', Kind.OPERATION_DEFINITION, undefined],
501+
['enter', Kind.SELECTION_SET, undefined],
502+
['enter', Kind.FRAGMENT_SPREAD, undefined],
503+
['enter', Kind.NAME, 'Foo'],
504+
['leave', Kind.NAME, 'Foo'],
505+
['enter', Kind.ARGUMENT, argumentNode],
506+
['enter', Kind.NAME, 'v'],
507+
['leave', Kind.NAME, 'v'],
508+
['enter', Kind.VARIABLE, undefined],
509+
['enter', Kind.NAME, 'v'],
510+
['leave', Kind.NAME, 'v'],
511+
['leave', Kind.VARIABLE, undefined],
512+
['leave', Kind.ARGUMENT, argumentNode],
513+
['leave', Kind.FRAGMENT_SPREAD, undefined],
514+
['leave', Kind.SELECTION_SET, undefined],
515+
['leave', Kind.OPERATION_DEFINITION, undefined],
516+
['leave', Kind.DOCUMENT, undefined],
517+
]);
518+
});
519+
473520
it('visits kitchen sink', () => {
474521
const ast = parse(kitchenSinkQuery);
475522
const visited = [];

src/language/ast.d.ts

+2
Original file line numberDiff line numberDiff line change
@@ -282,6 +282,8 @@ export interface FragmentSpreadNode {
282282
readonly kind: 'FragmentSpread';
283283
readonly loc?: Location;
284284
readonly name: NameNode;
285+
// Note: fragment variable definitions are deprecated and will removed in v17.0.0
286+
readonly arguments?: ReadonlyArray<ArgumentNode>;
285287
readonly directives?: ReadonlyArray<DirectiveNode>;
286288
}
287289

src/language/ast.js

+2
Original file line numberDiff line numberDiff line change
@@ -308,6 +308,8 @@ export type FragmentSpreadNode = {|
308308
+kind: 'FragmentSpread',
309309
+loc?: Location,
310310
+name: NameNode,
311+
// Note: fragment variable definitions are deprecated and will removed in v17.0.0
312+
+arguments?: $ReadOnlyArray<ArgumentNode>,
311313
+directives?: $ReadOnlyArray<DirectiveNode>,
312314
|};
313315

src/language/parser.js

+6
Original file line numberDiff line numberDiff line change
@@ -434,6 +434,12 @@ export class Parser {
434434
return {
435435
kind: Kind.FRAGMENT_SPREAD,
436436
name: this.parseFragmentName(),
437+
// Legacy support for fragment variables changes the grammar of a
438+
// FragmentSpreadNode.
439+
arguments:
440+
this._options?.allowLegacyFragmentVariables ?? false
441+
? this.parseArguments(false)
442+
: undefined,
437443
directives: this.parseDirectives(false),
438444
loc: this.loc(start),
439445
};

src/language/printer.js

+11-2
Original file line numberDiff line numberDiff line change
@@ -69,9 +69,18 @@ const printDocASTReducer: any = {
6969

7070
// Fragments
7171

72+
// Note: fragment variable definitions are deprecated and will removed in v17.0.0
7273
FragmentSpread: {
73-
leave: ({ name, directives }) =>
74-
'...' + 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+
},
7584
},
7685

7786
InlineFragment: {

src/language/visitor.js

+6-1
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,12 @@ const QueryDocumentKeys = {
5454
Field: ['alias', 'name', 'arguments', 'directives', 'selectionSet'],
5555
Argument: ['name', 'value'],
5656

57-
FragmentSpread: ['name', 'directives'],
57+
FragmentSpread: [
58+
'name',
59+
// Note: fragment variable definitions are deprecated and will removed in v17.0.0
60+
'arguments',
61+
'directives',
62+
],
5863
InlineFragment: ['typeCondition', 'directives', 'selectionSet'],
5964
FragmentDefinition: [
6065
'name',

0 commit comments

Comments
 (0)