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 kind: x.tag, 204 typeCondition: x[0], 205 directives: x[1], 206 selectionSet: x[2] 207}))` 208 (?: ${'...'} ${ignored}?) 209 ${typeCondition} 210 ${directives} 211 ${selectionSet} 212`; 213 214const fragmentSpread = match(Kind.FRAGMENT_SPREAD, x => ({ 215 kind: x.tag, 216 name: x[0], 217 directives: x[1] 218}))` 219 (?: ${'...'} ${ignored}?) 220 !${'on'} 221 ${name} 222 :${ignored}? 223 ${directives} 224`; 225 226const selectionSet = match(Kind.SELECTION_SET, x => ({ 227 kind: x.tag, 228 selections:x, 229}))` 230 (?: ${'{'} ${ignored}?) 231 ( 232 ${inlineFragment} | 233 ${fragmentSpread} | 234 ${field} 235 )+ 236 (?: ${'}'} ${ignored}?) 237`; 238 239const varDefinitionDefault = match('_defaultvariable', x => x[0])` 240 (?: ${'='} ${ignored}?) 241 ${value} 242`; 243 244const varDefinition = match(Kind.VARIABLE_DEFINITION, x => ({ 245 kind: x.tag, 246 variable: x[0], 247 type: x[1], 248 defaultValue: !x[2].tag ? x[2] : undefined, 249 directives: x[2].tag ? x[2] : x[3], 250}))` 251 ${variable} 252 (?: ${ignored}? ${':'} ${ignored}?) 253 ${type} 254 ${varDefinitionDefault}? 255 ${directives} 256 :${ignored}? 257`; 258 259const varDefinitions = match('_variabledefinitionset')` 260 (?: ${'('} ${ignored}?) 261 ${varDefinition}+ 262 (?: ${')'} ${ignored}?) 263`; 264 265const fragmentDefinition = match(Kind.FRAGMENT_DEFINITION, x => ({ 266 kind: x.tag, 267 name: x[0], 268 typeCondition: x[1], 269 directives: x[2], 270 selectionSet: x[3], 271}))` 272 (?: ${'fragment'} ${ignored}) 273 !${'on'} 274 ${name} 275 :${ignored} 276 ${typeCondition} 277 ${directives} 278 ${selectionSet} 279`; 280 281const operationDefinition = match(Kind.OPERATION_DEFINITION, x => { 282 let i = 1; 283 return { 284 kind: x.tag, 285 operation: x[0], 286 name: x.length === 5 ? x[i++] : undefined, 287 variableDefinitions: x[i].tag === '_variabledefinitionset' ? x[i++] : null, 288 directives: x[i++], 289 selectionSet: x[i], 290 }; 291})` 292 :${ignored}? 293 ${/query|mutation|subscription/} 294 ((?: ${ignored}) ${name})? 295 :${ignored}? 296 ${varDefinitions}? 297 ${directives} 298 ${selectionSet} 299`; 300 301const queryShorthand = match(Kind.OPERATION_DEFINITION, x => ({ 302 kind: x.tag, 303 operation: 'query', 304 name: undefined, 305 variableDefinitions: [], 306 directives: [], 307 selectionSet: x[0] 308}))` 309 :${ignored}? 310 ${selectionSet} 311`; 312 313const root = match(Kind.DOCUMENT, x => ( 314 x.length 315 ? { kind: x.tag, definitions: x } 316 : undefined 317))` 318 ${queryShorthand} 319 | (${operationDefinition} | ${fragmentDefinition})+ 320`; 321 322export const parse = makeParser(root); 323export const parseValue = makeParser(value); 324export const parseType = makeParser(type);