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 =
179 x[0].kind === 'Name'
180 ? { kind: Kind.NAMED_TYPE, name: x[0] }
181 : { kind: Kind.LIST_TYPE, type: x[0] };
182 return x[1] === '!' ? { kind: Kind.NON_NULL_TYPE, type: node } : node;
183})`
184 (
185 (
186 (?: ${'['} ${ignored}?)
187 ${type}
188 (?: ${ignored}? ${']'} ${ignored}?)
189 ) | ${name}
190 )
191 ${'!'}?
192 :${ignored}?
193`;
194
195const typeCondition = match(null, (x) => ({
196 kind: Kind.NAMED_TYPE,
197 name: x[0],
198}))`
199 (?: ${ignored} ${'on'} ${ignored})
200 ${name}
201 :${ignored}?
202`;
203
204const inlineFragment = match(Kind.INLINE_FRAGMENT, (x) => {
205 let i = 0;
206 return {
207 kind: x.tag,
208 typeCondition: x[i].kind === Kind.NAMED_TYPE ? x[i++] : undefined,
209 directives: x[i++],
210 selectionSet: x[i],
211 };
212})`
213 (?: ${'...'} ${ignored}?)
214 ${typeCondition}?
215 ${directives}
216 ${selectionSet}
217`;
218
219const fragmentSpread = match(Kind.FRAGMENT_SPREAD, (x) => ({
220 kind: x.tag,
221 name: x[0],
222 directives: x[1],
223}))`
224 (?: ${'...'} ${ignored}?)
225 !${'on'}
226 ${name}
227 :${ignored}?
228 ${directives}
229`;
230
231const selectionSet = match(Kind.SELECTION_SET, (x) => ({
232 kind: x.tag,
233 selections: x.slice(),
234}))`
235 (?: ${'{'} ${ignored}?)
236 (
237 ${inlineFragment} |
238 ${fragmentSpread} |
239 ${field}
240 )+
241 (?: ${'}'} ${ignored}?)
242`;
243
244const varDefinitionDefault = match(null, (x) => x[0])`
245 (?: ${'='} ${ignored}?)
246 ${value}
247`;
248
249const varDefinition = match(Kind.VARIABLE_DEFINITION, (x) => ({
250 kind: x.tag,
251 variable: x[0],
252 type: x[1],
253 defaultValue: x[2].kind ? x[2] : undefined,
254 directives: !x[2].kind ? x[2] : x[3],
255}))`
256 ${variable}
257 (?: ${ignored}? ${':'} ${ignored}?)
258 ${type}
259 ${varDefinitionDefault}?
260 ${directives}
261 :${ignored}?
262`;
263
264const varDefinitions = match('vars')`
265 (?: ${'('} ${ignored}?)
266 ${varDefinition}+
267 (?: ${')'} ${ignored}?)
268`;
269
270const fragmentDefinition = match(Kind.FRAGMENT_DEFINITION, (x) => ({
271 kind: x.tag,
272 name: x[0],
273 typeCondition: x[1],
274 directives: x[2],
275 selectionSet: x[3],
276}))`
277 (?: ${ignored}? ${'fragment'} ${ignored})
278 ${name}
279 ${typeCondition}
280 ${directives}
281 ${selectionSet}
282`;
283
284const operationDefinition = match(Kind.OPERATION_DEFINITION, (x) => {
285 let i = 1;
286 return {
287 kind: x.tag,
288 operation: x[0],
289 name: x.length === 5 ? x[i++] : undefined,
290 variableDefinitions: x[i].tag === 'vars' ? x[i++].slice() : null,
291 directives: x[i++],
292 selectionSet: x[i],
293 };
294})`
295 :${ignored}?
296 ${/query|mutation|subscription/}
297 ((?: ${ignored}) ${name})?
298 :${ignored}?
299 ${varDefinitions}?
300 ${directives}
301 ${selectionSet}
302`;
303
304const queryShorthand = match(Kind.OPERATION_DEFINITION, (x) => ({
305 kind: x.tag,
306 operation: 'query',
307 name: undefined,
308 variableDefinitions: [],
309 directives: [],
310 selectionSet: x[0],
311}))`
312 :${ignored}?
313 ${selectionSet}
314`;
315
316const root = match(Kind.DOCUMENT, (x) =>
317 x.length ? { kind: x.tag, definitions: x.slice() } : undefined
318)`
319 ${queryShorthand}
320 | (${operationDefinition} | ${fragmentDefinition})+
321`;
322
323const _parse = makeParser(root);
324const _parseValue = makeParser(value);
325const _parseType = makeParser(type);
326
327export function parse(input) {
328 const result = _parse(input);
329 if (result == null) throw new GraphQLError('Syntax Error');
330 return result;
331}
332
333export function parseValue(input) {
334 const result = _parseValue(input);
335 if (result == null) throw new GraphQLError('Syntax Error');
336 return result;
337}
338
339export function parseType(input) {
340 const result = _parseType(input);
341 if (result == null) throw new GraphQLError('Syntax Error');
342 return result;
343}