Skip to content

Commit 6858a1f

Browse files
committed
AST, parser and printer changes only
1 parent 1564174 commit 6858a1f

File tree

7 files changed

+112
-58
lines changed

7 files changed

+112
-58
lines changed

src/language/__tests__/parser-test.ts

+8-5
Original file line numberDiff line numberDiff line change
@@ -607,13 +607,16 @@ describe('Parser', () => {
607607
expect('loc' in result).to.equal(false);
608608
});
609609

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

613-
expect(() =>
614-
parse(document, { allowLegacyFragmentVariables: true }),
615-
).to.not.throw();
616-
expect(() => parse(document)).to.throw('Syntax Error');
613+
expect(() => parse(document)).to.not.throw();
614+
});
615+
616+
it('allows parsing fragment spread arguments', () => {
617+
const document = 'fragment a on t { ...b(v: $v) }';
618+
619+
expect(() => parse(document)).to.not.throw();
617620
});
618621

619622
it('contains location that can be Object.toStringified, JSON.stringified, or jsutils.inspected', () => {

src/language/__tests__/printer-test.ts

+32-8
Original file line numberDiff line numberDiff line change
@@ -110,34 +110,58 @@ describe('Printer: Query document', () => {
110110
`);
111111
});
112112

113-
it('Legacy: prints fragment with variable directives', () => {
114-
const queryASTWithVariableDirective = parse(
113+
it('prints fragment with argument definition directives', () => {
114+
const fragmentWithArgumentDefinitionDirective = parse(
115115
'fragment Foo($foo: TestType @test) on TestType @testDirective { id }',
116-
{ allowLegacyFragmentVariables: true },
117116
);
118-
expect(print(queryASTWithVariableDirective)).to.equal(dedent`
117+
expect(print(fragmentWithArgumentDefinitionDirective)).to.equal(dedent`
119118
fragment Foo($foo: TestType @test) on TestType @testDirective {
120119
id
121120
}
122121
`);
123122
});
124123

125-
it('Legacy: correctly prints fragment defined variables', () => {
126-
const fragmentWithVariable = parse(
124+
it('correctly prints fragment defined arguments', () => {
125+
const fragmentWithArgumentDefinition = parse(
127126
`
128127
fragment Foo($a: ComplexType, $b: Boolean = false) on TestType {
129128
id
130129
}
131130
`,
132-
{ allowLegacyFragmentVariables: true },
133131
);
134-
expect(print(fragmentWithVariable)).to.equal(dedent`
132+
expect(print(fragmentWithArgumentDefinition)).to.equal(dedent`
135133
fragment Foo($a: ComplexType, $b: Boolean = false) on TestType {
136134
id
137135
}
138136
`);
139137
});
140138

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

src/language/__tests__/visitor-test.ts

+44-2
Original file line numberDiff line numberDiff line change
@@ -455,10 +455,9 @@ describe('Visitor', () => {
455455
]);
456456
});
457457

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

@@ -505,6 +504,49 @@ describe('Visitor', () => {
505504
]);
506505
});
507506

507+
it('visits arguments on fragment spreads', () => {
508+
const ast = parse('fragment a on t { ...s(v: false) }', {
509+
noLocation: true,
510+
});
511+
const visited: Array<any> = [];
512+
513+
visit(ast, {
514+
enter(node) {
515+
checkVisitorFnArgs(ast, arguments);
516+
visited.push(['enter', node.kind, getValue(node)]);
517+
},
518+
leave(node) {
519+
checkVisitorFnArgs(ast, arguments);
520+
visited.push(['leave', node.kind, getValue(node)]);
521+
},
522+
});
523+
524+
expect(visited).to.deep.equal([
525+
['enter', 'Document', undefined],
526+
['enter', 'FragmentDefinition', undefined],
527+
['enter', 'Name', 'a'],
528+
['leave', 'Name', 'a'],
529+
['enter', 'NamedType', undefined],
530+
['enter', 'Name', 't'],
531+
['leave', 'Name', 't'],
532+
['leave', 'NamedType', undefined],
533+
['enter', 'SelectionSet', undefined],
534+
['enter', 'FragmentSpread', undefined],
535+
['enter', 'Name', 's'],
536+
['leave', 'Name', 's'],
537+
['enter', 'Argument', { kind: 'BooleanValue', value: false }],
538+
['enter', 'Name', 'v'],
539+
['leave', 'Name', 'v'],
540+
['enter', 'BooleanValue', false],
541+
['leave', 'BooleanValue', false],
542+
['leave', 'Argument', { kind: 'BooleanValue', value: false }],
543+
['leave', 'FragmentSpread', undefined],
544+
['leave', 'SelectionSet', undefined],
545+
['leave', 'FragmentDefinition', undefined],
546+
['leave', 'Document', undefined],
547+
]);
548+
});
549+
508550
it('n', () => {
509551
const ast = parse(kitchenSinkQuery, {
510552
experimentalClientControlledNullability: true,

src/language/ast.ts

+4-5
Original file line numberDiff line numberDiff line change
@@ -227,12 +227,11 @@ export const QueryDocumentKeys: {
227227
NonNullAssertion: ['nullabilityAssertion'],
228228
ErrorBoundary: ['nullabilityAssertion'],
229229

230-
FragmentSpread: ['name', 'directives'],
230+
FragmentSpread: ['name', 'arguments', 'directives'],
231231
InlineFragment: ['typeCondition', 'directives', 'selectionSet'],
232232
FragmentDefinition: [
233233
'name',
234-
// Note: fragment variable definitions are deprecated and will removed in v17.0.0
235-
'variableDefinitions',
234+
'argumentDefinitions',
236235
'typeCondition',
237236
'directives',
238237
'selectionSet',
@@ -428,6 +427,7 @@ export interface FragmentSpreadNode {
428427
readonly kind: Kind.FRAGMENT_SPREAD;
429428
readonly loc?: Location | undefined;
430429
readonly name: NameNode;
430+
readonly arguments?: ReadonlyArray<ArgumentNode> | undefined;
431431
readonly directives?: ReadonlyArray<DirectiveNode> | undefined;
432432
}
433433

@@ -443,8 +443,7 @@ export interface FragmentDefinitionNode {
443443
readonly kind: Kind.FRAGMENT_DEFINITION;
444444
readonly loc?: Location | undefined;
445445
readonly name: NameNode;
446-
/** @deprecated variableDefinitions will be removed in v17.0.0 */
447-
readonly variableDefinitions?:
446+
readonly argumentDefinitions?:
448447
| ReadonlyArray<VariableDefinitionNode>
449448
| undefined;
450449
readonly typeCondition: NamedTypeNode;

src/language/parser.ts

+13-33
Original file line numberDiff line numberDiff line change
@@ -91,23 +91,6 @@ export interface ParseOptions {
9191
*/
9292
maxTokens?: number | undefined;
9393

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-
11194
/**
11295
* EXPERIMENTAL:
11396
*
@@ -550,7 +533,7 @@ export class Parser {
550533
/**
551534
* Corresponds to both FragmentSpread and InlineFragment in the spec.
552535
*
553-
* FragmentSpread : ... FragmentName Directives?
536+
* FragmentSpread : ... FragmentName Arguments? Directives?
554537
*
555538
* InlineFragment : ... TypeCondition? Directives? SelectionSet
556539
*/
@@ -560,9 +543,18 @@ export class Parser {
560543

561544
const hasTypeCondition = this.expectOptionalKeyword('on');
562545
if (!hasTypeCondition && this.peek(TokenKind.NAME)) {
546+
const name = this.parseFragmentName();
547+
if (this.peek(TokenKind.PAREN_L)) {
548+
return this.node<FragmentSpreadNode>(start, {
549+
kind: Kind.FRAGMENT_SPREAD,
550+
name,
551+
arguments: this.parseArguments(false),
552+
directives: this.parseDirectives(false),
553+
});
554+
}
563555
return this.node<FragmentSpreadNode>(start, {
564556
kind: Kind.FRAGMENT_SPREAD,
565-
name: this.parseFragmentName(),
557+
name,
566558
directives: this.parseDirectives(false),
567559
});
568560
}
@@ -576,29 +568,17 @@ export class Parser {
576568

577569
/**
578570
* FragmentDefinition :
579-
* - fragment FragmentName on TypeCondition Directives? SelectionSet
571+
* - fragment FragmentName VariableDefinitions? on TypeCondition Directives? SelectionSet
580572
*
581573
* TypeCondition : NamedType
582574
*/
583575
parseFragmentDefinition(): FragmentDefinitionNode {
584576
const start = this._lexer.token;
585577
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-
}
599578
return this.node<FragmentDefinitionNode>(start, {
600579
kind: Kind.FRAGMENT_DEFINITION,
601580
name: this.parseFragmentName(),
581+
argumentDefinitions: this.parseVariableDefinitions(),
602582
typeCondition: (this.expectKeyword('on'), this.parseNamedType()),
603583
directives: this.parseDirectives(false),
604584
selectionSet: this.parseSelectionSet(),

src/language/printer.ts

+11-4
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,13 +133,13 @@ const printDocASTReducer: ASTReducer<string> = {
126133
leave: ({
127134
name,
128135
typeCondition,
129-
variableDefinitions,
136+
argumentDefinitions,
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(argumentDefinitions, ', '), ')')} ` +
136143
`on ${typeCondition} ${wrap('', join(directives, ' '), ' ')}` +
137144
selectionSet,
138145
},

src/utilities/buildASTSchema.ts

-1
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)