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