graphql
Version:
A Query Language and Runtime which can target any service.
521 lines (468 loc) • 13.8 kB
JavaScript
/**
* Copyright (c) 2015, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
;
/**
* Configuration options to control parser behavior
*/
var _Object$defineProperty = require('babel-runtime/core-js/object/define-property')['default'];
_Object$defineProperty(exports, '__esModule', {
value: true
});
exports.parse = parse;
var _source = require('./source');
var _error = require('./error');
var _lexer = require('./lexer');
var _kinds = require('./kinds');
/**
* Given a GraphQL source, parses it into a Document. Throws on error.
*/
function parse(source, options) {
var sourceObj = source instanceof _source.Source ? source : new _source.Source(source);
var parser = makeParser(sourceObj, options || {});
return parseDocument(parser);
}
/**
* Returns the parser object that is used to store state throughout the
* process of parsing.
*/
function makeParser(source, options) {
var _lexToken = (0, _lexer.lex)(source);
return {
_lexToken: _lexToken,
source: source,
options: options,
prevEnd: 0,
token: _lexToken()
};
}
/**
* Returns a location object, used to identify the place in
* the source that created a given parsed object.
*/
function loc(parser, start) {
if (parser.options.noLocation) {
return null;
}
if (parser.options.noSource) {
return {
start: start,
end: parser.prevEnd
};
}
return {
start: start,
end: parser.prevEnd,
source: parser.source
};
}
/**
* Moves the internal parser object to the next lexed token.
*/
function advance(parser) {
var prevEnd = parser.token.end;
parser.prevEnd = prevEnd;
parser.token = parser._lexToken(prevEnd);
}
/**
* Determines if the next token is of a given kind
*/
function peek(parser, kind) {
return parser.token.kind === kind;
}
/**
* If the next token is of the given kind, return true after advancing
* the parser. Otherwise, do not change the parser state and return false.
*/
function skip(parser, kind) {
var match = parser.token.kind === kind;
if (match) {
advance(parser);
}
return match;
}
/**
* If the next token is of the given kind, return that token after advancing
* the parser. Otherwise, do not change the parser state and return false.
*/
function expect(parser, kind) {
var token = parser.token;
if (token.kind === kind) {
advance(parser);
return token;
}
throw (0, _error.error)(parser.source, token.start, 'Expected ' + (0, _lexer.getTokenKindDesc)(kind) + ', found ' + (0, _lexer.getTokenDesc)(token));
}
/**
* If the next token is a keyword with the given value, return that token after
* advancing the parser. Otherwise, do not change the parser state and return
* false.
*/
function expectKeyword(parser, value) {
var token = parser.token;
if (token.kind === _lexer.TokenKind.NAME && token.value === value) {
advance(parser);
return token;
}
throw (0, _error.error)(parser.source, token.start, 'Expected "' + value + '", found ' + (0, _lexer.getTokenDesc)(token));
}
/**
* Helper function for creating an error when an unexpected lexed token
* is encountered.
*/
function unexpected(parser, atToken) {
var token = atToken || parser.token;
return (0, _error.error)(parser.source, token.start, 'Unexpected ' + (0, _lexer.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.
*/
function any(parser, openKind, parseFn, closeKind) {
expect(parser, openKind);
var nodes = [];
while (!skip(parser, closeKind)) {
nodes.push(parseFn(parser));
}
return nodes;
}
/**
* 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.
*/
function many(parser, openKind, parseFn, closeKind) {
expect(parser, openKind);
var nodes = [parseFn(parser)];
while (!skip(parser, closeKind)) {
nodes.push(parseFn(parser));
}
return nodes;
}
/**
* Converts a name lex token into a name parse node.
*/
function parseName(parser) {
var token = expect(parser, _lexer.TokenKind.NAME);
return {
kind: _kinds.NAME,
value: token.value,
loc: loc(parser, token.start)
};
}
// Implements the parsing rules in the Document section.
function parseDocument(parser) {
var start = parser.token.start;
var definitions = [];
do {
if (peek(parser, _lexer.TokenKind.BRACE_L)) {
definitions.push(parseOperationDefinition(parser));
} else if (peek(parser, _lexer.TokenKind.NAME)) {
if (parser.token.value === 'query' || parser.token.value === 'mutation') {
definitions.push(parseOperationDefinition(parser));
} else if (parser.token.value === 'fragment') {
definitions.push(parseFragmentDefinition(parser));
} else {
throw unexpected(parser);
}
} else {
throw unexpected(parser);
}
} while (!skip(parser, _lexer.TokenKind.EOF));
return {
kind: _kinds.DOCUMENT,
definitions: definitions,
loc: loc(parser, start)
};
}
// Implements the parsing rules in the Operations section.
function parseOperationDefinition(parser) {
var start = parser.token.start;
if (peek(parser, _lexer.TokenKind.BRACE_L)) {
return {
kind: _kinds.OPERATION_DEFINITION,
operation: 'query',
name: null,
variableDefinitions: null,
directives: [],
selectionSet: parseSelectionSet(parser),
loc: loc(parser, start)
};
}
var operationToken = expect(parser, _lexer.TokenKind.NAME);
var operation = operationToken.value;
return {
kind: _kinds.OPERATION_DEFINITION,
operation: operation,
name: parseName(parser),
variableDefinitions: parseVariableDefinitions(parser),
directives: parseDirectives(parser),
selectionSet: parseSelectionSet(parser),
loc: loc(parser, start)
};
}
function parseVariableDefinitions(parser) {
return peek(parser, _lexer.TokenKind.PAREN_L) ? many(parser, _lexer.TokenKind.PAREN_L, parseVariableDefinition, _lexer.TokenKind.PAREN_R) : [];
}
function parseVariableDefinition(parser) {
var start = parser.token.start;
return {
kind: _kinds.VARIABLE_DEFINITION,
variable: parseVariable(parser),
type: (expect(parser, _lexer.TokenKind.COLON), parseType(parser)),
defaultValue: skip(parser, _lexer.TokenKind.EQUALS) ? parseValue(parser, true) : null,
loc: loc(parser, start)
};
}
function parseVariable(parser) {
var start = parser.token.start;
expect(parser, _lexer.TokenKind.DOLLAR);
return {
kind: _kinds.VARIABLE,
name: parseName(parser),
loc: loc(parser, start)
};
}
function parseSelectionSet(parser) {
var start = parser.token.start;
return {
kind: _kinds.SELECTION_SET,
selections: many(parser, _lexer.TokenKind.BRACE_L, parseSelection, _lexer.TokenKind.BRACE_R),
loc: loc(parser, start)
};
}
function parseSelection(parser) {
return peek(parser, _lexer.TokenKind.SPREAD) ? parseFragment(parser) : parseField(parser);
}
/**
* Corresponds to both Field and Alias in the spec
*/
function parseField(parser) {
var start = parser.token.start;
var nameOrAlias = parseName(parser);
var alias;
var name;
if (skip(parser, _lexer.TokenKind.COLON)) {
alias = nameOrAlias;
name = parseName(parser);
} else {
alias = null;
name = nameOrAlias;
}
return {
kind: _kinds.FIELD,
alias: alias,
name: name,
arguments: parseArguments(parser),
directives: parseDirectives(parser),
selectionSet: peek(parser, _lexer.TokenKind.BRACE_L) ? parseSelectionSet(parser) : null,
loc: loc(parser, start)
};
}
function parseArguments(parser) {
return peek(parser, _lexer.TokenKind.PAREN_L) ? many(parser, _lexer.TokenKind.PAREN_L, parseArgument, _lexer.TokenKind.PAREN_R) : [];
}
function parseArgument(parser) {
var start = parser.token.start;
return {
kind: _kinds.ARGUMENT,
name: parseName(parser),
value: (expect(parser, _lexer.TokenKind.COLON), parseValue(parser, false)),
loc: loc(parser, start)
};
}
// Implements the parsing rules in the Fragments section.
/**
* Corresponds to both FragmentSpread and InlineFragment in the spec
*/
function parseFragment(parser) {
var start = parser.token.start;
expect(parser, _lexer.TokenKind.SPREAD);
if (parser.token.value === 'on') {
advance(parser);
return {
kind: _kinds.INLINE_FRAGMENT,
typeCondition: parseName(parser),
directives: parseDirectives(parser),
selectionSet: parseSelectionSet(parser),
loc: loc(parser, start)
};
}
return {
kind: _kinds.FRAGMENT_SPREAD,
name: parseName(parser),
directives: parseDirectives(parser),
loc: loc(parser, start)
};
}
function parseFragmentDefinition(parser) {
var start = parser.token.start;
expectKeyword(parser, 'fragment');
return {
kind: _kinds.FRAGMENT_DEFINITION,
name: parseName(parser),
typeCondition: (expectKeyword(parser, 'on'), parseName(parser)),
directives: parseDirectives(parser),
selectionSet: parseSelectionSet(parser),
loc: loc(parser, start)
};
}
// Implements the parsing rules in the Values section.
function parseVariableValue(parser) {
return parseValue(parser, false);
}
function parseConstValue(parser) {
return parseValue(parser, true);
}
function parseValue(parser, isConst) {
var token = parser.token;
switch (token.kind) {
case _lexer.TokenKind.BRACKET_L:
return parseArray(parser, isConst);
case _lexer.TokenKind.BRACE_L:
return parseObject(parser, isConst);
case _lexer.TokenKind.INT:
advance(parser);
return {
kind: _kinds.INT,
value: token.value,
loc: loc(parser, token.start)
};
case _lexer.TokenKind.FLOAT:
advance(parser);
return {
kind: _kinds.FLOAT,
value: token.value,
loc: loc(parser, token.start)
};
case _lexer.TokenKind.STRING:
advance(parser);
return {
kind: _kinds.STRING,
value: token.value,
loc: loc(parser, token.start)
};
case _lexer.TokenKind.NAME:
advance(parser);
switch (token.value) {
case 'true':
case 'false':
return {
kind: _kinds.BOOLEAN,
value: token.value === 'true',
loc: loc(parser, token.start)
};
}
return {
kind: _kinds.ENUM,
value: token.value,
loc: loc(parser, token.start)
};
case _lexer.TokenKind.DOLLAR:
if (!isConst) {
return parseVariable(parser);
}
break;
}
throw unexpected(parser);
}
function parseArray(parser, isConst) {
var start = parser.token.start;
var item = isConst ? parseConstValue : parseVariableValue;
return {
kind: _kinds.ARRAY,
values: any(parser, _lexer.TokenKind.BRACKET_L, item, _lexer.TokenKind.BRACKET_R),
loc: loc(parser, start)
};
}
function parseObject(parser, isConst) {
var start = parser.token.start;
expect(parser, _lexer.TokenKind.BRACE_L);
var fieldNames = {};
var fields = [];
while (!skip(parser, _lexer.TokenKind.BRACE_R)) {
fields.push(parseObjectField(parser, isConst, fieldNames));
}
return {
kind: _kinds.OBJECT,
fields: fields,
loc: loc(parser, start)
};
}
function parseObjectField(parser, isConst, fieldNames) {
var start = parser.token.start;
var name = parseName(parser);
if (fieldNames.hasOwnProperty(name.value)) {
throw (0, _error.error)(parser.source, start, 'Duplicate input object field ' + name.value + '.');
}
fieldNames[name.value] = true;
return {
kind: _kinds.OBJECT_FIELD,
name: name,
value: (expect(parser, _lexer.TokenKind.COLON), parseValue(parser, isConst)),
loc: loc(parser, start)
};
}
// Implements the parsing rules in the Directives section.
function parseDirectives(parser) {
var directives = [];
while (peek(parser, _lexer.TokenKind.AT)) {
directives.push(parseDirective(parser));
}
return directives;
}
function parseDirective(parser) {
var start = parser.token.start;
expect(parser, _lexer.TokenKind.AT);
return {
kind: _kinds.DIRECTIVE,
name: parseName(parser),
value: skip(parser, _lexer.TokenKind.COLON) ? parseValue(parser, false) : null,
loc: loc(parser, start)
};
}
// Implements the parsing rules in the Types section.
/**
* Handles the Type: TypeName, ListType, and NonNullType parsing rules.
*/
function parseType(parser) {
var start = parser.token.start;
var type;
if (skip(parser, _lexer.TokenKind.BRACKET_L)) {
type = parseType(parser);
expect(parser, _lexer.TokenKind.BRACKET_R);
type = {
kind: _kinds.LIST_TYPE,
type: type,
loc: loc(parser, start)
};
} else {
type = parseName(parser);
}
if (skip(parser, _lexer.TokenKind.BANG)) {
return {
kind: _kinds.NON_NULL_TYPE,
type: type,
loc: loc(parser, start)
};
}
return type;
}
/**
* 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.
*/
/**
* By default, the parser creates AST nodes that contain a reference
* to the source that they were created from. This configuration flag
* disables that behavior for performance or testing.
*/