/** * Copyright (c) 2015-present, Facebook, Inc. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * * @flow strict */ import { Source } from './source'; import { syntaxError } from '../error'; import type { GraphQLError } from '../error'; import { createLexer, TokenKind, getTokenDesc } from './lexer'; import type { Lexer, TokenKindEnum } from './lexer'; import type { Location, Token, NameNode, VariableNode, DocumentNode, DefinitionNode, ExecutableDefinitionNode, OperationDefinitionNode, OperationTypeNode, VariableDefinitionNode, SelectionSetNode, SelectionNode, FieldNode, ArgumentNode, FragmentSpreadNode, InlineFragmentNode, FragmentDefinitionNode, ValueNode, StringValueNode, ListValueNode, ObjectValueNode, ObjectFieldNode, DirectiveNode, TypeNode, NamedTypeNode, ListTypeNode, NonNullTypeNode, TypeSystemDefinitionNode, SchemaDefinitionNode, OperationTypeDefinitionNode, ScalarTypeDefinitionNode, ObjectTypeDefinitionNode, FieldDefinitionNode, InputValueDefinitionNode, InterfaceTypeDefinitionNode, UnionTypeDefinitionNode, EnumTypeDefinitionNode, EnumValueDefinitionNode, InputObjectTypeDefinitionNode, TypeExtensionNode, ScalarTypeExtensionNode, ObjectTypeExtensionNode, InterfaceTypeExtensionNode, UnionTypeExtensionNode, EnumTypeExtensionNode, InputObjectTypeExtensionNode, DirectiveDefinitionNode, } from './ast'; import { Kind } from './kinds'; import { DirectiveLocation } from './directiveLocation'; /** * Configuration options to control parser behavior */ export type ParseOptions = { /** * By default, the parser creates AST nodes that know the location * in the source that they correspond to. This configuration flag * disables that behavior for performance or testing. */ noLocation?: boolean, /** * If enabled, the parser will parse empty fields sets in the Schema * Definition Language. Otherwise, the parser will follow the current * specification. * * This option is provided to ease adoption of the final SDL specification * and will be removed in a future major release. */ allowLegacySDLEmptyFields?: boolean, /** * If enabled, the parser will parse implemented interfaces with no `&` * character between each interface. Otherwise, the parser will follow the * current specification. * * This option is provided to ease adoption of the final SDL specification * and will be removed in a future major release. */ allowLegacySDLImplementsInterfaces?: boolean, /** * EXPERIMENTAL: * * If enabled, the parser will understand and parse variable definitions * contained in a fragment definition. They'll be represented in the * `variableDefinitions` field of the FragmentDefinitionNode. * * The syntax is identical to normal, query-defined variables. For example: * * fragment A($var: Boolean = false) on T { * ... * } * * Note: this feature is experimental and may change or be removed in the * future. */ experimentalFragmentVariables?: boolean, }; /** * Given a GraphQL source, parses it into a Document. * Throws GraphQLError if a syntax error is encountered. */ export function parse( source: string | Source, options?: ParseOptions, ): DocumentNode { const sourceObj = typeof source === 'string' ? new Source(source) : source; if (!(sourceObj instanceof Source)) { throw new TypeError('Must provide Source. Received: ' + String(sourceObj)); } const lexer = createLexer(sourceObj, options || {}); return parseDocument(lexer); } /** * 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: string | Source, options?: ParseOptions, ): ValueNode { const sourceObj = typeof source === 'string' ? new Source(source) : source; const lexer = createLexer(sourceObj, options || {}); expect(lexer, TokenKind.SOF); const value = parseValueLiteral(lexer, false); expect(lexer, 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: string | Source, options?: ParseOptions, ): TypeNode { const sourceObj = typeof source === 'string' ? new Source(source) : source; const lexer = createLexer(sourceObj, options || {}); expect(lexer, TokenKind.SOF); const type = parseTypeReference(lexer); expect(lexer, TokenKind.EOF); return type; } /** * Converts a name lex token into a name parse node. */ function parseName(lexer: Lexer<*>): NameNode { const token = expect(lexer, TokenKind.NAME); return { kind: Kind.NAME, value: ((token.value: any): string), loc: loc(lexer, token), }; } // Implements the parsing rules in the Document section. /** * Document : Definition+ */ function parseDocument(lexer: Lexer<*>): DocumentNode { const start = lexer.token; expect(lexer, TokenKind.SOF); const definitions = []; do { definitions.push(parseDefinition(lexer)); } while (!skip(lexer, TokenKind.EOF)); return { kind: Kind.DOCUMENT, definitions, loc: loc(lexer, start), }; } /** * Definition : * - ExecutableDefinition * - TypeSystemDefinition */ function parseDefinition(lexer: Lexer<*>): DefinitionNode { if (peek(lexer, TokenKind.NAME)) { switch (lexer.token.value) { case 'query': case 'mutation': case 'subscription': case 'fragment': return parseExecutableDefinition(lexer); case 'schema': case 'scalar': case 'type': case 'interface': case 'union': case 'enum': case 'input': case 'extend': case 'directive': // Note: The schema definition language is an experimental addition. return parseTypeSystemDefinition(lexer); } } else if (peek(lexer, TokenKind.BRACE_L)) { return parseExecutableDefinition(lexer); } else if (peekDescription(lexer)) { // Note: The schema definition language is an experimental addition. return parseTypeSystemDefinition(lexer); } throw unexpected(lexer); } /** * ExecutableDefinition : * - OperationDefinition * - FragmentDefinition */ function parseExecutableDefinition(lexer: Lexer<*>): ExecutableDefinitionNode { if (peek(lexer, TokenKind.NAME)) { switch (lexer.token.value) { case 'query': case 'mutation': case 'subscription': return parseOperationDefinition(lexer); case 'fragment': return parseFragmentDefinition(lexer); } } else if (peek(lexer, TokenKind.BRACE_L)) { return parseOperationDefinition(lexer); } throw unexpected(lexer); } // Implements the parsing rules in the Operations section. /** * OperationDefinition : * - SelectionSet * - OperationType Name? VariableDefinitions? Directives? SelectionSet */ function parseOperationDefinition(lexer: Lexer<*>): OperationDefinitionNode { const start = lexer.token; if (peek(lexer, TokenKind.BRACE_L)) { return { kind: Kind.OPERATION_DEFINITION, operation: 'query', name: undefined, variableDefinitions: [], directives: [], selectionSet: parseSelectionSet(lexer), loc: loc(lexer, start), }; } const operation = parseOperationType(lexer); let name; if (peek(lexer, TokenKind.NAME)) { name = parseName(lexer); } return { kind: Kind.OPERATION_DEFINITION, operation, name, variableDefinitions: parseVariableDefinitions(lexer), directives: parseDirectives(lexer, false), selectionSet: parseSelectionSet(lexer), loc: loc(lexer, start), }; } /** * OperationType : one of query mutation subscription */ function parseOperationType(lexer: Lexer<*>): OperationTypeNode { const operationToken = expect(lexer, TokenKind.NAME); switch (operationToken.value) { case 'query': return 'query'; case 'mutation': return 'mutation'; case 'subscription': return 'subscription'; } throw unexpected(lexer, operationToken); } /** * VariableDefinitions : ( VariableDefinition+ ) */ function parseVariableDefinitions( lexer: Lexer<*>, ): Array<VariableDefinitionNode> { return peek(lexer, TokenKind.PAREN_L) ? many(lexer, TokenKind.PAREN_L, parseVariableDefinition, TokenKind.PAREN_R) : []; } /** * VariableDefinition : Variable : Type DefaultValue? */ function parseVariableDefinition(lexer: Lexer<*>): VariableDefinitionNode { const start = lexer.token; return { kind: Kind.VARIABLE_DEFINITION, variable: parseVariable(lexer), type: (expect(lexer, TokenKind.COLON), parseTypeReference(lexer)), defaultValue: skip(lexer, TokenKind.EQUALS) ? parseValueLiteral(lexer, true) : undefined, loc: loc(lexer, start), }; } /** * Variable : $ Name */ function parseVariable(lexer: Lexer<*>): VariableNode { const start = lexer.token; expect(lexer, TokenKind.DOLLAR); return { kind: Kind.VARIABLE, name: parseName(lexer), loc: loc(lexer, start), }; } /** * SelectionSet : { Selection+ } */ function parseSelectionSet(lexer: Lexer<*>): SelectionSetNode { const start = lexer.token; return { kind: Kind.SELECTION_SET, selections: many( lexer, TokenKind.BRACE_L, parseSelection, TokenKind.BRACE_R, ), loc: loc(lexer, start), }; } /** * Selection : * - Field * - FragmentSpread * - InlineFragment */ function parseSelection(lexer: Lexer<*>): SelectionNode { return peek(lexer, TokenKind.SPREAD) ? parseFragment(lexer) : parseField(lexer); } /** * Field : Alias? Name Arguments? Directives? SelectionSet? * * Alias : Name : */ function parseField(lexer: Lexer<*>): FieldNode { const start = lexer.token; const nameOrAlias = parseName(lexer); let alias; let name; if (skip(lexer, TokenKind.COLON)) { alias = nameOrAlias; name = parseName(lexer); } else { name = nameOrAlias; } return { kind: Kind.FIELD, alias, name, arguments: parseArguments(lexer, false), directives: parseDirectives(lexer, false), selectionSet: peek(lexer, TokenKind.BRACE_L) ? parseSelectionSet(lexer) : undefined, loc: loc(lexer, start), }; } /** * Arguments[Const] : ( Argument[?Const]+ ) */ function parseArguments( lexer: Lexer<*>, isConst: boolean, ): Array<ArgumentNode> { const item = isConst ? parseConstArgument : parseArgument; return peek(lexer, TokenKind.PAREN_L) ? many(lexer, TokenKind.PAREN_L, item, TokenKind.PAREN_R) : []; } /** * Argument[Const] : Name : Value[?Const] */ function parseArgument(lexer: Lexer<*>): ArgumentNode { const start = lexer.token; return { kind: Kind.ARGUMENT, name: parseName(lexer), value: (expect(lexer, TokenKind.COLON), parseValueLiteral(lexer, false)), loc: loc(lexer, start), }; } function parseConstArgument(lexer: Lexer<*>): ArgumentNode { const start = lexer.token; return { kind: Kind.ARGUMENT, name: parseName(lexer), value: (expect(lexer, TokenKind.COLON), parseConstValue(lexer)), loc: loc(lexer, start), }; } // Implements the parsing rules in the Fragments section. /** * Corresponds to both FragmentSpread and InlineFragment in the spec. * * FragmentSpread : ... FragmentName Directives? * * InlineFragment : ... TypeCondition? Directives? SelectionSet */ function parseFragment( lexer: Lexer<*>, ): FragmentSpreadNode | InlineFragmentNode { const start = lexer.token; expect(lexer, TokenKind.SPREAD); if (peek(lexer, TokenKind.NAME) && lexer.token.value !== 'on') { return { kind: Kind.FRAGMENT_SPREAD, name: parseFragmentName(lexer), directives: parseDirectives(lexer, false), loc: loc(lexer, start), }; } let typeCondition; if (lexer.token.value === 'on') { lexer.advance(); typeCondition = parseNamedType(lexer); } return { kind: Kind.INLINE_FRAGMENT, typeCondition, directives: parseDirectives(lexer, false), selectionSet: parseSelectionSet(lexer), loc: loc(lexer, start), }; } /** * FragmentDefinition : * - fragment FragmentName on TypeCondition Directives? SelectionSet * * TypeCondition : NamedType */ function parseFragmentDefinition(lexer: Lexer<*>): FragmentDefinitionNode { const start = lexer.token; expectKeyword(lexer, 'fragment'); // Experimental support for defining variables within fragments changes // the grammar of FragmentDefinition: // - fragment FragmentName VariableDefinitions? on TypeCondition Directives? SelectionSet if (lexer.options.experimentalFragmentVariables) { return { kind: Kind.FRAGMENT_DEFINITION, name: parseFragmentName(lexer), variableDefinitions: parseVariableDefinitions(lexer), typeCondition: (expectKeyword(lexer, 'on'), parseNamedType(lexer)), directives: parseDirectives(lexer, false), selectionSet: parseSelectionSet(lexer), loc: loc(lexer, start), }; } return { kind: Kind.FRAGMENT_DEFINITION, name: parseFragmentName(lexer), typeCondition: (expectKeyword(lexer, 'on'), parseNamedType(lexer)), directives: parseDirectives(lexer, false), selectionSet: parseSelectionSet(lexer), loc: loc(lexer, start), }; } /** * FragmentName : Name but not `on` */ function parseFragmentName(lexer: Lexer<*>): NameNode { if (lexer.token.value === 'on') { throw unexpected(lexer); } return parseName(lexer); } // 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` */ function parseValueLiteral(lexer: Lexer<*>, isConst: boolean): ValueNode { const token = lexer.token; switch (token.kind) { case TokenKind.BRACKET_L: return parseList(lexer, isConst); case TokenKind.BRACE_L: return parseObject(lexer, isConst); case TokenKind.INT: lexer.advance(); return { kind: Kind.INT, value: ((token.value: any): string), loc: loc(lexer, token), }; case TokenKind.FLOAT: lexer.advance(); return { kind: Kind.FLOAT, value: ((token.value: any): string), loc: loc(lexer, token), }; case TokenKind.STRING: case TokenKind.BLOCK_STRING: return parseStringLiteral(lexer); case TokenKind.NAME: if (token.value === 'true' || token.value === 'false') { lexer.advance(); return { kind: Kind.BOOLEAN, value: token.value === 'true', loc: loc(lexer, token), }; } else if (token.value === 'null') { lexer.advance(); return { kind: Kind.NULL, loc: loc(lexer, token), }; } lexer.advance(); return { kind: Kind.ENUM, value: ((token.value: any): string), loc: loc(lexer, token), }; case TokenKind.DOLLAR: if (!isConst) { return parseVariable(lexer); } break; } throw unexpected(lexer); } function parseStringLiteral(lexer: Lexer<*>): StringValueNode { const token = lexer.token; lexer.advance(); return { kind: Kind.STRING, value: ((token.value: any): string), block: token.kind === TokenKind.BLOCK_STRING, loc: loc(lexer, token), }; } export function parseConstValue(lexer: Lexer<*>): ValueNode { return parseValueLiteral(lexer, true); } function parseValueValue(lexer: Lexer<*>): ValueNode { return parseValueLiteral(lexer, false); } /** * ListValue[Const] : * - [ ] * - [ Value[?Const]+ ] */ function parseList(lexer: Lexer<*>, isConst: boolean): ListValueNode { const start = lexer.token; const item = isConst ? parseConstValue : parseValueValue; return { kind: Kind.LIST, values: any(lexer, TokenKind.BRACKET_L, item, TokenKind.BRACKET_R), loc: loc(lexer, start), }; } /** * ObjectValue[Const] : * - { } * - { ObjectField[?Const]+ } */ function parseObject(lexer: Lexer<*>, isConst: boolean): ObjectValueNode { const start = lexer.token; expect(lexer, TokenKind.BRACE_L); const fields = []; while (!skip(lexer, TokenKind.BRACE_R)) { fields.push(parseObjectField(lexer, isConst)); } return { kind: Kind.OBJECT, fields, loc: loc(lexer, start), }; } /** * ObjectField[Const] : Name : Value[?Const] */ function parseObjectField(lexer: Lexer<*>, isConst: boolean): ObjectFieldNode { const start = lexer.token; return { kind: Kind.OBJECT_FIELD, name: parseName(lexer), value: (expect(lexer, TokenKind.COLON), parseValueLiteral(lexer, isConst)), loc: loc(lexer, start), }; } // Implements the parsing rules in the Directives section. /** * Directives[Const] : Directive[?Const]+ */ function parseDirectives( lexer: Lexer<*>, isConst: boolean, ): Array<DirectiveNode> { const directives = []; while (peek(lexer, TokenKind.AT)) { directives.push(parseDirective(lexer, isConst)); } return directives; } /** * Directive[Const] : @ Name Arguments[?Const]? */ function parseDirective(lexer: Lexer<*>, isConst: boolean): DirectiveNode { const start = lexer.token; expect(lexer, TokenKind.AT); return { kind: Kind.DIRECTIVE, name: parseName(lexer), arguments: parseArguments(lexer, isConst), loc: loc(lexer, start), }; } // Implements the parsing rules in the Types section. /** * Type : * - NamedType * - ListType * - NonNullType */ export function parseTypeReference(lexer: Lexer<*>): TypeNode { const start = lexer.token; let type; if (skip(lexer, TokenKind.BRACKET_L)) { type = parseTypeReference(lexer); expect(lexer, TokenKind.BRACKET_R); type = ({ kind: Kind.LIST_TYPE, type, loc: loc(lexer, start), }: ListTypeNode); } else { type = parseNamedType(lexer); } if (skip(lexer, TokenKind.BANG)) { return ({ kind: Kind.NON_NULL_TYPE, type, loc: loc(lexer, start), }: NonNullTypeNode); } return type; } /** * NamedType : Name */ export function parseNamedType(lexer: Lexer<*>): NamedTypeNode { const start = lexer.token; return { kind: Kind.NAMED_TYPE, name: parseName(lexer), loc: loc(lexer, start), }; } // Implements the parsing rules in the Type Definition section. /** * TypeSystemDefinition : * - SchemaDefinition * - TypeDefinition * - TypeExtension * - DirectiveDefinition * * TypeDefinition : * - ScalarTypeDefinition * - ObjectTypeDefinition * - InterfaceTypeDefinition * - UnionTypeDefinition * - EnumTypeDefinition * - InputObjectTypeDefinition */ function parseTypeSystemDefinition(lexer: Lexer<*>): TypeSystemDefinitionNode { // Many definitions begin with a description and require a lookahead. const keywordToken = peekDescription(lexer) ? lexer.lookahead() : lexer.token; if (keywordToken.kind === TokenKind.NAME) { switch (keywordToken.value) { case 'schema': return parseSchemaDefinition(lexer); case 'scalar': return parseScalarTypeDefinition(lexer); case 'type': return parseObjectTypeDefinition(lexer); case 'interface': return parseInterfaceTypeDefinition(lexer); case 'union': return parseUnionTypeDefinition(lexer); case 'enum': return parseEnumTypeDefinition(lexer); case 'input': return parseInputObjectTypeDefinition(lexer); case 'extend': return parseTypeExtension(lexer); case 'directive': return parseDirectiveDefinition(lexer); } } throw unexpected(lexer, keywordToken); } function peekDescription(lexer: Lexer<*>): boolean { return peek(lexer, TokenKind.STRING) || peek(lexer, TokenKind.BLOCK_STRING); } /** * Description : StringValue */ function parseDescription(lexer: Lexer<*>): void | StringValueNode { if (peekDescription(lexer)) { return parseStringLiteral(lexer); } } /** * SchemaDefinition : schema Directives[Const]? { OperationTypeDefinition+ } */ function parseSchemaDefinition(lexer: Lexer<*>): SchemaDefinitionNode { const start = lexer.token; expectKeyword(lexer, 'schema'); const directives = parseDirectives(lexer, true); const operationTypes = many( lexer, TokenKind.BRACE_L, parseOperationTypeDefinition, TokenKind.BRACE_R, ); return { kind: Kind.SCHEMA_DEFINITION, directives, operationTypes, loc: loc(lexer, start), }; } /** * OperationTypeDefinition : OperationType : NamedType */ function parseOperationTypeDefinition( lexer: Lexer<*>, ): OperationTypeDefinitionNode { const start = lexer.token; const operation = parseOperationType(lexer); expect(lexer, TokenKind.COLON); const type = parseNamedType(lexer); return { kind: Kind.OPERATION_TYPE_DEFINITION, operation, type, loc: loc(lexer, start), }; } /** * ScalarTypeDefinition : Description? scalar Name Directives[Const]? */ function parseScalarTypeDefinition(lexer: Lexer<*>): ScalarTypeDefinitionNode { const start = lexer.token; const description = parseDescription(lexer); expectKeyword(lexer, 'scalar'); const name = parseName(lexer); const directives = parseDirectives(lexer, true); return { kind: Kind.SCALAR_TYPE_DEFINITION, description, name, directives, loc: loc(lexer, start), }; } /** * ObjectTypeDefinition : * Description? * type Name ImplementsInterfaces? Directives[Const]? FieldsDefinition? */ function parseObjectTypeDefinition(lexer: Lexer<*>): ObjectTypeDefinitionNode { const start = lexer.token; const description = parseDescription(lexer); expectKeyword(lexer, 'type'); const name = parseName(lexer); const interfaces = parseImplementsInterfaces(lexer); const directives = parseDirectives(lexer, true); const fields = parseFieldsDefinition(lexer); return { kind: Kind.OBJECT_TYPE_DEFINITION, description, name, interfaces, directives, fields, loc: loc(lexer, start), }; } /** * ImplementsInterfaces : * - implements `&`? NamedType * - ImplementsInterfaces & NamedType */ function parseImplementsInterfaces(lexer: Lexer<*>): Array<NamedTypeNode> { const types = []; if (lexer.token.value === 'implements') { lexer.advance(); // Optional leading ampersand skip(lexer, TokenKind.AMP); do { types.push(parseNamedType(lexer)); } while ( skip(lexer, TokenKind.AMP) || // Legacy support for the SDL? (lexer.options.allowLegacySDLImplementsInterfaces && peek(lexer, TokenKind.NAME)) ); } return types; } /** * FieldsDefinition : { FieldDefinition+ } */ function parseFieldsDefinition(lexer: Lexer<*>): Array<FieldDefinitionNode> { // Legacy support for the SDL? if ( lexer.options.allowLegacySDLEmptyFields && peek(lexer, TokenKind.BRACE_L) && lexer.lookahead().kind === TokenKind.BRACE_R ) { lexer.advance(); lexer.advance(); return []; } return peek(lexer, TokenKind.BRACE_L) ? many(lexer, TokenKind.BRACE_L, parseFieldDefinition, TokenKind.BRACE_R) : []; } /** * FieldDefinition : * - Description? Name ArgumentsDefinition? : Type Directives[Const]? */ function parseFieldDefinition(lexer: Lexer<*>): FieldDefinitionNode { const start = lexer.token; const description = parseDescription(lexer); const name = parseName(lexer); const args = parseArgumentDefs(lexer); expect(lexer, TokenKind.COLON); const type = parseTypeReference(lexer); const directives = parseDirectives(lexer, true); return { kind: Kind.FIELD_DEFINITION, description, name, arguments: args, type, directives, loc: loc(lexer, start), }; } /** * ArgumentsDefinition : ( InputValueDefinition+ ) */ function parseArgumentDefs(lexer: Lexer<*>): Array<InputValueDefinitionNode> { if (!peek(lexer, TokenKind.PAREN_L)) { return []; } return many(lexer, TokenKind.PAREN_L, parseInputValueDef, TokenKind.PAREN_R); } /** * InputValueDefinition : * - Description? Name : Type DefaultValue? Directives[Const]? */ function parseInputValueDef(lexer: Lexer<*>): InputValueDefinitionNode { const start = lexer.token; const description = parseDescription(lexer); const name = parseName(lexer); expect(lexer, TokenKind.COLON); const type = parseTypeReference(lexer); let defaultValue; if (skip(lexer, TokenKind.EQUALS)) { defaultValue = parseConstValue(lexer); } const directives = parseDirectives(lexer, true); return { kind: Kind.INPUT_VALUE_DEFINITION, description, name, type, defaultValue, directives, loc: loc(lexer, start), }; } /** * InterfaceTypeDefinition : * - Description? interface Name Directives[Const]? FieldsDefinition? */ function parseInterfaceTypeDefinition( lexer: Lexer<*>, ): InterfaceTypeDefinitionNode { const start = lexer.token; const description = parseDescription(lexer); expectKeyword(lexer, 'interface'); const name = parseName(lexer); const directives = parseDirectives(lexer, true); const fields = parseFieldsDefinition(lexer); return { kind: Kind.INTERFACE_TYPE_DEFINITION, description, name, directives, fields, loc: loc(lexer, start), }; } /** * UnionTypeDefinition : * - Description? union Name Directives[Const]? UnionMemberTypes? */ function parseUnionTypeDefinition(lexer: Lexer<*>): UnionTypeDefinitionNode { const start = lexer.token; const description = parseDescription(lexer); expectKeyword(lexer, 'union'); const name = parseName(lexer); const directives = parseDirectives(lexer, true); const types = parseUnionMemberTypes(lexer); return { kind: Kind.UNION_TYPE_DEFINITION, description, name, directives, types, loc: loc(lexer, start), }; } /** * UnionMemberTypes : * - = `|`? NamedType * - UnionMemberTypes | NamedType */ function parseUnionMemberTypes(lexer: Lexer<*>): Array<NamedTypeNode> { const types = []; if (skip(lexer, TokenKind.EQUALS)) { // Optional leading pipe skip(lexer, TokenKind.PIPE); do { types.push(parseNamedType(lexer)); } while (skip(lexer, TokenKind.PIPE)); } return types; } /** * EnumTypeDefinition : * - Description? enum Name Directives[Const]? EnumValuesDefinition? */ function parseEnumTypeDefinition(lexer: Lexer<*>): EnumTypeDefinitionNode { const start = lexer.token; const description = parseDescription(lexer); expectKeyword(lexer, 'enum'); const name = parseName(lexer); const directives = parseDirectives(lexer, true); const values = parseEnumValuesDefinition(lexer); return { kind: Kind.ENUM_TYPE_DEFINITION, description, name, directives, values, loc: loc(lexer, start), }; } /** * EnumValuesDefinition : { EnumValueDefinition+ } */ function parseEnumValuesDefinition( lexer: Lexer<*>, ): Array<EnumValueDefinitionNode> { return peek(lexer, TokenKind.BRACE_L) ? many( lexer, TokenKind.BRACE_L, parseEnumValueDefinition, TokenKind.BRACE_R, ) : []; } /** * EnumValueDefinition : Description? EnumValue Directives[Const]? * * EnumValue : Name */ function parseEnumValueDefinition(lexer: Lexer<*>): EnumValueDefinitionNode { const start = lexer.token; const description = parseDescription(lexer); const name = parseName(lexer); const directives = parseDirectives(lexer, true); return { kind: Kind.ENUM_VALUE_DEFINITION, description, name, directives, loc: loc(lexer, start), }; } /** * InputObjectTypeDefinition : * - Description? input Name Directives[Const]? InputFieldsDefinition? */ function parseInputObjectTypeDefinition( lexer: Lexer<*>, ): InputObjectTypeDefinitionNode { const start = lexer.token; const description = parseDescription(lexer); expectKeyword(lexer, 'input'); const name = parseName(lexer); const directives = parseDirectives(lexer, true); const fields = parseInputFieldsDefinition(lexer); return { kind: Kind.INPUT_OBJECT_TYPE_DEFINITION, description, name, directives, fields, loc: loc(lexer, start), }; } /** * InputFieldsDefinition : { InputValueDefinition+ } */ function parseInputFieldsDefinition( lexer: Lexer<*>, ): Array<InputValueDefinitionNode> { return peek(lexer, TokenKind.BRACE_L) ? many(lexer, TokenKind.BRACE_L, parseInputValueDef, TokenKind.BRACE_R) : []; } /** * TypeExtension : * - ScalarTypeExtension * - ObjectTypeExtension * - InterfaceTypeExtension * - UnionTypeExtension * - EnumTypeExtension * - InputObjectTypeDefinition */ function parseTypeExtension(lexer: Lexer<*>): TypeExtensionNode { const keywordToken = lexer.lookahead(); if (keywordToken.kind === TokenKind.NAME) { switch (keywordToken.value) { case 'scalar': return parseScalarTypeExtension(lexer); case 'type': return parseObjectTypeExtension(lexer); case 'interface': return parseInterfaceTypeExtension(lexer); case 'union': return parseUnionTypeExtension(lexer); case 'enum': return parseEnumTypeExtension(lexer); case 'input': return parseInputObjectTypeExtension(lexer); } } throw unexpected(lexer, keywordToken); } /** * ScalarTypeExtension : * - extend scalar Name Directives[Const] */ function parseScalarTypeExtension(lexer: Lexer<*>): ScalarTypeExtensionNode { const start = lexer.token; expectKeyword(lexer, 'extend'); expectKeyword(lexer, 'scalar'); const name = parseName(lexer); const directives = parseDirectives(lexer, true); if (directives.length === 0) { throw unexpected(lexer); } return { kind: Kind.SCALAR_TYPE_EXTENSION, name, directives, loc: loc(lexer, start), }; } /** * ObjectTypeExtension : * - extend type Name ImplementsInterfaces? Directives[Const]? FieldsDefinition * - extend type Name ImplementsInterfaces? Directives[Const] * - extend type Name ImplementsInterfaces */ function parseObjectTypeExtension(lexer: Lexer<*>): ObjectTypeExtensionNode { const start = lexer.token; expectKeyword(lexer, 'extend'); expectKeyword(lexer, 'type'); const name = parseName(lexer); const interfaces = parseImplementsInterfaces(lexer); const directives = parseDirectives(lexer, true); const fields = parseFieldsDefinition(lexer); if ( interfaces.length === 0 && directives.length === 0 && fields.length === 0 ) { throw unexpected(lexer); } return { kind: Kind.OBJECT_TYPE_EXTENSION, name, interfaces, directives, fields, loc: loc(lexer, start), }; } /** * InterfaceTypeExtension : * - extend interface Name Directives[Const]? FieldsDefinition * - extend interface Name Directives[Const] */ function parseInterfaceTypeExtension( lexer: Lexer<*>, ): InterfaceTypeExtensionNode { const start = lexer.token; expectKeyword(lexer, 'extend'); expectKeyword(lexer, 'interface'); const name = parseName(lexer); const directives = parseDirectives(lexer, true); const fields = parseFieldsDefinition(lexer); if (directives.length === 0 && fields.length === 0) { throw unexpected(lexer); } return { kind: Kind.INTERFACE_TYPE_EXTENSION, name, directives, fields, loc: loc(lexer, start), }; } /** * UnionTypeExtension : * - extend union Name Directives[Const]? UnionMemberTypes * - extend union Name Directives[Const] */ function parseUnionTypeExtension(lexer: Lexer<*>): UnionTypeExtensionNode { const start = lexer.token; expectKeyword(lexer, 'extend'); expectKeyword(lexer, 'union'); const name = parseName(lexer); const directives = parseDirectives(lexer, true); const types = parseUnionMemberTypes(lexer); if (directives.length === 0 && types.length === 0) { throw unexpected(lexer); } return { kind: Kind.UNION_TYPE_EXTENSION, name, directives, types, loc: loc(lexer, start), }; } /** * EnumTypeExtension : * - extend enum Name Directives[Const]? EnumValuesDefinition * - extend enum Name Directives[Const] */ function parseEnumTypeExtension(lexer: Lexer<*>): EnumTypeExtensionNode { const start = lexer.token; expectKeyword(lexer, 'extend'); expectKeyword(lexer, 'enum'); const name = parseName(lexer); const directives = parseDirectives(lexer, true); const values = parseEnumValuesDefinition(lexer); if (directives.length === 0 && values.length === 0) { throw unexpected(lexer); } return { kind: Kind.ENUM_TYPE_EXTENSION, name, directives, values, loc: loc(lexer, start), }; } /** * InputObjectTypeExtension : * - extend input Name Directives[Const]? InputFieldsDefinition * - extend input Name Directives[Const] */ function parseInputObjectTypeExtension( lexer: Lexer<*>, ): InputObjectTypeExtensionNode { const start = lexer.token; expectKeyword(lexer, 'extend'); expectKeyword(lexer, 'input'); const name = parseName(lexer); const directives = parseDirectives(lexer, true); const fields = parseInputFieldsDefinition(lexer); if (directives.length === 0 && fields.length === 0) { throw unexpected(lexer); } return { kind: Kind.INPUT_OBJECT_TYPE_EXTENSION, name, directives, fields, loc: loc(lexer, start), }; } /** * DirectiveDefinition : * - Description? directive @ Name ArgumentsDefinition? on DirectiveLocations */ function parseDirectiveDefinition(lexer: Lexer<*>): DirectiveDefinitionNode { const start = lexer.token; const description = parseDescription(lexer); expectKeyword(lexer, 'directive'); expect(lexer, TokenKind.AT); const name = parseName(lexer); const args = parseArgumentDefs(lexer); expectKeyword(lexer, 'on'); const locations = parseDirectiveLocations(lexer); return { kind: Kind.DIRECTIVE_DEFINITION, description, name, arguments: args, locations, loc: loc(lexer, start), }; } /** * DirectiveLocations : * - `|`? DirectiveLocation * - DirectiveLocations | DirectiveLocation */ function parseDirectiveLocations(lexer: Lexer<*>): Array<NameNode> { // Optional leading pipe skip(lexer, TokenKind.PIPE); const locations = []; do { locations.push(parseDirectiveLocation(lexer)); } while (skip(lexer, TokenKind.PIPE)); return locations; } /* * 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` */ function parseDirectiveLocation(lexer: Lexer<*>): NameNode { const start = lexer.token; const name = parseName(lexer); if (DirectiveLocation.hasOwnProperty(name.value)) { return name; } throw unexpected(lexer, start); } // Core parsing utility functions /** * Returns a location object, used to identify the place in * the source that created a given parsed object. */ function loc(lexer: Lexer<*>, startToken: Token): Location | void { if (!lexer.options.noLocation) { return new Loc(startToken, lexer.lastToken, lexer.source); } } function Loc(startToken: Token, endToken: Token, source: Source) { this.start = startToken.start; this.end = endToken.end; this.startToken = startToken; this.endToken = endToken; this.source = source; } // Print a simplified form when appearing in JSON/util.inspect. Loc.prototype.toJSON = Loc.prototype.inspect = function toJSON() { return { start: this.start, end: this.end }; }; /** * Determines if the next token is of a given kind */ function peek(lexer: Lexer<*>, kind: TokenKindEnum): boolean { return lexer.token.kind === kind; } /** * 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. */ function skip(lexer: Lexer<*>, kind: TokenKindEnum): boolean { const match = lexer.token.kind === kind; if (match) { lexer.advance(); } return match; } /** * 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. */ function expect(lexer: Lexer<*>, kind: TokenKindEnum): Token { const token = lexer.token; if (token.kind === kind) { lexer.advance(); return token; } throw syntaxError( lexer.source, token.start, `Expected ${kind}, found ${getTokenDesc(token)}`, ); } /** * If the next token is a keyword with the given value, return that token after * advancing the lexer. Otherwise, do not change the parser state and return * false. */ function expectKeyword(lexer: Lexer<*>, value: string): Token { const token = lexer.token; if (token.kind === TokenKind.NAME && token.value === value) { lexer.advance(); return token; } throw syntaxError( lexer.source, token.start, `Expected "${value}", found ${getTokenDesc(token)}`, ); } /** * Helper function for creating an error when an unexpected lexed token * is encountered. */ function unexpected(lexer: Lexer<*>, atToken?: ?Token): GraphQLError { const token = atToken || lexer.token; return syntaxError( 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. */ function any<T>( lexer: Lexer<*>, openKind: TokenKindEnum, parseFn: (lexer: Lexer<*>) => T, closeKind: TokenKindEnum, ): Array<T> { expect(lexer, openKind); const nodes = []; while (!skip(lexer, closeKind)) { nodes.push(parseFn(lexer)); } return nodes; } /** * 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. */ function many<T>( lexer: Lexer<*>, openKind: TokenKindEnum, parseFn: (lexer: Lexer<*>) => T, closeKind: TokenKindEnum, ): Array<T> { expect(lexer, openKind); const nodes = [parseFn(lexer)]; while (!skip(lexer, closeKind)) { nodes.push(parseFn(lexer)); } return nodes; }