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 { match, parse as makeParser } from 'reghex'; 9 10// 2.1.7: Includes commas, and line comments 11const ignored = match('_ignored')` 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 value: 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('_number', 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('_value', 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('_argumentset')` 130 ( 131 (?: ${'('} ${ignored}?) 132 ${arg}+ 133 (?: ${')'} ${ignored}?) 134 )? 135`; 136 137const directive = match(Kind.DIRECTIVE, x => ({ 138 kind: x.tag, 139 name: x[0], 140 arguments: x[1] 141}))` 142 :${'@'} ${name} :${ignored}? 143 ${args}? 144 :${ignored}? 145`; 146 147const directives = match('_directiveset')` 148 :${ignored}? 149 ${directive}* 150`; 151 152const field = match(Kind.FIELD, x => { 153 let i = 0; 154 return { 155 kind: x.tag, 156 alias: x[1].kind === 'Name' ? x[i++] : undefined, 157 name: x[i++], 158 arguments: x[i++], 159 directives: x[i++], 160 selectionSet: x[i++], 161 }; 162})` 163 ${name} 164 :${ignored}? 165 ((?: ${':'} ${ignored}?) ${name})? 166 :${ignored}? 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('_type', x => { 175 const node = x[0].kind === 'Name' 176 ? { kind: Kind.NAMED_TYPE, name: x[0] } 177 : { kind: Kind.LIST_TYPE, type: x[0] } 178 return x[1] === '!' 179 ? { kind: Kind.NON_NULL_TYPE, type: node } 180 : node; 181})` 182 ( 183 ( 184 (?: ${'['} ${ignored}?) 185 ${type} 186 (?: ${ignored}? ${']'} ${ignored}?) 187 ) | ${name} 188 ) 189 ${'!'}? 190 :${ignored}? 191`; 192 193const typeCondition = match('_typecondition', x => ({ 194 kind: Kind.NAMED_TYPE, 195 name: x[0] 196}))` 197 (?: ${'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].tag === '_typecondition' ? x[i++] : undefined, 207 directives: x[i++], 208 selectionSet: x[i] 209 }; 210})` 211 (?: ${'...'} ${ignored}?) 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, 232}))` 233 (?: ${'{'} ${ignored}?) 234 ( 235 ${inlineFragment} | 236 ${fragmentSpread} | 237 ${field} 238 )+ 239 (?: ${'}'} ${ignored}?) 240`; 241 242const varDefinitionDefault = match('_defaultvariable', 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].tag ? x[2] : undefined, 252 directives: x[2].tag ? x[2] : x[3], 253}))` 254 ${variable} 255 (?: ${ignored}? ${':'} ${ignored}?) 256 ${type} 257 ${varDefinitionDefault}? 258 ${directives} 259 :${ignored}? 260`; 261 262const varDefinitions = match('_variabledefinitionset')` 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 (?: ${'fragment'} ${ignored}) 276 !${'on'} 277 ${name} 278 :${ignored} 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 === '_variabledefinitionset' ? x[i++] : 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 318 ? { kind: x.tag, definitions: x } 319 : undefined 320))` 321 ${queryShorthand} 322 | (${operationDefinition} | ${fragmentDefinition})+ 323`; 324 325export const parse = makeParser(root); 326export const parseValue = makeParser(value); 327export const parseType = makeParser(type);