diff options
Diffstat (limited to 'school/node_modules/graphql/language/parser.js.flow')
-rw-r--r-- | school/node_modules/graphql/language/parser.js.flow | 1568 |
1 files changed, 1568 insertions, 0 deletions
diff --git a/school/node_modules/graphql/language/parser.js.flow b/school/node_modules/graphql/language/parser.js.flow new file mode 100644 index 0000000..ee8bd3e --- /dev/null +++ b/school/node_modules/graphql/language/parser.js.flow @@ -0,0 +1,1568 @@ +// @flow strict +import type { GraphQLError } from '../error/GraphQLError'; +import { syntaxError } from '../error/syntaxError'; + +import type { TokenKindEnum } from './tokenKind'; +import type { + Token, + NameNode, + VariableNode, + DocumentNode, + DefinitionNode, + OperationDefinitionNode, + OperationTypeNode, + VariableDefinitionNode, + SelectionSetNode, + SelectionNode, + FieldNode, + ArgumentNode, + FragmentSpreadNode, + InlineFragmentNode, + FragmentDefinitionNode, + ValueNode, + StringValueNode, + ListValueNode, + ObjectValueNode, + ObjectFieldNode, + DirectiveNode, + TypeNode, + NamedTypeNode, + TypeSystemDefinitionNode, + SchemaDefinitionNode, + OperationTypeDefinitionNode, + ScalarTypeDefinitionNode, + ObjectTypeDefinitionNode, + FieldDefinitionNode, + InputValueDefinitionNode, + InterfaceTypeDefinitionNode, + UnionTypeDefinitionNode, + EnumTypeDefinitionNode, + EnumValueDefinitionNode, + InputObjectTypeDefinitionNode, + DirectiveDefinitionNode, + TypeSystemExtensionNode, + SchemaExtensionNode, + ScalarTypeExtensionNode, + ObjectTypeExtensionNode, + InterfaceTypeExtensionNode, + UnionTypeExtensionNode, + EnumTypeExtensionNode, + InputObjectTypeExtensionNode, +} from './ast'; +import { Kind } from './kinds'; +import { Location } from './ast'; +import { TokenKind } from './tokenKind'; +import { Source, isSource } from './source'; +import { DirectiveLocation } from './directiveLocation'; +import { Lexer, isPunctuatorTokenKind } from './lexer'; + +/** + * 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 v16. + */ + 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 v16. + */ + 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 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: string | Source, + options?: ParseOptions, +): ValueNode { + const parser = new Parser(source, options); + parser.expectToken(TokenKind.SOF); + const value = parser.parseValueLiteral(false); + 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: string | Source, + options?: ParseOptions, +): TypeNode { + 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 { + _options: ?ParseOptions; + _lexer: Lexer; + + constructor(source: string | Source, options?: ParseOptions) { + const sourceObj = isSource(source) ? source : new Source(source); + + this._lexer = new Lexer(sourceObj); + this._options = options; + } + + /** + * Converts a name lex token into a name parse node. + */ + parseName(): NameNode { + const token = this.expectToken(TokenKind.NAME); + return { + kind: Kind.NAME, + value: ((token.value: any): string), + loc: this.loc(token), + }; + } + + // Implements the parsing rules in the Document section. + + /** + * Document : Definition+ + */ + parseDocument(): DocumentNode { + const start = this._lexer.token; + return { + kind: Kind.DOCUMENT, + definitions: this.many( + TokenKind.SOF, + this.parseDefinition, + TokenKind.EOF, + ), + loc: this.loc(start), + }; + } + + /** + * Definition : + * - ExecutableDefinition + * - TypeSystemDefinition + * - TypeSystemExtension + * + * ExecutableDefinition : + * - OperationDefinition + * - FragmentDefinition + */ + parseDefinition(): DefinitionNode { + if (this.peek(TokenKind.NAME)) { + switch (this._lexer.token.value) { + case 'query': + case 'mutation': + case 'subscription': + return this.parseOperationDefinition(); + case 'fragment': + return this.parseFragmentDefinition(); + case 'schema': + case 'scalar': + case 'type': + case 'interface': + case 'union': + case 'enum': + case 'input': + case 'directive': + return this.parseTypeSystemDefinition(); + case 'extend': + return this.parseTypeSystemExtension(); + } + } else if (this.peek(TokenKind.BRACE_L)) { + return this.parseOperationDefinition(); + } else if (this.peekDescription()) { + return this.parseTypeSystemDefinition(); + } + + throw this.unexpected(); + } + + // Implements the parsing rules in the Operations section. + + /** + * OperationDefinition : + * - SelectionSet + * - OperationType Name? VariableDefinitions? Directives? SelectionSet + */ + parseOperationDefinition(): OperationDefinitionNode { + const start = this._lexer.token; + if (this.peek(TokenKind.BRACE_L)) { + return { + kind: Kind.OPERATION_DEFINITION, + operation: 'query', + name: undefined, + variableDefinitions: [], + directives: [], + selectionSet: this.parseSelectionSet(), + loc: this.loc(start), + }; + } + const operation = this.parseOperationType(); + let name; + if (this.peek(TokenKind.NAME)) { + name = this.parseName(); + } + return { + kind: Kind.OPERATION_DEFINITION, + operation, + name, + variableDefinitions: this.parseVariableDefinitions(), + directives: this.parseDirectives(false), + selectionSet: this.parseSelectionSet(), + loc: this.loc(start), + }; + } + + /** + * OperationType : one of query mutation subscription + */ + parseOperationType(): OperationTypeNode { + const operationToken = this.expectToken(TokenKind.NAME); + switch (operationToken.value) { + case 'query': + return 'query'; + case 'mutation': + return 'mutation'; + case 'subscription': + return 'subscription'; + } + + throw this.unexpected(operationToken); + } + + /** + * VariableDefinitions : ( VariableDefinition+ ) + */ + parseVariableDefinitions(): Array<VariableDefinitionNode> { + return this.optionalMany( + TokenKind.PAREN_L, + this.parseVariableDefinition, + TokenKind.PAREN_R, + ); + } + + /** + * VariableDefinition : Variable : Type DefaultValue? Directives[Const]? + */ + parseVariableDefinition(): VariableDefinitionNode { + const start = this._lexer.token; + return { + kind: Kind.VARIABLE_DEFINITION, + variable: this.parseVariable(), + type: (this.expectToken(TokenKind.COLON), this.parseTypeReference()), + defaultValue: this.expectOptionalToken(TokenKind.EQUALS) + ? this.parseValueLiteral(true) + : undefined, + directives: this.parseDirectives(true), + loc: this.loc(start), + }; + } + + /** + * Variable : $ Name + */ + parseVariable(): VariableNode { + const start = this._lexer.token; + this.expectToken(TokenKind.DOLLAR); + return { + kind: Kind.VARIABLE, + name: this.parseName(), + loc: this.loc(start), + }; + } + + /** + * SelectionSet : { Selection+ } + */ + parseSelectionSet(): SelectionSetNode { + const start = this._lexer.token; + return { + kind: Kind.SELECTION_SET, + selections: this.many( + TokenKind.BRACE_L, + this.parseSelection, + TokenKind.BRACE_R, + ), + loc: this.loc(start), + }; + } + + /** + * Selection : + * - Field + * - FragmentSpread + * - InlineFragment + */ + parseSelection(): SelectionNode { + return this.peek(TokenKind.SPREAD) + ? this.parseFragment() + : this.parseField(); + } + + /** + * Field : Alias? Name Arguments? Directives? SelectionSet? + * + * Alias : Name : + */ + parseField(): FieldNode { + 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 { + kind: Kind.FIELD, + alias, + name, + arguments: this.parseArguments(false), + directives: this.parseDirectives(false), + selectionSet: this.peek(TokenKind.BRACE_L) + ? this.parseSelectionSet() + : undefined, + loc: this.loc(start), + }; + } + + /** + * Arguments[Const] : ( Argument[?Const]+ ) + */ + parseArguments(isConst: boolean): Array<ArgumentNode> { + const item = isConst ? this.parseConstArgument : this.parseArgument; + return this.optionalMany(TokenKind.PAREN_L, item, TokenKind.PAREN_R); + } + + /** + * Argument[Const] : Name : Value[?Const] + */ + parseArgument(): ArgumentNode { + const start = this._lexer.token; + const name = this.parseName(); + + this.expectToken(TokenKind.COLON); + return { + kind: Kind.ARGUMENT, + name, + value: this.parseValueLiteral(false), + loc: this.loc(start), + }; + } + + parseConstArgument(): ArgumentNode { + const start = this._lexer.token; + return { + kind: Kind.ARGUMENT, + name: this.parseName(), + value: (this.expectToken(TokenKind.COLON), this.parseValueLiteral(true)), + loc: this.loc(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 + */ + parseFragment(): FragmentSpreadNode | InlineFragmentNode { + const start = this._lexer.token; + this.expectToken(TokenKind.SPREAD); + + const hasTypeCondition = this.expectOptionalKeyword('on'); + if (!hasTypeCondition && this.peek(TokenKind.NAME)) { + return { + kind: Kind.FRAGMENT_SPREAD, + name: this.parseFragmentName(), + directives: this.parseDirectives(false), + loc: this.loc(start), + }; + } + return { + kind: Kind.INLINE_FRAGMENT, + typeCondition: hasTypeCondition ? this.parseNamedType() : undefined, + directives: this.parseDirectives(false), + selectionSet: this.parseSelectionSet(), + loc: this.loc(start), + }; + } + + /** + * FragmentDefinition : + * - fragment FragmentName on TypeCondition Directives? SelectionSet + * + * TypeCondition : NamedType + */ + parseFragmentDefinition(): FragmentDefinitionNode { + const start = this._lexer.token; + this.expectKeyword('fragment'); + // Experimental support for defining variables within fragments changes + // the grammar of FragmentDefinition: + // - fragment FragmentName VariableDefinitions? on TypeCondition Directives? SelectionSet + if (this._options?.experimentalFragmentVariables === true) { + return { + kind: Kind.FRAGMENT_DEFINITION, + name: this.parseFragmentName(), + variableDefinitions: this.parseVariableDefinitions(), + typeCondition: (this.expectKeyword('on'), this.parseNamedType()), + directives: this.parseDirectives(false), + selectionSet: this.parseSelectionSet(), + loc: this.loc(start), + }; + } + return { + kind: Kind.FRAGMENT_DEFINITION, + name: this.parseFragmentName(), + typeCondition: (this.expectKeyword('on'), this.parseNamedType()), + directives: this.parseDirectives(false), + selectionSet: this.parseSelectionSet(), + loc: this.loc(start), + }; + } + + /** + * FragmentName : Name but not `on` + */ + parseFragmentName(): NameNode { + 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: boolean): ValueNode { + 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._lexer.advance(); + return { + kind: Kind.INT, + value: ((token.value: any): string), + loc: this.loc(token), + }; + case TokenKind.FLOAT: + this._lexer.advance(); + return { + kind: Kind.FLOAT, + value: ((token.value: any): string), + loc: this.loc(token), + }; + case TokenKind.STRING: + case TokenKind.BLOCK_STRING: + return this.parseStringLiteral(); + case TokenKind.NAME: + this._lexer.advance(); + switch (token.value) { + case 'true': + return { kind: Kind.BOOLEAN, value: true, loc: this.loc(token) }; + case 'false': + return { kind: Kind.BOOLEAN, value: false, loc: this.loc(token) }; + case 'null': + return { kind: Kind.NULL, loc: this.loc(token) }; + default: + return { + kind: Kind.ENUM, + value: ((token.value: any): string), + loc: this.loc(token), + }; + } + case TokenKind.DOLLAR: + if (!isConst) { + return this.parseVariable(); + } + break; + } + throw this.unexpected(); + } + + parseStringLiteral(): StringValueNode { + const token = this._lexer.token; + this._lexer.advance(); + return { + kind: Kind.STRING, + value: ((token.value: any): string), + block: token.kind === TokenKind.BLOCK_STRING, + loc: this.loc(token), + }; + } + + /** + * ListValue[Const] : + * - [ ] + * - [ Value[?Const]+ ] + */ + parseList(isConst: boolean): ListValueNode { + const start = this._lexer.token; + const item = () => this.parseValueLiteral(isConst); + return { + kind: Kind.LIST, + values: this.any(TokenKind.BRACKET_L, item, TokenKind.BRACKET_R), + loc: this.loc(start), + }; + } + + /** + * ObjectValue[Const] : + * - { } + * - { ObjectField[?Const]+ } + */ + parseObject(isConst: boolean): ObjectValueNode { + const start = this._lexer.token; + const item = () => this.parseObjectField(isConst); + return { + kind: Kind.OBJECT, + fields: this.any(TokenKind.BRACE_L, item, TokenKind.BRACE_R), + loc: this.loc(start), + }; + } + + /** + * ObjectField[Const] : Name : Value[?Const] + */ + parseObjectField(isConst: boolean): ObjectFieldNode { + const start = this._lexer.token; + const name = this.parseName(); + this.expectToken(TokenKind.COLON); + + return { + kind: Kind.OBJECT_FIELD, + name, + value: this.parseValueLiteral(isConst), + loc: this.loc(start), + }; + } + + // Implements the parsing rules in the Directives section. + + /** + * Directives[Const] : Directive[?Const]+ + */ + parseDirectives(isConst: boolean): Array<DirectiveNode> { + const directives = []; + while (this.peek(TokenKind.AT)) { + directives.push(this.parseDirective(isConst)); + } + return directives; + } + + /** + * Directive[Const] : @ Name Arguments[?Const]? + */ + parseDirective(isConst: boolean): DirectiveNode { + const start = this._lexer.token; + this.expectToken(TokenKind.AT); + return { + kind: Kind.DIRECTIVE, + name: this.parseName(), + arguments: this.parseArguments(isConst), + loc: this.loc(start), + }; + } + + // Implements the parsing rules in the Types section. + + /** + * Type : + * - NamedType + * - ListType + * - NonNullType + */ + parseTypeReference(): TypeNode { + const start = this._lexer.token; + let type; + if (this.expectOptionalToken(TokenKind.BRACKET_L)) { + type = this.parseTypeReference(); + this.expectToken(TokenKind.BRACKET_R); + type = { + kind: Kind.LIST_TYPE, + type, + loc: this.loc(start), + }; + } else { + type = this.parseNamedType(); + } + + if (this.expectOptionalToken(TokenKind.BANG)) { + return { + kind: Kind.NON_NULL_TYPE, + type, + loc: this.loc(start), + }; + } + return type; + } + + /** + * NamedType : Name + */ + parseNamedType(): NamedTypeNode { + const start = this._lexer.token; + return { + kind: Kind.NAMED_TYPE, + name: this.parseName(), + loc: this.loc(start), + }; + } + + // Implements the parsing rules in the Type Definition section. + + /** + * TypeSystemDefinition : + * - SchemaDefinition + * - TypeDefinition + * - DirectiveDefinition + * + * TypeDefinition : + * - ScalarTypeDefinition + * - ObjectTypeDefinition + * - InterfaceTypeDefinition + * - UnionTypeDefinition + * - EnumTypeDefinition + * - InputObjectTypeDefinition + */ + parseTypeSystemDefinition(): TypeSystemDefinitionNode { + // Many definitions begin with a description and require a lookahead. + const keywordToken = this.peekDescription() + ? 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(); + } + } + + throw this.unexpected(keywordToken); + } + + peekDescription(): boolean { + return this.peek(TokenKind.STRING) || this.peek(TokenKind.BLOCK_STRING); + } + + /** + * Description : StringValue + */ + parseDescription(): void | StringValueNode { + if (this.peekDescription()) { + return this.parseStringLiteral(); + } + } + + /** + * SchemaDefinition : Description? schema Directives[Const]? { OperationTypeDefinition+ } + */ + parseSchemaDefinition(): SchemaDefinitionNode { + const start = this._lexer.token; + const description = this.parseDescription(); + this.expectKeyword('schema'); + const directives = this.parseDirectives(true); + const operationTypes = this.many( + TokenKind.BRACE_L, + this.parseOperationTypeDefinition, + TokenKind.BRACE_R, + ); + return { + kind: Kind.SCHEMA_DEFINITION, + description, + directives, + operationTypes, + loc: this.loc(start), + }; + } + + /** + * OperationTypeDefinition : OperationType : NamedType + */ + parseOperationTypeDefinition(): OperationTypeDefinitionNode { + const start = this._lexer.token; + const operation = this.parseOperationType(); + this.expectToken(TokenKind.COLON); + const type = this.parseNamedType(); + return { + kind: Kind.OPERATION_TYPE_DEFINITION, + operation, + type, + loc: this.loc(start), + }; + } + + /** + * ScalarTypeDefinition : Description? scalar Name Directives[Const]? + */ + parseScalarTypeDefinition(): ScalarTypeDefinitionNode { + const start = this._lexer.token; + const description = this.parseDescription(); + this.expectKeyword('scalar'); + const name = this.parseName(); + const directives = this.parseDirectives(true); + return { + kind: Kind.SCALAR_TYPE_DEFINITION, + description, + name, + directives, + loc: this.loc(start), + }; + } + + /** + * ObjectTypeDefinition : + * Description? + * type Name ImplementsInterfaces? Directives[Const]? FieldsDefinition? + */ + parseObjectTypeDefinition(): ObjectTypeDefinitionNode { + const start = this._lexer.token; + const description = this.parseDescription(); + this.expectKeyword('type'); + const name = this.parseName(); + const interfaces = this.parseImplementsInterfaces(); + const directives = this.parseDirectives(true); + const fields = this.parseFieldsDefinition(); + return { + kind: Kind.OBJECT_TYPE_DEFINITION, + description, + name, + interfaces, + directives, + fields, + loc: this.loc(start), + }; + } + + /** + * ImplementsInterfaces : + * - implements `&`? NamedType + * - ImplementsInterfaces & NamedType + */ + parseImplementsInterfaces(): Array<NamedTypeNode> { + if (!this.expectOptionalKeyword('implements')) { + return []; + } + + if (this._options?.allowLegacySDLImplementsInterfaces === true) { + const types = []; + // Optional leading ampersand + this.expectOptionalToken(TokenKind.AMP); + do { + types.push(this.parseNamedType()); + } while ( + this.expectOptionalToken(TokenKind.AMP) || + this.peek(TokenKind.NAME) + ); + return types; + } + + return this.delimitedMany(TokenKind.AMP, this.parseNamedType); + } + + /** + * FieldsDefinition : { FieldDefinition+ } + */ + parseFieldsDefinition(): Array<FieldDefinitionNode> { + // Legacy support for the SDL? + if ( + this._options?.allowLegacySDLEmptyFields === true && + this.peek(TokenKind.BRACE_L) && + this._lexer.lookahead().kind === TokenKind.BRACE_R + ) { + this._lexer.advance(); + this._lexer.advance(); + return []; + } + return this.optionalMany( + TokenKind.BRACE_L, + this.parseFieldDefinition, + TokenKind.BRACE_R, + ); + } + + /** + * FieldDefinition : + * - Description? Name ArgumentsDefinition? : Type Directives[Const]? + */ + parseFieldDefinition(): FieldDefinitionNode { + 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.parseDirectives(true); + return { + kind: Kind.FIELD_DEFINITION, + description, + name, + arguments: args, + type, + directives, + loc: this.loc(start), + }; + } + + /** + * ArgumentsDefinition : ( InputValueDefinition+ ) + */ + parseArgumentDefs(): Array<InputValueDefinitionNode> { + return this.optionalMany( + TokenKind.PAREN_L, + this.parseInputValueDef, + TokenKind.PAREN_R, + ); + } + + /** + * InputValueDefinition : + * - Description? Name : Type DefaultValue? Directives[Const]? + */ + parseInputValueDef(): InputValueDefinitionNode { + 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.parseValueLiteral(true); + } + const directives = this.parseDirectives(true); + return { + kind: Kind.INPUT_VALUE_DEFINITION, + description, + name, + type, + defaultValue, + directives, + loc: this.loc(start), + }; + } + + /** + * InterfaceTypeDefinition : + * - Description? interface Name Directives[Const]? FieldsDefinition? + */ + parseInterfaceTypeDefinition(): InterfaceTypeDefinitionNode { + const start = this._lexer.token; + const description = this.parseDescription(); + this.expectKeyword('interface'); + const name = this.parseName(); + const interfaces = this.parseImplementsInterfaces(); + const directives = this.parseDirectives(true); + const fields = this.parseFieldsDefinition(); + return { + kind: Kind.INTERFACE_TYPE_DEFINITION, + description, + name, + interfaces, + directives, + fields, + loc: this.loc(start), + }; + } + + /** + * UnionTypeDefinition : + * - Description? union Name Directives[Const]? UnionMemberTypes? + */ + parseUnionTypeDefinition(): UnionTypeDefinitionNode { + const start = this._lexer.token; + const description = this.parseDescription(); + this.expectKeyword('union'); + const name = this.parseName(); + const directives = this.parseDirectives(true); + const types = this.parseUnionMemberTypes(); + return { + kind: Kind.UNION_TYPE_DEFINITION, + description, + name, + directives, + types, + loc: this.loc(start), + }; + } + + /** + * UnionMemberTypes : + * - = `|`? NamedType + * - UnionMemberTypes | NamedType + */ + parseUnionMemberTypes(): Array<NamedTypeNode> { + return this.expectOptionalToken(TokenKind.EQUALS) + ? this.delimitedMany(TokenKind.PIPE, this.parseNamedType) + : []; + } + + /** + * EnumTypeDefinition : + * - Description? enum Name Directives[Const]? EnumValuesDefinition? + */ + parseEnumTypeDefinition(): EnumTypeDefinitionNode { + const start = this._lexer.token; + const description = this.parseDescription(); + this.expectKeyword('enum'); + const name = this.parseName(); + const directives = this.parseDirectives(true); + const values = this.parseEnumValuesDefinition(); + return { + kind: Kind.ENUM_TYPE_DEFINITION, + description, + name, + directives, + values, + loc: this.loc(start), + }; + } + + /** + * EnumValuesDefinition : { EnumValueDefinition+ } + */ + parseEnumValuesDefinition(): Array<EnumValueDefinitionNode> { + return this.optionalMany( + TokenKind.BRACE_L, + this.parseEnumValueDefinition, + TokenKind.BRACE_R, + ); + } + + /** + * EnumValueDefinition : Description? EnumValue Directives[Const]? + * + * EnumValue : Name + */ + parseEnumValueDefinition(): EnumValueDefinitionNode { + const start = this._lexer.token; + const description = this.parseDescription(); + const name = this.parseName(); + const directives = this.parseDirectives(true); + return { + kind: Kind.ENUM_VALUE_DEFINITION, + description, + name, + directives, + loc: this.loc(start), + }; + } + + /** + * InputObjectTypeDefinition : + * - Description? input Name Directives[Const]? InputFieldsDefinition? + */ + parseInputObjectTypeDefinition(): InputObjectTypeDefinitionNode { + const start = this._lexer.token; + const description = this.parseDescription(); + this.expectKeyword('input'); + const name = this.parseName(); + const directives = this.parseDirectives(true); + const fields = this.parseInputFieldsDefinition(); + return { + kind: Kind.INPUT_OBJECT_TYPE_DEFINITION, + description, + name, + directives, + fields, + loc: this.loc(start), + }; + } + + /** + * InputFieldsDefinition : { InputValueDefinition+ } + */ + parseInputFieldsDefinition(): Array<InputValueDefinitionNode> { + return this.optionalMany( + TokenKind.BRACE_L, + this.parseInputValueDef, + TokenKind.BRACE_R, + ); + } + + /** + * TypeSystemExtension : + * - SchemaExtension + * - TypeExtension + * + * TypeExtension : + * - ScalarTypeExtension + * - ObjectTypeExtension + * - InterfaceTypeExtension + * - UnionTypeExtension + * - EnumTypeExtension + * - InputObjectTypeDefinition + */ + parseTypeSystemExtension(): TypeSystemExtensionNode { + 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(): SchemaExtensionNode { + const start = this._lexer.token; + this.expectKeyword('extend'); + this.expectKeyword('schema'); + const directives = this.parseDirectives(true); + const operationTypes = this.optionalMany( + TokenKind.BRACE_L, + this.parseOperationTypeDefinition, + TokenKind.BRACE_R, + ); + if (directives.length === 0 && operationTypes.length === 0) { + throw this.unexpected(); + } + return { + kind: Kind.SCHEMA_EXTENSION, + directives, + operationTypes, + loc: this.loc(start), + }; + } + + /** + * ScalarTypeExtension : + * - extend scalar Name Directives[Const] + */ + parseScalarTypeExtension(): ScalarTypeExtensionNode { + const start = this._lexer.token; + this.expectKeyword('extend'); + this.expectKeyword('scalar'); + const name = this.parseName(); + const directives = this.parseDirectives(true); + if (directives.length === 0) { + throw this.unexpected(); + } + return { + kind: Kind.SCALAR_TYPE_EXTENSION, + name, + directives, + loc: this.loc(start), + }; + } + + /** + * ObjectTypeExtension : + * - extend type Name ImplementsInterfaces? Directives[Const]? FieldsDefinition + * - extend type Name ImplementsInterfaces? Directives[Const] + * - extend type Name ImplementsInterfaces + */ + parseObjectTypeExtension(): ObjectTypeExtensionNode { + const start = this._lexer.token; + this.expectKeyword('extend'); + this.expectKeyword('type'); + const name = this.parseName(); + const interfaces = this.parseImplementsInterfaces(); + const directives = this.parseDirectives(true); + const fields = this.parseFieldsDefinition(); + if ( + interfaces.length === 0 && + directives.length === 0 && + fields.length === 0 + ) { + throw this.unexpected(); + } + return { + kind: Kind.OBJECT_TYPE_EXTENSION, + name, + interfaces, + directives, + fields, + loc: this.loc(start), + }; + } + + /** + * InterfaceTypeExtension : + * - extend interface Name ImplementsInterfaces? Directives[Const]? FieldsDefinition + * - extend interface Name ImplementsInterfaces? Directives[Const] + * - extend interface Name ImplementsInterfaces + */ + parseInterfaceTypeExtension(): InterfaceTypeExtensionNode { + const start = this._lexer.token; + this.expectKeyword('extend'); + this.expectKeyword('interface'); + const name = this.parseName(); + const interfaces = this.parseImplementsInterfaces(); + const directives = this.parseDirectives(true); + const fields = this.parseFieldsDefinition(); + if ( + interfaces.length === 0 && + directives.length === 0 && + fields.length === 0 + ) { + throw this.unexpected(); + } + return { + kind: Kind.INTERFACE_TYPE_EXTENSION, + name, + interfaces, + directives, + fields, + loc: this.loc(start), + }; + } + + /** + * UnionTypeExtension : + * - extend union Name Directives[Const]? UnionMemberTypes + * - extend union Name Directives[Const] + */ + parseUnionTypeExtension(): UnionTypeExtensionNode { + const start = this._lexer.token; + this.expectKeyword('extend'); + this.expectKeyword('union'); + const name = this.parseName(); + const directives = this.parseDirectives(true); + const types = this.parseUnionMemberTypes(); + if (directives.length === 0 && types.length === 0) { + throw this.unexpected(); + } + return { + kind: Kind.UNION_TYPE_EXTENSION, + name, + directives, + types, + loc: this.loc(start), + }; + } + + /** + * EnumTypeExtension : + * - extend enum Name Directives[Const]? EnumValuesDefinition + * - extend enum Name Directives[Const] + */ + parseEnumTypeExtension(): EnumTypeExtensionNode { + const start = this._lexer.token; + this.expectKeyword('extend'); + this.expectKeyword('enum'); + const name = this.parseName(); + const directives = this.parseDirectives(true); + const values = this.parseEnumValuesDefinition(); + if (directives.length === 0 && values.length === 0) { + throw this.unexpected(); + } + return { + kind: Kind.ENUM_TYPE_EXTENSION, + name, + directives, + values, + loc: this.loc(start), + }; + } + + /** + * InputObjectTypeExtension : + * - extend input Name Directives[Const]? InputFieldsDefinition + * - extend input Name Directives[Const] + */ + parseInputObjectTypeExtension(): InputObjectTypeExtensionNode { + const start = this._lexer.token; + this.expectKeyword('extend'); + this.expectKeyword('input'); + const name = this.parseName(); + const directives = this.parseDirectives(true); + const fields = this.parseInputFieldsDefinition(); + if (directives.length === 0 && fields.length === 0) { + throw this.unexpected(); + } + return { + kind: Kind.INPUT_OBJECT_TYPE_EXTENSION, + name, + directives, + fields, + loc: this.loc(start), + }; + } + + /** + * DirectiveDefinition : + * - Description? directive @ Name ArgumentsDefinition? `repeatable`? on DirectiveLocations + */ + parseDirectiveDefinition(): DirectiveDefinitionNode { + 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 { + kind: Kind.DIRECTIVE_DEFINITION, + description, + name, + arguments: args, + repeatable, + locations, + loc: this.loc(start), + }; + } + + /** + * DirectiveLocations : + * - `|`? DirectiveLocation + * - DirectiveLocations | DirectiveLocation + */ + parseDirectiveLocations(): Array<NameNode> { + 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(): NameNode { + const start = this._lexer.token; + const name = this.parseName(); + if (DirectiveLocation[name.value] !== undefined) { + return name; + } + throw this.unexpected(start); + } + + // Core parsing utility functions + + /** + * Returns a location object, used to identify the place in the source that created a given parsed object. + */ + loc(startToken: Token): Location | void { + if (this._options?.noLocation !== true) { + return new Location( + startToken, + this._lexer.lastToken, + this._lexer.source, + ); + } + } + + /** + * Determines if the next token is of a given kind + */ + peek(kind: TokenKindEnum): boolean { + 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: TokenKindEnum): Token { + const token = this._lexer.token; + if (token.kind === kind) { + this._lexer.advance(); + 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 that token after advancing the lexer. + * Otherwise, do not change the parser state and return undefined. + */ + expectOptionalToken(kind: TokenKindEnum): ?Token { + const token = this._lexer.token; + if (token.kind === kind) { + this._lexer.advance(); + return token; + } + return undefined; + } + + /** + * If the next token is a given keyword, advance the lexer. + * Otherwise, do not change the parser state and throw an error. + */ + expectKeyword(value: string) { + const token = this._lexer.token; + if (token.kind === TokenKind.NAME && token.value === value) { + this._lexer.advance(); + } 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: string): boolean { + const token = this._lexer.token; + if (token.kind === TokenKind.NAME && token.value === value) { + this._lexer.advance(); + return true; + } + return false; + } + + /** + * Helper function for creating an error when an unexpected lexed token is encountered. + */ + unexpected(atToken?: ?Token): GraphQLError { + const token = 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<T>( + openKind: TokenKindEnum, + parseFn: () => T, + closeKind: TokenKindEnum, + ): Array<T> { + 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<T>( + openKind: TokenKindEnum, + parseFn: () => T, + closeKind: TokenKindEnum, + ): Array<T> { + 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<T>( + openKind: TokenKindEnum, + parseFn: () => T, + closeKind: TokenKindEnum, + ): Array<T> { + 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<T>(delimiterKind: TokenKindEnum, parseFn: () => T): Array<T> { + this.expectOptionalToken(delimiterKind); + + const nodes = []; + do { + nodes.push(parseFn.call(this)); + } while (this.expectOptionalToken(delimiterKind)); + return nodes; + } +} + +/** + * A helper function to describe a token as a string for debugging. + */ +function getTokenDesc(token: Token): string { + 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: TokenKindEnum): string { + return isPunctuatorTokenKind(kind) ? `"${kind}"` : kind; +} |