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}