Something went wrong. Try again.
Mirror: The spec-compliant minimum of client-side GraphQL.
Something went wrong. Try again.
18 kB · 607 lines
TypeScript
at main
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608/** * This is a spec-compliant implementation of a GraphQL query language parser, * up-to-date with the October 2021 Edition. Unlike the reference implementation * in graphql.js it will only parse the query language, but not the schema * language. */
import type * as GraphQL from 'graphql';
import type { Kind, OperationTypeNode } from './kind';import { GraphQLError } from './error';import type { Or, Location, Source } from './types';import type { StringValueNode } from './ast';import type * as ast from './ast';
let input: string;let idx: number;
function error(kind: string) { return new GraphQLError(`Syntax Error: Unexpected token at ${idx} in ${kind}`);}
function advance(pattern: RegExp) { pattern.lastIndex = idx; if (pattern.test(input)) { const match = input.slice(idx, (idx = pattern.lastIndex)); return match; }}
const leadingRe = / +(?=[^\s])/y;function blockString(string: string) { const lines = string.split('\n'); let out = ''; let commonIndent = 0; let firstNonEmptyLine = 0; let lastNonEmptyLine = lines.length - 1; for (let i = 0; i < lines.length; i++) { leadingRe.lastIndex = 0; if (leadingRe.test(lines[i])) { if (i && (!commonIndent || leadingRe.lastIndex < commonIndent)) commonIndent = leadingRe.lastIndex; firstNonEmptyLine = firstNonEmptyLine || i; lastNonEmptyLine = i; } } for (let i = firstNonEmptyLine; i <= lastNonEmptyLine; i++) { if (i !== firstNonEmptyLine) out += '\n'; out += lines[i].slice(commonIndent).replace(/\\"""/g, '"""'); } return out;}
// Note: This is equivalent to: /(?:[\s,]*|#[^\n\r]*)*/yfunction ignored() { for ( let char = input.charCodeAt(idx++) | 0; char === 9 /*'\t'*/ || char === 10 /*'\n'*/ || char === 13 /*'\r'*/ || char === 32 /*' '*/ || char === 35 /*'#'*/ || char === 44 /*','*/ || char === 65279 /*'\ufeff'*/; char = input.charCodeAt(idx++) | 0 ) { if (char === 35 /*'#'*/) while ((char = input.charCodeAt(idx++) | 0) && char !== 10 && char !== 13); } idx--;}
function name(): string { const start = idx; for ( let char = input.charCodeAt(idx++) | 0; (char >= 48 /*'0'*/ && char <= 57) /*'9'*/ || (char >= 65 /*'A'*/ && char <= 90) /*'Z'*/ || char === 95 /*'_'*/ || (char >= 97 /*'a'*/ && char <= 122) /*'z'*/; char = input.charCodeAt(idx++) | 0 ); if (start === idx - 1) throw error('Name'); const value = input.slice(start, --idx); ignored(); return value;}
function nameNode(): ast.NameNode { return { kind: 'Name' as Or<GraphQL.Kind.NAME, Kind.NAME>, value: name(), };}
const restBlockStringRe = /(?:"""|(?:[\s\S]*?[^\\])""")/y;const floatPartRe = /(?:(?:\.\d+)?[eE][+-]?\d+|\.\d+)/y;
function value(constant: true): ast.ConstValueNode;function value(constant: boolean): ast.ValueNode;
function value(constant: boolean): ast.ValueNode { let match: string | undefined; switch (input.charCodeAt(idx)) { case 91: // '[' idx++; ignored(); const values: ast.ValueNode[] = []; while (input.charCodeAt(idx) !== 93 /*']'*/) values.push(value(constant)); idx++; ignored(); return { kind: 'ListValue' as Or<GraphQL.Kind.LIST, Kind.LIST>, values, };
case 123: // '{' idx++; ignored(); const fields: ast.ObjectFieldNode[] = []; while (input.charCodeAt(idx) !== 125 /*'}'*/) { const name = nameNode(); if (input.charCodeAt(idx++) !== 58 /*':'*/) throw error('ObjectField'); ignored(); fields.push({ kind: 'ObjectField' as Or<GraphQL.Kind.OBJECT_FIELD, Kind.OBJECT_FIELD>, name, value: value(constant), }); } idx++; ignored(); return { kind: 'ObjectValue' as Or<GraphQL.Kind.OBJECT, Kind.OBJECT>, fields, };
case 36: // '$' if (constant) throw error('Variable'); idx++; return { kind: 'Variable' as Or<GraphQL.Kind.VARIABLE, Kind.VARIABLE>, name: nameNode(), };
case 34: // '"' if (input.charCodeAt(idx + 1) === 34 && input.charCodeAt(idx + 2) === 34) { idx += 3; if ((match = advance(restBlockStringRe)) == null) throw error('StringValue'); ignored(); return { kind: 'StringValue' as Or<GraphQL.Kind.STRING, Kind.STRING>, value: blockString(match.slice(0, -3)), block: true, }; } else { const start = idx; idx++; let char: number; let isComplex = false; for ( char = input.charCodeAt(idx++) | 0; (char === 92 /*'\\'*/ && (idx++, (isComplex = true))) || (char !== 10 /*'\n'*/ && char !== 13 /*'\r'*/ && char !== 34 /*'"'*/ && char); char = input.charCodeAt(idx++) | 0 ); if (char !== 34) throw error('StringValue'); match = input.slice(start, idx); ignored(); return { kind: 'StringValue' as Or<GraphQL.Kind.STRING, Kind.STRING>, value: isComplex ? (JSON.parse(match) as string) : match.slice(1, -1), block: false, }; }
case 45: // '-' case 48: // '0' case 49: // '1' case 50: // '2' case 51: // '3' case 52: // '4' case 53: // '5' case 54: // '6' case 55: // '7' case 56: // '8' case 57: // '9' const start = idx++; let char: number; while ((char = input.charCodeAt(idx++) | 0) >= 48 /*'0'*/ && char <= 57 /*'9'*/); const intPart = input.slice(start, --idx); if ( (char = input.charCodeAt(idx)) === 46 /*'.'*/ || char === 69 /*'E'*/ || char === 101 /*'e'*/ ) { if ((match = advance(floatPartRe)) == null) throw error('FloatValue'); ignored(); return { kind: 'FloatValue' as Or<GraphQL.Kind.FLOAT, Kind.FLOAT>, value: intPart + match, }; } else { ignored(); return { kind: 'IntValue' as Or<GraphQL.Kind.INT, Kind.INT>, value: intPart, }; }
case 110: // 'n' if ( input.charCodeAt(idx + 1) === 117 && input.charCodeAt(idx + 2) === 108 && input.charCodeAt(idx + 3) === 108 ) { idx += 4; ignored(); return { kind: 'NullValue' as Or<GraphQL.Kind.NULL, Kind.NULL> }; } else break;
case 116: // 't' if ( input.charCodeAt(idx + 1) === 114 && input.charCodeAt(idx + 2) === 117 && input.charCodeAt(idx + 3) === 101 ) { idx += 4; ignored(); return { kind: 'BooleanValue' as Or<GraphQL.Kind.BOOLEAN, Kind.BOOLEAN>, value: true }; } else break;
case 102: // 'f' if ( input.charCodeAt(idx + 1) === 97 && input.charCodeAt(idx + 2) === 108 && input.charCodeAt(idx + 3) === 115 && input.charCodeAt(idx + 4) === 101 ) { idx += 5; ignored(); return { kind: 'BooleanValue' as Or<GraphQL.Kind.BOOLEAN, Kind.BOOLEAN>, value: false }; } else break; }
return { kind: 'EnumValue' as Or<GraphQL.Kind.ENUM, Kind.ENUM>, value: name(), };}
function arguments_(constant: boolean): ast.ArgumentNode[] | undefined { if (input.charCodeAt(idx) === 40 /*'('*/) { const args: ast.ArgumentNode[] = []; idx++; ignored(); do { const name = nameNode(); if (input.charCodeAt(idx++) !== 58 /*':'*/) throw error('Argument'); ignored(); args.push({ kind: 'Argument' as Or<GraphQL.Kind.ARGUMENT, Kind.ARGUMENT>, name, value: value(constant), }); } while (input.charCodeAt(idx) !== 41 /*')'*/); idx++; ignored(); return args; }}
function directives(constant: true): ast.ConstDirectiveNode[] | undefined;function directives(constant: boolean): ast.DirectiveNode[] | undefined;
function directives(constant: boolean): ast.DirectiveNode[] | undefined { if (input.charCodeAt(idx) === 64 /*'@'*/) { const directives: ast.DirectiveNode[] = []; do { idx++; directives.push({ kind: 'Directive' as Or<GraphQL.Kind.DIRECTIVE, Kind.DIRECTIVE>, name: nameNode(), arguments: arguments_(constant), }); } while (input.charCodeAt(idx) === 64 /*'@'*/); return directives; }}
function type(): ast.TypeNode { let lists = 0; while (input.charCodeAt(idx) === 91 /*'['*/) { lists++; idx++; ignored(); } let type: ast.TypeNode = { kind: 'NamedType' as Or<GraphQL.Kind.NAMED_TYPE, Kind.NAMED_TYPE>, name: nameNode(), }; do { if (input.charCodeAt(idx) === 33 /*'!'*/) { idx++; ignored(); type = { kind: 'NonNullType' as Or<GraphQL.Kind.NON_NULL_TYPE, Kind.NON_NULL_TYPE>, type: type as ast.NamedTypeNode | ast.ListTypeNode, } satisfies ast.NonNullTypeNode; } if (lists) { if (input.charCodeAt(idx++) !== 93 /*']'*/) throw error('NamedType'); ignored(); type = { kind: 'ListType' as Or<GraphQL.Kind.LIST_TYPE, Kind.LIST_TYPE>, type: type as ast.NamedTypeNode | ast.ListTypeNode, } satisfies ast.ListTypeNode; } } while (lists--); return type;}
function selectionSetStart(): ast.SelectionSetNode { if (input.charCodeAt(idx++) !== 123 /*'{'*/) throw error('SelectionSet'); ignored(); return selectionSet();}
function selectionSet(): ast.SelectionSetNode { const selections: ast.SelectionNode[] = []; do { if (input.charCodeAt(idx) === 46 /*'.'*/) { if (input.charCodeAt(++idx) !== 46 /*'.'*/ || input.charCodeAt(++idx) !== 46 /*'.'*/) throw error('SelectionSet'); idx++; ignored(); switch (input.charCodeAt(idx)) { case 64 /*'@'*/: selections.push({ kind: 'InlineFragment' as Or<GraphQL.Kind.INLINE_FRAGMENT, Kind.INLINE_FRAGMENT>, typeCondition: undefined, directives: directives(false), selectionSet: selectionSetStart(), }); break;
case 111 /*'o'*/: if (input.charCodeAt(idx + 1) === 110 /*'n'*/) { idx += 2; ignored(); selections.push({ kind: 'InlineFragment' as Or<GraphQL.Kind.INLINE_FRAGMENT, Kind.INLINE_FRAGMENT>, typeCondition: { kind: 'NamedType' as Or<GraphQL.Kind.NAMED_TYPE, Kind.NAMED_TYPE>, name: nameNode(), }, directives: directives(false), selectionSet: selectionSetStart(), }); } else { selections.push({ kind: 'FragmentSpread' as Or<GraphQL.Kind.FRAGMENT_SPREAD, Kind.FRAGMENT_SPREAD>, name: nameNode(), directives: directives(false), }); } break;
case 123 /*'{'*/: idx++; ignored(); selections.push({ kind: 'InlineFragment' as Or<GraphQL.Kind.INLINE_FRAGMENT, Kind.INLINE_FRAGMENT>, typeCondition: undefined, directives: undefined, selectionSet: selectionSet(), }); break;
default: selections.push({ kind: 'FragmentSpread' as Or<GraphQL.Kind.FRAGMENT_SPREAD, Kind.FRAGMENT_SPREAD>, name: nameNode(), directives: directives(false), }); } } else { let name = nameNode(); let alias: ast.NameNode | undefined; if (input.charCodeAt(idx) === 58 /*':'*/) { idx++; ignored(); alias = name; name = nameNode(); } const _arguments = arguments_(false); const _directives = directives(false); let _selectionSet: ast.SelectionSetNode | undefined; if (input.charCodeAt(idx) === 123 /*'{'*/) { idx++; ignored(); _selectionSet = selectionSet(); } selections.push({ kind: 'Field' as Or<GraphQL.Kind.FIELD, Kind.FIELD>, alias, name, arguments: _arguments, directives: _directives, selectionSet: _selectionSet, }); } } while (input.charCodeAt(idx) !== 125 /*'}'*/); idx++; ignored(); return { kind: 'SelectionSet' as Or<GraphQL.Kind.SELECTION_SET, Kind.SELECTION_SET>, selections, };}
function variableDefinitions(): ast.VariableDefinitionNode[] | undefined { ignored(); if (input.charCodeAt(idx) === 40 /*'('*/) { const vars: ast.VariableDefinitionNode[] = []; idx++; ignored(); do { let _description: ast.StringValueNode | undefined; if (input.charCodeAt(idx) === 34 /*'"'*/) { _description = value(true) as ast.StringValueNode; } if (input.charCodeAt(idx++) !== 36 /*'$'*/) throw error('Variable'); const name = nameNode(); if (input.charCodeAt(idx++) !== 58 /*':'*/) throw error('VariableDefinition'); ignored(); const _type = type(); let _defaultValue: ast.ConstValueNode | undefined; if (input.charCodeAt(idx) === 61 /*'='*/) { idx++; ignored(); _defaultValue = value(true); } ignored(); const varDef: ast.VariableDefinitionNode = { kind: 'VariableDefinition' as Or< GraphQL.Kind.VARIABLE_DEFINITION, Kind.VARIABLE_DEFINITION >, variable: { kind: 'Variable' as Or<GraphQL.Kind.VARIABLE, Kind.VARIABLE>, name, }, type: _type, defaultValue: _defaultValue, directives: directives(true), }; if (_description) { varDef.description = _description; } vars.push(varDef); } while (input.charCodeAt(idx) !== 41 /*')'*/); idx++; ignored(); return vars; }}
function fragmentDefinition(description?: ast.StringValueNode): ast.FragmentDefinitionNode { const name = nameNode(); if (input.charCodeAt(idx++) !== 111 /*'o'*/ || input.charCodeAt(idx++) !== 110 /*'n'*/) throw error('FragmentDefinition'); ignored(); const fragDef: ast.FragmentDefinitionNode = { kind: 'FragmentDefinition' as Or<GraphQL.Kind.FRAGMENT_DEFINITION, Kind.FRAGMENT_DEFINITION>, name, typeCondition: { kind: 'NamedType' as Or<GraphQL.Kind.NAMED_TYPE, Kind.NAMED_TYPE>, name: nameNode(), }, directives: directives(false), selectionSet: selectionSetStart(), }; if (description) { fragDef.description = description; } return fragDef;}
function definitions(): ast.DefinitionNode[] { const _definitions: ast.ExecutableDefinitionNode[] = []; do { let _description: ast.StringValueNode | undefined; if (input.charCodeAt(idx) === 34 /*'"'*/) { _description = value(true) as ast.StringValueNode; } if (input.charCodeAt(idx) === 123 /*'{'*/) { // Anonymous operations can't have descriptions if (_description) throw error('Document'); idx++; ignored(); _definitions.push({ kind: 'OperationDefinition' as Or< GraphQL.Kind.OPERATION_DEFINITION, Kind.OPERATION_DEFINITION >, operation: 'query' as OperationTypeNode.QUERY, name: undefined, variableDefinitions: undefined, directives: undefined, selectionSet: selectionSet(), }); } else { const definition = name(); switch (definition) { case 'fragment': _definitions.push(fragmentDefinition(_description)); break; case 'query': case 'mutation': case 'subscription': let char: number; let name: ast.NameNode | undefined; if ( (char = input.charCodeAt(idx)) !== 40 /*'('*/ && char !== 64 /*'@'*/ && char !== 123 /*'{'*/ ) { name = nameNode(); } const opDef: ast.OperationDefinitionNode = { kind: 'OperationDefinition' as Or< GraphQL.Kind.OPERATION_DEFINITION, Kind.OPERATION_DEFINITION >, operation: definition as OperationTypeNode, name, variableDefinitions: variableDefinitions(), directives: directives(false), selectionSet: selectionSetStart(), }; if (_description) { (opDef as { description?: StringValueNode }).description = _description; } _definitions.push(opDef); break; default: throw error('Document'); } } } while (idx < input.length); return _definitions;}
type ParseOptions = { [option: string]: any;};
export function parse( string: string | Source, options?: ParseOptions | undefined): ast.DocumentNode { input = string.body ? string.body : string; idx = 0; ignored(); if (options && options.noLocation) { return { kind: 'Document' as Or<GraphQL.Kind.DOCUMENT, Kind.DOCUMENT>, definitions: definitions(), }; } else { return { kind: 'Document' as Or<GraphQL.Kind.DOCUMENT, Kind.DOCUMENT>, definitions: definitions(), loc: { start: 0, end: input.length, startToken: undefined, endToken: undefined, source: { body: input, name: 'graphql.web', locationOffset: { line: 1, column: 1 }, }, }, } as Location; }}
export function parseValue( string: string | Source, _options?: ParseOptions | undefined): ast.ValueNode { input = string.body ? string.body : string; idx = 0; ignored(); return value(false);}
export function parseType( string: string | Source, _options?: ParseOptions | undefined): ast.TypeNode { input = string.body ? string.body : string; idx = 0; return type();}