mirror of
https://github.com/retailcrm/graphql-php.git
synced 2024-11-22 04:46:04 +03:00
Updated parser to consume latest lexer; New public parser API methods: parseType and parseValue; added directives to schema parser/printer
This commit is contained in:
parent
3eeb4d450b
commit
cd14146032
@ -13,6 +13,11 @@ class EnumTypeDefinition extends Node implements TypeDefinition
|
||||
*/
|
||||
public $name;
|
||||
|
||||
/**
|
||||
* @var Directive[]
|
||||
*/
|
||||
public $directives;
|
||||
|
||||
/**
|
||||
* @var EnumValueDefinition[]
|
||||
*/
|
||||
|
@ -12,4 +12,9 @@ class EnumValueDefinition extends Node
|
||||
* @var Name
|
||||
*/
|
||||
public $name;
|
||||
|
||||
/**
|
||||
* @var Directive[]
|
||||
*/
|
||||
public $directives;
|
||||
}
|
||||
|
@ -22,4 +22,9 @@ class FieldDefinition extends Node
|
||||
* @var Type
|
||||
*/
|
||||
public $type;
|
||||
|
||||
/**
|
||||
* @var Directive[]
|
||||
*/
|
||||
public $directives;
|
||||
}
|
||||
|
@ -13,6 +13,11 @@ class InputObjectTypeDefinition extends Node implements TypeDefinition
|
||||
*/
|
||||
public $name;
|
||||
|
||||
/**
|
||||
* @var Directive[]
|
||||
*/
|
||||
public $directives;
|
||||
|
||||
/**
|
||||
* @var InputValueDefinition[]
|
||||
*/
|
||||
|
@ -22,4 +22,9 @@ class InputValueDefinition extends Node
|
||||
* @var Value
|
||||
*/
|
||||
public $defaultValue;
|
||||
|
||||
/**
|
||||
* @var Directive[]
|
||||
*/
|
||||
public $directives;
|
||||
}
|
@ -13,6 +13,11 @@ class InterfaceTypeDefinition extends Node implements TypeDefinition
|
||||
*/
|
||||
public $name;
|
||||
|
||||
/**
|
||||
* @var Directive[]
|
||||
*/
|
||||
public $directives;
|
||||
|
||||
/**
|
||||
* @var FieldDefinition[]
|
||||
*/
|
||||
|
@ -3,6 +3,10 @@ namespace GraphQL\Language\AST;
|
||||
|
||||
use GraphQL\Utils;
|
||||
|
||||
/**
|
||||
* Class Node
|
||||
* @package GraphQL\Language\AST
|
||||
*/
|
||||
abstract class Node
|
||||
{
|
||||
// constants from language/kinds.js:
|
||||
|
@ -18,6 +18,11 @@ class ObjectTypeDefinition extends Node implements TypeDefinition
|
||||
*/
|
||||
public $interfaces = [];
|
||||
|
||||
/**
|
||||
* @var Directive[]
|
||||
*/
|
||||
public $directives;
|
||||
|
||||
/**
|
||||
* @var FieldDefinition[]
|
||||
*/
|
||||
|
@ -4,10 +4,18 @@ namespace GraphQL\Language\AST;
|
||||
|
||||
class ScalarTypeDefinition extends Node implements TypeDefinition
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
public $kind = Node::SCALAR_TYPE_DEFINITION;
|
||||
|
||||
/**
|
||||
* @var Name
|
||||
*/
|
||||
public $name;
|
||||
|
||||
/**
|
||||
* @var Directive[]
|
||||
*/
|
||||
public $directives;
|
||||
}
|
||||
|
@ -8,6 +8,11 @@ class SchemaDefinition extends Node implements TypeSystemDefinition
|
||||
*/
|
||||
public $kind = Node::SCHEMA_DEFINITION;
|
||||
|
||||
/**
|
||||
* @var Directive[]
|
||||
*/
|
||||
public $directives;
|
||||
|
||||
/**
|
||||
* @var OperationTypeDefinition[]
|
||||
*/
|
||||
|
@ -13,6 +13,11 @@ class UnionTypeDefinition extends Node implements TypeDefinition
|
||||
*/
|
||||
public $name;
|
||||
|
||||
/**
|
||||
* @var Directive[]
|
||||
*/
|
||||
public $directives;
|
||||
|
||||
/**
|
||||
* @var NamedType[]
|
||||
*/
|
||||
|
@ -53,11 +53,6 @@ class Parser
|
||||
* in the source that they correspond to. This configuration flag
|
||||
* disables that behavior for performance or testing.)
|
||||
*
|
||||
* noSource: boolean,
|
||||
* 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.
|
||||
*
|
||||
* @param Source|string $source
|
||||
* @param array $options
|
||||
* @return Document
|
||||
@ -69,66 +64,77 @@ class Parser
|
||||
return $parser->parseDocument();
|
||||
}
|
||||
|
||||
/**
|
||||
* @var Source
|
||||
*/
|
||||
private $source;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
* 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().
|
||||
*
|
||||
* @param Source|string $source
|
||||
* @param array $options
|
||||
* @return BooleanValue|EnumValue|FloatValue|IntValue|ListValue|ObjectValue|StringValue|Variable
|
||||
*/
|
||||
private $options;
|
||||
public static function parseValue($source, array $options = [])
|
||||
{
|
||||
$sourceObj = $source instanceof Source ? $source : new Source($source);
|
||||
$parser = new Parser($sourceObj, $options);
|
||||
$parser->expect(Token::SOF);
|
||||
$value = $parser->parseValueLiteral(false);
|
||||
$parser->expect(Token::EOF);
|
||||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @var int
|
||||
* 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().
|
||||
* @param Source|string $source
|
||||
* @param array $options
|
||||
* @return ListType|Name|NonNullType
|
||||
*/
|
||||
private $prevEnd;
|
||||
public static function parseType($source, array $options = [])
|
||||
{
|
||||
$sourceObj = $source instanceof Source ? $source : new Source($source);
|
||||
$parser = new Parser($sourceObj, $options);
|
||||
$parser->expect(Token::SOF);
|
||||
$type = $parser->parseTypeReference();
|
||||
$parser->expect(Token::EOF);
|
||||
return $type;
|
||||
}
|
||||
|
||||
/**
|
||||
* @var Lexer
|
||||
*/
|
||||
private $lexer;
|
||||
|
||||
/**
|
||||
* @var Token
|
||||
*/
|
||||
private $token;
|
||||
|
||||
function __construct(Source $source, array $options = [])
|
||||
{
|
||||
$this->lexer = new Lexer($source);
|
||||
$this->source = $source;
|
||||
$this->options = $options;
|
||||
$this->prevEnd = 0;
|
||||
$this->token = $this->lexer->nextToken();
|
||||
$this->lexer = new Lexer($source, $options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a location object, used to identify the place in
|
||||
* the source that created a given parsed object.
|
||||
*
|
||||
* @param int $start
|
||||
* @param Token $startToken
|
||||
* @return Location|null
|
||||
*/
|
||||
function loc($start)
|
||||
function loc(Token $startToken)
|
||||
{
|
||||
if (!empty($this->options['noLocation'])) {
|
||||
return null;
|
||||
if (empty($this->lexer->options['noLocation'])) {
|
||||
return new Location($startToken, $this->lexer->lastToken, $this->lexer->source);
|
||||
}
|
||||
if (!empty($this->options['noSource'])) {
|
||||
return new Location($start, $this->prevEnd);
|
||||
}
|
||||
return new Location($start, $this->prevEnd, $this->source);
|
||||
}
|
||||
|
||||
/**
|
||||
* Moves the internal parser object to the next lexed token.
|
||||
*/
|
||||
function advance()
|
||||
{
|
||||
$prevEnd = $this->token->end;
|
||||
$this->prevEnd = $prevEnd;
|
||||
$this->token = $this->lexer->nextToken($prevEnd);
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -139,7 +145,7 @@ class Parser
|
||||
*/
|
||||
function peek($kind)
|
||||
{
|
||||
return $this->token->kind === $kind;
|
||||
return $this->lexer->token->kind === $kind;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -151,10 +157,10 @@ class Parser
|
||||
*/
|
||||
function skip($kind)
|
||||
{
|
||||
$match = $this->token->kind === $kind;
|
||||
$match = $this->lexer->token->kind === $kind;
|
||||
|
||||
if ($match) {
|
||||
$this->advance();
|
||||
$this->lexer->advance();
|
||||
}
|
||||
return $match;
|
||||
}
|
||||
@ -168,17 +174,17 @@ class Parser
|
||||
*/
|
||||
function expect($kind)
|
||||
{
|
||||
$token = $this->token;
|
||||
$token = $this->lexer->token;
|
||||
|
||||
if ($token->kind === $kind) {
|
||||
$this->advance();
|
||||
$this->lexer->advance();
|
||||
return $token;
|
||||
}
|
||||
|
||||
throw new SyntaxError(
|
||||
$this->source,
|
||||
$this->lexer->source,
|
||||
$token->start,
|
||||
"Expected " . Token::getKindDescription($kind) . ", found " . $token->getDescription()
|
||||
"Expected $kind, found " . $token->getDescription()
|
||||
);
|
||||
}
|
||||
|
||||
@ -193,14 +199,14 @@ class Parser
|
||||
*/
|
||||
function expectKeyword($value)
|
||||
{
|
||||
$token = $this->token;
|
||||
$token = $this->lexer->token;
|
||||
|
||||
if ($token->kind === Token::NAME && $token->value === $value) {
|
||||
$this->advance();
|
||||
$this->lexer->advance();
|
||||
return $token;
|
||||
}
|
||||
throw new SyntaxError(
|
||||
$this->source,
|
||||
$this->lexer->source,
|
||||
$token->start,
|
||||
'Expected "' . $value . '", found ' . $token->getDescription()
|
||||
);
|
||||
@ -212,8 +218,8 @@ class Parser
|
||||
*/
|
||||
function unexpected(Token $atToken = null)
|
||||
{
|
||||
$token = $atToken ?: $this->token;
|
||||
return new SyntaxError($this->source, $token->start, "Unexpected " . $token->getDescription());
|
||||
$token = $atToken ?: $this->lexer->token;
|
||||
return new SyntaxError($this->lexer->source, $token->start, "Unexpected " . $token->getDescription());
|
||||
}
|
||||
|
||||
/**
|
||||
@ -274,22 +280,10 @@ class Parser
|
||||
|
||||
return new Name([
|
||||
'value' => $token->value,
|
||||
'loc' => $this->loc($token->start)
|
||||
'loc' => $this->loc($token)
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Name
|
||||
* @throws SyntaxError
|
||||
*/
|
||||
function parseFragmentName()
|
||||
{
|
||||
if ($this->token->value === 'on') {
|
||||
throw $this->unexpected();
|
||||
}
|
||||
return $this->parseName();
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements the parsing rules in the Document section.
|
||||
*
|
||||
@ -298,9 +292,10 @@ class Parser
|
||||
*/
|
||||
function parseDocument()
|
||||
{
|
||||
$start = $this->token->start;
|
||||
$definitions = [];
|
||||
$start = $this->lexer->token;
|
||||
$this->expect(Token::SOF);
|
||||
|
||||
$definitions = [];
|
||||
do {
|
||||
$definitions[] = $this->parseDefinition();
|
||||
} while (!$this->skip(Token::EOF));
|
||||
@ -322,10 +317,9 @@ class Parser
|
||||
}
|
||||
|
||||
if ($this->peek(Token::NAME)) {
|
||||
switch ($this->token->value) {
|
||||
switch ($this->lexer->token->value) {
|
||||
case 'query':
|
||||
case 'mutation':
|
||||
// Note: subscription is an experimental non-spec addition.
|
||||
case 'subscription':
|
||||
return $this->parseOperationDefinition();
|
||||
|
||||
@ -357,7 +351,7 @@ class Parser
|
||||
*/
|
||||
function parseOperationDefinition()
|
||||
{
|
||||
$start = $this->token->start;
|
||||
$start = $this->lexer->token;
|
||||
if ($this->peek(Token::BRACE_L)) {
|
||||
return new OperationDefinition([
|
||||
'operation' => 'query',
|
||||
@ -423,11 +417,11 @@ class Parser
|
||||
*/
|
||||
function parseVariableDefinition()
|
||||
{
|
||||
$start = $this->token->start;
|
||||
$start = $this->lexer->token;
|
||||
$var = $this->parseVariable();
|
||||
|
||||
$this->expect(Token::COLON);
|
||||
$type = $this->parseType();
|
||||
$type = $this->parseTypeReference();
|
||||
|
||||
return new VariableDefinition([
|
||||
'variable' => $var,
|
||||
@ -444,7 +438,7 @@ class Parser
|
||||
*/
|
||||
function parseVariable()
|
||||
{
|
||||
$start = $this->token->start;
|
||||
$start = $this->lexer->token;
|
||||
$this->expect(Token::DOLLAR);
|
||||
|
||||
return new Variable([
|
||||
@ -458,7 +452,7 @@ class Parser
|
||||
*/
|
||||
function parseSelectionSet()
|
||||
{
|
||||
$start = $this->token->start;
|
||||
$start = $this->lexer->token;
|
||||
return new SelectionSet([
|
||||
'selections' => $this->many(Token::BRACE_L, [$this, 'parseSelection'], Token::BRACE_R),
|
||||
'loc' => $this->loc($start)
|
||||
@ -466,6 +460,11 @@ class Parser
|
||||
}
|
||||
|
||||
/**
|
||||
* Selection :
|
||||
* - Field
|
||||
* - FragmentSpread
|
||||
* - InlineFragment
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
function parseSelection()
|
||||
@ -480,7 +479,7 @@ class Parser
|
||||
*/
|
||||
function parseField()
|
||||
{
|
||||
$start = $this->token->start;
|
||||
$start = $this->lexer->token;
|
||||
$nameOrAlias = $this->parseName();
|
||||
|
||||
if ($this->skip(Token::COLON)) {
|
||||
@ -517,7 +516,7 @@ class Parser
|
||||
*/
|
||||
function parseArgument()
|
||||
{
|
||||
$start = $this->token->start;
|
||||
$start = $this->lexer->token;
|
||||
$name = $this->parseName();
|
||||
|
||||
$this->expect(Token::COLON);
|
||||
@ -538,10 +537,10 @@ class Parser
|
||||
*/
|
||||
function parseFragment()
|
||||
{
|
||||
$start = $this->token->start;
|
||||
$start = $this->lexer->token;
|
||||
$this->expect(Token::SPREAD);
|
||||
|
||||
if ($this->peek(Token::NAME) && $this->token->value !== 'on') {
|
||||
if ($this->peek(Token::NAME) && $this->lexer->token->value !== 'on') {
|
||||
return new FragmentSpread([
|
||||
'name' => $this->parseFragmentName(),
|
||||
'directives' => $this->parseDirectives(),
|
||||
@ -550,8 +549,8 @@ class Parser
|
||||
}
|
||||
|
||||
$typeCondition = null;
|
||||
if ($this->token->value === 'on') {
|
||||
$this->advance();
|
||||
if ($this->lexer->token->value === 'on') {
|
||||
$this->lexer->advance();
|
||||
$typeCondition = $this->parseNamedType();
|
||||
}
|
||||
|
||||
@ -569,7 +568,7 @@ class Parser
|
||||
*/
|
||||
function parseFragmentDefinition()
|
||||
{
|
||||
$start = $this->token->start;
|
||||
$start = $this->lexer->token;
|
||||
$this->expectKeyword('fragment');
|
||||
|
||||
$name = $this->parseFragmentName();
|
||||
@ -585,64 +584,77 @@ class Parser
|
||||
]);
|
||||
}
|
||||
|
||||
// Implements the parsing rules in the Values section.
|
||||
function parseVariableValue()
|
||||
{
|
||||
return $this->parseValueLiteral(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return BooleanValue|EnumValue|FloatValue|IntValue|StringValue|Variable
|
||||
* @return Name
|
||||
* @throws SyntaxError
|
||||
*/
|
||||
function parseConstValue()
|
||||
function parseFragmentName()
|
||||
{
|
||||
return $this->parseValueLiteral(true);
|
||||
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
|
||||
* - EnumValue
|
||||
* - ListValue[?Const]
|
||||
* - ObjectValue[?Const]
|
||||
*
|
||||
* BooleanValue : one of `true` `false`
|
||||
*
|
||||
* EnumValue : Name but not `true`, `false` or `null`
|
||||
*
|
||||
* @param $isConst
|
||||
* @return BooleanValue|EnumValue|FloatValue|IntValue|StringValue|Variable
|
||||
* @return BooleanValue|EnumValue|FloatValue|IntValue|StringValue|Variable|ListValue|ObjectValue
|
||||
* @throws SyntaxError
|
||||
*/
|
||||
function parseValueLiteral($isConst)
|
||||
{
|
||||
$token = $this->token;
|
||||
$token = $this->lexer->token;
|
||||
switch ($token->kind) {
|
||||
case Token::BRACKET_L:
|
||||
return $this->parseArray($isConst);
|
||||
case Token::BRACE_L:
|
||||
return $this->parseObject($isConst);
|
||||
case Token::INT:
|
||||
$this->advance();
|
||||
$this->lexer->advance();
|
||||
return new IntValue([
|
||||
'value' => $token->value,
|
||||
'loc' => $this->loc($token->start)
|
||||
'loc' => $this->loc($token)
|
||||
]);
|
||||
case Token::FLOAT:
|
||||
$this->advance();
|
||||
$this->lexer->advance();
|
||||
return new FloatValue([
|
||||
'value' => $token->value,
|
||||
'loc' => $this->loc($token->start)
|
||||
'loc' => $this->loc($token)
|
||||
]);
|
||||
case Token::STRING:
|
||||
$this->advance();
|
||||
$this->lexer->advance();
|
||||
return new StringValue([
|
||||
'value' => $token->value,
|
||||
'loc' => $this->loc($token->start)
|
||||
'loc' => $this->loc($token)
|
||||
]);
|
||||
case Token::NAME:
|
||||
if ($token->value === 'true' || $token->value === 'false') {
|
||||
$this->advance();
|
||||
$this->lexer->advance();
|
||||
return new BooleanValue([
|
||||
'value' => $token->value === 'true',
|
||||
'loc' => $this->loc($token->start)
|
||||
'loc' => $this->loc($token)
|
||||
]);
|
||||
} else if ($token->value !== 'null') {
|
||||
$this->advance();
|
||||
$this->lexer->advance();
|
||||
return new EnumValue([
|
||||
'value' => $token->value,
|
||||
'loc' => $this->loc($token->start)
|
||||
'loc' => $this->loc($token)
|
||||
]);
|
||||
}
|
||||
break;
|
||||
@ -656,13 +668,30 @@ class Parser
|
||||
throw $this->unexpected();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return BooleanValue|EnumValue|FloatValue|IntValue|StringValue|Variable
|
||||
* @throws SyntaxError
|
||||
*/
|
||||
function parseConstValue()
|
||||
{
|
||||
return $this->parseValueLiteral(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return BooleanValue|EnumValue|FloatValue|IntValue|ListValue|ObjectValue|StringValue|Variable
|
||||
*/
|
||||
function parseVariableValue()
|
||||
{
|
||||
return $this->parseValueLiteral(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param bool $isConst
|
||||
* @return ListValue
|
||||
*/
|
||||
function parseArray($isConst)
|
||||
{
|
||||
$start = $this->token->start;
|
||||
$start = $this->lexer->token;
|
||||
$item = $isConst ? 'parseConstValue' : 'parseVariableValue';
|
||||
return new ListValue([
|
||||
'values' => $this->any(Token::BRACKET_L, [$this, $item], Token::BRACKET_R),
|
||||
@ -670,9 +699,13 @@ class Parser
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $isConst
|
||||
* @return ObjectValue
|
||||
*/
|
||||
function parseObject($isConst)
|
||||
{
|
||||
$start = $this->token->start;
|
||||
$start = $this->lexer->token;
|
||||
$this->expect(Token::BRACE_L);
|
||||
$fields = [];
|
||||
while (!$this->skip(Token::BRACE_R)) {
|
||||
@ -684,9 +717,13 @@ class Parser
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $isConst
|
||||
* @return ObjectField
|
||||
*/
|
||||
function parseObjectField($isConst)
|
||||
{
|
||||
$start = $this->token->start;
|
||||
$start = $this->lexer->token;
|
||||
$name = $this->parseName();
|
||||
|
||||
$this->expect(Token::COLON);
|
||||
@ -718,7 +755,7 @@ class Parser
|
||||
*/
|
||||
function parseDirective()
|
||||
{
|
||||
$start = $this->token->start;
|
||||
$start = $this->lexer->token;
|
||||
$this->expect(Token::AT);
|
||||
return new Directive([
|
||||
'name' => $this->parseName(),
|
||||
@ -735,12 +772,12 @@ class Parser
|
||||
* @return ListType|Name|NonNullType
|
||||
* @throws SyntaxError
|
||||
*/
|
||||
function parseType()
|
||||
function parseTypeReference()
|
||||
{
|
||||
$start = $this->token->start;
|
||||
$start = $this->lexer->token;
|
||||
|
||||
if ($this->skip(Token::BRACKET_L)) {
|
||||
$type = $this->parseType();
|
||||
$type = $this->parseTypeReference();
|
||||
$this->expect(Token::BRACKET_R);
|
||||
$type = new ListType([
|
||||
'type' => $type,
|
||||
@ -761,7 +798,7 @@ class Parser
|
||||
|
||||
function parseNamedType()
|
||||
{
|
||||
$start = $this->token->start;
|
||||
$start = $this->lexer->token;
|
||||
|
||||
return new NamedType([
|
||||
'name' => $this->parseName(),
|
||||
@ -773,6 +810,7 @@ class Parser
|
||||
|
||||
/**
|
||||
* TypeSystemDefinition :
|
||||
* - SchemaDefinition
|
||||
* - TypeDefinition
|
||||
* - TypeExtensionDefinition
|
||||
* - DirectiveDefinition
|
||||
@ -791,7 +829,7 @@ class Parser
|
||||
function parseTypeSystemDefinition()
|
||||
{
|
||||
if ($this->peek(Token::NAME)) {
|
||||
switch ($this->token->value) {
|
||||
switch ($this->lexer->token->value) {
|
||||
case 'schema': return $this->parseSchemaDefinition();
|
||||
case 'scalar': return $this->parseScalarTypeDefinition();
|
||||
case 'type': return $this->parseObjectTypeDefinition();
|
||||
@ -813,8 +851,9 @@ class Parser
|
||||
*/
|
||||
function parseSchemaDefinition()
|
||||
{
|
||||
$start = $this->token->start;
|
||||
$start = $this->lexer->token;
|
||||
$this->expectKeyword('schema');
|
||||
$directives = $this->parseDirectives();
|
||||
|
||||
$operationTypes = $this->many(
|
||||
Token::BRACE_L,
|
||||
@ -823,14 +862,18 @@ class Parser
|
||||
);
|
||||
|
||||
return new SchemaDefinition([
|
||||
'directives' => $directives,
|
||||
'operationTypes' => $operationTypes,
|
||||
'loc' => $this->loc($start)
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return OperationTypeDefinition
|
||||
*/
|
||||
function parseOperationTypeDefinition()
|
||||
{
|
||||
$start = $this->token->start;
|
||||
$start = $this->lexer->token;
|
||||
$operation = $this->parseOperationType();
|
||||
$this->expect(Token::COLON);
|
||||
$type = $this->parseNamedType();
|
||||
@ -848,12 +891,14 @@ class Parser
|
||||
*/
|
||||
function parseScalarTypeDefinition()
|
||||
{
|
||||
$start = $this->token->start;
|
||||
$start = $this->lexer->token;
|
||||
$this->expectKeyword('scalar');
|
||||
$name = $this->parseName();
|
||||
$directives = $this->parseDirectives();
|
||||
|
||||
return new ScalarTypeDefinition([
|
||||
'name' => $name,
|
||||
'directives' => $directives,
|
||||
'loc' => $this->loc($start)
|
||||
]);
|
||||
}
|
||||
@ -864,10 +909,12 @@ class Parser
|
||||
*/
|
||||
function parseObjectTypeDefinition()
|
||||
{
|
||||
$start = $this->token->start;
|
||||
$start = $this->lexer->token;
|
||||
$this->expectKeyword('type');
|
||||
$name = $this->parseName();
|
||||
$interfaces = $this->parseImplementsInterfaces();
|
||||
$directives = $this->parseDirectives();
|
||||
|
||||
$fields = $this->any(
|
||||
Token::BRACE_L,
|
||||
[$this, 'parseFieldDefinition'],
|
||||
@ -877,6 +924,7 @@ class Parser
|
||||
return new ObjectTypeDefinition([
|
||||
'name' => $name,
|
||||
'interfaces' => $interfaces,
|
||||
'directives' => $directives,
|
||||
'fields' => $fields,
|
||||
'loc' => $this->loc($start)
|
||||
]);
|
||||
@ -888,11 +936,11 @@ class Parser
|
||||
function parseImplementsInterfaces()
|
||||
{
|
||||
$types = [];
|
||||
if ($this->token->value === 'implements') {
|
||||
$this->advance();
|
||||
if ($this->lexer->token->value === 'implements') {
|
||||
$this->lexer->advance();
|
||||
do {
|
||||
$types[] = $this->parseNamedType();
|
||||
} while (!$this->peek(Token::BRACE_L));
|
||||
} while ($this->peek(Token::NAME));
|
||||
}
|
||||
return $types;
|
||||
}
|
||||
@ -903,16 +951,18 @@ class Parser
|
||||
*/
|
||||
function parseFieldDefinition()
|
||||
{
|
||||
$start = $this->token->start;
|
||||
$start = $this->lexer->token;
|
||||
$name = $this->parseName();
|
||||
$args = $this->parseArgumentDefs();
|
||||
$this->expect(Token::COLON);
|
||||
$type = $this->parseType();
|
||||
$type = $this->parseTypeReference();
|
||||
$directives = $this->parseDirectives();
|
||||
|
||||
return new FieldDefinition([
|
||||
'name' => $name,
|
||||
'arguments' => $args,
|
||||
'type' => $type,
|
||||
'directives' => $directives,
|
||||
'loc' => $this->loc($start)
|
||||
]);
|
||||
}
|
||||
@ -934,18 +984,20 @@ class Parser
|
||||
*/
|
||||
function parseInputValueDef()
|
||||
{
|
||||
$start = $this->token->start;
|
||||
$start = $this->lexer->token;
|
||||
$name = $this->parseName();
|
||||
$this->expect(Token::COLON);
|
||||
$type = $this->parseType();
|
||||
$type = $this->parseTypeReference();
|
||||
$defaultValue = null;
|
||||
if ($this->skip(Token::EQUALS)) {
|
||||
$defaultValue = $this->parseConstValue();
|
||||
}
|
||||
$directives = $this->parseDirectives();
|
||||
return new InputValueDefinition([
|
||||
'name' => $name,
|
||||
'type' => $type,
|
||||
'defaultValue' => $defaultValue,
|
||||
'directives' => $directives,
|
||||
'loc' => $this->loc($start)
|
||||
]);
|
||||
}
|
||||
@ -956,9 +1008,10 @@ class Parser
|
||||
*/
|
||||
function parseInterfaceTypeDefinition()
|
||||
{
|
||||
$start = $this->token->start;
|
||||
$start = $this->lexer->token;
|
||||
$this->expectKeyword('interface');
|
||||
$name = $this->parseName();
|
||||
$directives = $this->parseDirectives();
|
||||
$fields = $this->any(
|
||||
Token::BRACE_L,
|
||||
[$this, 'parseFieldDefinition'],
|
||||
@ -967,6 +1020,7 @@ class Parser
|
||||
|
||||
return new InterfaceTypeDefinition([
|
||||
'name' => $name,
|
||||
'directives' => $directives,
|
||||
'fields' => $fields,
|
||||
'loc' => $this->loc($start)
|
||||
]);
|
||||
@ -978,20 +1032,26 @@ class Parser
|
||||
*/
|
||||
function parseUnionTypeDefinition()
|
||||
{
|
||||
$start = $this->token->start;
|
||||
$start = $this->lexer->token;
|
||||
$this->expectKeyword('union');
|
||||
$name = $this->parseName();
|
||||
$directives = $this->parseDirectives();
|
||||
$this->expect(Token::EQUALS);
|
||||
$types = $this->parseUnionMembers();
|
||||
|
||||
return new UnionTypeDefinition([
|
||||
'name' => $name,
|
||||
'directives' => $directives,
|
||||
'types' => $types,
|
||||
'loc' => $this->loc($start)
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* UnionMembers :
|
||||
* - NamedType
|
||||
* - UnionMembers | NamedType
|
||||
*
|
||||
* @return NamedType[]
|
||||
*/
|
||||
function parseUnionMembers()
|
||||
@ -1009,9 +1069,10 @@ class Parser
|
||||
*/
|
||||
function parseEnumTypeDefinition()
|
||||
{
|
||||
$start = $this->token->start;
|
||||
$start = $this->lexer->token;
|
||||
$this->expectKeyword('enum');
|
||||
$name = $this->parseName();
|
||||
$directives = $this->parseDirectives();
|
||||
$values = $this->many(
|
||||
Token::BRACE_L,
|
||||
[$this, 'parseEnumValueDefinition'],
|
||||
@ -1020,6 +1081,7 @@ class Parser
|
||||
|
||||
return new EnumTypeDefinition([
|
||||
'name' => $name,
|
||||
'directives' => $directives,
|
||||
'values' => $values,
|
||||
'loc' => $this->loc($start)
|
||||
]);
|
||||
@ -1030,11 +1092,13 @@ class Parser
|
||||
*/
|
||||
function parseEnumValueDefinition()
|
||||
{
|
||||
$start = $this->token->start;
|
||||
$start = $this->lexer->token;
|
||||
$name = $this->parseName();
|
||||
$directives = $this->parseDirectives();
|
||||
|
||||
return new EnumValueDefinition([
|
||||
'name' => $name,
|
||||
'directives' => $directives,
|
||||
'loc' => $this->loc($start)
|
||||
]);
|
||||
}
|
||||
@ -1045,9 +1109,10 @@ class Parser
|
||||
*/
|
||||
function parseInputObjectTypeDefinition()
|
||||
{
|
||||
$start = $this->token->start;
|
||||
$start = $this->lexer->token;
|
||||
$this->expectKeyword('input');
|
||||
$name = $this->parseName();
|
||||
$directives = $this->parseDirectives();
|
||||
$fields = $this->any(
|
||||
Token::BRACE_L,
|
||||
[$this, 'parseInputValueDef'],
|
||||
@ -1056,6 +1121,7 @@ class Parser
|
||||
|
||||
return new InputObjectTypeDefinition([
|
||||
'name' => $name,
|
||||
'directives' => $directives,
|
||||
'fields' => $fields,
|
||||
'loc' => $this->loc($start)
|
||||
]);
|
||||
@ -1067,7 +1133,7 @@ class Parser
|
||||
*/
|
||||
function parseTypeExtensionDefinition()
|
||||
{
|
||||
$start = $this->token->start;
|
||||
$start = $this->lexer->token;
|
||||
$this->expectKeyword('extend');
|
||||
$definition = $this->parseObjectTypeDefinition();
|
||||
|
||||
@ -1078,12 +1144,15 @@ class Parser
|
||||
}
|
||||
|
||||
/**
|
||||
* DirectiveDefinition :
|
||||
* - directive @ Name ArgumentsDefinition? on DirectiveLocations
|
||||
*
|
||||
* @return DirectiveDefinition
|
||||
* @throws SyntaxError
|
||||
*/
|
||||
function parseDirectiveDefinition()
|
||||
{
|
||||
$start = $this->token->start;
|
||||
$start = $this->lexer->token;
|
||||
$this->expectKeyword('directive');
|
||||
$this->expect(Token::AT);
|
||||
$name = $this->parseName();
|
||||
|
@ -115,33 +115,77 @@ class Printer
|
||||
Node::NON_NULL_TYPE => function(NonNullType $node) {return $node->type . '!';},
|
||||
|
||||
// Type System Definitions
|
||||
Node::SCHEMA_DEFINITION => function(SchemaDefinition $def) {return 'schema ' . self::block($def->operationTypes);},
|
||||
Node::SCHEMA_DEFINITION => function(SchemaDefinition $def) {
|
||||
return self::join([
|
||||
'schema',
|
||||
self::join($def->directives, ' '),
|
||||
self::block($def->operationTypes)
|
||||
], ' ');
|
||||
},
|
||||
Node::OPERATION_TYPE_DEFINITION => function(OperationTypeDefinition $def) {return $def->operation . ': ' . $def->type;},
|
||||
|
||||
Node::SCALAR_TYPE_DEFINITION => function(ScalarTypeDefinition $def) {return "scalar {$def->name}";},
|
||||
Node::SCALAR_TYPE_DEFINITION => function(ScalarTypeDefinition $def) {
|
||||
return self::join(['scalar', $def->name, self::join($def->directives, ' ')], ' ');
|
||||
},
|
||||
Node::OBJECT_TYPE_DEFINITION => function(ObjectTypeDefinition $def) {
|
||||
return 'type ' . $def->name . ' ' .
|
||||
self::wrap('implements ', self::join($def->interfaces, ', '), ' ') .
|
||||
self::block($def->fields);
|
||||
return self::join([
|
||||
'type',
|
||||
$def->name,
|
||||
self::wrap('implements ', self::join($def->interfaces, ', ')),
|
||||
self::join($def->directives, ' '),
|
||||
self::block($def->fields)
|
||||
], ' ');
|
||||
},
|
||||
Node::FIELD_DEFINITION => function(FieldDefinition $def) {
|
||||
return $def->name . self::wrap('(', self::join($def->arguments, ', '), ')') . ': ' . $def->type;
|
||||
return $def->name
|
||||
. self::wrap('(', self::join($def->arguments, ', '), ')')
|
||||
. ': ' . $def->type
|
||||
. self::wrap(' ', self::join($def->directives, ' '));
|
||||
},
|
||||
Node::INPUT_VALUE_DEFINITION => function(InputValueDefinition $def) {
|
||||
return $def->name . ': ' . $def->type . self::wrap(' = ', $def->defaultValue);
|
||||
return self::join([
|
||||
$def->name . ': ' . $def->type,
|
||||
self::wrap('= ', $def->defaultValue),
|
||||
self::join($def->directives, ' ')
|
||||
], ' ');
|
||||
},
|
||||
Node::INTERFACE_TYPE_DEFINITION => function(InterfaceTypeDefinition $def) {
|
||||
return 'interface ' . $def->name . ' ' . self::block($def->fields);
|
||||
return self::join([
|
||||
'interface',
|
||||
$def->name,
|
||||
self::join($def->directives, ' '),
|
||||
self::block($def->fields)
|
||||
], ' ');
|
||||
},
|
||||
Node::UNION_TYPE_DEFINITION => function(UnionTypeDefinition $def) {
|
||||
return 'union ' . $def->name . ' = ' . self::join($def->types, ' | ');
|
||||
return self::join([
|
||||
'union',
|
||||
$def->name,
|
||||
self::join($def->directives, ' '),
|
||||
'= ' . self::join($def->types, ' | ')
|
||||
], ' ');
|
||||
},
|
||||
Node::ENUM_TYPE_DEFINITION => function(EnumTypeDefinition $def) {
|
||||
return 'enum ' . $def->name . ' ' . self::block($def->values);
|
||||
return self::join([
|
||||
'enum',
|
||||
$def->name,
|
||||
self::join($def->directives, ' '),
|
||||
self::block($def->values)
|
||||
], ' ');
|
||||
},
|
||||
Node::ENUM_VALUE_DEFINITION => function(EnumValueDefinition $def) {
|
||||
return self::join([
|
||||
$def->name,
|
||||
self::join($def->directives, ' ')
|
||||
], ' ');
|
||||
},
|
||||
Node::ENUM_VALUE_DEFINITION => function(EnumValueDefinition $def) {return $def->name;},
|
||||
Node::INPUT_OBJECT_TYPE_DEFINITION => function(InputObjectTypeDefinition $def) {
|
||||
return 'input ' . $def->name . ' ' . self::block($def->fields);
|
||||
return self::join([
|
||||
'input',
|
||||
$def->name,
|
||||
self::join($def->directives, ' '),
|
||||
self::block($def->fields)
|
||||
], ' ');
|
||||
},
|
||||
Node::TYPE_EXTENSION_DEFINITION => function(TypeExtensionDefinition $def) {return "extend {$def->definition}";},
|
||||
Node::DIRECTIVE_DEFINITION => function(DirectiveDefinition $def) {
|
||||
@ -162,12 +206,12 @@ class Printer
|
||||
}
|
||||
|
||||
/**
|
||||
* Given maybeArray, print an empty string if it is null or empty, otherwise
|
||||
* print each item on it's own line, wrapped in an indented "{ }" block.
|
||||
* Given array, print each item on its own line, wrapped in an
|
||||
* indented "{ }" block.
|
||||
*/
|
||||
public static function block($maybeArray)
|
||||
public static function block($array)
|
||||
{
|
||||
return self::length($maybeArray) ? self::indent("{\n" . self::join($maybeArray, "\n")) . "\n}" : '';
|
||||
return $array && self::length($array) ? self::indent("{\n" . self::join($array, "\n")) . "\n}" : '{}';
|
||||
}
|
||||
|
||||
public static function indent($maybeString)
|
||||
|
@ -64,19 +64,17 @@ class Visitor
|
||||
Node::LIST_TYPE => ['type'],
|
||||
Node::NON_NULL_TYPE => ['type'],
|
||||
|
||||
Node::SCHEMA_DEFINITION => ['operationTypes'],
|
||||
Node::SCHEMA_DEFINITION => ['directives', 'operationTypes'],
|
||||
Node::OPERATION_TYPE_DEFINITION => ['type'],
|
||||
Node::SCALAR_TYPE_DEFINITION => ['name'],
|
||||
Node::OBJECT_TYPE_DEFINITION => ['name', 'interfaces', 'fields'],
|
||||
Node::FIELD_DEFINITION => ['name', 'arguments', 'type'],
|
||||
Node::INPUT_VALUE_DEFINITION => ['name', 'type', 'defaultValue'],
|
||||
Node::INPUT_VALUE_DEFINITION => [ 'name', 'type', 'defaultValue' ],
|
||||
Node::INTERFACE_TYPE_DEFINITION => [ 'name', 'fields' ],
|
||||
Node::UNION_TYPE_DEFINITION => [ 'name', 'types' ],
|
||||
Node::ENUM_TYPE_DEFINITION => [ 'name', 'values' ],
|
||||
|
||||
Node::ENUM_VALUE_DEFINITION => [ 'name' ],
|
||||
Node::INPUT_OBJECT_TYPE_DEFINITION => [ 'name', 'fields' ],
|
||||
Node::SCALAR_TYPE_DEFINITION => ['name', 'directives'],
|
||||
Node::OBJECT_TYPE_DEFINITION => ['name', 'interfaces', 'directives', 'fields'],
|
||||
Node::FIELD_DEFINITION => ['name', 'arguments', 'type', 'directives'],
|
||||
Node::INPUT_VALUE_DEFINITION => ['name', 'type', 'defaultValue', 'directives'],
|
||||
Node::INTERFACE_TYPE_DEFINITION => [ 'name', 'directives', 'fields' ],
|
||||
Node::UNION_TYPE_DEFINITION => [ 'name', 'directives', 'types' ],
|
||||
Node::ENUM_TYPE_DEFINITION => [ 'name', 'directives', 'values' ],
|
||||
Node::ENUM_VALUE_DEFINITION => [ 'name', 'directives' ],
|
||||
Node::INPUT_OBJECT_TYPE_DEFINITION => [ 'name', 'directives', 'fields' ],
|
||||
Node::TYPE_EXTENSION_DEFINITION => [ 'definition' ],
|
||||
Node::DIRECTIVE_DEFINITION => [ 'name', 'arguments', 'locations' ]
|
||||
);
|
||||
|
@ -7,6 +7,7 @@ use GraphQL\Language\AST\Field;
|
||||
use GraphQL\Language\AST\IntValue;
|
||||
use GraphQL\Language\AST\Location;
|
||||
use GraphQL\Language\AST\Name;
|
||||
use GraphQL\Language\AST\Node;
|
||||
use GraphQL\Language\AST\OperationDefinition;
|
||||
use GraphQL\Language\AST\SelectionSet;
|
||||
use GraphQL\Language\AST\StringValue;
|
||||
@ -18,45 +19,6 @@ use GraphQL\Utils;
|
||||
|
||||
class ParserTest extends \PHPUnit_Framework_TestCase
|
||||
{
|
||||
/**
|
||||
* @it accepts option to not include source
|
||||
*/
|
||||
public function testAcceptsOptionToNotIncludeSource()
|
||||
{
|
||||
$actual = Parser::parse('{ field }', ['noSource' => true]);
|
||||
|
||||
$expected = new Document([
|
||||
'loc' => new Location(0, 9),
|
||||
'definitions' => [
|
||||
new OperationDefinition([
|
||||
'loc' => new Location(0, 9),
|
||||
'operation' => 'query',
|
||||
'name' => null,
|
||||
'variableDefinitions' => null,
|
||||
'directives' => [],
|
||||
'selectionSet' => new SelectionSet([
|
||||
'loc' => new Location(0, 9),
|
||||
'selections' => [
|
||||
new Field([
|
||||
'loc' => new Location(2, 7),
|
||||
'alias' => null,
|
||||
'name' => new Name([
|
||||
'loc' => new Location(2, 7),
|
||||
'value' => 'field'
|
||||
]),
|
||||
'arguments' => [],
|
||||
'directives' => [],
|
||||
'selectionSet' => null
|
||||
])
|
||||
]
|
||||
])
|
||||
])
|
||||
]
|
||||
]);
|
||||
|
||||
$this->assertEquals($expected, $actual);
|
||||
}
|
||||
|
||||
/**
|
||||
* @it parse provides useful errors
|
||||
*/
|
||||
@ -78,7 +40,7 @@ class ParserTest extends \PHPUnit_Framework_TestCase
|
||||
}
|
||||
};
|
||||
|
||||
$run(0, '{', "Syntax Error GraphQL (1:2) Expected Name, found EOF\n\n1: {\n ^\n", [1], [new SourceLocation(1,2)]);
|
||||
$run(0, '{', "Syntax Error GraphQL (1:2) Expected Name, found <EOF>\n\n1: {\n ^\n", [1], [new SourceLocation(1,2)]);
|
||||
$run(1,
|
||||
'{ ...MissingOn }
|
||||
fragment MissingOn Type
|
||||
@ -100,7 +62,7 @@ fragment MissingOn Type
|
||||
Parser::parse(new Source('query', 'MyQuery.graphql'));
|
||||
$this->fail('Expected exception not thrown');
|
||||
} catch (SyntaxError $e) {
|
||||
$this->assertEquals("Syntax Error MyQuery.graphql (1:6) Expected {, found EOF\n\n1: query\n ^\n", $e->getMessage());
|
||||
$this->assertEquals("Syntax Error MyQuery.graphql (1:6) Expected {, found <EOF>\n\n1: query\n ^\n", $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@ -287,7 +249,7 @@ fragment $fragmentName on Type {
|
||||
}
|
||||
|
||||
/**
|
||||
* @it parse creates ast
|
||||
* @it creates ast
|
||||
*/
|
||||
public function testParseCreatesAst()
|
||||
{
|
||||
@ -300,73 +262,267 @@ fragment $fragmentName on Type {
|
||||
');
|
||||
$result = Parser::parse($source);
|
||||
|
||||
$expected = new Document(array(
|
||||
'loc' => new Location(0, 41, $source),
|
||||
'definitions' => array(
|
||||
new OperationDefinition(array(
|
||||
'loc' => new Location(0, 40, $source),
|
||||
$loc = function($start, $end) use ($source) {
|
||||
return [
|
||||
'start' => $start,
|
||||
'end' => $end
|
||||
];
|
||||
};
|
||||
|
||||
$expected = [
|
||||
'kind' => Node::DOCUMENT,
|
||||
'loc' => $loc(0, 41),
|
||||
'definitions' => [
|
||||
[
|
||||
'kind' => Node::OPERATION_DEFINITION,
|
||||
'loc' => $loc(0, 40),
|
||||
'operation' => 'query',
|
||||
'name' => null,
|
||||
'variableDefinitions' => null,
|
||||
'directives' => array(),
|
||||
'selectionSet' => new SelectionSet(array(
|
||||
'loc' => new Location(0, 40, $source),
|
||||
'selections' => array(
|
||||
new Field(array(
|
||||
'loc' => new Location(4, 38, $source),
|
||||
'directives' => [],
|
||||
'selectionSet' => [
|
||||
'kind' => Node::SELECTION_SET,
|
||||
'loc' => $loc(0, 40),
|
||||
'selections' => [
|
||||
[
|
||||
'kind' => Node::FIELD,
|
||||
'loc' => $loc(4, 38),
|
||||
'alias' => null,
|
||||
'name' => new Name(array(
|
||||
'loc' => new Location(4, 8, $source),
|
||||
'name' => [
|
||||
'kind' => Node::NAME,
|
||||
'loc' => $loc(4, 8),
|
||||
'value' => 'node'
|
||||
)),
|
||||
'arguments' => array(
|
||||
new Argument(array(
|
||||
'name' => new Name(array(
|
||||
'loc' => new Location(9, 11, $source),
|
||||
],
|
||||
'arguments' => [
|
||||
[
|
||||
'kind' => Node::ARGUMENT,
|
||||
'name' => [
|
||||
'kind' => Node::NAME,
|
||||
'loc' => $loc(9, 11),
|
||||
'value' => 'id'
|
||||
)),
|
||||
'value' => new IntValue(array(
|
||||
'loc' => new Location(13, 14, $source),
|
||||
],
|
||||
'value' => [
|
||||
'kind' => Node::INT,
|
||||
'loc' => $loc(13, 14),
|
||||
'value' => '4'
|
||||
)),
|
||||
'loc' => new Location(9, 14, $source)
|
||||
))
|
||||
),
|
||||
],
|
||||
'loc' => $loc(9, 14, $source)
|
||||
]
|
||||
],
|
||||
'directives' => [],
|
||||
'selectionSet' => new SelectionSet(array(
|
||||
'loc' => new Location(16, 38, $source),
|
||||
'selections' => array(
|
||||
new Field(array(
|
||||
'loc' => new Location(22, 24, $source),
|
||||
'selectionSet' => [
|
||||
'kind' => Node::SELECTION_SET,
|
||||
'loc' => $loc(16, 38),
|
||||
'selections' => [
|
||||
[
|
||||
'kind' => Node::FIELD,
|
||||
'loc' => $loc(22, 24),
|
||||
'alias' => null,
|
||||
'name' => new Name(array(
|
||||
'loc' => new Location(22, 24, $source),
|
||||
'name' => [
|
||||
'kind' => Node::NAME,
|
||||
'loc' => $loc(22, 24),
|
||||
'value' => 'id'
|
||||
)),
|
||||
],
|
||||
'arguments' => [],
|
||||
'directives' => [],
|
||||
'selectionSet' => null
|
||||
)),
|
||||
new Field(array(
|
||||
'loc' => new Location(30, 34, $source),
|
||||
],
|
||||
[
|
||||
'kind' => Node::FIELD,
|
||||
'loc' => $loc(30, 34),
|
||||
'alias' => null,
|
||||
'name' => new Name(array(
|
||||
'loc' => new Location(30, 34, $source),
|
||||
'name' => [
|
||||
'kind' => Node::NAME,
|
||||
'loc' => $loc(30, 34),
|
||||
'value' => 'name'
|
||||
)),
|
||||
],
|
||||
'arguments' => [],
|
||||
'directives' => [],
|
||||
'selectionSet' => null
|
||||
))
|
||||
)
|
||||
))
|
||||
))
|
||||
)
|
||||
))
|
||||
))
|
||||
)
|
||||
));
|
||||
]
|
||||
]
|
||||
]
|
||||
]
|
||||
]
|
||||
]
|
||||
]
|
||||
]
|
||||
];
|
||||
|
||||
$this->assertEquals($expected, $result);
|
||||
$this->assertEquals($expected, $this->nodeToArray($result));
|
||||
}
|
||||
|
||||
/**
|
||||
* @it allows parsing without source location information
|
||||
*/
|
||||
public function testAllowsParsingWithoutSourceLocationInformation()
|
||||
{
|
||||
$source = new Source('{ id }');
|
||||
$result = Parser::parse($source, ['noLocation' => true]);
|
||||
|
||||
$this->assertEquals(null, $result->loc);
|
||||
}
|
||||
|
||||
/**
|
||||
* @it contains location information that only stringifys start/end
|
||||
*/
|
||||
public function testConvertToArray()
|
||||
{
|
||||
$source = new Source('{ id }');
|
||||
$result = Parser::parse($source);
|
||||
$this->assertEquals(['start' => 0, 'end' => '6'], TestUtils::locationToArray($result->loc));
|
||||
}
|
||||
|
||||
/**
|
||||
* @it contains references to source
|
||||
*/
|
||||
public function testContainsReferencesToSource()
|
||||
{
|
||||
$source = new Source('{ id }');
|
||||
$result = Parser::parse($source);
|
||||
$this->assertEquals($source, $result->loc->source);
|
||||
}
|
||||
|
||||
/**
|
||||
* @it contains references to start and end tokens
|
||||
*/
|
||||
public function testContainsReferencesToStartAndEndTokens()
|
||||
{
|
||||
$source = new Source('{ id }');
|
||||
$result = Parser::parse($source);
|
||||
$this->assertEquals('<SOF>', $result->loc->startToken->kind);
|
||||
$this->assertEquals('<EOF>', $result->loc->endToken->kind);
|
||||
}
|
||||
|
||||
// Describe: parseValue
|
||||
|
||||
/**
|
||||
* @it parses list values
|
||||
*/
|
||||
public function testParsesListValues()
|
||||
{
|
||||
$this->assertEquals([
|
||||
'kind' => Node::LST,
|
||||
'loc' => ['start' => 0, 'end' => 11],
|
||||
'values' => [
|
||||
[
|
||||
'kind' => Node::INT,
|
||||
'loc' => ['start' => 1, 'end' => 4],
|
||||
'value' => '123'
|
||||
],
|
||||
[
|
||||
'kind' => Node::STRING,
|
||||
'loc' => ['start' => 5, 'end' => 10],
|
||||
'value' => 'abc'
|
||||
]
|
||||
]
|
||||
], $this->nodeToArray(Parser::parseValue('[123 "abc"]')));
|
||||
}
|
||||
|
||||
// Describe: parseType
|
||||
|
||||
/**
|
||||
* @it parses well known types
|
||||
*/
|
||||
public function testParsesWellKnownTypes()
|
||||
{
|
||||
$this->assertEquals([
|
||||
'kind' => Node::NAMED_TYPE,
|
||||
'loc' => ['start' => 0, 'end' => 6],
|
||||
'name' => [
|
||||
'kind' => Node::NAME,
|
||||
'loc' => ['start' => 0, 'end' => 6],
|
||||
'value' => 'String'
|
||||
]
|
||||
], $this->nodeToArray(Parser::parseType('String')));
|
||||
}
|
||||
|
||||
/**
|
||||
* @it parses custom types
|
||||
*/
|
||||
public function testParsesCustomTypes()
|
||||
{
|
||||
$this->assertEquals([
|
||||
'kind' => Node::NAMED_TYPE,
|
||||
'loc' => ['start' => 0, 'end' => 6],
|
||||
'name' => [
|
||||
'kind' => Node::NAME,
|
||||
'loc' => ['start' => 0, 'end' => 6],
|
||||
'value' => 'MyType'
|
||||
]
|
||||
], $this->nodeToArray(Parser::parseType('MyType')));
|
||||
}
|
||||
|
||||
/**
|
||||
* @it parses list types
|
||||
*/
|
||||
public function testParsesListTypes()
|
||||
{
|
||||
$this->assertEquals([
|
||||
'kind' => Node::LIST_TYPE,
|
||||
'loc' => ['start' => 0, 'end' => 8],
|
||||
'type' => [
|
||||
'kind' => Node::NAMED_TYPE,
|
||||
'loc' => ['start' => 1, 'end' => 7],
|
||||
'name' => [
|
||||
'kind' => Node::NAME,
|
||||
'loc' => ['start' => 1, 'end' => 7],
|
||||
'value' => 'MyType'
|
||||
]
|
||||
]
|
||||
], $this->nodeToArray(Parser::parseType('[MyType]')));
|
||||
}
|
||||
|
||||
/**
|
||||
* @it parses non-null types
|
||||
*/
|
||||
public function testParsesNonNullTypes()
|
||||
{
|
||||
$this->assertEquals([
|
||||
'kind' => Node::NON_NULL_TYPE,
|
||||
'loc' => ['start' => 0, 'end' => 7],
|
||||
'type' => [
|
||||
'kind' => Node::NAMED_TYPE,
|
||||
'loc' => ['start' => 0, 'end' => 6],
|
||||
'name' => [
|
||||
'kind' => Node::NAME,
|
||||
'loc' => ['start' => 0, 'end' => 6],
|
||||
'value' => 'MyType'
|
||||
]
|
||||
]
|
||||
], $this->nodeToArray(Parser::parseType('MyType!')));
|
||||
}
|
||||
|
||||
/**
|
||||
* @it parses nested types
|
||||
*/
|
||||
public function testParsesNestedTypes()
|
||||
{
|
||||
$this->assertEquals([
|
||||
'kind' => Node::LIST_TYPE,
|
||||
'loc' => ['start' => 0, 'end' => 9],
|
||||
'type' => [
|
||||
'kind' => Node::NON_NULL_TYPE,
|
||||
'loc' => ['start' => 1, 'end' => 8],
|
||||
'type' => [
|
||||
'kind' => Node::NAMED_TYPE,
|
||||
'loc' => ['start' => 1, 'end' => 7],
|
||||
'name' => [
|
||||
'kind' => Node::NAME,
|
||||
'loc' => ['start' => 1, 'end' => 7],
|
||||
'value' => 'MyType'
|
||||
]
|
||||
]
|
||||
]
|
||||
], $this->nodeToArray(Parser::parseType('[MyType!]')));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Node $node
|
||||
* @return array
|
||||
*/
|
||||
public static function nodeToArray(Node $node)
|
||||
{
|
||||
return TestUtils::nodeToArray($node);
|
||||
}
|
||||
}
|
||||
|
@ -13,6 +13,7 @@ use GraphQL\Language\AST\ListType;
|
||||
use GraphQL\Language\AST\Location;
|
||||
use GraphQL\Language\AST\Name;
|
||||
use GraphQL\Language\AST\NamedType;
|
||||
use GraphQL\Language\AST\Node;
|
||||
use GraphQL\Language\AST\NonNullType;
|
||||
use GraphQL\Language\AST\ObjectTypeDefinition;
|
||||
use GraphQL\Language\AST\ScalarTypeDefinition;
|
||||
@ -23,6 +24,8 @@ use GraphQL\Language\Source;
|
||||
|
||||
class SchemaParserTest extends \PHPUnit_Framework_TestCase
|
||||
{
|
||||
// Describe: Schema Parser
|
||||
|
||||
/**
|
||||
* @it Simple type
|
||||
*/
|
||||
@ -33,13 +36,16 @@ type Hello {
|
||||
world: String
|
||||
}';
|
||||
$doc = Parser::parse($body);
|
||||
$loc = $this->createLocFn($body);
|
||||
$loc = function($start, $end) {return TestUtils::locArray($start, $end);};
|
||||
|
||||
$expected = new Document([
|
||||
$expected = [
|
||||
'kind' => Node::DOCUMENT,
|
||||
'definitions' => [
|
||||
new ObjectTypeDefinition([
|
||||
[
|
||||
'kind' => Node::OBJECT_TYPE_DEFINITION,
|
||||
'name' => $this->nameNode('Hello', $loc(6, 11)),
|
||||
'interfaces' => [],
|
||||
'directives' => [],
|
||||
'fields' => [
|
||||
$this->fieldNode(
|
||||
$this->nameNode('world', $loc(16, 21)),
|
||||
@ -48,11 +54,11 @@ type Hello {
|
||||
)
|
||||
],
|
||||
'loc' => $loc(1, 31)
|
||||
])
|
||||
]
|
||||
],
|
||||
'loc' => $loc(1, 31)
|
||||
]);
|
||||
$this->assertEquals($doc, $expected);
|
||||
'loc' => $loc(0, 31)
|
||||
];
|
||||
$this->assertEquals($expected, TestUtils::nodeToArray($doc));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -63,15 +69,22 @@ type Hello {
|
||||
$body = '
|
||||
extend type Hello {
|
||||
world: String
|
||||
}';
|
||||
}
|
||||
';
|
||||
$doc = Parser::parse($body);
|
||||
$loc = $this->createLocFn($body);
|
||||
$expected = new Document([
|
||||
$loc = function($start, $end) {
|
||||
return TestUtils::locArray($start, $end);
|
||||
};
|
||||
$expected = [
|
||||
'kind' => Node::DOCUMENT,
|
||||
'definitions' => [
|
||||
new TypeExtensionDefinition([
|
||||
'definition' => new ObjectTypeDefinition([
|
||||
[
|
||||
'kind' => Node::TYPE_EXTENSION_DEFINITION,
|
||||
'definition' => [
|
||||
'kind' => Node::OBJECT_TYPE_DEFINITION,
|
||||
'name' => $this->nameNode('Hello', $loc(13, 18)),
|
||||
'interfaces' => [],
|
||||
'directives' => [],
|
||||
'fields' => [
|
||||
$this->fieldNode(
|
||||
$this->nameNode('world', $loc(23, 28)),
|
||||
@ -80,13 +93,13 @@ extend type Hello {
|
||||
)
|
||||
],
|
||||
'loc' => $loc(8, 38)
|
||||
]),
|
||||
],
|
||||
'loc' => $loc(1, 38)
|
||||
])
|
||||
]
|
||||
],
|
||||
'loc' => $loc(1, 38)
|
||||
]);
|
||||
$this->assertEquals($expected, $doc);
|
||||
'loc' => $loc(0, 39)
|
||||
];
|
||||
$this->assertEquals($expected, TestUtils::nodeToArray($doc));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -98,31 +111,37 @@ extend type Hello {
|
||||
type Hello {
|
||||
world: String!
|
||||
}';
|
||||
$loc = $this->createLocFn($body);
|
||||
$loc = function($start, $end) {
|
||||
return TestUtils::locArray($start, $end);
|
||||
};
|
||||
$doc = Parser::parse($body);
|
||||
|
||||
$expected = new Document([
|
||||
$expected = [
|
||||
'kind' => Node::DOCUMENT,
|
||||
'definitions' => [
|
||||
new ObjectTypeDefinition([
|
||||
[
|
||||
'kind' => Node::OBJECT_TYPE_DEFINITION,
|
||||
'name' => $this->nameNode('Hello', $loc(6,11)),
|
||||
'interfaces' => [],
|
||||
'directives' => [],
|
||||
'fields' => [
|
||||
$this->fieldNode(
|
||||
$this->nameNode('world', $loc(16, 21)),
|
||||
new NonNullType([
|
||||
[
|
||||
'kind' => Node::NON_NULL_TYPE,
|
||||
'type' => $this->typeNode('String', $loc(23, 29)),
|
||||
'loc' => $loc(23, 30)
|
||||
]),
|
||||
],
|
||||
$loc(16,30)
|
||||
)
|
||||
],
|
||||
'loc' => $loc(1,32)
|
||||
])
|
||||
]
|
||||
],
|
||||
'loc' => $loc(1,32)
|
||||
]);
|
||||
'loc' => $loc(0,32)
|
||||
];
|
||||
|
||||
$this->assertEquals($expected, $doc);
|
||||
$this->assertEquals($expected, TestUtils::nodeToArray($doc));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -131,24 +150,27 @@ type Hello {
|
||||
public function testSimpleTypeInheritingInterface()
|
||||
{
|
||||
$body = 'type Hello implements World { }';
|
||||
$loc = $this->createLocFn($body);
|
||||
$loc = function($start, $end) { return TestUtils::locArray($start, $end); };
|
||||
$doc = Parser::parse($body);
|
||||
|
||||
$expected = new Document([
|
||||
$expected = [
|
||||
'kind' => Node::DOCUMENT,
|
||||
'definitions' => [
|
||||
new ObjectTypeDefinition([
|
||||
[
|
||||
'kind' => Node::OBJECT_TYPE_DEFINITION,
|
||||
'name' => $this->nameNode('Hello', $loc(5, 10)),
|
||||
'interfaces' => [
|
||||
$this->typeNode('World', $loc(22, 27))
|
||||
],
|
||||
'directives' => [],
|
||||
'fields' => [],
|
||||
'loc' => $loc(0,31)
|
||||
])
|
||||
]
|
||||
],
|
||||
'loc' => $loc(0,31)
|
||||
]);
|
||||
];
|
||||
|
||||
$this->assertEquals($expected, $doc);
|
||||
$this->assertEquals($expected, TestUtils::nodeToArray($doc));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -157,25 +179,28 @@ type Hello {
|
||||
public function testSimpleTypeInheritingMultipleInterfaces()
|
||||
{
|
||||
$body = 'type Hello implements Wo, rld { }';
|
||||
$loc = $this->createLocFn($body);
|
||||
$loc = function($start, $end) {return TestUtils::locArray($start, $end);};
|
||||
$doc = Parser::parse($body);
|
||||
|
||||
$expected = new Document([
|
||||
$expected = [
|
||||
'kind' => Node::DOCUMENT,
|
||||
'definitions' => [
|
||||
new ObjectTypeDefinition([
|
||||
[
|
||||
'kind' => Node::OBJECT_TYPE_DEFINITION,
|
||||
'name' => $this->nameNode('Hello', $loc(5, 10)),
|
||||
'interfaces' => [
|
||||
$this->typeNode('Wo', $loc(22,24)),
|
||||
$this->typeNode('rld', $loc(26,29))
|
||||
],
|
||||
'directives' => [],
|
||||
'fields' => [],
|
||||
'loc' => $loc(0, 33)
|
||||
])
|
||||
]
|
||||
],
|
||||
'loc' => $loc(0, 33)
|
||||
]);
|
||||
];
|
||||
|
||||
$this->assertEquals($expected, $doc);
|
||||
$this->assertEquals($expected, TestUtils::nodeToArray($doc));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -184,21 +209,24 @@ type Hello {
|
||||
public function testSingleValueEnum()
|
||||
{
|
||||
$body = 'enum Hello { WORLD }';
|
||||
$loc = $this->createLocFn($body);
|
||||
$loc = function($start, $end) {return TestUtils::locArray($start, $end);};
|
||||
$doc = Parser::parse($body);
|
||||
|
||||
$expected = new Document([
|
||||
$expected = [
|
||||
'kind' => Node::DOCUMENT,
|
||||
'definitions' => [
|
||||
new EnumTypeDefinition([
|
||||
[
|
||||
'kind' => Node::ENUM_TYPE_DEFINITION,
|
||||
'name' => $this->nameNode('Hello', $loc(5, 10)),
|
||||
'directives' => [],
|
||||
'values' => [$this->enumValueNode('WORLD', $loc(13, 18))],
|
||||
'loc' => $loc(0, 20)
|
||||
])
|
||||
]
|
||||
],
|
||||
'loc' => $loc(0, 20)
|
||||
]);
|
||||
];
|
||||
|
||||
$this->assertEquals($expected, $doc);
|
||||
$this->assertEquals($expected, TestUtils::nodeToArray($doc));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -207,24 +235,27 @@ type Hello {
|
||||
public function testDoubleValueEnum()
|
||||
{
|
||||
$body = 'enum Hello { WO, RLD }';
|
||||
$loc = $this->createLocFn($body);
|
||||
$loc = function($start, $end) {return TestUtils::locArray($start, $end);};
|
||||
$doc = Parser::parse($body);
|
||||
|
||||
$expected = new Document([
|
||||
$expected = [
|
||||
'kind' => Node::DOCUMENT,
|
||||
'definitions' => [
|
||||
new EnumTypeDefinition([
|
||||
[
|
||||
'kind' => Node::ENUM_TYPE_DEFINITION,
|
||||
'name' => $this->nameNode('Hello', $loc(5, 10)),
|
||||
'directives' => [],
|
||||
'values' => [
|
||||
$this->enumValueNode('WO', $loc(13, 15)),
|
||||
$this->enumValueNode('RLD', $loc(17, 20))
|
||||
],
|
||||
'loc' => $loc(0, 22)
|
||||
])
|
||||
]
|
||||
],
|
||||
'loc' => $loc(0, 22)
|
||||
]);
|
||||
];
|
||||
|
||||
$this->assertEquals($expected, $doc);
|
||||
$this->assertEquals($expected, TestUtils::nodeToArray($doc));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -237,12 +268,15 @@ interface Hello {
|
||||
world: String
|
||||
}';
|
||||
$doc = Parser::parse($body);
|
||||
$loc = $this->createLocFn($body);
|
||||
$loc = function($start, $end) {return TestUtils::locArray($start, $end);};
|
||||
|
||||
$expected = new Document([
|
||||
$expected = [
|
||||
'kind' => Node::DOCUMENT,
|
||||
'definitions' => [
|
||||
new InterfaceTypeDefinition([
|
||||
[
|
||||
'kind' => Node::INTERFACE_TYPE_DEFINITION,
|
||||
'name' => $this->nameNode('Hello', $loc(11, 16)),
|
||||
'directives' => [],
|
||||
'fields' => [
|
||||
$this->fieldNode(
|
||||
$this->nameNode('world', $loc(21, 26)),
|
||||
@ -251,11 +285,11 @@ interface Hello {
|
||||
)
|
||||
],
|
||||
'loc' => $loc(1, 36)
|
||||
])
|
||||
]
|
||||
],
|
||||
'loc' => $loc(1,36)
|
||||
]);
|
||||
$this->assertEquals($expected, $doc);
|
||||
'loc' => $loc(0,36)
|
||||
];
|
||||
$this->assertEquals($expected, TestUtils::nodeToArray($doc));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -268,13 +302,16 @@ type Hello {
|
||||
world(flag: Boolean): String
|
||||
}';
|
||||
$doc = Parser::parse($body);
|
||||
$loc = $this->createLocFn($body);
|
||||
$loc = function($start, $end) {return TestUtils::locArray($start, $end);};
|
||||
|
||||
$expected = new Document([
|
||||
$expected = [
|
||||
'kind' => Node::DOCUMENT,
|
||||
'definitions' => [
|
||||
new ObjectTypeDefinition([
|
||||
[
|
||||
'kind' => Node::OBJECT_TYPE_DEFINITION,
|
||||
'name' => $this->nameNode('Hello', $loc(6, 11)),
|
||||
'interfaces' => [],
|
||||
'directives' => [],
|
||||
'fields' => [
|
||||
$this->fieldNodeWithArgs(
|
||||
$this->nameNode('world', $loc(16, 21)),
|
||||
@ -291,12 +328,12 @@ type Hello {
|
||||
)
|
||||
],
|
||||
'loc' => $loc(1, 46)
|
||||
])
|
||||
]
|
||||
],
|
||||
'loc' => $loc(1, 46)
|
||||
]);
|
||||
'loc' => $loc(0, 46)
|
||||
];
|
||||
|
||||
$this->assertEquals($expected, $doc);
|
||||
$this->assertEquals($expected, TestUtils::nodeToArray($doc));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -309,13 +346,16 @@ type Hello {
|
||||
world(flag: Boolean = true): String
|
||||
}';
|
||||
$doc = Parser::parse($body);
|
||||
$loc = $this->createLocFn($body);
|
||||
$loc = function($start, $end) {return TestUtils::locArray($start, $end);};
|
||||
|
||||
$expected = new Document([
|
||||
$expected = [
|
||||
'kind' => Node::DOCUMENT,
|
||||
'definitions' => [
|
||||
new ObjectTypeDefinition([
|
||||
[
|
||||
'kind' => Node::OBJECT_TYPE_DEFINITION,
|
||||
'name' => $this->nameNode('Hello', $loc(6, 11)),
|
||||
'interfaces' => [],
|
||||
'directives' => [],
|
||||
'fields' => [
|
||||
$this->fieldNodeWithArgs(
|
||||
$this->nameNode('world', $loc(16, 21)),
|
||||
@ -324,7 +364,7 @@ type Hello {
|
||||
$this->inputValueNode(
|
||||
$this->nameNode('flag', $loc(22, 26)),
|
||||
$this->typeNode('Boolean', $loc(28, 35)),
|
||||
new BooleanValue(['value' => true, 'loc' => $loc(38, 42)]),
|
||||
['kind' => Node::BOOLEAN, 'value' => true, 'loc' => $loc(38, 42)],
|
||||
$loc(22, 42)
|
||||
)
|
||||
],
|
||||
@ -332,11 +372,11 @@ type Hello {
|
||||
)
|
||||
],
|
||||
'loc' => $loc(1, 53)
|
||||
])
|
||||
]
|
||||
],
|
||||
'loc' => $loc(1, 53)
|
||||
]);
|
||||
$this->assertEquals($expected, $doc);
|
||||
'loc' => $loc(0, 53)
|
||||
];
|
||||
$this->assertEquals($expected, TestUtils::nodeToArray($doc));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -349,13 +389,16 @@ type Hello {
|
||||
world(things: [String]): String
|
||||
}';
|
||||
$doc = Parser::parse($body);
|
||||
$loc = $this->createLocFn($body);
|
||||
$loc = function($start, $end) {return TestUtils::locArray($start, $end);};
|
||||
|
||||
$expected = new Document([
|
||||
$expected = [
|
||||
'kind' => Node::DOCUMENT,
|
||||
'definitions' => [
|
||||
new ObjectTypeDefinition([
|
||||
[
|
||||
'kind' => Node::OBJECT_TYPE_DEFINITION,
|
||||
'name' => $this->nameNode('Hello', $loc(6, 11)),
|
||||
'interfaces' => [],
|
||||
'directives' => [],
|
||||
'fields' => [
|
||||
$this->fieldNodeWithArgs(
|
||||
$this->nameNode('world', $loc(16, 21)),
|
||||
@ -363,7 +406,7 @@ type Hello {
|
||||
[
|
||||
$this->inputValueNode(
|
||||
$this->nameNode('things', $loc(22,28)),
|
||||
new ListType(['type' => $this->typeNode('String', $loc(31, 37)), 'loc' => $loc(30, 38)]),
|
||||
['kind' => Node::LIST_TYPE, 'type' => $this->typeNode('String', $loc(31, 37)), 'loc' => $loc(30, 38)],
|
||||
null,
|
||||
$loc(22, 38)
|
||||
)
|
||||
@ -372,12 +415,12 @@ type Hello {
|
||||
)
|
||||
],
|
||||
'loc' => $loc(1, 49)
|
||||
])
|
||||
]
|
||||
],
|
||||
'loc' => $loc(1, 49)
|
||||
]);
|
||||
'loc' => $loc(0, 49)
|
||||
];
|
||||
|
||||
$this->assertEquals($expected, $doc);
|
||||
$this->assertEquals($expected, TestUtils::nodeToArray($doc));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -390,13 +433,16 @@ type Hello {
|
||||
world(argOne: Boolean, argTwo: Int): String
|
||||
}';
|
||||
$doc = Parser::parse($body);
|
||||
$loc = $this->createLocFn($body);
|
||||
$loc = function($start, $end) {return TestUtils::locArray($start, $end);};
|
||||
|
||||
$expected = new Document([
|
||||
$expected = [
|
||||
'kind' => Node::DOCUMENT,
|
||||
'definitions' => [
|
||||
new ObjectTypeDefinition([
|
||||
[
|
||||
'kind' => Node::OBJECT_TYPE_DEFINITION,
|
||||
'name' => $this->nameNode('Hello', $loc(6, 11)),
|
||||
'interfaces' => [],
|
||||
'directives' => [],
|
||||
'fields' => [
|
||||
$this->fieldNodeWithArgs(
|
||||
$this->nameNode('world', $loc(16, 21)),
|
||||
@ -419,12 +465,12 @@ type Hello {
|
||||
)
|
||||
],
|
||||
'loc' => $loc(1, 61)
|
||||
])
|
||||
]
|
||||
],
|
||||
'loc' => $loc(1, 61)
|
||||
]);
|
||||
'loc' => $loc(0, 61)
|
||||
];
|
||||
|
||||
$this->assertEquals($expected, $doc);
|
||||
$this->assertEquals($expected, TestUtils::nodeToArray($doc));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -434,19 +480,22 @@ type Hello {
|
||||
{
|
||||
$body = 'union Hello = World';
|
||||
$doc = Parser::parse($body);
|
||||
$loc = $this->createLocFn($body);
|
||||
$expected = new Document([
|
||||
$loc = function($start, $end) {return TestUtils::locArray($start, $end);};
|
||||
$expected = [
|
||||
'kind' => Node::DOCUMENT,
|
||||
'definitions' => [
|
||||
new UnionTypeDefinition([
|
||||
[
|
||||
'kind' => Node::UNION_TYPE_DEFINITION,
|
||||
'name' => $this->nameNode('Hello', $loc(6, 11)),
|
||||
'directives' => [],
|
||||
'types' => [$this->typeNode('World', $loc(14, 19))],
|
||||
'loc' => $loc(0, 19)
|
||||
])
|
||||
]
|
||||
],
|
||||
'loc' => $loc(0, 19)
|
||||
]);
|
||||
];
|
||||
|
||||
$this->assertEquals($expected, $doc);
|
||||
$this->assertEquals($expected, TestUtils::nodeToArray($doc));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -456,22 +505,25 @@ type Hello {
|
||||
{
|
||||
$body = 'union Hello = Wo | Rld';
|
||||
$doc = Parser::parse($body);
|
||||
$loc = $this->createLocFn($body);
|
||||
$loc = function($start, $end) {return TestUtils::locArray($start, $end);};
|
||||
|
||||
$expected = new Document([
|
||||
$expected = [
|
||||
'kind' => Node::DOCUMENT,
|
||||
'definitions' => [
|
||||
new UnionTypeDefinition([
|
||||
[
|
||||
'kind' => Node::UNION_TYPE_DEFINITION,
|
||||
'name' => $this->nameNode('Hello', $loc(6, 11)),
|
||||
'directives' => [],
|
||||
'types' => [
|
||||
$this->typeNode('Wo', $loc(14, 16)),
|
||||
$this->typeNode('Rld', $loc(19, 22))
|
||||
],
|
||||
'loc' => $loc(0, 22)
|
||||
])
|
||||
]
|
||||
],
|
||||
'loc' => $loc(0, 22)
|
||||
]);
|
||||
$this->assertEquals($expected, $doc);
|
||||
];
|
||||
$this->assertEquals($expected, TestUtils::nodeToArray($doc));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -481,17 +533,20 @@ type Hello {
|
||||
{
|
||||
$body = 'scalar Hello';
|
||||
$doc = Parser::parse($body);
|
||||
$loc = $this->createLocFn($body);
|
||||
$expected = new Document([
|
||||
$loc = function($start, $end) {return TestUtils::locArray($start, $end);};
|
||||
$expected = [
|
||||
'kind' => Node::DOCUMENT,
|
||||
'definitions' => [
|
||||
new ScalarTypeDefinition([
|
||||
[
|
||||
'kind' => Node::SCALAR_TYPE_DEFINITION,
|
||||
'name' => $this->nameNode('Hello', $loc(7, 12)),
|
||||
'directives' => [],
|
||||
'loc' => $loc(0, 12)
|
||||
])
|
||||
]
|
||||
],
|
||||
'loc' => $loc(0, 12)
|
||||
]);
|
||||
$this->assertEquals($expected, $doc);
|
||||
];
|
||||
$this->assertEquals($expected, TestUtils::nodeToArray($doc));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -504,12 +559,15 @@ input Hello {
|
||||
world: String
|
||||
}';
|
||||
$doc = Parser::parse($body);
|
||||
$loc = $this->createLocFn($body);
|
||||
$loc = function($start, $end) {return TestUtils::locArray($start, $end);};
|
||||
|
||||
$expected = new Document([
|
||||
$expected = [
|
||||
'kind' => Node::DOCUMENT,
|
||||
'definitions' => [
|
||||
new InputObjectTypeDefinition([
|
||||
[
|
||||
'kind' => Node::INPUT_OBJECT_TYPE_DEFINITION,
|
||||
'name' => $this->nameNode('Hello', $loc(7, 12)),
|
||||
'directives' => [],
|
||||
'fields' => [
|
||||
$this->inputValueNode(
|
||||
$this->nameNode('world', $loc(17, 22)),
|
||||
@ -519,11 +577,11 @@ input Hello {
|
||||
)
|
||||
],
|
||||
'loc' => $loc(1, 32)
|
||||
])
|
||||
]
|
||||
],
|
||||
'loc' => $loc(1, 32)
|
||||
]);
|
||||
$this->assertEquals($expected, $doc);
|
||||
'loc' => $loc(0, 32)
|
||||
];
|
||||
$this->assertEquals($expected, TestUtils::nodeToArray($doc));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -539,28 +597,22 @@ input Hello {
|
||||
Parser::parse($body);
|
||||
}
|
||||
|
||||
|
||||
private function createLocFn($body)
|
||||
{
|
||||
return function($start, $end) use ($body) {
|
||||
return new Location($start, $end, new Source($body));
|
||||
};
|
||||
}
|
||||
|
||||
private function typeNode($name, $loc)
|
||||
{
|
||||
return new NamedType([
|
||||
'name' => new Name(['value' => $name, 'loc' => $loc]),
|
||||
return [
|
||||
'kind' => Node::NAMED_TYPE,
|
||||
'name' => ['kind' => Node::NAME, 'value' => $name, 'loc' => $loc],
|
||||
'loc' => $loc
|
||||
]);
|
||||
];
|
||||
}
|
||||
|
||||
private function nameNode($name, $loc)
|
||||
{
|
||||
return new Name([
|
||||
return [
|
||||
'kind' => Node::NAME,
|
||||
'value' => $name,
|
||||
'loc' => $loc
|
||||
]);
|
||||
];
|
||||
}
|
||||
|
||||
private function fieldNode($name, $type, $loc)
|
||||
@ -570,29 +622,35 @@ input Hello {
|
||||
|
||||
private function fieldNodeWithArgs($name, $type, $args, $loc)
|
||||
{
|
||||
return new FieldDefinition([
|
||||
return [
|
||||
'kind' => Node::FIELD_DEFINITION,
|
||||
'name' => $name,
|
||||
'arguments' => $args,
|
||||
'type' => $type,
|
||||
'directives' => [],
|
||||
'loc' => $loc
|
||||
]);
|
||||
];
|
||||
}
|
||||
|
||||
private function enumValueNode($name, $loc)
|
||||
{
|
||||
return new EnumValueDefinition([
|
||||
return [
|
||||
'kind' => Node::ENUM_VALUE_DEFINITION,
|
||||
'name' => $this->nameNode($name, $loc),
|
||||
'directives' => [],
|
||||
'loc' => $loc
|
||||
]);
|
||||
];
|
||||
}
|
||||
|
||||
private function inputValueNode($name, $type, $defaultValue, $loc)
|
||||
{
|
||||
return new InputValueDefinition([
|
||||
return [
|
||||
'kind' => Node::INPUT_VALUE_DEFINITION,
|
||||
'name' => $name,
|
||||
'type' => $type,
|
||||
'defaultValue' => $defaultValue,
|
||||
'directives' => [],
|
||||
'loc' => $loc
|
||||
]);
|
||||
];
|
||||
}
|
||||
}
|
||||
|
@ -65,29 +65,54 @@ type Foo implements Bar {
|
||||
six(argument: InputType = {key: "value"}): Type
|
||||
}
|
||||
|
||||
type AnnotatedObject @onObject(arg: "value") {
|
||||
annotatedField(arg: Type = "default" @onArg): Type @onField
|
||||
}
|
||||
|
||||
interface Bar {
|
||||
one: Type
|
||||
four(argument: String = "string"): String
|
||||
}
|
||||
|
||||
interface AnnotatedInterface @onInterface {
|
||||
annotatedField(arg: Type @onArg): Type @onField
|
||||
}
|
||||
|
||||
union Feed = Story | Article | Advert
|
||||
|
||||
union AnnotatedUnion @onUnion = A | B
|
||||
|
||||
scalar CustomScalar
|
||||
|
||||
scalar AnnotatedScalar @onScalar
|
||||
|
||||
enum Site {
|
||||
DESKTOP
|
||||
MOBILE
|
||||
}
|
||||
|
||||
enum AnnotatedEnum @onEnum {
|
||||
ANNOTATED_VALUE @onEnumValue
|
||||
OTHER_VALUE
|
||||
}
|
||||
|
||||
input InputType {
|
||||
key: String!
|
||||
answer: Int = 42
|
||||
}
|
||||
|
||||
input AnnotatedInput @onInputObjectType {
|
||||
annotatedField: Type @onField
|
||||
}
|
||||
|
||||
extend type Foo {
|
||||
seven(argument: [String]): Type
|
||||
}
|
||||
|
||||
extend type Foo @onType {}
|
||||
|
||||
type NoFields {}
|
||||
|
||||
directive @skip(if: Boolean!) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT
|
||||
|
||||
directive @include(if: Boolean!) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT
|
||||
|
64
tests/Language/TestUtils.php
Normal file
64
tests/Language/TestUtils.php
Normal file
@ -0,0 +1,64 @@
|
||||
<?php
|
||||
namespace GraphQL\Tests\Language;
|
||||
|
||||
|
||||
use GraphQL\Language\AST\Location;
|
||||
use GraphQL\Language\AST\Node;
|
||||
|
||||
class TestUtils
|
||||
{
|
||||
/**
|
||||
* @param Node $node
|
||||
* @return array
|
||||
*/
|
||||
public static function nodeToArray(Node $node)
|
||||
{
|
||||
$result = [
|
||||
'kind' => $node->kind,
|
||||
'loc' => self::locationToArray($node->loc)
|
||||
];
|
||||
|
||||
foreach (get_object_vars($node) as $prop => $propValue) {
|
||||
if (isset($result[$prop]))
|
||||
continue;
|
||||
|
||||
if (is_array($propValue)) {
|
||||
$tmp = [];
|
||||
foreach ($propValue as $tmp1) {
|
||||
$tmp[] = $tmp1 instanceof Node ? self::nodeToArray($tmp1) : (array) $tmp1;
|
||||
}
|
||||
} else if ($propValue instanceof Node) {
|
||||
$tmp = self::nodeToArray($propValue);
|
||||
} else if (is_scalar($propValue) || null === $propValue) {
|
||||
$tmp = $propValue;
|
||||
} else {
|
||||
$tmp = null;
|
||||
}
|
||||
|
||||
$result[$prop] = $tmp;
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Location $loc
|
||||
* @return array
|
||||
*/
|
||||
public static function locationToArray(Location $loc)
|
||||
{
|
||||
return [
|
||||
'start' => $loc->start,
|
||||
'end' => $loc->end
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $start
|
||||
* @param $end
|
||||
* @return array
|
||||
*/
|
||||
public static function locArray($start, $end)
|
||||
{
|
||||
return ['start' => $start, 'end' => $end];
|
||||
}
|
||||
}
|
@ -19,29 +19,54 @@ type Foo implements Bar {
|
||||
six(argument: InputType = {key: "value"}): Type
|
||||
}
|
||||
|
||||
type AnnotatedObject @onObject(arg: "value") {
|
||||
annotatedField(arg: Type = "default" @onArg): Type @onField
|
||||
}
|
||||
|
||||
interface Bar {
|
||||
one: Type
|
||||
four(argument: String = "string"): String
|
||||
}
|
||||
|
||||
interface AnnotatedInterface @onInterface {
|
||||
annotatedField(arg: Type @onArg): Type @onField
|
||||
}
|
||||
|
||||
union Feed = Story | Article | Advert
|
||||
|
||||
union AnnotatedUnion @onUnion = A | B
|
||||
|
||||
scalar CustomScalar
|
||||
|
||||
scalar AnnotatedScalar @onScalar
|
||||
|
||||
enum Site {
|
||||
DESKTOP
|
||||
MOBILE
|
||||
}
|
||||
|
||||
enum AnnotatedEnum @onEnum {
|
||||
ANNOTATED_VALUE @onEnumValue
|
||||
OTHER_VALUE
|
||||
}
|
||||
|
||||
input InputType {
|
||||
key: String!
|
||||
answer: Int = 42
|
||||
}
|
||||
|
||||
input AnnotatedInput @onInputObjectType {
|
||||
annotatedField: Type @onField
|
||||
}
|
||||
|
||||
extend type Foo {
|
||||
seven(argument: [String]): Type
|
||||
}
|
||||
|
||||
extend type Foo @onType {}
|
||||
|
||||
type NoFields {}
|
||||
|
||||
directive @skip(if: Boolean!) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT
|
||||
|
||||
directive @include(if: Boolean!)
|
||||
|
Loading…
Reference in New Issue
Block a user