Spaces:
Running
Running
; | |
Object.defineProperty(exports, '__esModule', { | |
value: true, | |
}); | |
exports.Parser = void 0; | |
exports.parse = parse; | |
exports.parseConstValue = parseConstValue; | |
exports.parseType = parseType; | |
exports.parseValue = parseValue; | |
var _syntaxError = require('../error/syntaxError.js'); | |
var _ast = require('./ast.js'); | |
var _directiveLocation = require('./directiveLocation.js'); | |
var _kinds = require('./kinds.js'); | |
var _lexer = require('./lexer.js'); | |
var _source = require('./source.js'); | |
var _tokenKind = require('./tokenKind.js'); | |
/** | |
* Given a GraphQL source, parses it into a Document. | |
* Throws GraphQLError if a syntax error is encountered. | |
*/ | |
function parse(source, options) { | |
const parser = new Parser(source, options); | |
const document = parser.parseDocument(); | |
Object.defineProperty(document, 'tokenCount', { | |
enumerable: false, | |
value: parser.tokenCount, | |
}); | |
return document; | |
} | |
/** | |
* 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(). | |
*/ | |
function parseValue(source, options) { | |
const parser = new Parser(source, options); | |
parser.expectToken(_tokenKind.TokenKind.SOF); | |
const value = parser.parseValueLiteral(false); | |
parser.expectToken(_tokenKind.TokenKind.EOF); | |
return value; | |
} | |
/** | |
* Similar to parseValue(), but raises a parse error if it encounters a | |
* variable. The return type will be a constant value. | |
*/ | |
function parseConstValue(source, options) { | |
const parser = new Parser(source, options); | |
parser.expectToken(_tokenKind.TokenKind.SOF); | |
const value = parser.parseConstValueLiteral(); | |
parser.expectToken(_tokenKind.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(). | |
*/ | |
function parseType(source, options) { | |
const parser = new Parser(source, options); | |
parser.expectToken(_tokenKind.TokenKind.SOF); | |
const type = parser.parseTypeReference(); | |
parser.expectToken(_tokenKind.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 | |
*/ | |
class Parser { | |
constructor(source, options = {}) { | |
const sourceObj = (0, _source.isSource)(source) | |
? source | |
: new _source.Source(source); | |
this._lexer = new _lexer.Lexer(sourceObj); | |
this._options = options; | |
this._tokenCounter = 0; | |
} | |
get tokenCount() { | |
return this._tokenCounter; | |
} | |
/** | |
* Converts a name lex token into a name parse node. | |
*/ | |
parseName() { | |
const token = this.expectToken(_tokenKind.TokenKind.NAME); | |
return this.node(token, { | |
kind: _kinds.Kind.NAME, | |
value: token.value, | |
}); | |
} // Implements the parsing rules in the Document section. | |
/** | |
* Document : Definition+ | |
*/ | |
parseDocument() { | |
return this.node(this._lexer.token, { | |
kind: _kinds.Kind.DOCUMENT, | |
definitions: this.many( | |
_tokenKind.TokenKind.SOF, | |
this.parseDefinition, | |
_tokenKind.TokenKind.EOF, | |
), | |
}); | |
} | |
/** | |
* Definition : | |
* - ExecutableDefinition | |
* - TypeSystemDefinition | |
* - TypeSystemExtension | |
* | |
* ExecutableDefinition : | |
* - OperationDefinition | |
* - FragmentDefinition | |
* | |
* TypeSystemDefinition : | |
* - SchemaDefinition | |
* - TypeDefinition | |
* - DirectiveDefinition | |
* | |
* TypeDefinition : | |
* - ScalarTypeDefinition | |
* - ObjectTypeDefinition | |
* - InterfaceTypeDefinition | |
* - UnionTypeDefinition | |
* - EnumTypeDefinition | |
* - InputObjectTypeDefinition | |
*/ | |
parseDefinition() { | |
if (this.peek(_tokenKind.TokenKind.BRACE_L)) { | |
return this.parseOperationDefinition(); | |
} // Many definitions begin with a description and require a lookahead. | |
const hasDescription = this.peekDescription(); | |
const keywordToken = hasDescription | |
? this._lexer.lookahead() | |
: this._lexer.token; | |
if (keywordToken.kind === _tokenKind.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 (0, _syntaxError.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); | |
} // Implements the parsing rules in the Operations section. | |
/** | |
* OperationDefinition : | |
* - SelectionSet | |
* - OperationType Name? VariableDefinitions? Directives? SelectionSet | |
*/ | |
parseOperationDefinition() { | |
const start = this._lexer.token; | |
if (this.peek(_tokenKind.TokenKind.BRACE_L)) { | |
return this.node(start, { | |
kind: _kinds.Kind.OPERATION_DEFINITION, | |
operation: _ast.OperationTypeNode.QUERY, | |
name: undefined, | |
variableDefinitions: [], | |
directives: [], | |
selectionSet: this.parseSelectionSet(), | |
}); | |
} | |
const operation = this.parseOperationType(); | |
let name; | |
if (this.peek(_tokenKind.TokenKind.NAME)) { | |
name = this.parseName(); | |
} | |
return this.node(start, { | |
kind: _kinds.Kind.OPERATION_DEFINITION, | |
operation, | |
name, | |
variableDefinitions: this.parseVariableDefinitions(), | |
directives: this.parseDirectives(false), | |
selectionSet: this.parseSelectionSet(), | |
}); | |
} | |
/** | |
* OperationType : one of query mutation subscription | |
*/ | |
parseOperationType() { | |
const operationToken = this.expectToken(_tokenKind.TokenKind.NAME); | |
switch (operationToken.value) { | |
case 'query': | |
return _ast.OperationTypeNode.QUERY; | |
case 'mutation': | |
return _ast.OperationTypeNode.MUTATION; | |
case 'subscription': | |
return _ast.OperationTypeNode.SUBSCRIPTION; | |
} | |
throw this.unexpected(operationToken); | |
} | |
/** | |
* VariableDefinitions : ( VariableDefinition+ ) | |
*/ | |
parseVariableDefinitions() { | |
return this.optionalMany( | |
_tokenKind.TokenKind.PAREN_L, | |
this.parseVariableDefinition, | |
_tokenKind.TokenKind.PAREN_R, | |
); | |
} | |
/** | |
* VariableDefinition : Variable : Type DefaultValue? Directives[Const]? | |
*/ | |
parseVariableDefinition() { | |
return this.node(this._lexer.token, { | |
kind: _kinds.Kind.VARIABLE_DEFINITION, | |
variable: this.parseVariable(), | |
type: | |
(this.expectToken(_tokenKind.TokenKind.COLON), | |
this.parseTypeReference()), | |
defaultValue: this.expectOptionalToken(_tokenKind.TokenKind.EQUALS) | |
? this.parseConstValueLiteral() | |
: undefined, | |
directives: this.parseConstDirectives(), | |
}); | |
} | |
/** | |
* Variable : $ Name | |
*/ | |
parseVariable() { | |
const start = this._lexer.token; | |
this.expectToken(_tokenKind.TokenKind.DOLLAR); | |
return this.node(start, { | |
kind: _kinds.Kind.VARIABLE, | |
name: this.parseName(), | |
}); | |
} | |
/** | |
* ``` | |
* SelectionSet : { Selection+ } | |
* ``` | |
*/ | |
parseSelectionSet() { | |
return this.node(this._lexer.token, { | |
kind: _kinds.Kind.SELECTION_SET, | |
selections: this.many( | |
_tokenKind.TokenKind.BRACE_L, | |
this.parseSelection, | |
_tokenKind.TokenKind.BRACE_R, | |
), | |
}); | |
} | |
/** | |
* Selection : | |
* - Field | |
* - FragmentSpread | |
* - InlineFragment | |
*/ | |
parseSelection() { | |
return this.peek(_tokenKind.TokenKind.SPREAD) | |
? this.parseFragment() | |
: this.parseField(); | |
} | |
/** | |
* Field : Alias? Name Arguments? Directives? SelectionSet? | |
* | |
* Alias : Name : | |
*/ | |
parseField() { | |
const start = this._lexer.token; | |
const nameOrAlias = this.parseName(); | |
let alias; | |
let name; | |
if (this.expectOptionalToken(_tokenKind.TokenKind.COLON)) { | |
alias = nameOrAlias; | |
name = this.parseName(); | |
} else { | |
name = nameOrAlias; | |
} | |
return this.node(start, { | |
kind: _kinds.Kind.FIELD, | |
alias, | |
name, | |
arguments: this.parseArguments(false), | |
directives: this.parseDirectives(false), | |
selectionSet: this.peek(_tokenKind.TokenKind.BRACE_L) | |
? this.parseSelectionSet() | |
: undefined, | |
}); | |
} | |
/** | |
* Arguments[Const] : ( Argument[?Const]+ ) | |
*/ | |
parseArguments(isConst) { | |
const item = isConst ? this.parseConstArgument : this.parseArgument; | |
return this.optionalMany( | |
_tokenKind.TokenKind.PAREN_L, | |
item, | |
_tokenKind.TokenKind.PAREN_R, | |
); | |
} | |
/** | |
* Argument[Const] : Name : Value[?Const] | |
*/ | |
parseArgument(isConst = false) { | |
const start = this._lexer.token; | |
const name = this.parseName(); | |
this.expectToken(_tokenKind.TokenKind.COLON); | |
return this.node(start, { | |
kind: _kinds.Kind.ARGUMENT, | |
name, | |
value: this.parseValueLiteral(isConst), | |
}); | |
} | |
parseConstArgument() { | |
return this.parseArgument(true); | |
} // 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() { | |
const start = this._lexer.token; | |
this.expectToken(_tokenKind.TokenKind.SPREAD); | |
const hasTypeCondition = this.expectOptionalKeyword('on'); | |
if (!hasTypeCondition && this.peek(_tokenKind.TokenKind.NAME)) { | |
return this.node(start, { | |
kind: _kinds.Kind.FRAGMENT_SPREAD, | |
name: this.parseFragmentName(), | |
directives: this.parseDirectives(false), | |
}); | |
} | |
return this.node(start, { | |
kind: _kinds.Kind.INLINE_FRAGMENT, | |
typeCondition: hasTypeCondition ? this.parseNamedType() : undefined, | |
directives: this.parseDirectives(false), | |
selectionSet: this.parseSelectionSet(), | |
}); | |
} | |
/** | |
* FragmentDefinition : | |
* - fragment FragmentName on TypeCondition Directives? SelectionSet | |
* | |
* TypeCondition : NamedType | |
*/ | |
parseFragmentDefinition() { | |
const start = this._lexer.token; | |
this.expectKeyword('fragment'); // Legacy support for defining variables within fragments changes | |
// the grammar of FragmentDefinition: | |
// - fragment FragmentName VariableDefinitions? on TypeCondition Directives? SelectionSet | |
if (this._options.allowLegacyFragmentVariables === true) { | |
return this.node(start, { | |
kind: _kinds.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(start, { | |
kind: _kinds.Kind.FRAGMENT_DEFINITION, | |
name: this.parseFragmentName(), | |
typeCondition: (this.expectKeyword('on'), this.parseNamedType()), | |
directives: this.parseDirectives(false), | |
selectionSet: this.parseSelectionSet(), | |
}); | |
} | |
/** | |
* FragmentName : Name but not `on` | |
*/ | |
parseFragmentName() { | |
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) { | |
const token = this._lexer.token; | |
switch (token.kind) { | |
case _tokenKind.TokenKind.BRACKET_L: | |
return this.parseList(isConst); | |
case _tokenKind.TokenKind.BRACE_L: | |
return this.parseObject(isConst); | |
case _tokenKind.TokenKind.INT: | |
this.advanceLexer(); | |
return this.node(token, { | |
kind: _kinds.Kind.INT, | |
value: token.value, | |
}); | |
case _tokenKind.TokenKind.FLOAT: | |
this.advanceLexer(); | |
return this.node(token, { | |
kind: _kinds.Kind.FLOAT, | |
value: token.value, | |
}); | |
case _tokenKind.TokenKind.STRING: | |
case _tokenKind.TokenKind.BLOCK_STRING: | |
return this.parseStringLiteral(); | |
case _tokenKind.TokenKind.NAME: | |
this.advanceLexer(); | |
switch (token.value) { | |
case 'true': | |
return this.node(token, { | |
kind: _kinds.Kind.BOOLEAN, | |
value: true, | |
}); | |
case 'false': | |
return this.node(token, { | |
kind: _kinds.Kind.BOOLEAN, | |
value: false, | |
}); | |
case 'null': | |
return this.node(token, { | |
kind: _kinds.Kind.NULL, | |
}); | |
default: | |
return this.node(token, { | |
kind: _kinds.Kind.ENUM, | |
value: token.value, | |
}); | |
} | |
case _tokenKind.TokenKind.DOLLAR: | |
if (isConst) { | |
this.expectToken(_tokenKind.TokenKind.DOLLAR); | |
if (this._lexer.token.kind === _tokenKind.TokenKind.NAME) { | |
const varName = this._lexer.token.value; | |
throw (0, _syntaxError.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() { | |
return this.parseValueLiteral(true); | |
} | |
parseStringLiteral() { | |
const token = this._lexer.token; | |
this.advanceLexer(); | |
return this.node(token, { | |
kind: _kinds.Kind.STRING, | |
value: token.value, | |
block: token.kind === _tokenKind.TokenKind.BLOCK_STRING, | |
}); | |
} | |
/** | |
* ListValue[Const] : | |
* - [ ] | |
* - [ Value[?Const]+ ] | |
*/ | |
parseList(isConst) { | |
const item = () => this.parseValueLiteral(isConst); | |
return this.node(this._lexer.token, { | |
kind: _kinds.Kind.LIST, | |
values: this.any( | |
_tokenKind.TokenKind.BRACKET_L, | |
item, | |
_tokenKind.TokenKind.BRACKET_R, | |
), | |
}); | |
} | |
/** | |
* ``` | |
* ObjectValue[Const] : | |
* - { } | |
* - { ObjectField[?Const]+ } | |
* ``` | |
*/ | |
parseObject(isConst) { | |
const item = () => this.parseObjectField(isConst); | |
return this.node(this._lexer.token, { | |
kind: _kinds.Kind.OBJECT, | |
fields: this.any( | |
_tokenKind.TokenKind.BRACE_L, | |
item, | |
_tokenKind.TokenKind.BRACE_R, | |
), | |
}); | |
} | |
/** | |
* ObjectField[Const] : Name : Value[?Const] | |
*/ | |
parseObjectField(isConst) { | |
const start = this._lexer.token; | |
const name = this.parseName(); | |
this.expectToken(_tokenKind.TokenKind.COLON); | |
return this.node(start, { | |
kind: _kinds.Kind.OBJECT_FIELD, | |
name, | |
value: this.parseValueLiteral(isConst), | |
}); | |
} // Implements the parsing rules in the Directives section. | |
/** | |
* Directives[Const] : Directive[?Const]+ | |
*/ | |
parseDirectives(isConst) { | |
const directives = []; | |
while (this.peek(_tokenKind.TokenKind.AT)) { | |
directives.push(this.parseDirective(isConst)); | |
} | |
return directives; | |
} | |
parseConstDirectives() { | |
return this.parseDirectives(true); | |
} | |
/** | |
* ``` | |
* Directive[Const] : @ Name Arguments[?Const]? | |
* ``` | |
*/ | |
parseDirective(isConst) { | |
const start = this._lexer.token; | |
this.expectToken(_tokenKind.TokenKind.AT); | |
return this.node(start, { | |
kind: _kinds.Kind.DIRECTIVE, | |
name: this.parseName(), | |
arguments: this.parseArguments(isConst), | |
}); | |
} // Implements the parsing rules in the Types section. | |
/** | |
* Type : | |
* - NamedType | |
* - ListType | |
* - NonNullType | |
*/ | |
parseTypeReference() { | |
const start = this._lexer.token; | |
let type; | |
if (this.expectOptionalToken(_tokenKind.TokenKind.BRACKET_L)) { | |
const innerType = this.parseTypeReference(); | |
this.expectToken(_tokenKind.TokenKind.BRACKET_R); | |
type = this.node(start, { | |
kind: _kinds.Kind.LIST_TYPE, | |
type: innerType, | |
}); | |
} else { | |
type = this.parseNamedType(); | |
} | |
if (this.expectOptionalToken(_tokenKind.TokenKind.BANG)) { | |
return this.node(start, { | |
kind: _kinds.Kind.NON_NULL_TYPE, | |
type, | |
}); | |
} | |
return type; | |
} | |
/** | |
* NamedType : Name | |
*/ | |
parseNamedType() { | |
return this.node(this._lexer.token, { | |
kind: _kinds.Kind.NAMED_TYPE, | |
name: this.parseName(), | |
}); | |
} // Implements the parsing rules in the Type Definition section. | |
peekDescription() { | |
return ( | |
this.peek(_tokenKind.TokenKind.STRING) || | |
this.peek(_tokenKind.TokenKind.BLOCK_STRING) | |
); | |
} | |
/** | |
* Description : StringValue | |
*/ | |
parseDescription() { | |
if (this.peekDescription()) { | |
return this.parseStringLiteral(); | |
} | |
} | |
/** | |
* ``` | |
* SchemaDefinition : Description? schema Directives[Const]? { OperationTypeDefinition+ } | |
* ``` | |
*/ | |
parseSchemaDefinition() { | |
const start = this._lexer.token; | |
const description = this.parseDescription(); | |
this.expectKeyword('schema'); | |
const directives = this.parseConstDirectives(); | |
const operationTypes = this.many( | |
_tokenKind.TokenKind.BRACE_L, | |
this.parseOperationTypeDefinition, | |
_tokenKind.TokenKind.BRACE_R, | |
); | |
return this.node(start, { | |
kind: _kinds.Kind.SCHEMA_DEFINITION, | |
description, | |
directives, | |
operationTypes, | |
}); | |
} | |
/** | |
* OperationTypeDefinition : OperationType : NamedType | |
*/ | |
parseOperationTypeDefinition() { | |
const start = this._lexer.token; | |
const operation = this.parseOperationType(); | |
this.expectToken(_tokenKind.TokenKind.COLON); | |
const type = this.parseNamedType(); | |
return this.node(start, { | |
kind: _kinds.Kind.OPERATION_TYPE_DEFINITION, | |
operation, | |
type, | |
}); | |
} | |
/** | |
* ScalarTypeDefinition : Description? scalar Name Directives[Const]? | |
*/ | |
parseScalarTypeDefinition() { | |
const start = this._lexer.token; | |
const description = this.parseDescription(); | |
this.expectKeyword('scalar'); | |
const name = this.parseName(); | |
const directives = this.parseConstDirectives(); | |
return this.node(start, { | |
kind: _kinds.Kind.SCALAR_TYPE_DEFINITION, | |
description, | |
name, | |
directives, | |
}); | |
} | |
/** | |
* ObjectTypeDefinition : | |
* Description? | |
* type Name ImplementsInterfaces? Directives[Const]? FieldsDefinition? | |
*/ | |
parseObjectTypeDefinition() { | |
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(start, { | |
kind: _kinds.Kind.OBJECT_TYPE_DEFINITION, | |
description, | |
name, | |
interfaces, | |
directives, | |
fields, | |
}); | |
} | |
/** | |
* ImplementsInterfaces : | |
* - implements `&`? NamedType | |
* - ImplementsInterfaces & NamedType | |
*/ | |
parseImplementsInterfaces() { | |
return this.expectOptionalKeyword('implements') | |
? this.delimitedMany(_tokenKind.TokenKind.AMP, this.parseNamedType) | |
: []; | |
} | |
/** | |
* ``` | |
* FieldsDefinition : { FieldDefinition+ } | |
* ``` | |
*/ | |
parseFieldsDefinition() { | |
return this.optionalMany( | |
_tokenKind.TokenKind.BRACE_L, | |
this.parseFieldDefinition, | |
_tokenKind.TokenKind.BRACE_R, | |
); | |
} | |
/** | |
* FieldDefinition : | |
* - Description? Name ArgumentsDefinition? : Type Directives[Const]? | |
*/ | |
parseFieldDefinition() { | |
const start = this._lexer.token; | |
const description = this.parseDescription(); | |
const name = this.parseName(); | |
const args = this.parseArgumentDefs(); | |
this.expectToken(_tokenKind.TokenKind.COLON); | |
const type = this.parseTypeReference(); | |
const directives = this.parseConstDirectives(); | |
return this.node(start, { | |
kind: _kinds.Kind.FIELD_DEFINITION, | |
description, | |
name, | |
arguments: args, | |
type, | |
directives, | |
}); | |
} | |
/** | |
* ArgumentsDefinition : ( InputValueDefinition+ ) | |
*/ | |
parseArgumentDefs() { | |
return this.optionalMany( | |
_tokenKind.TokenKind.PAREN_L, | |
this.parseInputValueDef, | |
_tokenKind.TokenKind.PAREN_R, | |
); | |
} | |
/** | |
* InputValueDefinition : | |
* - Description? Name : Type DefaultValue? Directives[Const]? | |
*/ | |
parseInputValueDef() { | |
const start = this._lexer.token; | |
const description = this.parseDescription(); | |
const name = this.parseName(); | |
this.expectToken(_tokenKind.TokenKind.COLON); | |
const type = this.parseTypeReference(); | |
let defaultValue; | |
if (this.expectOptionalToken(_tokenKind.TokenKind.EQUALS)) { | |
defaultValue = this.parseConstValueLiteral(); | |
} | |
const directives = this.parseConstDirectives(); | |
return this.node(start, { | |
kind: _kinds.Kind.INPUT_VALUE_DEFINITION, | |
description, | |
name, | |
type, | |
defaultValue, | |
directives, | |
}); | |
} | |
/** | |
* InterfaceTypeDefinition : | |
* - Description? interface Name Directives[Const]? FieldsDefinition? | |
*/ | |
parseInterfaceTypeDefinition() { | |
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(start, { | |
kind: _kinds.Kind.INTERFACE_TYPE_DEFINITION, | |
description, | |
name, | |
interfaces, | |
directives, | |
fields, | |
}); | |
} | |
/** | |
* UnionTypeDefinition : | |
* - Description? union Name Directives[Const]? UnionMemberTypes? | |
*/ | |
parseUnionTypeDefinition() { | |
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(start, { | |
kind: _kinds.Kind.UNION_TYPE_DEFINITION, | |
description, | |
name, | |
directives, | |
types, | |
}); | |
} | |
/** | |
* UnionMemberTypes : | |
* - = `|`? NamedType | |
* - UnionMemberTypes | NamedType | |
*/ | |
parseUnionMemberTypes() { | |
return this.expectOptionalToken(_tokenKind.TokenKind.EQUALS) | |
? this.delimitedMany(_tokenKind.TokenKind.PIPE, this.parseNamedType) | |
: []; | |
} | |
/** | |
* EnumTypeDefinition : | |
* - Description? enum Name Directives[Const]? EnumValuesDefinition? | |
*/ | |
parseEnumTypeDefinition() { | |
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(start, { | |
kind: _kinds.Kind.ENUM_TYPE_DEFINITION, | |
description, | |
name, | |
directives, | |
values, | |
}); | |
} | |
/** | |
* ``` | |
* EnumValuesDefinition : { EnumValueDefinition+ } | |
* ``` | |
*/ | |
parseEnumValuesDefinition() { | |
return this.optionalMany( | |
_tokenKind.TokenKind.BRACE_L, | |
this.parseEnumValueDefinition, | |
_tokenKind.TokenKind.BRACE_R, | |
); | |
} | |
/** | |
* EnumValueDefinition : Description? EnumValue Directives[Const]? | |
*/ | |
parseEnumValueDefinition() { | |
const start = this._lexer.token; | |
const description = this.parseDescription(); | |
const name = this.parseEnumValueName(); | |
const directives = this.parseConstDirectives(); | |
return this.node(start, { | |
kind: _kinds.Kind.ENUM_VALUE_DEFINITION, | |
description, | |
name, | |
directives, | |
}); | |
} | |
/** | |
* EnumValue : Name but not `true`, `false` or `null` | |
*/ | |
parseEnumValueName() { | |
if ( | |
this._lexer.token.value === 'true' || | |
this._lexer.token.value === 'false' || | |
this._lexer.token.value === 'null' | |
) { | |
throw (0, _syntaxError.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(); | |
} | |
/** | |
* InputObjectTypeDefinition : | |
* - Description? input Name Directives[Const]? InputFieldsDefinition? | |
*/ | |
parseInputObjectTypeDefinition() { | |
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(start, { | |
kind: _kinds.Kind.INPUT_OBJECT_TYPE_DEFINITION, | |
description, | |
name, | |
directives, | |
fields, | |
}); | |
} | |
/** | |
* ``` | |
* InputFieldsDefinition : { InputValueDefinition+ } | |
* ``` | |
*/ | |
parseInputFieldsDefinition() { | |
return this.optionalMany( | |
_tokenKind.TokenKind.BRACE_L, | |
this.parseInputValueDef, | |
_tokenKind.TokenKind.BRACE_R, | |
); | |
} | |
/** | |
* TypeSystemExtension : | |
* - SchemaExtension | |
* - TypeExtension | |
* | |
* TypeExtension : | |
* - ScalarTypeExtension | |
* - ObjectTypeExtension | |
* - InterfaceTypeExtension | |
* - UnionTypeExtension | |
* - EnumTypeExtension | |
* - InputObjectTypeDefinition | |
*/ | |
parseTypeSystemExtension() { | |
const keywordToken = this._lexer.lookahead(); | |
if (keywordToken.kind === _tokenKind.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() { | |
const start = this._lexer.token; | |
this.expectKeyword('extend'); | |
this.expectKeyword('schema'); | |
const directives = this.parseConstDirectives(); | |
const operationTypes = this.optionalMany( | |
_tokenKind.TokenKind.BRACE_L, | |
this.parseOperationTypeDefinition, | |
_tokenKind.TokenKind.BRACE_R, | |
); | |
if (directives.length === 0 && operationTypes.length === 0) { | |
throw this.unexpected(); | |
} | |
return this.node(start, { | |
kind: _kinds.Kind.SCHEMA_EXTENSION, | |
directives, | |
operationTypes, | |
}); | |
} | |
/** | |
* ScalarTypeExtension : | |
* - extend scalar Name Directives[Const] | |
*/ | |
parseScalarTypeExtension() { | |
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(start, { | |
kind: _kinds.Kind.SCALAR_TYPE_EXTENSION, | |
name, | |
directives, | |
}); | |
} | |
/** | |
* ObjectTypeExtension : | |
* - extend type Name ImplementsInterfaces? Directives[Const]? FieldsDefinition | |
* - extend type Name ImplementsInterfaces? Directives[Const] | |
* - extend type Name ImplementsInterfaces | |
*/ | |
parseObjectTypeExtension() { | |
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(start, { | |
kind: _kinds.Kind.OBJECT_TYPE_EXTENSION, | |
name, | |
interfaces, | |
directives, | |
fields, | |
}); | |
} | |
/** | |
* InterfaceTypeExtension : | |
* - extend interface Name ImplementsInterfaces? Directives[Const]? FieldsDefinition | |
* - extend interface Name ImplementsInterfaces? Directives[Const] | |
* - extend interface Name ImplementsInterfaces | |
*/ | |
parseInterfaceTypeExtension() { | |
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(start, { | |
kind: _kinds.Kind.INTERFACE_TYPE_EXTENSION, | |
name, | |
interfaces, | |
directives, | |
fields, | |
}); | |
} | |
/** | |
* UnionTypeExtension : | |
* - extend union Name Directives[Const]? UnionMemberTypes | |
* - extend union Name Directives[Const] | |
*/ | |
parseUnionTypeExtension() { | |
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(start, { | |
kind: _kinds.Kind.UNION_TYPE_EXTENSION, | |
name, | |
directives, | |
types, | |
}); | |
} | |
/** | |
* EnumTypeExtension : | |
* - extend enum Name Directives[Const]? EnumValuesDefinition | |
* - extend enum Name Directives[Const] | |
*/ | |
parseEnumTypeExtension() { | |
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(start, { | |
kind: _kinds.Kind.ENUM_TYPE_EXTENSION, | |
name, | |
directives, | |
values, | |
}); | |
} | |
/** | |
* InputObjectTypeExtension : | |
* - extend input Name Directives[Const]? InputFieldsDefinition | |
* - extend input Name Directives[Const] | |
*/ | |
parseInputObjectTypeExtension() { | |
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(start, { | |
kind: _kinds.Kind.INPUT_OBJECT_TYPE_EXTENSION, | |
name, | |
directives, | |
fields, | |
}); | |
} | |
/** | |
* ``` | |
* DirectiveDefinition : | |
* - Description? directive @ Name ArgumentsDefinition? `repeatable`? on DirectiveLocations | |
* ``` | |
*/ | |
parseDirectiveDefinition() { | |
const start = this._lexer.token; | |
const description = this.parseDescription(); | |
this.expectKeyword('directive'); | |
this.expectToken(_tokenKind.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(start, { | |
kind: _kinds.Kind.DIRECTIVE_DEFINITION, | |
description, | |
name, | |
arguments: args, | |
repeatable, | |
locations, | |
}); | |
} | |
/** | |
* DirectiveLocations : | |
* - `|`? DirectiveLocation | |
* - DirectiveLocations | DirectiveLocation | |
*/ | |
parseDirectiveLocations() { | |
return this.delimitedMany( | |
_tokenKind.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() { | |
const start = this._lexer.token; | |
const name = this.parseName(); | |
if ( | |
Object.prototype.hasOwnProperty.call( | |
_directiveLocation.DirectiveLocation, | |
name.value, | |
) | |
) { | |
return name; | |
} | |
throw this.unexpected(start); | |
} // Core parsing utility functions | |
/** | |
* Returns a node that, if configured to do so, sets a "loc" field as a | |
* location object, used to identify the place in the source that created a | |
* given parsed object. | |
*/ | |
node(startToken, node) { | |
if (this._options.noLocation !== true) { | |
node.loc = new _ast.Location( | |
startToken, | |
this._lexer.lastToken, | |
this._lexer.source, | |
); | |
} | |
return node; | |
} | |
/** | |
* Determines if the next token is of a given kind | |
*/ | |
peek(kind) { | |
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) { | |
const token = this._lexer.token; | |
if (token.kind === kind) { | |
this.advanceLexer(); | |
return token; | |
} | |
throw (0, _syntaxError.syntaxError)( | |
this._lexer.source, | |
token.start, | |
`Expected ${getTokenKindDesc(kind)}, found ${getTokenDesc(token)}.`, | |
); | |
} | |
/** | |
* If the next token is of the given kind, return "true" after advancing the lexer. | |
* Otherwise, do not change the parser state and return "false". | |
*/ | |
expectOptionalToken(kind) { | |
const token = this._lexer.token; | |
if (token.kind === kind) { | |
this.advanceLexer(); | |
return true; | |
} | |
return false; | |
} | |
/** | |
* If the next token is a given keyword, advance the lexer. | |
* Otherwise, do not change the parser state and throw an error. | |
*/ | |
expectKeyword(value) { | |
const token = this._lexer.token; | |
if (token.kind === _tokenKind.TokenKind.NAME && token.value === value) { | |
this.advanceLexer(); | |
} else { | |
throw (0, _syntaxError.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) { | |
const token = this._lexer.token; | |
if (token.kind === _tokenKind.TokenKind.NAME && token.value === value) { | |
this.advanceLexer(); | |
return true; | |
} | |
return false; | |
} | |
/** | |
* Helper function for creating an error when an unexpected lexed token is encountered. | |
*/ | |
unexpected(atToken) { | |
const token = | |
atToken !== null && atToken !== void 0 ? atToken : this._lexer.token; | |
return (0, _syntaxError.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(openKind, parseFn, closeKind) { | |
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(openKind, parseFn, closeKind) { | |
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(openKind, parseFn, closeKind) { | |
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(delimiterKind, parseFn) { | |
this.expectOptionalToken(delimiterKind); | |
const nodes = []; | |
do { | |
nodes.push(parseFn.call(this)); | |
} while (this.expectOptionalToken(delimiterKind)); | |
return nodes; | |
} | |
advanceLexer() { | |
const { maxTokens } = this._options; | |
const token = this._lexer.advance(); | |
if (token.kind !== _tokenKind.TokenKind.EOF) { | |
++this._tokenCounter; | |
if (maxTokens !== undefined && this._tokenCounter > maxTokens) { | |
throw (0, _syntaxError.syntaxError)( | |
this._lexer.source, | |
token.start, | |
`Document contains more that ${maxTokens} tokens. Parsing aborted.`, | |
); | |
} | |
} | |
} | |
} | |
/** | |
* A helper function to describe a token as a string for debugging. | |
*/ | |
exports.Parser = Parser; | |
function getTokenDesc(token) { | |
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) { | |
return (0, _lexer.isPunctuatorTokenKind)(kind) ? `"${kind}"` : kind; | |
} | |