summaryrefslogtreecommitdiff
path: root/school/node_modules/graphql/language/parser.js.flow
diff options
context:
space:
mode:
Diffstat (limited to 'school/node_modules/graphql/language/parser.js.flow')
-rw-r--r--school/node_modules/graphql/language/parser.js.flow1568
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;
+}