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 = 179 x[0].kind === 'Name' 180 ? { kind: Kind.NAMED_TYPE, name: x[0] } 181 : { kind: Kind.LIST_TYPE, type: x[0] }; 182 return x[1] === '!' ? { kind: Kind.NON_NULL_TYPE, type: node } : node; 183})` 184 ( 185 ( 186 (?: ${'['} ${ignored}?) 187 ${type} 188 (?: ${ignored}? ${']'} ${ignored}?) 189 ) | ${name} 190 ) 191 ${'!'}? 192 :${ignored}? 193`; 194 195const typeCondition = match(null, (x) => ({ 196 kind: Kind.NAMED_TYPE, 197 name: x[0], 198}))` 199 (?: ${ignored} ${'on'} ${ignored}) 200 ${name} 201 :${ignored}? 202`; 203 204const inlineFragment = match(Kind.INLINE_FRAGMENT, (x) => { 205 let i = 0; 206 return { 207 kind: x.tag, 208 typeCondition: x[i].kind === Kind.NAMED_TYPE ? x[i++] : undefined, 209 directives: x[i++], 210 selectionSet: x[i], 211 }; 212})` 213 (?: ${'...'} ${ignored}?) 214 ${typeCondition}? 215 ${directives} 216 ${selectionSet} 217`; 218 219const fragmentSpread = match(Kind.FRAGMENT_SPREAD, (x) => ({ 220 kind: x.tag, 221 name: x[0], 222 directives: x[1], 223}))` 224 (?: ${'...'} ${ignored}?) 225 !${'on'} 226 ${name} 227 :${ignored}? 228 ${directives} 229`; 230 231const selectionSet = match(Kind.SELECTION_SET, (x) => ({ 232 kind: x.tag, 233 selections: x.slice(), 234}))` 235 (?: ${'{'} ${ignored}?) 236 ( 237 ${inlineFragment} | 238 ${fragmentSpread} | 239 ${field} 240 )+ 241 (?: ${'}'} ${ignored}?) 242`; 243 244const varDefinitionDefault = match(null, (x) => x[0])` 245 (?: ${'='} ${ignored}?) 246 ${value} 247`; 248 249const varDefinition = match(Kind.VARIABLE_DEFINITION, (x) => ({ 250 kind: x.tag, 251 variable: x[0], 252 type: x[1], 253 defaultValue: x[2].kind ? x[2] : undefined, 254 directives: !x[2].kind ? x[2] : x[3], 255}))` 256 ${variable} 257 (?: ${ignored}? ${':'} ${ignored}?) 258 ${type} 259 ${varDefinitionDefault}? 260 ${directives} 261 :${ignored}? 262`; 263 264const varDefinitions = match('vars')` 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.length === 5 ? x[i++] : undefined, 290 variableDefinitions: x[i].tag === 'vars' ? x[i++].slice() : null, 291 directives: x[i++], 292 selectionSet: x[i], 293 }; 294})` 295 :${ignored}? 296 ${/query|mutation|subscription/} 297 ((?: ${ignored}) ${name})? 298 :${ignored}? 299 ${varDefinitions}? 300 ${directives} 301 ${selectionSet} 302`; 303 304const queryShorthand = match(Kind.OPERATION_DEFINITION, (x) => ({ 305 kind: x.tag, 306 operation: 'query', 307 name: undefined, 308 variableDefinitions: [], 309 directives: [], 310 selectionSet: x[0], 311}))` 312 :${ignored}? 313 ${selectionSet} 314`; 315 316const root = match(Kind.DOCUMENT, (x) => 317 x.length ? { kind: x.tag, definitions: x.slice() } : undefined 318)` 319 ${queryShorthand} 320 | (${operationDefinition} | ${fragmentDefinition})+ 321`; 322 323const _parse = makeParser(root); 324const _parseValue = makeParser(value); 325const _parseType = makeParser(type); 326 327export function parse(input) { 328 const result = _parse(input); 329 if (result == null) throw new GraphQLError('Syntax Error'); 330 return result; 331} 332 333export function parseValue(input) { 334 const result = _parseValue(input); 335 if (result == null) throw new GraphQLError('Syntax Error'); 336 return result; 337} 338 339export function parseType(input) { 340 const result = _parseType(input); 341 if (result == null) throw new GraphQLError('Syntax Error'); 342 return result; 343}