This repository has been archived on 2023-08-20. You can view files and clone it, but cannot push or open issues or pull requests.
admin-panel/graphql-types/node_modules/graphql/language/parser.js.flow

1569 lines
42 KiB
Plaintext

// @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;
}