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