Skip to content

Commit bb7c523

Browse files
JoviDeCroockkitten
authored andcommitted
Add support for fragment arguments
1 parent 4f3e17a commit bb7c523

File tree

6 files changed

+199
-14
lines changed

6 files changed

+199
-14
lines changed

src/__tests__/__snapshots__/parser.test.ts.snap

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,7 @@ exports[`parse > parses the kitchen sink document like graphql.js does 1`] = `
185185
"selectionSet": undefined,
186186
},
187187
{
188+
"arguments": undefined,
188189
"directives": [
189190
{
190191
"arguments": undefined,
@@ -669,6 +670,7 @@ exports[`parse > parses the kitchen sink document like graphql.js does 1`] = `
669670
"value": "Friend",
670671
},
671672
},
673+
"variableDefinitions": undefined,
672674
},
673675
{
674676
"directives": undefined,

src/__tests__/parser.test.ts

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,90 @@ describe('parse', () => {
134134
expect(() => parse('fragment Name on Type { field }')).not.toThrow();
135135
});
136136

137+
it('parses fragment variable definitions', () => {
138+
expect(parse('fragment x($var: Int = 1) on Type { field }').definitions[0]).toEqual({
139+
kind: Kind.FRAGMENT_DEFINITION,
140+
directives: undefined,
141+
name: {
142+
kind: Kind.NAME,
143+
value: 'x',
144+
},
145+
typeCondition: {
146+
kind: Kind.NAMED_TYPE,
147+
name: {
148+
kind: Kind.NAME,
149+
value: 'Type',
150+
},
151+
},
152+
variableDefinitions: [
153+
{
154+
kind: Kind.VARIABLE_DEFINITION,
155+
type: {
156+
kind: Kind.NAMED_TYPE,
157+
name: {
158+
kind: Kind.NAME,
159+
value: 'Int',
160+
},
161+
},
162+
variable: {
163+
kind: Kind.VARIABLE,
164+
name: {
165+
kind: Kind.NAME,
166+
value: 'var',
167+
},
168+
},
169+
defaultValue: {
170+
kind: Kind.INT,
171+
value: '1',
172+
},
173+
directives: undefined,
174+
},
175+
],
176+
selectionSet: {
177+
kind: Kind.SELECTION_SET,
178+
selections: [
179+
{
180+
alias: undefined,
181+
kind: Kind.FIELD,
182+
directives: undefined,
183+
selectionSet: undefined,
184+
arguments: undefined,
185+
name: {
186+
kind: Kind.NAME,
187+
value: 'field',
188+
},
189+
},
190+
],
191+
},
192+
});
193+
});
194+
195+
it('parses fragment spread arguments', () => {
196+
expect(
197+
parse('query x { ...x(var: 2) } fragment x($var: Int = 1) on Type { field }').definitions[0]
198+
).toHaveProperty('selectionSet.selections.0', {
199+
kind: Kind.FRAGMENT_SPREAD,
200+
directives: undefined,
201+
name: {
202+
kind: Kind.NAME,
203+
value: 'x',
204+
},
205+
arguments: [
206+
{
207+
kind: 'Argument',
208+
name: {
209+
kind: 'Name',
210+
value: 'var',
211+
},
212+
value: {
213+
kind: 'IntValue',
214+
value: '2',
215+
},
216+
},
217+
],
218+
});
219+
});
220+
137221
it('parses fields', () => {
138222
expect(() => parse('{ field: }')).toThrow();
139223
expect(() => parse('{ alias: field() }')).toThrow();

src/__tests__/printer.test.ts

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import * as graphql16 from 'graphql16';
44
import { parse } from '../parser';
55
import { print, printString, printBlockString } from '../printer';
66
import kitchenSinkAST from './fixtures/kitchen_sink.json';
7+
import { Kind } from 'src/kind';
78

89
function dedentString(string: string) {
910
const trimmedStr = string
@@ -115,6 +116,94 @@ describe('print', () => {
115116
).toBe('[Type!]');
116117
});
117118

119+
it('prints fragment-definition with variables', () => {
120+
expect(
121+
print({
122+
kind: Kind.FRAGMENT_DEFINITION,
123+
directives: [],
124+
name: {
125+
kind: Kind.NAME,
126+
value: 'x',
127+
},
128+
typeCondition: {
129+
kind: Kind.NAMED_TYPE,
130+
name: {
131+
kind: Kind.NAME,
132+
value: 'Type',
133+
},
134+
},
135+
variableDefinitions: [
136+
{
137+
kind: Kind.VARIABLE_DEFINITION,
138+
type: {
139+
kind: Kind.NAMED_TYPE,
140+
name: {
141+
kind: Kind.NAME,
142+
value: 'Int',
143+
},
144+
},
145+
variable: {
146+
kind: Kind.VARIABLE,
147+
name: {
148+
kind: Kind.NAME,
149+
value: 'var',
150+
},
151+
},
152+
defaultValue: {
153+
kind: Kind.INT,
154+
value: '1',
155+
},
156+
directives: [],
157+
},
158+
],
159+
selectionSet: {
160+
kind: Kind.SELECTION_SET,
161+
selections: [
162+
{
163+
alias: undefined,
164+
kind: Kind.FIELD,
165+
directives: [],
166+
selectionSet: undefined,
167+
arguments: [],
168+
name: {
169+
kind: Kind.NAME,
170+
value: 'field',
171+
},
172+
},
173+
],
174+
},
175+
} as any)
176+
).toBe(`fragment x($var: Int = 1) on Type {
177+
field
178+
}`);
179+
});
180+
181+
it('prints fragment-spread with arguments', () => {
182+
expect(
183+
print({
184+
kind: Kind.FRAGMENT_SPREAD,
185+
directives: [],
186+
name: {
187+
kind: Kind.NAME,
188+
value: 'x',
189+
},
190+
arguments: [
191+
{
192+
kind: 'Argument',
193+
name: {
194+
kind: 'Name',
195+
value: 'var',
196+
},
197+
value: {
198+
kind: 'IntValue',
199+
value: '2',
200+
},
201+
},
202+
],
203+
} as any)
204+
).toBe(`...x(var: 2)`);
205+
});
206+
118207
// NOTE: The shim won't throw for invalid AST nodes
119208
it('returns empty strings for invalid AST', () => {
120209
const badAST = { random: 'Data' };

src/ast.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,7 @@ export type FragmentSpreadNode = Or<
188188
{
189189
readonly kind: Kind.FRAGMENT_SPREAD;
190190
readonly name: NameNode;
191+
readonly arguments?: ReadonlyArray<ArgumentNode>;
191192
readonly directives?: ReadonlyArray<DirectiveNode>;
192193
readonly loc?: Location;
193194
}
@@ -209,6 +210,7 @@ export type FragmentDefinitionNode = Or<
209210
{
210211
readonly kind: Kind.FRAGMENT_DEFINITION;
211212
readonly name: NameNode;
213+
readonly variableDefinitions?: ReadonlyArray<VariableDefinitionNode>;
212214
readonly typeCondition: NamedTypeNode;
213215
readonly directives?: ReadonlyArray<DirectiveNode>;
214216
readonly selectionSet: SelectionSetNode;

src/parser.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -328,6 +328,7 @@ function selectionSet(): ast.SelectionSetNode {
328328
selections.push({
329329
kind: 'FragmentSpread' as Kind.FRAGMENT_SPREAD,
330330
name: { kind: 'Name' as Kind.NAME, value: match },
331+
arguments: arguments_(false),
331332
directives: directives(false),
332333
});
333334
} else {
@@ -434,7 +435,7 @@ function fragmentDefinition(): ast.FragmentDefinitionNode {
434435
let _name: string | undefined;
435436
let _condition: string | undefined;
436437
if ((_name = advance(nameRe)) == null) throw error('FragmentDefinition');
437-
ignored();
438+
const _variableDefinitions = variableDefinitions();
438439
if (advance(nameRe) !== 'on') throw error('FragmentDefinition');
439440
ignored();
440441
if ((_condition = advance(nameRe)) == null) throw error('FragmentDefinition');
@@ -449,6 +450,7 @@ function fragmentDefinition(): ast.FragmentDefinitionNode {
449450
kind: 'NamedType' as Kind.NAMED_TYPE,
450451
name: { kind: 'Name' as Kind.NAME, value: _condition },
451452
},
453+
variableDefinitions: _variableDefinitions,
452454
directives: _directives,
453455
selectionSet: selectionSet(),
454456
};

src/printer.ts

Lines changed: 19 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,19 @@ const MAX_LINE_LENGTH = 80;
4747

4848
let LF = '\n';
4949

50+
function Arguments(length: number, node: readonly ArgumentNode[]): string {
51+
const args = mapJoin(node, ', ', nodes.Argument);
52+
if (length + args.length + 2 > MAX_LINE_LENGTH) {
53+
return '(' +
54+
(LF += ' ') +
55+
mapJoin(node, LF, nodes.Argument) +
56+
(LF = LF.slice(0, -2)) +
57+
')';
58+
} else {
59+
return '(' + args + ')';
60+
}
61+
}
62+
5063
const nodes = {
5164
OperationDefinition(node: OperationDefinitionNode): string {
5265
let out: string = node.operation;
@@ -70,19 +83,8 @@ const nodes = {
7083
},
7184
Field(node: FieldNode): string {
7285
let out = node.alias ? node.alias.value + ': ' + node.name.value : node.name.value;
73-
if (node.arguments && node.arguments.length) {
74-
const args = mapJoin(node.arguments, ', ', nodes.Argument);
75-
if (out.length + args.length + 2 > MAX_LINE_LENGTH) {
76-
out +=
77-
'(' +
78-
(LF += ' ') +
79-
mapJoin(node.arguments, LF, nodes.Argument) +
80-
(LF = LF.slice(0, -2)) +
81-
')';
82-
} else {
83-
out += '(' + args + ')';
84-
}
85-
}
86+
if (node.arguments && node.arguments.length)
87+
out += Arguments(out.length, node.arguments);
8688
if (node.directives && node.directives.length)
8789
out += ' ' + mapJoin(node.directives, ' ', nodes.Directive);
8890
if (node.selectionSet) out += ' ' + nodes.SelectionSet(node.selectionSet);
@@ -137,6 +139,8 @@ const nodes = {
137139
},
138140
FragmentSpread(node: FragmentSpreadNode): string {
139141
let out = '...' + node.name.value;
142+
if (node.arguments && node.arguments.length)
143+
out += Arguments(out.length, node.arguments);
140144
if (node.directives && node.directives.length)
141145
out += ' ' + mapJoin(node.directives, ' ', nodes.Directive);
142146
return out;
@@ -151,6 +155,8 @@ const nodes = {
151155
},
152156
FragmentDefinition(node: FragmentDefinitionNode): string {
153157
let out = 'fragment ' + node.name.value;
158+
if (node.variableDefinitions && node.variableDefinitions.length)
159+
out += '(' + mapJoin(node.variableDefinitions, ', ', nodes.VariableDefinition) + ')';
154160
out += ' on ' + node.typeCondition.name.value;
155161
if (node.directives && node.directives.length)
156162
out += ' ' + mapJoin(node.directives, ' ', nodes.Directive);

0 commit comments

Comments
 (0)