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}