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 { GraphQLError } from '../error/GraphQLError';
9import { match, parse as makeParser } from 'reghex';
10
11// 2.1.7: Includes commas, and line comments
12const ignored = /([\s,]|#[^\n\r]+)+/;
13
14// 2.1.9: Limited to ASCII character set, so regex shortcodes are fine
15const name = match(Kind.NAME, (x) => ({
16 kind: x.tag,
17 value: x[0],
18}))`
19 ${/[_\w][_\d\w]*/}
20`;
21
22const null_ = match(Kind.NULL, (x) => ({
23 kind: x.tag,
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 quotes.
63const string = match(Kind.STRING, (x) => ({
64 kind: x.tag,
65 value: x[0],
66}))`
67 (:${'"""'} ${/[\s\S]+?(?=""")/} :${'"""'})
68 | (:${'"'} ${/[^"\r\n]*/} :${'"'})
69`;
70
71const list = match(Kind.LIST, (x) => ({
72 kind: x.tag,
73 values: x.slice(),
74}))`
75 :${'['}
76 ${() => value}*
77 (?: ${ignored}? ${']'} ${ignored}?)
78`;
79
80const objectField = match(Kind.OBJECT_FIELD, (x) => ({
81 kind: x.tag,
82 name: x[0],
83 value: x[1],
84}))`
85 :${ignored}?
86 ${name}
87 (?: ${ignored}? ${':'})
88 ${() => value}
89`;
90
91const object = match(Kind.OBJECT, (x) => ({
92 kind: x.tag,
93 fields: x.slice(),
94}))`
95 :${'{'}
96 ${objectField}*
97 (?: ${'}'} ${ignored}?)
98`;
99
100// 2.9: This matches the spec closely and is complete
101const value = match(null, (x) => x[0])`
102 :${ignored}?
103 (
104 ${null_}
105 | ${bool}
106 | ${variable}
107 | ${string}
108 | ${number}
109 | ${enum_}
110 | ${list}
111 | ${object}
112 )
113 :${ignored}?
114`;
115
116const arg = match(Kind.ARGUMENT, (x) => ({
117 kind: x.tag,
118 name: x[0],
119 value: x[1],
120}))`
121 ${name}
122 (?: ${ignored}? ${':'} ${ignored}?)
123 ${value}
124`;
125
126const args = match()`
127 :${ignored}?
128 (
129 (?: ${'('} ${ignored}?)
130 ${arg}+
131 (?: ${')'} ${ignored}?)
132 )?
133`;
134
135const directive = match(Kind.DIRECTIVE, (x) => ({
136 kind: x.tag,
137 name: x[0],
138 arguments: x[1],
139}))`
140 :${'@'} ${name} :${ignored}?
141 ${args}?
142 :${ignored}?
143`;
144
145const directives = match()`
146 :${ignored}?
147 ${directive}*
148`;
149
150const nullability = match(null, (x) => {
151 return x[0] === '?' ? 'optional' : 'required';
152})`
153 :${ignored}?
154 ${/[?!]/}
155`;
156
157const field = match(Kind.FIELD, (x) => {
158 let i = 0;
159 return {
160 kind: x.tag,
161 alias: x[1].kind === Kind.NAME ? x[i++] : undefined,
162 name: x[i++],
163 required: typeof x[i] === 'string' ? x[i++] : 'unset',
164 arguments: x[i++],
165 directives: x[i++],
166 selectionSet: x[i++],
167 };
168})`
169 :${ignored}?
170 ${name}
171 (
172 (?: ${ignored}? ${':'} ${ignored}?)
173 ${name}
174 )?
175 ${nullability}?
176 ${args}
177 ${directives}
178 ${() => selectionSet}?
179`;
180
181// 2.11: The type declarations may be simplified since there's little room
182// for error in this limited type system.
183const type = match(null, (x) => {
184 const node =
185 x[0].kind === 'Name'
186 ? { kind: Kind.NAMED_TYPE, name: x[0] }
187 : { kind: Kind.LIST_TYPE, type: x[0] };
188 return x[1] === '!' ? { kind: Kind.NON_NULL_TYPE, type: node } : node;
189})`
190 (
191 (
192 (?: ${'['} ${ignored}?)
193 ${() => type}
194 (?: ${ignored}? ${']'} ${ignored}?)
195 ) | ${name}
196 )
197 ${'!'}?
198 :${ignored}?
199`;
200
201const typeCondition = match(null, (x) => ({
202 kind: Kind.NAMED_TYPE,
203 name: x[0],
204}))`
205 (?: ${ignored}? ${'on'} ${ignored})
206 ${name}
207 :${ignored}?
208`;
209
210const inlineFragment = match(Kind.INLINE_FRAGMENT, (x) => {
211 let i = 0;
212 return {
213 kind: x.tag,
214 typeCondition: x[i].kind === Kind.NAMED_TYPE ? x[i++] : undefined,
215 directives: x[i++],
216 selectionSet: x[i],
217 };
218})`
219 :${'...'}
220 ${typeCondition}?
221 ${directives}
222 ${() => selectionSet}
223`;
224
225const fragmentSpread = match(Kind.FRAGMENT_SPREAD, (x) => ({
226 kind: x.tag,
227 name: x[0],
228 directives: x[1],
229}))`
230 (?: ${'...'} ${ignored}?)
231 !${'on'}
232 ${name}
233 :${ignored}?
234 ${directives}
235`;
236
237const selectionSet = match(Kind.SELECTION_SET, (x) => ({
238 kind: x.tag,
239 selections: x.slice(),
240}))`
241 :${ignored}?
242 (?: ${'{'} ${ignored}?)
243 (
244 ${inlineFragment} |
245 ${fragmentSpread} |
246 ${field}
247 )+
248 (?: ${'}'} ${ignored}?)
249`;
250
251const varDefinitionDefault = match(null, (x) => x[0])`
252 (?: ${'='} ${ignored}?)
253 ${value}
254`;
255
256const varDefinition = match(Kind.VARIABLE_DEFINITION, (x) => ({
257 kind: x.tag,
258 variable: x[0],
259 type: x[1],
260 defaultValue: x[2].kind ? x[2] : undefined,
261 directives: !x[2].kind ? x[2] : x[3],
262}))`
263 ${variable}
264 (?: ${ignored}? ${':'} ${ignored}?)
265 ${type}
266 ${varDefinitionDefault}?
267 ${directives}
268 :${ignored}?
269`;
270
271const varDefinitions = match('vars')`
272 :${ignored}?
273 (?: ${'('} ${ignored}?)
274 ${varDefinition}+
275 (?: ${')'} ${ignored}?)
276`;
277
278const fragmentDefinition = match(Kind.FRAGMENT_DEFINITION, (x) => ({
279 kind: x.tag,
280 name: x[0],
281 typeCondition: x[1],
282 directives: x[2],
283 selectionSet: x[3],
284}))`
285 (?: ${ignored}? ${'fragment'} ${ignored})
286 ${name}
287 ${typeCondition}
288 ${directives}
289 ${selectionSet}
290`;
291
292const operationDefinition = match(Kind.OPERATION_DEFINITION, (x) => {
293 let i = 1;
294 return {
295 kind: x.tag,
296 operation: x[0],
297 name: x[i].kind === Kind.NAME ? x[i++] : undefined,
298 variableDefinitions: x[i].tag === 'vars' ? x[i++].slice() : [],
299 directives: x[i++],
300 selectionSet: x[i],
301 };
302})`
303 :${ignored}?
304 ${/query|mutation|subscription/}
305 (:${ignored} ${name})?
306 ${varDefinitions}?
307 ${directives}
308 ${selectionSet}
309`;
310
311const queryShorthand = match(Kind.OPERATION_DEFINITION, (x) => ({
312 kind: x.tag,
313 operation: 'query',
314 name: undefined,
315 variableDefinitions: [],
316 directives: [],
317 selectionSet: x[0],
318}))`
319 ${selectionSet}
320`;
321
322const root = match(Kind.DOCUMENT, (x) =>
323 x.length ? { kind: x.tag, definitions: x.slice() } : undefined
324)`
325 (${queryShorthand} | ${operationDefinition} | ${fragmentDefinition})*
326`;
327
328const _parse = makeParser(root);
329const _parseValue = makeParser(value);
330const _parseType = makeParser(type);
331
332export function parse(input) {
333 const result = _parse(input);
334 if (result == null) throw new GraphQLError('Syntax Error');
335 return result;
336}
337
338export function parseValue(input) {
339 const result = _parseValue(input);
340 if (result == null) throw new GraphQLError('Syntax Error');
341 return result;
342}
343
344export function parseType(input) {
345 const result = _parseType(input);
346 if (result == null) throw new GraphQLError('Syntax Error');
347 return result;
348}