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); return parser.parseDocument(); } /** * 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; } /** * 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 (maxTokens !== undefined && token.kind !== TokenKind.EOF) { ++this._tokenCounter; if (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; }