import type { Maybe } from '../jsutils/Maybe.js';
import type { GraphQLError } from '../error/GraphQLError.js';
import { syntaxError } from '../error/syntaxError.js';
import type {
ArgumentNode,
BooleanValueNode,
ConstArgumentNode,
ConstDirectiveNode,
ConstListValueNode,
ConstObjectFieldNode,
ConstObjectValueNode,
ConstValueNode,
DefinitionNode,
DirectiveDefinitionNode,
DirectiveNode,
DocumentNode,
EnumTypeDefinitionNode,
EnumTypeExtensionNode,
EnumValueDefinitionNode,
EnumValueNode,
ErrorBoundaryNode,
FieldDefinitionNode,
FieldNode,
FloatValueNode,
FragmentDefinitionNode,
FragmentSpreadNode,
InlineFragmentNode,
InputObjectTypeDefinitionNode,
InputObjectTypeExtensionNode,
InputValueDefinitionNode,
InterfaceTypeDefinitionNode,
InterfaceTypeExtensionNode,
IntValueNode,
ListNullabilityOperatorNode,
ListTypeNode,
ListValueNode,
NamedTypeNode,
NameNode,
NonNullAssertionNode,
NonNullTypeNode,
NullabilityAssertionNode,
NullValueNode,
ObjectFieldNode,
ObjectTypeDefinitionNode,
ObjectTypeExtensionNode,
ObjectValueNode,
OperationDefinitionNode,
OperationTypeDefinitionNode,
ScalarTypeDefinitionNode,
ScalarTypeExtensionNode,
SchemaDefinitionNode,
SchemaExtensionNode,
SelectionNode,
SelectionSetNode,
StringValueNode,
Token,
TypeNode,
TypeSystemExtensionNode,
UnionTypeDefinitionNode,
UnionTypeExtensionNode,
ValueNode,
VariableDefinitionNode,
VariableNode,
} from './ast.js';
import { Location, OperationTypeNode } from './ast.js';
import { DirectiveLocation } from './directiveLocation.js';
import { Kind } from './kinds.js';
import { isPunctuatorTokenKind, Lexer } from './lexer.js';
import { isSource, Source } from './source.js';
import { TokenKind } from './tokenKind.js';
export interface ParseOptions {
noLocation?: boolean | undefined;
maxTokens?: number | undefined;
allowLegacyFragmentVariables?: boolean | undefined;
experimentalClientControlledNullability?: boolean | undefined;
}
export function parse(
source: string | Source,
options?: ParseOptions | undefined,
): DocumentNode {
const parser = new Parser(source, options);
return parser.parseDocument();
}
export function parseValue(
source: string | Source,
options?: ParseOptions | undefined,
): ValueNode {
const parser = new Parser(source, options);
parser.expectToken(TokenKind.SOF);
const value = parser.parseValueLiteral(false);
parser.expectToken(TokenKind.EOF);
return value;
}
export function parseConstValue(
source: string | Source,
options?: ParseOptions | undefined,
): ConstValueNode {
const parser = new Parser(source, options);
parser.expectToken(TokenKind.SOF);
const value = parser.parseConstValueLiteral();
parser.expectToken(TokenKind.EOF);
return value;
}
export function parseType(
source: string | Source,
options?: ParseOptions | undefined,
): TypeNode {
const parser = new Parser(source, options);
parser.expectToken(TokenKind.SOF);
const type = parser.parseTypeReference();
parser.expectToken(TokenKind.EOF);
return type;
}
export class Parser {
protected _options: ParseOptions;
protected _lexer: Lexer;
protected _tokenCounter: number;
constructor(source: string | Source, options: ParseOptions = {}) {
const sourceObj = isSource(source) ? source : new Source(source);
this._lexer = new Lexer(sourceObj);
this._options = options;
this._tokenCounter = 0;
}
parseName(): NameNode {
const token = this.expectToken(TokenKind.NAME);
return this.node<NameNode>(token, {
kind: Kind.NAME,
value: token.value,
});
}
parseDocument(): DocumentNode {
return this.node<DocumentNode>(this._lexer.token, {
kind: Kind.DOCUMENT,
definitions: this.many(
TokenKind.SOF,
this.parseDefinition,
TokenKind.EOF,
),
});
}
parseDefinition(): DefinitionNode {
if (this.peek(TokenKind.BRACE_L)) {
return this.parseOperationDefinition();
}
const hasDescription = this.peekDescription();
const keywordToken = hasDescription
? this._lexer.lookahead()
: this._lexer.token;
if (keywordToken.kind === TokenKind.NAME) {
switch (keywordToken.value) {
case 'schema':
return this.parseSchemaDefinition();
case 'scalar':
return this.parseScalarTypeDefinition();
case 'type':
return this.parseObjectTypeDefinition();
case 'interface':
return this.parseInterfaceTypeDefinition();
case 'union':
return this.parseUnionTypeDefinition();
case 'enum':
return this.parseEnumTypeDefinition();
case 'input':
return this.parseInputObjectTypeDefinition();
case 'directive':
return this.parseDirectiveDefinition();
}
if (hasDescription) {
throw syntaxError(
this._lexer.source,
this._lexer.token.start,
'Unexpected description, descriptions are supported only on type definitions.',
);
}
switch (keywordToken.value) {
case 'query':
case 'mutation':
case 'subscription':
return this.parseOperationDefinition();
case 'fragment':
return this.parseFragmentDefinition();
case 'extend':
return this.parseTypeSystemExtension();
}
}
throw this.unexpected(keywordToken);
}
parseOperationDefinition(): OperationDefinitionNode {
const start = this._lexer.token;
if (this.peek(TokenKind.BRACE_L)) {
return this.node<OperationDefinitionNode>(start, {
kind: Kind.OPERATION_DEFINITION,
operation: OperationTypeNode.QUERY,
name: undefined,
variableDefinitions: [],
directives: [],
selectionSet: this.parseSelectionSet(),
});
}
const operation = this.parseOperationType();
let name;
if (this.peek(TokenKind.NAME)) {
name = this.parseName();
}
return this.node<OperationDefinitionNode>(start, {
kind: Kind.OPERATION_DEFINITION,
operation,
name,
variableDefinitions: this.parseVariableDefinitions(),
directives: this.parseDirectives(false),
selectionSet: this.parseSelectionSet(),
});
}
parseOperationType(): OperationTypeNode {
const operationToken = this.expectToken(TokenKind.NAME);
switch (operationToken.value) {
case 'query':
return OperationTypeNode.QUERY;
case 'mutation':
return OperationTypeNode.MUTATION;
case 'subscription':
return OperationTypeNode.SUBSCRIPTION;
}
throw this.unexpected(operationToken);
}
parseVariableDefinitions(): Array<VariableDefinitionNode> {
return this.optionalMany(
TokenKind.PAREN_L,
this.parseVariableDefinition,
TokenKind.PAREN_R,
);
}
parseVariableDefinition(): VariableDefinitionNode {
return this.node<VariableDefinitionNode>(this._lexer.token, {
kind: Kind.VARIABLE_DEFINITION,
variable: this.parseVariable(),
type: (this.expectToken(TokenKind.COLON), this.parseTypeReference()),
defaultValue: this.expectOptionalToken(TokenKind.EQUALS)
? this.parseConstValueLiteral()
: undefined,
directives: this.parseConstDirectives(),
});
}
parseVariable(): VariableNode {
const start = this._lexer.token;
this.expectToken(TokenKind.DOLLAR);
return this.node<VariableNode>(start, {
kind: Kind.VARIABLE,
name: this.parseName(),
});
}
parseSelectionSet(): SelectionSetNode {
return this.node<SelectionSetNode>(this._lexer.token, {
kind: Kind.SELECTION_SET,
selections: this.many(
TokenKind.BRACE_L,
this.parseSelection,
TokenKind.BRACE_R,
),
});
}
parseSelection(): SelectionNode {
return this.peek(TokenKind.SPREAD)
? this.parseFragment()
: this.parseField();
}
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 this.node<FieldNode>(start, {
kind: Kind.FIELD,
alias,
name,
arguments: this.parseArguments(false),
nullabilityAssertion: this.parseNullabilityAssertion(),
directives: this.parseDirectives(false),
selectionSet: this.peek(TokenKind.BRACE_L)
? this.parseSelectionSet()
: undefined,
});
}
parseNullabilityAssertion(): NullabilityAssertionNode | undefined {
if (this._options.experimentalClientControlledNullability !== true) {
return undefined;
}
const start = this._lexer.token;
let nullabilityAssertion;
if (this.expectOptionalToken(TokenKind.BRACKET_L)) {
const innerModifier = this.parseNullabilityAssertion();
this.expectToken(TokenKind.BRACKET_R);
nullabilityAssertion = this.node<ListNullabilityOperatorNode>(start, {
kind: Kind.LIST_NULLABILITY_OPERATOR,
nullabilityAssertion: innerModifier,
});
}
if (this.expectOptionalToken(TokenKind.BANG)) {
nullabilityAssertion = this.node<NonNullAssertionNode>(start, {
kind: Kind.NON_NULL_ASSERTION,
nullabilityAssertion,
});
} else if (this.expectOptionalToken(TokenKind.QUESTION_MARK)) {
nullabilityAssertion = this.node<ErrorBoundaryNode>(start, {
kind: Kind.ERROR_BOUNDARY,
nullabilityAssertion,
});
}
return nullabilityAssertion;
}
parseArguments(isConst: true): Array<ConstArgumentNode>;
parseArguments(isConst: boolean): Array<ArgumentNode>;
parseArguments(isConst: boolean): Array<ArgumentNode> {
const item = isConst ? this.parseConstArgument : this.parseArgument;
return this.optionalMany(TokenKind.PAREN_L, item, TokenKind.PAREN_R);
}
parseArgument(isConst: true): ConstArgumentNode;
parseArgument(isConst?: boolean): ArgumentNode;
parseArgument(isConst: boolean = false): ArgumentNode {
const start = this._lexer.token;
const name = this.parseName();
this.expectToken(TokenKind.COLON);
return this.node<ArgumentNode>(start, {
kind: Kind.ARGUMENT,
name,
value: this.parseValueLiteral(isConst),
});
}
parseConstArgument(): ConstArgumentNode {
return this.parseArgument(true);
}
parseFragment(): FragmentSpreadNode | InlineFragmentNode {
const start = this._lexer.token;
this.expectToken(TokenKind.SPREAD);
const hasTypeCondition = this.expectOptionalKeyword('on');
if (!hasTypeCondition && this.peek(TokenKind.NAME)) {
return this.node<FragmentSpreadNode>(start, {
kind: Kind.FRAGMENT_SPREAD,
name: this.parseFragmentName(),
directives: this.parseDirectives(false),
});
}
return this.node<InlineFragmentNode>(start, {
kind: Kind.INLINE_FRAGMENT,
typeCondition: hasTypeCondition ? this.parseNamedType() : undefined,
directives: this.parseDirectives(false),
selectionSet: this.parseSelectionSet(),
});
}
parseFragmentDefinition(): FragmentDefinitionNode {
const start = this._lexer.token;
this.expectKeyword('fragment');
if (this._options.allowLegacyFragmentVariables === true) {
return this.node<FragmentDefinitionNode>(start, {
kind: Kind.FRAGMENT_DEFINITION,
name: this.parseFragmentName(),
variableDefinitions: this.parseVariableDefinitions(),
typeCondition: (this.expectKeyword('on'), this.parseNamedType()),
directives: this.parseDirectives(false),
selectionSet: this.parseSelectionSet(),
});
}
return this.node<FragmentDefinitionNode>(start, {
kind: Kind.FRAGMENT_DEFINITION,
name: this.parseFragmentName(),
typeCondition: (this.expectKeyword('on'), this.parseNamedType()),
directives: this.parseDirectives(false),
selectionSet: this.parseSelectionSet(),
});
}
parseFragmentName(): NameNode {
if (this._lexer.token.value === 'on') {
throw this.unexpected();
}
return this.parseName();
}
parseValueLiteral(isConst: true): ConstValueNode;
parseValueLiteral(isConst: boolean): ValueNode;
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.advanceLexer();
return this.node<IntValueNode>(token, {
kind: Kind.INT,
value: token.value,
});
case TokenKind.FLOAT:
this.advanceLexer();
return this.node<FloatValueNode>(token, {
kind: Kind.FLOAT,
value: token.value,
});
case TokenKind.STRING:
case TokenKind.BLOCK_STRING:
return this.parseStringLiteral();
case TokenKind.NAME:
this.advanceLexer();
switch (token.value) {
case 'true':
return this.node<BooleanValueNode>(token, {
kind: Kind.BOOLEAN,
value: true,
});
case 'false':
return this.node<BooleanValueNode>(token, {
kind: Kind.BOOLEAN,
value: false,
});
case 'null':
return this.node<NullValueNode>(token, { kind: Kind.NULL });
default:
return this.node<EnumValueNode>(token, {
kind: Kind.ENUM,
value: token.value,
});
}
case TokenKind.DOLLAR:
if (isConst) {
this.expectToken(TokenKind.DOLLAR);
if (this._lexer.token.kind === TokenKind.NAME) {
const varName = this._lexer.token.value;
throw syntaxError(
this._lexer.source,
token.start,
`Unexpected variable "$${varName}" in constant value.`,
);
} else {
throw this.unexpected(token);
}
}
return this.parseVariable();
default:
throw this.unexpected();
}
}
parseConstValueLiteral(): ConstValueNode {
return this.parseValueLiteral(true);
}
parseStringLiteral(): StringValueNode {
const token = this._lexer.token;
this.advanceLexer();
return this.node<StringValueNode>(token, {
kind: Kind.STRING,
value: token.value,
block: token.kind === TokenKind.BLOCK_STRING,
});
}
parseList(isConst: true): ConstListValueNode;
parseList(isConst: boolean): ListValueNode;
parseList(isConst: boolean): ListValueNode {
const item = () => this.parseValueLiteral(isConst);
return this.node<ListValueNode>(this._lexer.token, {
kind: Kind.LIST,
values: this.any(TokenKind.BRACKET_L, item, TokenKind.BRACKET_R),
});
}
parseObject(isConst: true): ConstObjectValueNode;
parseObject(isConst: boolean): ObjectValueNode;
parseObject(isConst: boolean): ObjectValueNode {
const item = () => this.parseObjectField(isConst);
return this.node<ObjectValueNode>(this._lexer.token, {
kind: Kind.OBJECT,
fields: this.any(TokenKind.BRACE_L, item, TokenKind.BRACE_R),
});
}
parseObjectField(isConst: true): ConstObjectFieldNode;
parseObjectField(isConst: boolean): ObjectFieldNode;
parseObjectField(isConst: boolean): ObjectFieldNode {
const start = this._lexer.token;
const name = this.parseName();
this.expectToken(TokenKind.COLON);
return this.node<ObjectFieldNode>(start, {
kind: Kind.OBJECT_FIELD,
name,
value: this.parseValueLiteral(isConst),
});
}
parseDirectives(isConst: true): Array<ConstDirectiveNode>;
parseDirectives(isConst: boolean): Array<DirectiveNode>;
parseDirectives(isConst: boolean): Array<DirectiveNode> {
const directives = [];
while (this.peek(TokenKind.AT)) {
directives.push(this.parseDirective(isConst));
}
return directives;
}
parseConstDirectives(): Array<ConstDirectiveNode> {
return this.parseDirectives(true);
}
parseDirective(isConst: true): ConstDirectiveNode;
parseDirective(isConst: boolean): DirectiveNode;
parseDirective(isConst: boolean): DirectiveNode {
const start = this._lexer.token;
this.expectToken(TokenKind.AT);
return this.node<DirectiveNode>(start, {
kind: Kind.DIRECTIVE,
name: this.parseName(),
arguments: this.parseArguments(isConst),
});
}
parseTypeReference(): TypeNode {
const start = this._lexer.token;
let type;
if (this.expectOptionalToken(TokenKind.BRACKET_L)) {
const innerType = this.parseTypeReference();
this.expectToken(TokenKind.BRACKET_R);
type = this.node<ListTypeNode>(start, {
kind: Kind.LIST_TYPE,
type: innerType,
});
} else {
type = this.parseNamedType();
}
if (this.expectOptionalToken(TokenKind.BANG)) {
return this.node<NonNullTypeNode>(start, {
kind: Kind.NON_NULL_TYPE,
type,
});
}
return type;
}
parseNamedType(): NamedTypeNode {
return this.node<NamedTypeNode>(this._lexer.token, {
kind: Kind.NAMED_TYPE,
name: this.parseName(),
});
}
peekDescription(): boolean {
return this.peek(TokenKind.STRING) || this.peek(TokenKind.BLOCK_STRING);
}
parseDescription(): undefined | StringValueNode {
if (this.peekDescription()) {
return this.parseStringLiteral();
}
}
parseSchemaDefinition(): SchemaDefinitionNode {
const start = this._lexer.token;
const description = this.parseDescription();
this.expectKeyword('schema');
const directives = this.parseConstDirectives();
const operationTypes = this.many(
TokenKind.BRACE_L,
this.parseOperationTypeDefinition,
TokenKind.BRACE_R,
);
return this.node<SchemaDefinitionNode>(start, {
kind: Kind.SCHEMA_DEFINITION,
description,
directives,
operationTypes,
});
}
parseOperationTypeDefinition(): OperationTypeDefinitionNode {
const start = this._lexer.token;
const operation = this.parseOperationType();
this.expectToken(TokenKind.COLON);
const type = this.parseNamedType();
return this.node<OperationTypeDefinitionNode>(start, {
kind: Kind.OPERATION_TYPE_DEFINITION,
operation,
type,
});
}
parseScalarTypeDefinition(): ScalarTypeDefinitionNode {
const start = this._lexer.token;
const description = this.parseDescription();
this.expectKeyword('scalar');
const name = this.parseName();
const directives = this.parseConstDirectives();
return this.node<ScalarTypeDefinitionNode>(start, {
kind: Kind.SCALAR_TYPE_DEFINITION,
description,
name,
directives,
});
}
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.parseConstDirectives();
const fields = this.parseFieldsDefinition();
return this.node<ObjectTypeDefinitionNode>(start, {
kind: Kind.OBJECT_TYPE_DEFINITION,
description,
name,
interfaces,
directives,
fields,
});
}
parseImplementsInterfaces(): Array<NamedTypeNode> {
return this.expectOptionalKeyword('implements')
? this.delimitedMany(TokenKind.AMP, this.parseNamedType)
: [];
}
parseFieldsDefinition(): Array<FieldDefinitionNode> {
return this.optionalMany(
TokenKind.BRACE_L,
this.parseFieldDefinition,
TokenKind.BRACE_R,
);
}
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.parseConstDirectives();
return this.node<FieldDefinitionNode>(start, {
kind: Kind.FIELD_DEFINITION,
description,
name,
arguments: args,
type,
directives,
});
}
parseArgumentDefs(): Array<InputValueDefinitionNode> {
return this.optionalMany(
TokenKind.PAREN_L,
this.parseInputValueDef,
TokenKind.PAREN_R,
);
}
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.parseConstValueLiteral();
}
const directives = this.parseConstDirectives();
return this.node<InputValueDefinitionNode>(start, {
kind: Kind.INPUT_VALUE_DEFINITION,
description,
name,
type,
defaultValue,
directives,
});
}
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.parseConstDirectives();
const fields = this.parseFieldsDefinition();
return this.node<InterfaceTypeDefinitionNode>(start, {
kind: Kind.INTERFACE_TYPE_DEFINITION,
description,
name,
interfaces,
directives,
fields,
});
}
parseUnionTypeDefinition(): UnionTypeDefinitionNode {
const start = this._lexer.token;
const description = this.parseDescription();
this.expectKeyword('union');
const name = this.parseName();
const directives = this.parseConstDirectives();
const types = this.parseUnionMemberTypes();
return this.node<UnionTypeDefinitionNode>(start, {
kind: Kind.UNION_TYPE_DEFINITION,
description,
name,
directives,
types,
});
}
parseUnionMemberTypes(): Array<NamedTypeNode> {
return this.expectOptionalToken(TokenKind.EQUALS)
? this.delimitedMany(TokenKind.PIPE, this.parseNamedType)
: [];
}
parseEnumTypeDefinition(): EnumTypeDefinitionNode {
const start = this._lexer.token;
const description = this.parseDescription();
this.expectKeyword('enum');
const name = this.parseName();
const directives = this.parseConstDirectives();
const values = this.parseEnumValuesDefinition();
return this.node<EnumTypeDefinitionNode>(start, {
kind: Kind.ENUM_TYPE_DEFINITION,
description,
name,
directives,
values,
});
}
parseEnumValuesDefinition(): Array<EnumValueDefinitionNode> {
return this.optionalMany(
TokenKind.BRACE_L,
this.parseEnumValueDefinition,
TokenKind.BRACE_R,
);
}
parseEnumValueDefinition(): EnumValueDefinitionNode {
const start = this._lexer.token;
const description = this.parseDescription();
const name = this.parseEnumValueName();
const directives = this.parseConstDirectives();
return this.node<EnumValueDefinitionNode>(start, {
kind: Kind.ENUM_VALUE_DEFINITION,
description,
name,
directives,
});
}
parseEnumValueName(): NameNode {
if (
this._lexer.token.value === 'true' ||
this._lexer.token.value === 'false' ||
this._lexer.token.value === 'null'
) {
throw syntaxError(
this._lexer.source,
this._lexer.token.start,
`${getTokenDesc(
this._lexer.token,
)} is reserved and cannot be used for an enum value.`,
);
}
return this.parseName();
}
parseInputObjectTypeDefinition(): InputObjectTypeDefinitionNode {
const start = this._lexer.token;
const description = this.parseDescription();
this.expectKeyword('input');
const name = this.parseName();
const directives = this.parseConstDirectives();
const fields = this.parseInputFieldsDefinition();
return this.node<InputObjectTypeDefinitionNode>(start, {
kind: Kind.INPUT_OBJECT_TYPE_DEFINITION,
description,
name,
directives,
fields,
});
}
parseInputFieldsDefinition(): Array<InputValueDefinitionNode> {
return this.optionalMany(
TokenKind.BRACE_L,
this.parseInputValueDef,
TokenKind.BRACE_R,
);
}
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);
}
parseSchemaExtension(): SchemaExtensionNode {
const start = this._lexer.token;
this.expectKeyword('extend');
this.expectKeyword('schema');
const directives = this.parseConstDirectives();
const operationTypes = this.optionalMany(
TokenKind.BRACE_L,
this.parseOperationTypeDefinition,
TokenKind.BRACE_R,
);
if (directives.length === 0 && operationTypes.length === 0) {
throw this.unexpected();
}
return this.node<SchemaExtensionNode>(start, {
kind: Kind.SCHEMA_EXTENSION,
directives,
operationTypes,
});
}
parseScalarTypeExtension(): ScalarTypeExtensionNode {
const start = this._lexer.token;
this.expectKeyword('extend');
this.expectKeyword('scalar');
const name = this.parseName();
const directives = this.parseConstDirectives();
if (directives.length === 0) {
throw this.unexpected();
}
return this.node<ScalarTypeExtensionNode>(start, {
kind: Kind.SCALAR_TYPE_EXTENSION,
name,
directives,
});
}
parseObjectTypeExtension(): ObjectTypeExtensionNode {
const start = this._lexer.token;
this.expectKeyword('extend');
this.expectKeyword('type');
const name = this.parseName();
const interfaces = this.parseImplementsInterfaces();
const directives = this.parseConstDirectives();
const fields = this.parseFieldsDefinition();
if (
interfaces.length === 0 &&
directives.length === 0 &&
fields.length === 0
) {
throw this.unexpected();
}
return this.node<ObjectTypeExtensionNode>(start, {
kind: Kind.OBJECT_TYPE_EXTENSION,
name,
interfaces,
directives,
fields,
});
}
parseInterfaceTypeExtension(): InterfaceTypeExtensionNode {
const start = this._lexer.token;
this.expectKeyword('extend');
this.expectKeyword('interface');
const name = this.parseName();
const interfaces = this.parseImplementsInterfaces();
const directives = this.parseConstDirectives();
const fields = this.parseFieldsDefinition();
if (
interfaces.length === 0 &&
directives.length === 0 &&
fields.length === 0
) {
throw this.unexpected();
}
return this.node<InterfaceTypeExtensionNode>(start, {
kind: Kind.INTERFACE_TYPE_EXTENSION,
name,
interfaces,
directives,
fields,
});
}
parseUnionTypeExtension(): UnionTypeExtensionNode {
const start = this._lexer.token;
this.expectKeyword('extend');
this.expectKeyword('union');
const name = this.parseName();
const directives = this.parseConstDirectives();
const types = this.parseUnionMemberTypes();
if (directives.length === 0 && types.length === 0) {
throw this.unexpected();
}
return this.node<UnionTypeExtensionNode>(start, {
kind: Kind.UNION_TYPE_EXTENSION,
name,
directives,
types,
});
}
parseEnumTypeExtension(): EnumTypeExtensionNode {
const start = this._lexer.token;
this.expectKeyword('extend');
this.expectKeyword('enum');
const name = this.parseName();
const directives = this.parseConstDirectives();
const values = this.parseEnumValuesDefinition();
if (directives.length === 0 && values.length === 0) {
throw this.unexpected();
}
return this.node<EnumTypeExtensionNode>(start, {
kind: Kind.ENUM_TYPE_EXTENSION,
name,
directives,
values,
});
}
parseInputObjectTypeExtension(): InputObjectTypeExtensionNode {
const start = this._lexer.token;
this.expectKeyword('extend');
this.expectKeyword('input');
const name = this.parseName();
const directives = this.parseConstDirectives();
const fields = this.parseInputFieldsDefinition();
if (directives.length === 0 && fields.length === 0) {
throw this.unexpected();
}
return this.node<InputObjectTypeExtensionNode>(start, {
kind: Kind.INPUT_OBJECT_TYPE_EXTENSION,
name,
directives,
fields,
});
}
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 this.node<DirectiveDefinitionNode>(start, {
kind: Kind.DIRECTIVE_DEFINITION,
description,
name,
arguments: args,
repeatable,
locations,
});
}
parseDirectiveLocations(): Array<NameNode> {
return this.delimitedMany(TokenKind.PIPE, this.parseDirectiveLocation);
}
parseDirectiveLocation(): NameNode {
const start = this._lexer.token;
const name = this.parseName();
if (Object.hasOwn(DirectiveLocation, name.value)) {
return name;
}
throw this.unexpected(start);
}
node<T extends { loc?: Location | undefined }>(
startToken: Token,
node: T,
): T {
if (this._options.noLocation !== true) {
node.loc = new Location(
startToken,
this._lexer.lastToken,
this._lexer.source,
);
}
return node;
}
peek(kind: TokenKind): boolean {
return this._lexer.token.kind === kind;
}
expectToken(kind: TokenKind): Token {
const token = this._lexer.token;
if (token.kind === kind) {
this.advanceLexer();
return token;
}
throw syntaxError(
this._lexer.source,
token.start,
`Expected ${getTokenKindDesc(kind)}, found ${getTokenDesc(token)}.`,
);
}
expectOptionalToken(kind: TokenKind): boolean {
const token = this._lexer.token;
if (token.kind === kind) {
this.advanceLexer();
return true;
}
return false;
}
expectKeyword(value: string): void {
const token = this._lexer.token;
if (token.kind === TokenKind.NAME && token.value === value) {
this.advanceLexer();
} else {
throw syntaxError(
this._lexer.source,
token.start,
`Expected "${value}", found ${getTokenDesc(token)}.`,
);
}
}
expectOptionalKeyword(value: string): boolean {
const token = this._lexer.token;
if (token.kind === TokenKind.NAME && token.value === value) {
this.advanceLexer();
return true;
}
return false;
}
unexpected(atToken?: Maybe<Token>): GraphQLError {
const token = atToken ?? this._lexer.token;
return syntaxError(
this._lexer.source,
token.start,
`Unexpected ${getTokenDesc(token)}.`,
);
}
any<T>(
openKind: TokenKind,
parseFn: () => T,
closeKind: TokenKind,
): Array<T> {
this.expectToken(openKind);
const nodes = [];
while (!this.expectOptionalToken(closeKind)) {
nodes.push(parseFn.call(this));
}
return nodes;
}
optionalMany<T>(
openKind: TokenKind,
parseFn: () => T,
closeKind: TokenKind,
): Array<T> {
if (this.expectOptionalToken(openKind)) {
const nodes = [];
do {
nodes.push(parseFn.call(this));
} while (!this.expectOptionalToken(closeKind));
return nodes;
}
return [];
}
many<T>(
openKind: TokenKind,
parseFn: () => T,
closeKind: TokenKind,
): Array<T> {
this.expectToken(openKind);
const nodes = [];
do {
nodes.push(parseFn.call(this));
} while (!this.expectOptionalToken(closeKind));
return nodes;
}
delimitedMany<T>(delimiterKind: TokenKind, parseFn: () => T): Array<T> {
this.expectOptionalToken(delimiterKind);
const nodes = [];
do {
nodes.push(parseFn.call(this));
} while (this.expectOptionalToken(delimiterKind));
return nodes;
}
advanceLexer(): void {
const { maxTokens } = this._options;
const token = this._lexer.advance();
if (maxTokens !== undefined && token.kind !== TokenKind.EOF) {
++this._tokenCounter;
if (this._tokenCounter > maxTokens) {
throw syntaxError(
this._lexer.source,
token.start,
`Document contains more than ${maxTokens} tokens. Parsing aborted.`,
);
}
}
}
}
function getTokenDesc(token: Token): string {
const value = token.value;
return getTokenKindDesc(token.kind) + (value != null ? ` "${value}"` : '');
}
function getTokenKindDesc(kind: TokenKind): string {
return isPunctuatorTokenKind(kind) ? `"${kind}"` : kind;
}