Spaces:
Paused
Paused
| import { syntaxError } from '../error/syntaxError.mjs'; | |
| import { Location, OperationTypeNode } from './ast.mjs'; | |
| import { DirectiveLocation } from './directiveLocation.mjs'; | |
| import { Kind } from './kinds.mjs'; | |
| import { isPunctuatorTokenKind, Lexer } from './lexer.mjs'; | |
| import { isSource, Source } from './source.mjs'; | |
| import { TokenKind } from './tokenKind.mjs'; | |
| /** | |
| * Configuration options to control parser behavior | |
| */ | |
| /** | |
| * Given a GraphQL source, parses it into a Document. | |
| * Throws GraphQLError if a syntax error is encountered. | |
| */ | |
| export function parse(source, options) { | |
| const parser = new Parser(source, options); | |
| const document = parser.parseDocument(); | |
| Object.defineProperty(document, 'tokenCount', { | |
| enumerable: false, | |
| value: parser.tokenCount, | |
| }); | |
| return document; | |
| } | |
| /** | |
| * Given a string containing a GraphQL value (ex. `[42]`), parse the AST for | |
| * that value. | |
| * Throws GraphQLError if a syntax error is encountered. | |
| * | |
| * This is useful within tools that operate upon GraphQL Values directly and | |
| * in isolation of complete GraphQL documents. | |
| * | |
| * Consider providing the results to the utility function: valueFromAST(). | |
| */ | |
| export function parseValue(source, options) { | |
| const parser = new Parser(source, options); | |
| parser.expectToken(TokenKind.SOF); | |
| const value = parser.parseValueLiteral(false); | |
| parser.expectToken(TokenKind.EOF); | |
| return value; | |
| } | |
| /** | |
| * Similar to parseValue(), but raises a parse error if it encounters a | |
| * variable. The return type will be a constant value. | |
| */ | |
| export function parseConstValue(source, options) { | |
| const parser = new Parser(source, options); | |
| parser.expectToken(TokenKind.SOF); | |
| const value = parser.parseConstValueLiteral(); | |
| parser.expectToken(TokenKind.EOF); | |
| return value; | |
| } | |
| /** | |
| * Given a string containing a GraphQL Type (ex. `[Int!]`), parse the AST for | |
| * that type. | |
| * Throws GraphQLError if a syntax error is encountered. | |
| * | |
| * This is useful within tools that operate upon GraphQL Types directly and | |
| * in isolation of complete GraphQL documents. | |
| * | |
| * Consider providing the results to the utility function: typeFromAST(). | |
| */ | |
| export function parseType(source, options) { | |
| const parser = new Parser(source, options); | |
| parser.expectToken(TokenKind.SOF); | |
| const type = parser.parseTypeReference(); | |
| parser.expectToken(TokenKind.EOF); | |
| return type; | |
| } | |
| /** | |
| * This class is exported only to assist people in implementing their own parsers | |
| * without duplicating too much code and should be used only as last resort for cases | |
| * such as experimental syntax or if certain features could not be contributed upstream. | |
| * | |
| * It is still part of the internal API and is versioned, so any changes to it are never | |
| * considered breaking changes. If you still need to support multiple versions of the | |
| * library, please use the `versionInfo` variable for version detection. | |
| * | |
| * @internal | |
| */ | |
| export class Parser { | |
| constructor(source, options = {}) { | |
| const sourceObj = isSource(source) ? source : new Source(source); | |
| this._lexer = new Lexer(sourceObj); | |
| this._options = options; | |
| this._tokenCounter = 0; | |
| } | |
| get tokenCount() { | |
| return this._tokenCounter; | |
| } | |
| /** | |
| * Converts a name lex token into a name parse node. | |
| */ | |
| parseName() { | |
| const token = this.expectToken(TokenKind.NAME); | |
| return this.node(token, { | |
| kind: Kind.NAME, | |
| value: token.value, | |
| }); | |
| } // Implements the parsing rules in the Document section. | |
| /** | |
| * Document : Definition+ | |
| */ | |
| parseDocument() { | |
| return this.node(this._lexer.token, { | |
| kind: Kind.DOCUMENT, | |
| definitions: this.many( | |
| TokenKind.SOF, | |
| this.parseDefinition, | |
| TokenKind.EOF, | |
| ), | |
| }); | |
| } | |
| /** | |
| * Definition : | |
| * - ExecutableDefinition | |
| * - TypeSystemDefinition | |
| * - TypeSystemExtension | |
| * | |
| * ExecutableDefinition : | |
| * - OperationDefinition | |
| * - FragmentDefinition | |
| * | |
| * TypeSystemDefinition : | |
| * - SchemaDefinition | |
| * - TypeDefinition | |
| * - DirectiveDefinition | |
| * | |
| * TypeDefinition : | |
| * - ScalarTypeDefinition | |
| * - ObjectTypeDefinition | |
| * - InterfaceTypeDefinition | |
| * - UnionTypeDefinition | |
| * - EnumTypeDefinition | |
| * - InputObjectTypeDefinition | |
| */ | |
| parseDefinition() { | |
| if (this.peek(TokenKind.BRACE_L)) { | |
| return this.parseOperationDefinition(); | |
| } // Many definitions begin with a description and require a lookahead. | |
| const hasDescription = this.peekDescription(); | |
| const keywordToken = hasDescription | |
| ? this._lexer.lookahead() | |
| : this._lexer.token; | |
| if (keywordToken.kind === TokenKind.NAME) { | |
| switch (keywordToken.value) { | |
| case 'schema': | |
| return this.parseSchemaDefinition(); | |
| case 'scalar': | |
| return this.parseScalarTypeDefinition(); | |
| case 'type': | |
| return this.parseObjectTypeDefinition(); | |
| case 'interface': | |
| return this.parseInterfaceTypeDefinition(); | |
| case 'union': | |
| return this.parseUnionTypeDefinition(); | |
| case 'enum': | |
| return this.parseEnumTypeDefinition(); | |
| case 'input': | |
| return this.parseInputObjectTypeDefinition(); | |
| case 'directive': | |
| return this.parseDirectiveDefinition(); | |
| } | |
| if (hasDescription) { | |
| throw syntaxError( | |
| this._lexer.source, | |
| this._lexer.token.start, | |
| 'Unexpected description, descriptions are supported only on type definitions.', | |
| ); | |
| } | |
| switch (keywordToken.value) { | |
| case 'query': | |
| case 'mutation': | |
| case 'subscription': | |
| return this.parseOperationDefinition(); | |
| case 'fragment': | |
| return this.parseFragmentDefinition(); | |
| case 'extend': | |
| return this.parseTypeSystemExtension(); | |
| } | |
| } | |
| throw this.unexpected(keywordToken); | |
| } // Implements the parsing rules in the Operations section. | |
| /** | |
| * OperationDefinition : | |
| * - SelectionSet | |
| * - OperationType Name? VariableDefinitions? Directives? SelectionSet | |
| */ | |
| parseOperationDefinition() { | |
| const start = this._lexer.token; | |
| if (this.peek(TokenKind.BRACE_L)) { | |
| return this.node(start, { | |
| kind: Kind.OPERATION_DEFINITION, | |
| operation: OperationTypeNode.QUERY, | |
| name: undefined, | |
| variableDefinitions: [], | |
| directives: [], | |
| selectionSet: this.parseSelectionSet(), | |
| }); | |
| } | |
| const operation = this.parseOperationType(); | |
| let name; | |
| if (this.peek(TokenKind.NAME)) { | |
| name = this.parseName(); | |
| } | |
| return this.node(start, { | |
| kind: Kind.OPERATION_DEFINITION, | |
| operation, | |
| name, | |
| variableDefinitions: this.parseVariableDefinitions(), | |
| directives: this.parseDirectives(false), | |
| selectionSet: this.parseSelectionSet(), | |
| }); | |
| } | |
| /** | |
| * OperationType : one of query mutation subscription | |
| */ | |
| parseOperationType() { | |
| const operationToken = this.expectToken(TokenKind.NAME); | |
| switch (operationToken.value) { | |
| case 'query': | |
| return OperationTypeNode.QUERY; | |
| case 'mutation': | |
| return OperationTypeNode.MUTATION; | |
| case 'subscription': | |
| return OperationTypeNode.SUBSCRIPTION; | |
| } | |
| throw this.unexpected(operationToken); | |
| } | |
| /** | |
| * VariableDefinitions : ( VariableDefinition+ ) | |
| */ | |
| parseVariableDefinitions() { | |
| return this.optionalMany( | |
| TokenKind.PAREN_L, | |
| this.parseVariableDefinition, | |
| TokenKind.PAREN_R, | |
| ); | |
| } | |
| /** | |
| * VariableDefinition : Variable : Type DefaultValue? Directives[Const]? | |
| */ | |
| parseVariableDefinition() { | |
| return this.node(this._lexer.token, { | |
| kind: Kind.VARIABLE_DEFINITION, | |
| variable: this.parseVariable(), | |
| type: (this.expectToken(TokenKind.COLON), this.parseTypeReference()), | |
| defaultValue: this.expectOptionalToken(TokenKind.EQUALS) | |
| ? this.parseConstValueLiteral() | |
| : undefined, | |
| directives: this.parseConstDirectives(), | |
| }); | |
| } | |
| /** | |
| * Variable : $ Name | |
| */ | |
| parseVariable() { | |
| const start = this._lexer.token; | |
| this.expectToken(TokenKind.DOLLAR); | |
| return this.node(start, { | |
| kind: Kind.VARIABLE, | |
| name: this.parseName(), | |
| }); | |
| } | |
| /** | |
| * ``` | |
| * SelectionSet : { Selection+ } | |
| * ``` | |
| */ | |
| parseSelectionSet() { | |
| return this.node(this._lexer.token, { | |
| kind: Kind.SELECTION_SET, | |
| selections: this.many( | |
| TokenKind.BRACE_L, | |
| this.parseSelection, | |
| TokenKind.BRACE_R, | |
| ), | |
| }); | |
| } | |
| /** | |
| * Selection : | |
| * - Field | |
| * - FragmentSpread | |
| * - InlineFragment | |
| */ | |
| parseSelection() { | |
| return this.peek(TokenKind.SPREAD) | |
| ? this.parseFragment() | |
| : this.parseField(); | |
| } | |
| /** | |
| * Field : Alias? Name Arguments? Directives? SelectionSet? | |
| * | |
| * Alias : Name : | |
| */ | |
| parseField() { | |
| const start = this._lexer.token; | |
| const nameOrAlias = this.parseName(); | |
| let alias; | |
| let name; | |
| if (this.expectOptionalToken(TokenKind.COLON)) { | |
| alias = nameOrAlias; | |
| name = this.parseName(); | |
| } else { | |
| name = nameOrAlias; | |
| } | |
| return this.node(start, { | |
| kind: Kind.FIELD, | |
| alias, | |
| name, | |
| arguments: this.parseArguments(false), | |
| directives: this.parseDirectives(false), | |
| selectionSet: this.peek(TokenKind.BRACE_L) | |
| ? this.parseSelectionSet() | |
| : undefined, | |
| }); | |
| } | |
| /** | |
| * Arguments[Const] : ( Argument[?Const]+ ) | |
| */ | |
| parseArguments(isConst) { | |
| const item = isConst ? this.parseConstArgument : this.parseArgument; | |
| return this.optionalMany(TokenKind.PAREN_L, item, TokenKind.PAREN_R); | |
| } | |
| /** | |
| * Argument[Const] : Name : Value[?Const] | |
| */ | |
| parseArgument(isConst = false) { | |
| const start = this._lexer.token; | |
| const name = this.parseName(); | |
| this.expectToken(TokenKind.COLON); | |
| return this.node(start, { | |
| kind: Kind.ARGUMENT, | |
| name, | |
| value: this.parseValueLiteral(isConst), | |
| }); | |
| } | |
| parseConstArgument() { | |
| return this.parseArgument(true); | |
| } // Implements the parsing rules in the Fragments section. | |
| /** | |
| * Corresponds to both FragmentSpread and InlineFragment in the spec. | |
| * | |
| * FragmentSpread : ... FragmentName Directives? | |
| * | |
| * InlineFragment : ... TypeCondition? Directives? SelectionSet | |
| */ | |
| parseFragment() { | |
| const start = this._lexer.token; | |
| this.expectToken(TokenKind.SPREAD); | |
| const hasTypeCondition = this.expectOptionalKeyword('on'); | |
| if (!hasTypeCondition && this.peek(TokenKind.NAME)) { | |
| return this.node(start, { | |
| kind: Kind.FRAGMENT_SPREAD, | |
| name: this.parseFragmentName(), | |
| directives: this.parseDirectives(false), | |
| }); | |
| } | |
| return this.node(start, { | |
| kind: Kind.INLINE_FRAGMENT, | |
| typeCondition: hasTypeCondition ? this.parseNamedType() : undefined, | |
| directives: this.parseDirectives(false), | |
| selectionSet: this.parseSelectionSet(), | |
| }); | |
| } | |
| /** | |
| * FragmentDefinition : | |
| * - fragment FragmentName on TypeCondition Directives? SelectionSet | |
| * | |
| * TypeCondition : NamedType | |
| */ | |
| parseFragmentDefinition() { | |
| const start = this._lexer.token; | |
| this.expectKeyword('fragment'); // Legacy support for defining variables within fragments changes | |
| // the grammar of FragmentDefinition: | |
| // - fragment FragmentName VariableDefinitions? on TypeCondition Directives? SelectionSet | |
| if (this._options.allowLegacyFragmentVariables === true) { | |
| return this.node(start, { | |
| kind: Kind.FRAGMENT_DEFINITION, | |
| name: this.parseFragmentName(), | |
| variableDefinitions: this.parseVariableDefinitions(), | |
| typeCondition: (this.expectKeyword('on'), this.parseNamedType()), | |
| directives: this.parseDirectives(false), | |
| selectionSet: this.parseSelectionSet(), | |
| }); | |
| } | |
| return this.node(start, { | |
| kind: Kind.FRAGMENT_DEFINITION, | |
| name: this.parseFragmentName(), | |
| typeCondition: (this.expectKeyword('on'), this.parseNamedType()), | |
| directives: this.parseDirectives(false), | |
| selectionSet: this.parseSelectionSet(), | |
| }); | |
| } | |
| /** | |
| * FragmentName : Name but not `on` | |
| */ | |
| parseFragmentName() { | |
| if (this._lexer.token.value === 'on') { | |
| throw this.unexpected(); | |
| } | |
| return this.parseName(); | |
| } // Implements the parsing rules in the Values section. | |
| /** | |
| * Value[Const] : | |
| * - [~Const] Variable | |
| * - IntValue | |
| * - FloatValue | |
| * - StringValue | |
| * - BooleanValue | |
| * - NullValue | |
| * - EnumValue | |
| * - ListValue[?Const] | |
| * - ObjectValue[?Const] | |
| * | |
| * BooleanValue : one of `true` `false` | |
| * | |
| * NullValue : `null` | |
| * | |
| * EnumValue : Name but not `true`, `false` or `null` | |
| */ | |
| parseValueLiteral(isConst) { | |
| const token = this._lexer.token; | |
| switch (token.kind) { | |
| case TokenKind.BRACKET_L: | |
| return this.parseList(isConst); | |
| case TokenKind.BRACE_L: | |
| return this.parseObject(isConst); | |
| case TokenKind.INT: | |
| this.advanceLexer(); | |
| return this.node(token, { | |
| kind: Kind.INT, | |
| value: token.value, | |
| }); | |
| case TokenKind.FLOAT: | |
| this.advanceLexer(); | |
| return this.node(token, { | |
| kind: Kind.FLOAT, | |
| value: token.value, | |
| }); | |
| case TokenKind.STRING: | |
| case TokenKind.BLOCK_STRING: | |
| return this.parseStringLiteral(); | |
| case TokenKind.NAME: | |
| this.advanceLexer(); | |
| switch (token.value) { | |
| case 'true': | |
| return this.node(token, { | |
| kind: Kind.BOOLEAN, | |
| value: true, | |
| }); | |
| case 'false': | |
| return this.node(token, { | |
| kind: Kind.BOOLEAN, | |
| value: false, | |
| }); | |
| case 'null': | |
| return this.node(token, { | |
| kind: Kind.NULL, | |
| }); | |
| default: | |
| return this.node(token, { | |
| kind: Kind.ENUM, | |
| value: token.value, | |
| }); | |
| } | |
| case TokenKind.DOLLAR: | |
| if (isConst) { | |
| this.expectToken(TokenKind.DOLLAR); | |
| if (this._lexer.token.kind === TokenKind.NAME) { | |
| const varName = this._lexer.token.value; | |
| throw syntaxError( | |
| this._lexer.source, | |
| token.start, | |
| `Unexpected variable "$${varName}" in constant value.`, | |
| ); | |
| } else { | |
| throw this.unexpected(token); | |
| } | |
| } | |
| return this.parseVariable(); | |
| default: | |
| throw this.unexpected(); | |
| } | |
| } | |
| parseConstValueLiteral() { | |
| return this.parseValueLiteral(true); | |
| } | |
| parseStringLiteral() { | |
| const token = this._lexer.token; | |
| this.advanceLexer(); | |
| return this.node(token, { | |
| kind: Kind.STRING, | |
| value: token.value, | |
| block: token.kind === TokenKind.BLOCK_STRING, | |
| }); | |
| } | |
| /** | |
| * ListValue[Const] : | |
| * - [ ] | |
| * - [ Value[?Const]+ ] | |
| */ | |
| parseList(isConst) { | |
| const item = () => this.parseValueLiteral(isConst); | |
| return this.node(this._lexer.token, { | |
| kind: Kind.LIST, | |
| values: this.any(TokenKind.BRACKET_L, item, TokenKind.BRACKET_R), | |
| }); | |
| } | |
| /** | |
| * ``` | |
| * ObjectValue[Const] : | |
| * - { } | |
| * - { ObjectField[?Const]+ } | |
| * ``` | |
| */ | |
| parseObject(isConst) { | |
| const item = () => this.parseObjectField(isConst); | |
| return this.node(this._lexer.token, { | |
| kind: Kind.OBJECT, | |
| fields: this.any(TokenKind.BRACE_L, item, TokenKind.BRACE_R), | |
| }); | |
| } | |
| /** | |
| * ObjectField[Const] : Name : Value[?Const] | |
| */ | |
| parseObjectField(isConst) { | |
| const start = this._lexer.token; | |
| const name = this.parseName(); | |
| this.expectToken(TokenKind.COLON); | |
| return this.node(start, { | |
| kind: Kind.OBJECT_FIELD, | |
| name, | |
| value: this.parseValueLiteral(isConst), | |
| }); | |
| } // Implements the parsing rules in the Directives section. | |
| /** | |
| * Directives[Const] : Directive[?Const]+ | |
| */ | |
| parseDirectives(isConst) { | |
| const directives = []; | |
| while (this.peek(TokenKind.AT)) { | |
| directives.push(this.parseDirective(isConst)); | |
| } | |
| return directives; | |
| } | |
| parseConstDirectives() { | |
| return this.parseDirectives(true); | |
| } | |
| /** | |
| * ``` | |
| * Directive[Const] : @ Name Arguments[?Const]? | |
| * ``` | |
| */ | |
| parseDirective(isConst) { | |
| const start = this._lexer.token; | |
| this.expectToken(TokenKind.AT); | |
| return this.node(start, { | |
| kind: Kind.DIRECTIVE, | |
| name: this.parseName(), | |
| arguments: this.parseArguments(isConst), | |
| }); | |
| } // Implements the parsing rules in the Types section. | |
| /** | |
| * Type : | |
| * - NamedType | |
| * - ListType | |
| * - NonNullType | |
| */ | |
| parseTypeReference() { | |
| const start = this._lexer.token; | |
| let type; | |
| if (this.expectOptionalToken(TokenKind.BRACKET_L)) { | |
| const innerType = this.parseTypeReference(); | |
| this.expectToken(TokenKind.BRACKET_R); | |
| type = this.node(start, { | |
| kind: Kind.LIST_TYPE, | |
| type: innerType, | |
| }); | |
| } else { | |
| type = this.parseNamedType(); | |
| } | |
| if (this.expectOptionalToken(TokenKind.BANG)) { | |
| return this.node(start, { | |
| kind: Kind.NON_NULL_TYPE, | |
| type, | |
| }); | |
| } | |
| return type; | |
| } | |
| /** | |
| * NamedType : Name | |
| */ | |
| parseNamedType() { | |
| return this.node(this._lexer.token, { | |
| kind: Kind.NAMED_TYPE, | |
| name: this.parseName(), | |
| }); | |
| } // Implements the parsing rules in the Type Definition section. | |
| peekDescription() { | |
| return this.peek(TokenKind.STRING) || this.peek(TokenKind.BLOCK_STRING); | |
| } | |
| /** | |
| * Description : StringValue | |
| */ | |
| parseDescription() { | |
| if (this.peekDescription()) { | |
| return this.parseStringLiteral(); | |
| } | |
| } | |
| /** | |
| * ``` | |
| * SchemaDefinition : Description? schema Directives[Const]? { OperationTypeDefinition+ } | |
| * ``` | |
| */ | |
| parseSchemaDefinition() { | |
| const start = this._lexer.token; | |
| const description = this.parseDescription(); | |
| this.expectKeyword('schema'); | |
| const directives = this.parseConstDirectives(); | |
| const operationTypes = this.many( | |
| TokenKind.BRACE_L, | |
| this.parseOperationTypeDefinition, | |
| TokenKind.BRACE_R, | |
| ); | |
| return this.node(start, { | |
| kind: Kind.SCHEMA_DEFINITION, | |
| description, | |
| directives, | |
| operationTypes, | |
| }); | |
| } | |
| /** | |
| * OperationTypeDefinition : OperationType : NamedType | |
| */ | |
| parseOperationTypeDefinition() { | |
| const start = this._lexer.token; | |
| const operation = this.parseOperationType(); | |
| this.expectToken(TokenKind.COLON); | |
| const type = this.parseNamedType(); | |
| return this.node(start, { | |
| kind: Kind.OPERATION_TYPE_DEFINITION, | |
| operation, | |
| type, | |
| }); | |
| } | |
| /** | |
| * ScalarTypeDefinition : Description? scalar Name Directives[Const]? | |
| */ | |
| parseScalarTypeDefinition() { | |
| const start = this._lexer.token; | |
| const description = this.parseDescription(); | |
| this.expectKeyword('scalar'); | |
| const name = this.parseName(); | |
| const directives = this.parseConstDirectives(); | |
| return this.node(start, { | |
| kind: Kind.SCALAR_TYPE_DEFINITION, | |
| description, | |
| name, | |
| directives, | |
| }); | |
| } | |
| /** | |
| * ObjectTypeDefinition : | |
| * Description? | |
| * type Name ImplementsInterfaces? Directives[Const]? FieldsDefinition? | |
| */ | |
| parseObjectTypeDefinition() { | |
| const start = this._lexer.token; | |
| const description = this.parseDescription(); | |
| this.expectKeyword('type'); | |
| const name = this.parseName(); | |
| const interfaces = this.parseImplementsInterfaces(); | |
| const directives = this.parseConstDirectives(); | |
| const fields = this.parseFieldsDefinition(); | |
| return this.node(start, { | |
| kind: Kind.OBJECT_TYPE_DEFINITION, | |
| description, | |
| name, | |
| interfaces, | |
| directives, | |
| fields, | |
| }); | |
| } | |
| /** | |
| * ImplementsInterfaces : | |
| * - implements `&`? NamedType | |
| * - ImplementsInterfaces & NamedType | |
| */ | |
| parseImplementsInterfaces() { | |
| return this.expectOptionalKeyword('implements') | |
| ? this.delimitedMany(TokenKind.AMP, this.parseNamedType) | |
| : []; | |
| } | |
| /** | |
| * ``` | |
| * FieldsDefinition : { FieldDefinition+ } | |
| * ``` | |
| */ | |
| parseFieldsDefinition() { | |
| return this.optionalMany( | |
| TokenKind.BRACE_L, | |
| this.parseFieldDefinition, | |
| TokenKind.BRACE_R, | |
| ); | |
| } | |
| /** | |
| * FieldDefinition : | |
| * - Description? Name ArgumentsDefinition? : Type Directives[Const]? | |
| */ | |
| parseFieldDefinition() { | |
| const start = this._lexer.token; | |
| const description = this.parseDescription(); | |
| const name = this.parseName(); | |
| const args = this.parseArgumentDefs(); | |
| this.expectToken(TokenKind.COLON); | |
| const type = this.parseTypeReference(); | |
| const directives = this.parseConstDirectives(); | |
| return this.node(start, { | |
| kind: Kind.FIELD_DEFINITION, | |
| description, | |
| name, | |
| arguments: args, | |
| type, | |
| directives, | |
| }); | |
| } | |
| /** | |
| * ArgumentsDefinition : ( InputValueDefinition+ ) | |
| */ | |
| parseArgumentDefs() { | |
| return this.optionalMany( | |
| TokenKind.PAREN_L, | |
| this.parseInputValueDef, | |
| TokenKind.PAREN_R, | |
| ); | |
| } | |
| /** | |
| * InputValueDefinition : | |
| * - Description? Name : Type DefaultValue? Directives[Const]? | |
| */ | |
| parseInputValueDef() { | |
| const start = this._lexer.token; | |
| const description = this.parseDescription(); | |
| const name = this.parseName(); | |
| this.expectToken(TokenKind.COLON); | |
| const type = this.parseTypeReference(); | |
| let defaultValue; | |
| if (this.expectOptionalToken(TokenKind.EQUALS)) { | |
| defaultValue = this.parseConstValueLiteral(); | |
| } | |
| const directives = this.parseConstDirectives(); | |
| return this.node(start, { | |
| kind: Kind.INPUT_VALUE_DEFINITION, | |
| description, | |
| name, | |
| type, | |
| defaultValue, | |
| directives, | |
| }); | |
| } | |
| /** | |
| * InterfaceTypeDefinition : | |
| * - Description? interface Name Directives[Const]? FieldsDefinition? | |
| */ | |
| parseInterfaceTypeDefinition() { | |
| const start = this._lexer.token; | |
| const description = this.parseDescription(); | |
| this.expectKeyword('interface'); | |
| const name = this.parseName(); | |
| const interfaces = this.parseImplementsInterfaces(); | |
| const directives = this.parseConstDirectives(); | |
| const fields = this.parseFieldsDefinition(); | |
| return this.node(start, { | |
| kind: Kind.INTERFACE_TYPE_DEFINITION, | |
| description, | |
| name, | |
| interfaces, | |
| directives, | |
| fields, | |
| }); | |
| } | |
| /** | |
| * UnionTypeDefinition : | |
| * - Description? union Name Directives[Const]? UnionMemberTypes? | |
| */ | |
| parseUnionTypeDefinition() { | |
| const start = this._lexer.token; | |
| const description = this.parseDescription(); | |
| this.expectKeyword('union'); | |
| const name = this.parseName(); | |
| const directives = this.parseConstDirectives(); | |
| const types = this.parseUnionMemberTypes(); | |
| return this.node(start, { | |
| kind: Kind.UNION_TYPE_DEFINITION, | |
| description, | |
| name, | |
| directives, | |
| types, | |
| }); | |
| } | |
| /** | |
| * UnionMemberTypes : | |
| * - = `|`? NamedType | |
| * - UnionMemberTypes | NamedType | |
| */ | |
| parseUnionMemberTypes() { | |
| return this.expectOptionalToken(TokenKind.EQUALS) | |
| ? this.delimitedMany(TokenKind.PIPE, this.parseNamedType) | |
| : []; | |
| } | |
| /** | |
| * EnumTypeDefinition : | |
| * - Description? enum Name Directives[Const]? EnumValuesDefinition? | |
| */ | |
| parseEnumTypeDefinition() { | |
| const start = this._lexer.token; | |
| const description = this.parseDescription(); | |
| this.expectKeyword('enum'); | |
| const name = this.parseName(); | |
| const directives = this.parseConstDirectives(); | |
| const values = this.parseEnumValuesDefinition(); | |
| return this.node(start, { | |
| kind: Kind.ENUM_TYPE_DEFINITION, | |
| description, | |
| name, | |
| directives, | |
| values, | |
| }); | |
| } | |
| /** | |
| * ``` | |
| * EnumValuesDefinition : { EnumValueDefinition+ } | |
| * ``` | |
| */ | |
| parseEnumValuesDefinition() { | |
| return this.optionalMany( | |
| TokenKind.BRACE_L, | |
| this.parseEnumValueDefinition, | |
| TokenKind.BRACE_R, | |
| ); | |
| } | |
| /** | |
| * EnumValueDefinition : Description? EnumValue Directives[Const]? | |
| */ | |
| parseEnumValueDefinition() { | |
| const start = this._lexer.token; | |
| const description = this.parseDescription(); | |
| const name = this.parseEnumValueName(); | |
| const directives = this.parseConstDirectives(); | |
| return this.node(start, { | |
| kind: Kind.ENUM_VALUE_DEFINITION, | |
| description, | |
| name, | |
| directives, | |
| }); | |
| } | |
| /** | |
| * EnumValue : Name but not `true`, `false` or `null` | |
| */ | |
| parseEnumValueName() { | |
| if ( | |
| this._lexer.token.value === 'true' || | |
| this._lexer.token.value === 'false' || | |
| this._lexer.token.value === 'null' | |
| ) { | |
| throw syntaxError( | |
| this._lexer.source, | |
| this._lexer.token.start, | |
| `${getTokenDesc( | |
| this._lexer.token, | |
| )} is reserved and cannot be used for an enum value.`, | |
| ); | |
| } | |
| return this.parseName(); | |
| } | |
| /** | |
| * InputObjectTypeDefinition : | |
| * - Description? input Name Directives[Const]? InputFieldsDefinition? | |
| */ | |
| parseInputObjectTypeDefinition() { | |
| const start = this._lexer.token; | |
| const description = this.parseDescription(); | |
| this.expectKeyword('input'); | |
| const name = this.parseName(); | |
| const directives = this.parseConstDirectives(); | |
| const fields = this.parseInputFieldsDefinition(); | |
| return this.node(start, { | |
| kind: Kind.INPUT_OBJECT_TYPE_DEFINITION, | |
| description, | |
| name, | |
| directives, | |
| fields, | |
| }); | |
| } | |
| /** | |
| * ``` | |
| * InputFieldsDefinition : { InputValueDefinition+ } | |
| * ``` | |
| */ | |
| parseInputFieldsDefinition() { | |
| return this.optionalMany( | |
| TokenKind.BRACE_L, | |
| this.parseInputValueDef, | |
| TokenKind.BRACE_R, | |
| ); | |
| } | |
| /** | |
| * TypeSystemExtension : | |
| * - SchemaExtension | |
| * - TypeExtension | |
| * | |
| * TypeExtension : | |
| * - ScalarTypeExtension | |
| * - ObjectTypeExtension | |
| * - InterfaceTypeExtension | |
| * - UnionTypeExtension | |
| * - EnumTypeExtension | |
| * - InputObjectTypeDefinition | |
| */ | |
| parseTypeSystemExtension() { | |
| const keywordToken = this._lexer.lookahead(); | |
| if (keywordToken.kind === TokenKind.NAME) { | |
| switch (keywordToken.value) { | |
| case 'schema': | |
| return this.parseSchemaExtension(); | |
| case 'scalar': | |
| return this.parseScalarTypeExtension(); | |
| case 'type': | |
| return this.parseObjectTypeExtension(); | |
| case 'interface': | |
| return this.parseInterfaceTypeExtension(); | |
| case 'union': | |
| return this.parseUnionTypeExtension(); | |
| case 'enum': | |
| return this.parseEnumTypeExtension(); | |
| case 'input': | |
| return this.parseInputObjectTypeExtension(); | |
| } | |
| } | |
| throw this.unexpected(keywordToken); | |
| } | |
| /** | |
| * ``` | |
| * SchemaExtension : | |
| * - extend schema Directives[Const]? { OperationTypeDefinition+ } | |
| * - extend schema Directives[Const] | |
| * ``` | |
| */ | |
| parseSchemaExtension() { | |
| const start = this._lexer.token; | |
| this.expectKeyword('extend'); | |
| this.expectKeyword('schema'); | |
| const directives = this.parseConstDirectives(); | |
| const operationTypes = this.optionalMany( | |
| TokenKind.BRACE_L, | |
| this.parseOperationTypeDefinition, | |
| TokenKind.BRACE_R, | |
| ); | |
| if (directives.length === 0 && operationTypes.length === 0) { | |
| throw this.unexpected(); | |
| } | |
| return this.node(start, { | |
| kind: Kind.SCHEMA_EXTENSION, | |
| directives, | |
| operationTypes, | |
| }); | |
| } | |
| /** | |
| * ScalarTypeExtension : | |
| * - extend scalar Name Directives[Const] | |
| */ | |
| parseScalarTypeExtension() { | |
| const start = this._lexer.token; | |
| this.expectKeyword('extend'); | |
| this.expectKeyword('scalar'); | |
| const name = this.parseName(); | |
| const directives = this.parseConstDirectives(); | |
| if (directives.length === 0) { | |
| throw this.unexpected(); | |
| } | |
| return this.node(start, { | |
| kind: Kind.SCALAR_TYPE_EXTENSION, | |
| name, | |
| directives, | |
| }); | |
| } | |
| /** | |
| * ObjectTypeExtension : | |
| * - extend type Name ImplementsInterfaces? Directives[Const]? FieldsDefinition | |
| * - extend type Name ImplementsInterfaces? Directives[Const] | |
| * - extend type Name ImplementsInterfaces | |
| */ | |
| parseObjectTypeExtension() { | |
| const start = this._lexer.token; | |
| this.expectKeyword('extend'); | |
| this.expectKeyword('type'); | |
| const name = this.parseName(); | |
| const interfaces = this.parseImplementsInterfaces(); | |
| const directives = this.parseConstDirectives(); | |
| const fields = this.parseFieldsDefinition(); | |
| if ( | |
| interfaces.length === 0 && | |
| directives.length === 0 && | |
| fields.length === 0 | |
| ) { | |
| throw this.unexpected(); | |
| } | |
| return this.node(start, { | |
| kind: Kind.OBJECT_TYPE_EXTENSION, | |
| name, | |
| interfaces, | |
| directives, | |
| fields, | |
| }); | |
| } | |
| /** | |
| * InterfaceTypeExtension : | |
| * - extend interface Name ImplementsInterfaces? Directives[Const]? FieldsDefinition | |
| * - extend interface Name ImplementsInterfaces? Directives[Const] | |
| * - extend interface Name ImplementsInterfaces | |
| */ | |
| parseInterfaceTypeExtension() { | |
| const start = this._lexer.token; | |
| this.expectKeyword('extend'); | |
| this.expectKeyword('interface'); | |
| const name = this.parseName(); | |
| const interfaces = this.parseImplementsInterfaces(); | |
| const directives = this.parseConstDirectives(); | |
| const fields = this.parseFieldsDefinition(); | |
| if ( | |
| interfaces.length === 0 && | |
| directives.length === 0 && | |
| fields.length === 0 | |
| ) { | |
| throw this.unexpected(); | |
| } | |
| return this.node(start, { | |
| kind: Kind.INTERFACE_TYPE_EXTENSION, | |
| name, | |
| interfaces, | |
| directives, | |
| fields, | |
| }); | |
| } | |
| /** | |
| * UnionTypeExtension : | |
| * - extend union Name Directives[Const]? UnionMemberTypes | |
| * - extend union Name Directives[Const] | |
| */ | |
| parseUnionTypeExtension() { | |
| const start = this._lexer.token; | |
| this.expectKeyword('extend'); | |
| this.expectKeyword('union'); | |
| const name = this.parseName(); | |
| const directives = this.parseConstDirectives(); | |
| const types = this.parseUnionMemberTypes(); | |
| if (directives.length === 0 && types.length === 0) { | |
| throw this.unexpected(); | |
| } | |
| return this.node(start, { | |
| kind: Kind.UNION_TYPE_EXTENSION, | |
| name, | |
| directives, | |
| types, | |
| }); | |
| } | |
| /** | |
| * EnumTypeExtension : | |
| * - extend enum Name Directives[Const]? EnumValuesDefinition | |
| * - extend enum Name Directives[Const] | |
| */ | |
| parseEnumTypeExtension() { | |
| const start = this._lexer.token; | |
| this.expectKeyword('extend'); | |
| this.expectKeyword('enum'); | |
| const name = this.parseName(); | |
| const directives = this.parseConstDirectives(); | |
| const values = this.parseEnumValuesDefinition(); | |
| if (directives.length === 0 && values.length === 0) { | |
| throw this.unexpected(); | |
| } | |
| return this.node(start, { | |
| kind: Kind.ENUM_TYPE_EXTENSION, | |
| name, | |
| directives, | |
| values, | |
| }); | |
| } | |
| /** | |
| * InputObjectTypeExtension : | |
| * - extend input Name Directives[Const]? InputFieldsDefinition | |
| * - extend input Name Directives[Const] | |
| */ | |
| parseInputObjectTypeExtension() { | |
| const start = this._lexer.token; | |
| this.expectKeyword('extend'); | |
| this.expectKeyword('input'); | |
| const name = this.parseName(); | |
| const directives = this.parseConstDirectives(); | |
| const fields = this.parseInputFieldsDefinition(); | |
| if (directives.length === 0 && fields.length === 0) { | |
| throw this.unexpected(); | |
| } | |
| return this.node(start, { | |
| kind: Kind.INPUT_OBJECT_TYPE_EXTENSION, | |
| name, | |
| directives, | |
| fields, | |
| }); | |
| } | |
| /** | |
| * ``` | |
| * DirectiveDefinition : | |
| * - Description? directive @ Name ArgumentsDefinition? `repeatable`? on DirectiveLocations | |
| * ``` | |
| */ | |
| parseDirectiveDefinition() { | |
| const start = this._lexer.token; | |
| const description = this.parseDescription(); | |
| this.expectKeyword('directive'); | |
| this.expectToken(TokenKind.AT); | |
| const name = this.parseName(); | |
| const args = this.parseArgumentDefs(); | |
| const repeatable = this.expectOptionalKeyword('repeatable'); | |
| this.expectKeyword('on'); | |
| const locations = this.parseDirectiveLocations(); | |
| return this.node(start, { | |
| kind: Kind.DIRECTIVE_DEFINITION, | |
| description, | |
| name, | |
| arguments: args, | |
| repeatable, | |
| locations, | |
| }); | |
| } | |
| /** | |
| * DirectiveLocations : | |
| * - `|`? DirectiveLocation | |
| * - DirectiveLocations | DirectiveLocation | |
| */ | |
| parseDirectiveLocations() { | |
| return this.delimitedMany(TokenKind.PIPE, this.parseDirectiveLocation); | |
| } | |
| /* | |
| * DirectiveLocation : | |
| * - ExecutableDirectiveLocation | |
| * - TypeSystemDirectiveLocation | |
| * | |
| * ExecutableDirectiveLocation : one of | |
| * `QUERY` | |
| * `MUTATION` | |
| * `SUBSCRIPTION` | |
| * `FIELD` | |
| * `FRAGMENT_DEFINITION` | |
| * `FRAGMENT_SPREAD` | |
| * `INLINE_FRAGMENT` | |
| * | |
| * TypeSystemDirectiveLocation : one of | |
| * `SCHEMA` | |
| * `SCALAR` | |
| * `OBJECT` | |
| * `FIELD_DEFINITION` | |
| * `ARGUMENT_DEFINITION` | |
| * `INTERFACE` | |
| * `UNION` | |
| * `ENUM` | |
| * `ENUM_VALUE` | |
| * `INPUT_OBJECT` | |
| * `INPUT_FIELD_DEFINITION` | |
| */ | |
| parseDirectiveLocation() { | |
| const start = this._lexer.token; | |
| const name = this.parseName(); | |
| if (Object.prototype.hasOwnProperty.call(DirectiveLocation, name.value)) { | |
| return name; | |
| } | |
| throw this.unexpected(start); | |
| } // Core parsing utility functions | |
| /** | |
| * Returns a node that, if configured to do so, sets a "loc" field as a | |
| * location object, used to identify the place in the source that created a | |
| * given parsed object. | |
| */ | |
| node(startToken, node) { | |
| if (this._options.noLocation !== true) { | |
| node.loc = new Location( | |
| startToken, | |
| this._lexer.lastToken, | |
| this._lexer.source, | |
| ); | |
| } | |
| return node; | |
| } | |
| /** | |
| * Determines if the next token is of a given kind | |
| */ | |
| peek(kind) { | |
| return this._lexer.token.kind === kind; | |
| } | |
| /** | |
| * If the next token is of the given kind, return that token after advancing the lexer. | |
| * Otherwise, do not change the parser state and throw an error. | |
| */ | |
| expectToken(kind) { | |
| const token = this._lexer.token; | |
| if (token.kind === kind) { | |
| this.advanceLexer(); | |
| return token; | |
| } | |
| throw syntaxError( | |
| this._lexer.source, | |
| token.start, | |
| `Expected ${getTokenKindDesc(kind)}, found ${getTokenDesc(token)}.`, | |
| ); | |
| } | |
| /** | |
| * If the next token is of the given kind, return "true" after advancing the lexer. | |
| * Otherwise, do not change the parser state and return "false". | |
| */ | |
| expectOptionalToken(kind) { | |
| const token = this._lexer.token; | |
| if (token.kind === kind) { | |
| this.advanceLexer(); | |
| return true; | |
| } | |
| return false; | |
| } | |
| /** | |
| * If the next token is a given keyword, advance the lexer. | |
| * Otherwise, do not change the parser state and throw an error. | |
| */ | |
| expectKeyword(value) { | |
| const token = this._lexer.token; | |
| if (token.kind === TokenKind.NAME && token.value === value) { | |
| this.advanceLexer(); | |
| } else { | |
| throw syntaxError( | |
| this._lexer.source, | |
| token.start, | |
| `Expected "${value}", found ${getTokenDesc(token)}.`, | |
| ); | |
| } | |
| } | |
| /** | |
| * If the next token is a given keyword, return "true" after advancing the lexer. | |
| * Otherwise, do not change the parser state and return "false". | |
| */ | |
| expectOptionalKeyword(value) { | |
| const token = this._lexer.token; | |
| if (token.kind === TokenKind.NAME && token.value === value) { | |
| this.advanceLexer(); | |
| return true; | |
| } | |
| return false; | |
| } | |
| /** | |
| * Helper function for creating an error when an unexpected lexed token is encountered. | |
| */ | |
| unexpected(atToken) { | |
| const token = | |
| atToken !== null && atToken !== void 0 ? atToken : this._lexer.token; | |
| return syntaxError( | |
| this._lexer.source, | |
| token.start, | |
| `Unexpected ${getTokenDesc(token)}.`, | |
| ); | |
| } | |
| /** | |
| * Returns a possibly empty list of parse nodes, determined by the parseFn. | |
| * This list begins with a lex token of openKind and ends with a lex token of closeKind. | |
| * Advances the parser to the next lex token after the closing token. | |
| */ | |
| any(openKind, parseFn, closeKind) { | |
| this.expectToken(openKind); | |
| const nodes = []; | |
| while (!this.expectOptionalToken(closeKind)) { | |
| nodes.push(parseFn.call(this)); | |
| } | |
| return nodes; | |
| } | |
| /** | |
| * Returns a list of parse nodes, determined by the parseFn. | |
| * It can be empty only if open token is missing otherwise it will always return non-empty list | |
| * that begins with a lex token of openKind and ends with a lex token of closeKind. | |
| * Advances the parser to the next lex token after the closing token. | |
| */ | |
| optionalMany(openKind, parseFn, closeKind) { | |
| if (this.expectOptionalToken(openKind)) { | |
| const nodes = []; | |
| do { | |
| nodes.push(parseFn.call(this)); | |
| } while (!this.expectOptionalToken(closeKind)); | |
| return nodes; | |
| } | |
| return []; | |
| } | |
| /** | |
| * Returns a non-empty list of parse nodes, determined by the parseFn. | |
| * This list begins with a lex token of openKind and ends with a lex token of closeKind. | |
| * Advances the parser to the next lex token after the closing token. | |
| */ | |
| many(openKind, parseFn, closeKind) { | |
| this.expectToken(openKind); | |
| const nodes = []; | |
| do { | |
| nodes.push(parseFn.call(this)); | |
| } while (!this.expectOptionalToken(closeKind)); | |
| return nodes; | |
| } | |
| /** | |
| * Returns a non-empty list of parse nodes, determined by the parseFn. | |
| * This list may begin with a lex token of delimiterKind followed by items separated by lex tokens of tokenKind. | |
| * Advances the parser to the next lex token after last item in the list. | |
| */ | |
| delimitedMany(delimiterKind, parseFn) { | |
| this.expectOptionalToken(delimiterKind); | |
| const nodes = []; | |
| do { | |
| nodes.push(parseFn.call(this)); | |
| } while (this.expectOptionalToken(delimiterKind)); | |
| return nodes; | |
| } | |
| advanceLexer() { | |
| const { maxTokens } = this._options; | |
| const token = this._lexer.advance(); | |
| if (token.kind !== TokenKind.EOF) { | |
| ++this._tokenCounter; | |
| if (maxTokens !== undefined && this._tokenCounter > maxTokens) { | |
| throw syntaxError( | |
| this._lexer.source, | |
| token.start, | |
| `Document contains more that ${maxTokens} tokens. Parsing aborted.`, | |
| ); | |
| } | |
| } | |
| } | |
| } | |
| /** | |
| * A helper function to describe a token as a string for debugging. | |
| */ | |
| function getTokenDesc(token) { | |
| const value = token.value; | |
| return getTokenKindDesc(token.kind) + (value != null ? ` "${value}"` : ''); | |
| } | |
| /** | |
| * A helper function to describe a token kind as a string for debugging. | |
| */ | |
| function getTokenKindDesc(kind) { | |
| return isPunctuatorTokenKind(kind) ? `"${kind}"` : kind; | |
| } | |