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}