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