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 field = match(Kind.FIELD, (x) => { 151 let i = 0; 152 return { 153 kind: x.tag, 154 alias: x[1].kind === Kind.NAME ? x[i++] : undefined, 155 name: x[i++], 156 arguments: x[i++], 157 directives: x[i++], 158 selectionSet: x[i++], 159 }; 160})` 161 :${ignored}? 162 ${name} 163 ( 164 (?: ${ignored}? ${':'} ${ignored}?) 165 ${name} 166 )? 167 ${args} 168 ${directives} 169 ${() => selectionSet}? 170`; 171 172// 2.11: The type declarations may be simplified since there's little room 173// for error in this limited type system. 174const type = match(null, (x) => { 175 const node = 176 x[0].kind === 'Name' 177 ? { kind: Kind.NAMED_TYPE, name: x[0] } 178 : { kind: Kind.LIST_TYPE, type: x[0] }; 179 return x[1] === '!' ? { kind: Kind.NON_NULL_TYPE, type: node } : node; 180})` 181 ( 182 ( 183 (?: ${'['} ${ignored}?) 184 ${() => type} 185 (?: ${ignored}? ${']'} ${ignored}?) 186 ) | ${name} 187 ) 188 ${'!'}? 189 :${ignored}? 190`; 191 192const typeCondition = match(null, (x) => ({ 193 kind: Kind.NAMED_TYPE, 194 name: x[0], 195}))` 196 (?: ${ignored}? ${'on'} ${ignored}) 197 ${name} 198 :${ignored}? 199`; 200 201const inlineFragment = match(Kind.INLINE_FRAGMENT, (x) => { 202 let i = 0; 203 return { 204 kind: x.tag, 205 typeCondition: x[i].kind === Kind.NAMED_TYPE ? x[i++] : undefined, 206 directives: x[i++], 207 selectionSet: x[i], 208 }; 209})` 210 :${'...'} 211 ${typeCondition}? 212 ${directives} 213 ${() => selectionSet} 214`; 215 216const fragmentSpread = match(Kind.FRAGMENT_SPREAD, (x) => ({ 217 kind: x.tag, 218 name: x[0], 219 directives: x[1], 220}))` 221 (?: ${'...'} ${ignored}?) 222 !${'on'} 223 ${name} 224 :${ignored}? 225 ${directives} 226`; 227 228const selectionSet = match(Kind.SELECTION_SET, (x) => ({ 229 kind: x.tag, 230 selections: x.slice(), 231}))` 232 :${ignored}? 233 (?: ${'{'} ${ignored}?) 234 ( 235 ${inlineFragment} | 236 ${fragmentSpread} | 237 ${field} 238 )+ 239 (?: ${'}'} ${ignored}?) 240`; 241 242const varDefinitionDefault = match(null, (x) => x[0])` 243 (?: ${'='} ${ignored}?) 244 ${value} 245`; 246 247const varDefinition = match(Kind.VARIABLE_DEFINITION, (x) => ({ 248 kind: x.tag, 249 variable: x[0], 250 type: x[1], 251 defaultValue: x[2].kind ? x[2] : undefined, 252 directives: !x[2].kind ? x[2] : x[3], 253}))` 254 ${variable} 255 (?: ${ignored}? ${':'} ${ignored}?) 256 ${type} 257 ${varDefinitionDefault}? 258 ${directives} 259 :${ignored}? 260`; 261 262const varDefinitions = match('vars')` 263 :${ignored}? 264 (?: ${'('} ${ignored}?) 265 ${varDefinition}+ 266 (?: ${')'} ${ignored}?) 267`; 268 269const fragmentDefinition = match(Kind.FRAGMENT_DEFINITION, (x) => ({ 270 kind: x.tag, 271 name: x[0], 272 typeCondition: x[1], 273 directives: x[2], 274 selectionSet: x[3], 275}))` 276 (?: ${ignored}? ${'fragment'} ${ignored}) 277 ${name} 278 ${typeCondition} 279 ${directives} 280 ${selectionSet} 281`; 282 283const operationDefinition = match(Kind.OPERATION_DEFINITION, (x) => { 284 let i = 1; 285 return { 286 kind: x.tag, 287 operation: x[0], 288 name: x[i].kind === Kind.NAME ? x[i++] : undefined, 289 variableDefinitions: x[i].tag === 'vars' ? x[i++].slice() : [], 290 directives: x[i++], 291 selectionSet: x[i], 292 }; 293})` 294 :${ignored}? 295 ${/query|mutation|subscription/} 296 (:${ignored} ${name})? 297 ${varDefinitions}? 298 ${directives} 299 ${selectionSet} 300`; 301 302const queryShorthand = match(Kind.OPERATION_DEFINITION, (x) => ({ 303 kind: x.tag, 304 operation: 'query', 305 name: undefined, 306 variableDefinitions: [], 307 directives: [], 308 selectionSet: x[0], 309}))` 310 ${selectionSet} 311`; 312 313const root = match(Kind.DOCUMENT, (x) => 314 x.length ? { kind: x.tag, definitions: x.slice() } : undefined 315)` 316 (${queryShorthand} | ${operationDefinition} | ${fragmentDefinition})* 317`; 318 319const _parse = makeParser(root); 320const _parseValue = makeParser(value); 321const _parseType = makeParser(type); 322 323export function parse(input) { 324 const result = _parse(input); 325 if (result == null) throw new GraphQLError('Syntax Error'); 326 return result; 327} 328 329export function parseValue(input) { 330 const result = _parseValue(input); 331 if (result == null) throw new GraphQLError('Syntax Error'); 332 return result; 333} 334 335export function parseType(input) { 336 const result = _parseType(input); 337 if (result == null) throw new GraphQLError('Syntax Error'); 338 return result; 339}