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