Mirror: The small sibling of the graphql package, slimmed down for client-side libraries.
1/**
2 * This is a spec-compliant implementation of a GraphQL query language parser,
3 * up-to-date with the June 2018 Edition. Unlike the reference implementation
4 * in graphql.js it will only parse the query language, but not the schema
5 * language.
6 */
7import { Kind, GraphQLError } from 'graphql';
8import { match, parse as makeParser } from 'reghex';
9
10// 2.1.7: Includes commas, and line comments
11const ignored = match()`
12 ${/([\s,]|#[^\n\r]+)+/}
13`;
14
15// 2.1.9: Limited to ASCII character set, so regex shortcodes are fine
16const name = match(Kind.NAME, x => ({
17 kind: x.tag,
18 value: x[0]
19}))`
20 ${/[_\w][_\d\w]*/}
21`;
22
23const null_ = match(Kind.NULL, x => ({
24 kind: x.tag,
25 value: null,
26}))`
27 ${'null'}
28`;
29
30const bool = match(Kind.BOOLEAN, x => ({
31 kind: x.tag,
32 value: x[0] === 'true'
33}))`
34 ${'true'} | ${'false'}
35`;
36
37const variable = match(Kind.VARIABLE, x => ({
38 kind: x.tag,
39 name: x[0]
40}))`
41 :${'$'} ${name}
42`;
43
44// 2.9.6: Technically, this parser doesn't need to check that true, false, and null
45// aren't used as enums, but this prevents mistakes and follows the spec closely
46const enum_ = match(Kind.ENUM, x => ({
47 kind: x.tag,
48 value: x[0].value
49}))`
50 ${name}
51`;
52
53// 2.9.1-2: These combine both number values for the sake of simplicity.
54// It allows for leading zeroes, unlike graphql.js, which shouldn't matter;
55const number = match(null, x => ({
56 kind: x.length === 1 ? Kind.INT : Kind.FLOAT,
57 value: x.join('')
58}))`
59 ${/[-]?\d+/}
60 ${/[.]\d+/}?
61 ${/[eE][+-]?\d+/}?
62`;
63
64// 2.9.4: Notably, this skips checks for unicode escape sequences and escaped
65// quotes. This is mainly meant for client-side use, so we won't have to be strict.
66const string = match(Kind.STRING, x => ({
67 kind: x.tag,
68 value: x[0]
69}))`
70 (:${'"""'} ${/.*(?=""")/} :${'"""'})
71 | (:${'"'} ${/[^"\r\n]*/} :${'"'})
72`;
73
74const list = match(Kind.LIST, x => ({
75 kind: x.tag,
76 values: x
77}))`
78 (?: ${'['} ${ignored}?)
79 ${value}*
80 (?: ${']'} ${ignored}?)
81`;
82
83const objectField = match(Kind.OBJECT_FIELD, x => ({
84 kind: x.tag,
85 name: x[0],
86 value: x[1]
87}))`
88 ${name}
89 (?: ${ignored} ${/:/} ${ignored})?
90 ${value}
91 (?: ${ignored})?
92`;
93
94const object = match(Kind.OBJECT, x => ({
95 kind: x.tag,
96 fields: x,
97}))`
98 (?: ${'{'} ${ignored}?)
99 ${objectField}*
100 (?: ${'}'} ${ignored}?)
101`;
102
103// 2.9: This matches the spec closely and is complete
104const value = match(null, x => x[0])`
105 :${ignored}?
106 (
107 ${null_}
108 | ${bool}
109 | ${variable}
110 | ${string}
111 | ${number}
112 | ${enum_}
113 | ${list}
114 | ${object}
115 )
116 :${ignored}?
117`;
118
119const arg = match(Kind.ARGUMENT, x => ({
120 kind: x.tag,
121 name: x[0],
122 value: x[1]
123}))`
124 ${name}
125 (?: ${ignored}? ${':'} ${ignored}?)
126 ${value}
127`;
128
129const args = match()`
130 :${ignored}?
131 (
132 (?: ${'('} ${ignored}?)
133 ${arg}+
134 (?: ${')'} ${ignored}?)
135 )?
136`;
137
138const directive = match(Kind.DIRECTIVE, x => ({
139 kind: x.tag,
140 name: x[0],
141 arguments: x[1]
142}))`
143 :${'@'} ${name} :${ignored}?
144 ${args}?
145 :${ignored}?
146`;
147
148const directives = match()`
149 :${ignored}?
150 ${directive}*
151`;
152
153const field = match(Kind.FIELD, x => {
154 let i = 0;
155 return {
156 kind: x.tag,
157 alias: x[1].kind === Kind.NAME ? x[i++] : undefined,
158 name: x[i++],
159 arguments: x[i++],
160 directives: x[i++],
161 selectionSet: x[i++],
162 };
163})`
164 :${ignored}?
165 ${name}
166 (
167 (?: ${ignored}? ${':'} ${ignored}?)
168 ${name}
169 )?
170 ${args}
171 ${directives}
172 ${selectionSet}?
173`;
174
175// 2.11: The type declarations may be simplified since there's little room
176// for error in this limited type system.
177const type = match(null, x => {
178 const node = x[0].kind === 'Name'
179 ? { kind: Kind.NAMED_TYPE, name: x[0] }
180 : { kind: Kind.LIST_TYPE, type: x[0] }
181 return x[1] === '!'
182 ? { kind: Kind.NON_NULL_TYPE, type: node }
183 : node;
184})`
185 (
186 (
187 (?: ${'['} ${ignored}?)
188 ${type}
189 (?: ${ignored}? ${']'} ${ignored}?)
190 ) | ${name}
191 )
192 ${'!'}?
193 :${ignored}?
194`;
195
196const typeCondition = match(null, x => ({
197 kind: Kind.NAMED_TYPE,
198 name: x[0]
199}))`
200 (?: ${ignored} ${'on'} ${ignored})
201 ${name}
202 :${ignored}?
203`;
204
205const inlineFragment = match(Kind.INLINE_FRAGMENT, x => {
206 let i = 0;
207 return {
208 kind: x.tag,
209 typeCondition: x[i].kind === Kind.NAMED_TYPE ? x[i++] : undefined,
210 directives: x[i++],
211 selectionSet: x[i]
212 };
213})`
214 (?: ${'...'} ${ignored}?)
215 ${typeCondition}?
216 ${directives}
217 ${selectionSet}
218`;
219
220const fragmentSpread = match(Kind.FRAGMENT_SPREAD, x => ({
221 kind: x.tag,
222 name: x[0],
223 directives: x[1]
224}))`
225 (?: ${'...'} ${ignored}?)
226 !${'on'}
227 ${name}
228 :${ignored}?
229 ${directives}
230`;
231
232const selectionSet = match(Kind.SELECTION_SET, x => ({
233 kind: x.tag,
234 selections: x.slice(),
235}))`
236 (?: ${'{'} ${ignored}?)
237 (
238 ${inlineFragment} |
239 ${fragmentSpread} |
240 ${field}
241 )+
242 (?: ${'}'} ${ignored}?)
243`;
244
245const varDefinitionDefault = match(null, x => x[0])`
246 (?: ${'='} ${ignored}?)
247 ${value}
248`;
249
250const varDefinition = match(Kind.VARIABLE_DEFINITION, x => ({
251 kind: x.tag,
252 variable: x[0],
253 type: x[1],
254 defaultValue: x[2].kind ? x[2] : undefined,
255 directives: !x[2].kind ? x[2] : x[3],
256}))`
257 ${variable}
258 (?: ${ignored}? ${':'} ${ignored}?)
259 ${type}
260 ${varDefinitionDefault}?
261 ${directives}
262 :${ignored}?
263`;
264
265const varDefinitions = match('vars')`
266 (?: ${'('} ${ignored}?)
267 ${varDefinition}+
268 (?: ${')'} ${ignored}?)
269`;
270
271const fragmentDefinition = match(Kind.FRAGMENT_DEFINITION, x => ({
272 kind: x.tag,
273 name: x[0],
274 typeCondition: x[1],
275 directives: x[2],
276 selectionSet: x[3],
277}))`
278 (?: ${ignored}? ${'fragment'} ${ignored})
279 ${name}
280 ${typeCondition}
281 ${directives}
282 ${selectionSet}
283`;
284
285const operationDefinition = match(Kind.OPERATION_DEFINITION, x => {
286 let i = 1;
287 return {
288 kind: x.tag,
289 operation: x[0],
290 name: x.length === 5 ? x[i++] : undefined,
291 variableDefinitions: x[i].tag === 'vars' ? x[i++].slice() : null,
292 directives: x[i++],
293 selectionSet: x[i],
294 };
295})`
296 :${ignored}?
297 ${/query|mutation|subscription/}
298 ((?: ${ignored}) ${name})?
299 :${ignored}?
300 ${varDefinitions}?
301 ${directives}
302 ${selectionSet}
303`;
304
305const queryShorthand = match(Kind.OPERATION_DEFINITION, x => ({
306 kind: x.tag,
307 operation: 'query',
308 name: undefined,
309 variableDefinitions: [],
310 directives: [],
311 selectionSet: x[0]
312}))`
313 :${ignored}?
314 ${selectionSet}
315`;
316
317const root = match(Kind.DOCUMENT, x => (
318 x.length
319 ? { kind: x.tag, definitions: x.slice() }
320 : undefined
321))`
322 ${queryShorthand}
323 | (${operationDefinition} | ${fragmentDefinition})+
324`;
325
326const _parse = makeParser(root);
327const _parseValue = makeParser(value);
328const _parseType = makeParser(type);
329
330export function parse(input) {
331 const result = _parse(input);
332 if (result == null) throw new GraphQLError('Syntax Error');
333 return result;
334}
335
336export function parseValue(input) {
337 const result = _parseValue(input);
338 if (result == null) throw new GraphQLError('Syntax Error');
339 return result;
340}
341
342export function parseType(input) {
343 const result = _parseType(input);
344 if (result == null) throw new GraphQLError('Syntax Error');
345 return result;
346}