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 } from 'graphql';
8import { match, parse as makeParser } from 'reghex';
9
10// 2.1.7: Includes commas, and line comments
11const ignored = match('_ignored')`
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 value: 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('_number', 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('_value', 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('_argumentset')`
130 (
131 (?: ${'('} ${ignored}?)
132 ${arg}+
133 (?: ${')'} ${ignored}?)
134 )?
135`;
136
137const directive = match(Kind.DIRECTIVE, x => ({
138 kind: x.tag,
139 name: x[0],
140 arguments: x[1]
141}))`
142 :${'@'} ${name} :${ignored}?
143 ${args}?
144 :${ignored}?
145`;
146
147const directives = match('_directiveset')`
148 :${ignored}?
149 ${directive}*
150`;
151
152const field = match(Kind.FIELD, x => {
153 let i = 0;
154 return {
155 kind: x.tag,
156 alias: x[1].kind === 'Name' ? x[i++] : undefined,
157 name: x[i++],
158 arguments: x[i++],
159 directives: x[i++],
160 selectionSet: x[i++],
161 };
162})`
163 ${name}
164 :${ignored}?
165 ((?: ${':'} ${ignored}?) ${name})?
166 :${ignored}?
167 ${args}
168 ${directives}
169 ${selectionSet}?
170`;
171
172// 2.11: The type declarations may be simplified since there's little room
173// for error in this limited type system.
174const type = match('_type', x => {
175 const node = x[0].kind === 'Name'
176 ? { kind: Kind.NAMED_TYPE, name: x[0] }
177 : { kind: Kind.LIST_TYPE, type: x[0] }
178 return x[1] === '!'
179 ? { kind: Kind.NON_NULL_TYPE, type: node }
180 : node;
181})`
182 (
183 (
184 (?: ${'['} ${ignored}?)
185 ${type}
186 (?: ${ignored}? ${']'} ${ignored}?)
187 ) | ${name}
188 )
189 ${'!'}?
190 :${ignored}?
191`;
192
193const typeCondition = match('_typecondition', x => ({
194 kind: Kind.NAMED_TYPE,
195 name: x[0]
196}))`
197 (?: ${'on'} ${ignored})
198 ${name}
199 :${ignored}?
200`;
201
202const inlineFragment = match(Kind.INLINE_FRAGMENT, x => ({
203 kind: x.tag,
204 typeCondition: x[0],
205 directives: x[1],
206 selectionSet: x[2]
207}))`
208 (?: ${'...'} ${ignored}?)
209 ${typeCondition}
210 ${directives}
211 ${selectionSet}
212`;
213
214const fragmentSpread = match(Kind.FRAGMENT_SPREAD, x => ({
215 kind: x.tag,
216 name: x[0],
217 directives: x[1]
218}))`
219 (?: ${'...'} ${ignored}?)
220 !${'on'}
221 ${name}
222 :${ignored}?
223 ${directives}
224`;
225
226const selectionSet = match(Kind.SELECTION_SET, x => ({
227 kind: x.tag,
228 selections:x,
229}))`
230 (?: ${'{'} ${ignored}?)
231 (
232 ${inlineFragment} |
233 ${fragmentSpread} |
234 ${field}
235 )+
236 (?: ${'}'} ${ignored}?)
237`;
238
239const varDefinitionDefault = match('_defaultvariable', x => x[0])`
240 (?: ${'='} ${ignored}?)
241 ${value}
242`;
243
244const varDefinition = match(Kind.VARIABLE_DEFINITION, x => ({
245 kind: x.tag,
246 variable: x[0],
247 type: x[1],
248 defaultValue: !x[2].tag ? x[2] : undefined,
249 directives: x[2].tag ? x[2] : x[3],
250}))`
251 ${variable}
252 (?: ${ignored}? ${':'} ${ignored}?)
253 ${type}
254 ${varDefinitionDefault}?
255 ${directives}
256 :${ignored}?
257`;
258
259const varDefinitions = match('_variabledefinitionset')`
260 (?: ${'('} ${ignored}?)
261 ${varDefinition}+
262 (?: ${')'} ${ignored}?)
263`;
264
265const fragmentDefinition = match(Kind.FRAGMENT_DEFINITION, x => ({
266 kind: x.tag,
267 name: x[0],
268 typeCondition: x[1],
269 directives: x[2],
270 selectionSet: x[3],
271}))`
272 (?: ${'fragment'} ${ignored})
273 !${'on'}
274 ${name}
275 :${ignored}
276 ${typeCondition}
277 ${directives}
278 ${selectionSet}
279`;
280
281const operationDefinition = match(Kind.OPERATION_DEFINITION, x => {
282 let i = 1;
283 return {
284 kind: x.tag,
285 operation: x[0],
286 name: x.length === 5 ? x[i++] : undefined,
287 variableDefinitions: x[i].tag === '_variabledefinitionset' ? x[i++] : null,
288 directives: x[i++],
289 selectionSet: x[i],
290 };
291})`
292 :${ignored}?
293 ${/query|mutation|subscription/}
294 ((?: ${ignored}) ${name})?
295 :${ignored}?
296 ${varDefinitions}?
297 ${directives}
298 ${selectionSet}
299`;
300
301const queryShorthand = match(Kind.OPERATION_DEFINITION, x => ({
302 kind: x.tag,
303 operation: 'query',
304 name: undefined,
305 variableDefinitions: [],
306 directives: [],
307 selectionSet: x[0]
308}))`
309 :${ignored}?
310 ${selectionSet}
311`;
312
313const root = match(Kind.DOCUMENT, x => (
314 x.length
315 ? { kind: x.tag, definitions: x }
316 : undefined
317))`
318 ${queryShorthand}
319 | (${operationDefinition} | ${fragmentDefinition})+
320`;
321
322export const parse = makeParser(root);
323export const parseValue = makeParser(value);
324export const parseType = makeParser(type);