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:
vladar 2016-10-17 02:57:24 +07:00
parent 3eeb4d450b
commit cd14146032
19 changed files with 876 additions and 380 deletions

View File

@ -13,6 +13,11 @@ class EnumTypeDefinition extends Node implements TypeDefinition
*/ */
public $name; public $name;
/**
* @var Directive[]
*/
public $directives;
/** /**
* @var EnumValueDefinition[] * @var EnumValueDefinition[]
*/ */

View File

@ -12,4 +12,9 @@ class EnumValueDefinition extends Node
* @var Name * @var Name
*/ */
public $name; public $name;
/**
* @var Directive[]
*/
public $directives;
} }

View File

@ -22,4 +22,9 @@ class FieldDefinition extends Node
* @var Type * @var Type
*/ */
public $type; public $type;
/**
* @var Directive[]
*/
public $directives;
} }

View File

@ -13,6 +13,11 @@ class InputObjectTypeDefinition extends Node implements TypeDefinition
*/ */
public $name; public $name;
/**
* @var Directive[]
*/
public $directives;
/** /**
* @var InputValueDefinition[] * @var InputValueDefinition[]
*/ */

View File

@ -22,4 +22,9 @@ class InputValueDefinition extends Node
* @var Value * @var Value
*/ */
public $defaultValue; public $defaultValue;
/**
* @var Directive[]
*/
public $directives;
} }

View File

@ -13,6 +13,11 @@ class InterfaceTypeDefinition extends Node implements TypeDefinition
*/ */
public $name; public $name;
/**
* @var Directive[]
*/
public $directives;
/** /**
* @var FieldDefinition[] * @var FieldDefinition[]
*/ */

View File

@ -3,6 +3,10 @@ namespace GraphQL\Language\AST;
use GraphQL\Utils; use GraphQL\Utils;
/**
* Class Node
* @package GraphQL\Language\AST
*/
abstract class Node abstract class Node
{ {
// constants from language/kinds.js: // constants from language/kinds.js:

View File

@ -18,6 +18,11 @@ class ObjectTypeDefinition extends Node implements TypeDefinition
*/ */
public $interfaces = []; public $interfaces = [];
/**
* @var Directive[]
*/
public $directives;
/** /**
* @var FieldDefinition[] * @var FieldDefinition[]
*/ */

View File

@ -4,10 +4,18 @@ namespace GraphQL\Language\AST;
class ScalarTypeDefinition extends Node implements TypeDefinition class ScalarTypeDefinition extends Node implements TypeDefinition
{ {
/**
* @var string
*/
public $kind = Node::SCALAR_TYPE_DEFINITION; public $kind = Node::SCALAR_TYPE_DEFINITION;
/** /**
* @var Name * @var Name
*/ */
public $name; public $name;
/**
* @var Directive[]
*/
public $directives;
} }

View File

@ -8,6 +8,11 @@ class SchemaDefinition extends Node implements TypeSystemDefinition
*/ */
public $kind = Node::SCHEMA_DEFINITION; public $kind = Node::SCHEMA_DEFINITION;
/**
* @var Directive[]
*/
public $directives;
/** /**
* @var OperationTypeDefinition[] * @var OperationTypeDefinition[]
*/ */

View File

@ -13,6 +13,11 @@ class UnionTypeDefinition extends Node implements TypeDefinition
*/ */
public $name; public $name;
/**
* @var Directive[]
*/
public $directives;
/** /**
* @var NamedType[] * @var NamedType[]
*/ */

View File

@ -53,11 +53,6 @@ class Parser
* in the source that they correspond to. This configuration flag * in the source that they correspond to. This configuration flag
* disables that behavior for performance or testing.) * 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 Source|string $source
* @param array $options * @param array $options
* @return Document * @return Document
@ -69,67 +64,78 @@ class Parser
return $parser->parseDocument(); 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 * @var Lexer
*/ */
private $lexer; private $lexer;
/**
* @var Token
*/
private $token;
function __construct(Source $source, array $options = []) function __construct(Source $source, array $options = [])
{ {
$this->lexer = new Lexer($source); $this->lexer = new Lexer($source, $options);
$this->source = $source;
$this->options = $options;
$this->prevEnd = 0;
$this->token = $this->lexer->nextToken();
} }
/** /**
* Returns a location object, used to identify the place in * Returns a location object, used to identify the place in
* the source that created a given parsed object. * the source that created a given parsed object.
* *
* @param int $start * @param Token $startToken
* @return Location|null * @return Location|null
*/ */
function loc($start) function loc(Token $startToken)
{ {
if (!empty($this->options['noLocation'])) { if (empty($this->lexer->options['noLocation'])) {
return new Location($startToken, $this->lexer->lastToken, $this->lexer->source);
}
return null; return null;
} }
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);
}
/** /**
* Determines if the next token is of a given kind * Determines if the next token is of a given kind
@ -139,7 +145,7 @@ class Parser
*/ */
function peek($kind) function peek($kind)
{ {
return $this->token->kind === $kind; return $this->lexer->token->kind === $kind;
} }
/** /**
@ -151,10 +157,10 @@ class Parser
*/ */
function skip($kind) function skip($kind)
{ {
$match = $this->token->kind === $kind; $match = $this->lexer->token->kind === $kind;
if ($match) { if ($match) {
$this->advance(); $this->lexer->advance();
} }
return $match; return $match;
} }
@ -168,17 +174,17 @@ class Parser
*/ */
function expect($kind) function expect($kind)
{ {
$token = $this->token; $token = $this->lexer->token;
if ($token->kind === $kind) { if ($token->kind === $kind) {
$this->advance(); $this->lexer->advance();
return $token; return $token;
} }
throw new SyntaxError( throw new SyntaxError(
$this->source, $this->lexer->source,
$token->start, $token->start,
"Expected " . Token::getKindDescription($kind) . ", found " . $token->getDescription() "Expected $kind, found " . $token->getDescription()
); );
} }
@ -193,14 +199,14 @@ class Parser
*/ */
function expectKeyword($value) function expectKeyword($value)
{ {
$token = $this->token; $token = $this->lexer->token;
if ($token->kind === Token::NAME && $token->value === $value) { if ($token->kind === Token::NAME && $token->value === $value) {
$this->advance(); $this->lexer->advance();
return $token; return $token;
} }
throw new SyntaxError( throw new SyntaxError(
$this->source, $this->lexer->source,
$token->start, $token->start,
'Expected "' . $value . '", found ' . $token->getDescription() 'Expected "' . $value . '", found ' . $token->getDescription()
); );
@ -212,8 +218,8 @@ class Parser
*/ */
function unexpected(Token $atToken = null) function unexpected(Token $atToken = null)
{ {
$token = $atToken ?: $this->token; $token = $atToken ?: $this->lexer->token;
return new SyntaxError($this->source, $token->start, "Unexpected " . $token->getDescription()); return new SyntaxError($this->lexer->source, $token->start, "Unexpected " . $token->getDescription());
} }
/** /**
@ -274,22 +280,10 @@ class Parser
return new Name([ return new Name([
'value' => $token->value, '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. * Implements the parsing rules in the Document section.
* *
@ -298,9 +292,10 @@ class Parser
*/ */
function parseDocument() function parseDocument()
{ {
$start = $this->token->start; $start = $this->lexer->token;
$definitions = []; $this->expect(Token::SOF);
$definitions = [];
do { do {
$definitions[] = $this->parseDefinition(); $definitions[] = $this->parseDefinition();
} while (!$this->skip(Token::EOF)); } while (!$this->skip(Token::EOF));
@ -322,10 +317,9 @@ class Parser
} }
if ($this->peek(Token::NAME)) { if ($this->peek(Token::NAME)) {
switch ($this->token->value) { switch ($this->lexer->token->value) {
case 'query': case 'query':
case 'mutation': case 'mutation':
// Note: subscription is an experimental non-spec addition.
case 'subscription': case 'subscription':
return $this->parseOperationDefinition(); return $this->parseOperationDefinition();
@ -357,7 +351,7 @@ class Parser
*/ */
function parseOperationDefinition() function parseOperationDefinition()
{ {
$start = $this->token->start; $start = $this->lexer->token;
if ($this->peek(Token::BRACE_L)) { if ($this->peek(Token::BRACE_L)) {
return new OperationDefinition([ return new OperationDefinition([
'operation' => 'query', 'operation' => 'query',
@ -423,11 +417,11 @@ class Parser
*/ */
function parseVariableDefinition() function parseVariableDefinition()
{ {
$start = $this->token->start; $start = $this->lexer->token;
$var = $this->parseVariable(); $var = $this->parseVariable();
$this->expect(Token::COLON); $this->expect(Token::COLON);
$type = $this->parseType(); $type = $this->parseTypeReference();
return new VariableDefinition([ return new VariableDefinition([
'variable' => $var, 'variable' => $var,
@ -444,7 +438,7 @@ class Parser
*/ */
function parseVariable() function parseVariable()
{ {
$start = $this->token->start; $start = $this->lexer->token;
$this->expect(Token::DOLLAR); $this->expect(Token::DOLLAR);
return new Variable([ return new Variable([
@ -458,7 +452,7 @@ class Parser
*/ */
function parseSelectionSet() function parseSelectionSet()
{ {
$start = $this->token->start; $start = $this->lexer->token;
return new SelectionSet([ return new SelectionSet([
'selections' => $this->many(Token::BRACE_L, [$this, 'parseSelection'], Token::BRACE_R), 'selections' => $this->many(Token::BRACE_L, [$this, 'parseSelection'], Token::BRACE_R),
'loc' => $this->loc($start) 'loc' => $this->loc($start)
@ -466,6 +460,11 @@ class Parser
} }
/** /**
* Selection :
* - Field
* - FragmentSpread
* - InlineFragment
*
* @return mixed * @return mixed
*/ */
function parseSelection() function parseSelection()
@ -480,7 +479,7 @@ class Parser
*/ */
function parseField() function parseField()
{ {
$start = $this->token->start; $start = $this->lexer->token;
$nameOrAlias = $this->parseName(); $nameOrAlias = $this->parseName();
if ($this->skip(Token::COLON)) { if ($this->skip(Token::COLON)) {
@ -517,7 +516,7 @@ class Parser
*/ */
function parseArgument() function parseArgument()
{ {
$start = $this->token->start; $start = $this->lexer->token;
$name = $this->parseName(); $name = $this->parseName();
$this->expect(Token::COLON); $this->expect(Token::COLON);
@ -538,10 +537,10 @@ class Parser
*/ */
function parseFragment() function parseFragment()
{ {
$start = $this->token->start; $start = $this->lexer->token;
$this->expect(Token::SPREAD); $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([ return new FragmentSpread([
'name' => $this->parseFragmentName(), 'name' => $this->parseFragmentName(),
'directives' => $this->parseDirectives(), 'directives' => $this->parseDirectives(),
@ -550,8 +549,8 @@ class Parser
} }
$typeCondition = null; $typeCondition = null;
if ($this->token->value === 'on') { if ($this->lexer->token->value === 'on') {
$this->advance(); $this->lexer->advance();
$typeCondition = $this->parseNamedType(); $typeCondition = $this->parseNamedType();
} }
@ -569,7 +568,7 @@ class Parser
*/ */
function parseFragmentDefinition() function parseFragmentDefinition()
{ {
$start = $this->token->start; $start = $this->lexer->token;
$this->expectKeyword('fragment'); $this->expectKeyword('fragment');
$name = $this->parseFragmentName(); $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 * @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 * @param $isConst
* @return BooleanValue|EnumValue|FloatValue|IntValue|StringValue|Variable * @return BooleanValue|EnumValue|FloatValue|IntValue|StringValue|Variable|ListValue|ObjectValue
* @throws SyntaxError * @throws SyntaxError
*/ */
function parseValueLiteral($isConst) function parseValueLiteral($isConst)
{ {
$token = $this->token; $token = $this->lexer->token;
switch ($token->kind) { switch ($token->kind) {
case Token::BRACKET_L: case Token::BRACKET_L:
return $this->parseArray($isConst); return $this->parseArray($isConst);
case Token::BRACE_L: case Token::BRACE_L:
return $this->parseObject($isConst); return $this->parseObject($isConst);
case Token::INT: case Token::INT:
$this->advance(); $this->lexer->advance();
return new IntValue([ return new IntValue([
'value' => $token->value, 'value' => $token->value,
'loc' => $this->loc($token->start) 'loc' => $this->loc($token)
]); ]);
case Token::FLOAT: case Token::FLOAT:
$this->advance(); $this->lexer->advance();
return new FloatValue([ return new FloatValue([
'value' => $token->value, 'value' => $token->value,
'loc' => $this->loc($token->start) 'loc' => $this->loc($token)
]); ]);
case Token::STRING: case Token::STRING:
$this->advance(); $this->lexer->advance();
return new StringValue([ return new StringValue([
'value' => $token->value, 'value' => $token->value,
'loc' => $this->loc($token->start) 'loc' => $this->loc($token)
]); ]);
case Token::NAME: case Token::NAME:
if ($token->value === 'true' || $token->value === 'false') { if ($token->value === 'true' || $token->value === 'false') {
$this->advance(); $this->lexer->advance();
return new BooleanValue([ return new BooleanValue([
'value' => $token->value === 'true', 'value' => $token->value === 'true',
'loc' => $this->loc($token->start) 'loc' => $this->loc($token)
]); ]);
} else if ($token->value !== 'null') { } else if ($token->value !== 'null') {
$this->advance(); $this->lexer->advance();
return new EnumValue([ return new EnumValue([
'value' => $token->value, 'value' => $token->value,
'loc' => $this->loc($token->start) 'loc' => $this->loc($token)
]); ]);
} }
break; break;
@ -656,13 +668,30 @@ class Parser
throw $this->unexpected(); 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 * @param bool $isConst
* @return ListValue * @return ListValue
*/ */
function parseArray($isConst) function parseArray($isConst)
{ {
$start = $this->token->start; $start = $this->lexer->token;
$item = $isConst ? 'parseConstValue' : 'parseVariableValue'; $item = $isConst ? 'parseConstValue' : 'parseVariableValue';
return new ListValue([ return new ListValue([
'values' => $this->any(Token::BRACKET_L, [$this, $item], Token::BRACKET_R), 'values' => $this->any(Token::BRACKET_L, [$this, $item], Token::BRACKET_R),
@ -670,9 +699,13 @@ class Parser
]); ]);
} }
/**
* @param $isConst
* @return ObjectValue
*/
function parseObject($isConst) function parseObject($isConst)
{ {
$start = $this->token->start; $start = $this->lexer->token;
$this->expect(Token::BRACE_L); $this->expect(Token::BRACE_L);
$fields = []; $fields = [];
while (!$this->skip(Token::BRACE_R)) { while (!$this->skip(Token::BRACE_R)) {
@ -684,9 +717,13 @@ class Parser
]); ]);
} }
/**
* @param $isConst
* @return ObjectField
*/
function parseObjectField($isConst) function parseObjectField($isConst)
{ {
$start = $this->token->start; $start = $this->lexer->token;
$name = $this->parseName(); $name = $this->parseName();
$this->expect(Token::COLON); $this->expect(Token::COLON);
@ -718,7 +755,7 @@ class Parser
*/ */
function parseDirective() function parseDirective()
{ {
$start = $this->token->start; $start = $this->lexer->token;
$this->expect(Token::AT); $this->expect(Token::AT);
return new Directive([ return new Directive([
'name' => $this->parseName(), 'name' => $this->parseName(),
@ -735,12 +772,12 @@ class Parser
* @return ListType|Name|NonNullType * @return ListType|Name|NonNullType
* @throws SyntaxError * @throws SyntaxError
*/ */
function parseType() function parseTypeReference()
{ {
$start = $this->token->start; $start = $this->lexer->token;
if ($this->skip(Token::BRACKET_L)) { if ($this->skip(Token::BRACKET_L)) {
$type = $this->parseType(); $type = $this->parseTypeReference();
$this->expect(Token::BRACKET_R); $this->expect(Token::BRACKET_R);
$type = new ListType([ $type = new ListType([
'type' => $type, 'type' => $type,
@ -761,7 +798,7 @@ class Parser
function parseNamedType() function parseNamedType()
{ {
$start = $this->token->start; $start = $this->lexer->token;
return new NamedType([ return new NamedType([
'name' => $this->parseName(), 'name' => $this->parseName(),
@ -773,6 +810,7 @@ class Parser
/** /**
* TypeSystemDefinition : * TypeSystemDefinition :
* - SchemaDefinition
* - TypeDefinition * - TypeDefinition
* - TypeExtensionDefinition * - TypeExtensionDefinition
* - DirectiveDefinition * - DirectiveDefinition
@ -791,7 +829,7 @@ class Parser
function parseTypeSystemDefinition() function parseTypeSystemDefinition()
{ {
if ($this->peek(Token::NAME)) { if ($this->peek(Token::NAME)) {
switch ($this->token->value) { switch ($this->lexer->token->value) {
case 'schema': return $this->parseSchemaDefinition(); case 'schema': return $this->parseSchemaDefinition();
case 'scalar': return $this->parseScalarTypeDefinition(); case 'scalar': return $this->parseScalarTypeDefinition();
case 'type': return $this->parseObjectTypeDefinition(); case 'type': return $this->parseObjectTypeDefinition();
@ -813,8 +851,9 @@ class Parser
*/ */
function parseSchemaDefinition() function parseSchemaDefinition()
{ {
$start = $this->token->start; $start = $this->lexer->token;
$this->expectKeyword('schema'); $this->expectKeyword('schema');
$directives = $this->parseDirectives();
$operationTypes = $this->many( $operationTypes = $this->many(
Token::BRACE_L, Token::BRACE_L,
@ -823,14 +862,18 @@ class Parser
); );
return new SchemaDefinition([ return new SchemaDefinition([
'directives' => $directives,
'operationTypes' => $operationTypes, 'operationTypes' => $operationTypes,
'loc' => $this->loc($start) 'loc' => $this->loc($start)
]); ]);
} }
/**
* @return OperationTypeDefinition
*/
function parseOperationTypeDefinition() function parseOperationTypeDefinition()
{ {
$start = $this->token->start; $start = $this->lexer->token;
$operation = $this->parseOperationType(); $operation = $this->parseOperationType();
$this->expect(Token::COLON); $this->expect(Token::COLON);
$type = $this->parseNamedType(); $type = $this->parseNamedType();
@ -848,12 +891,14 @@ class Parser
*/ */
function parseScalarTypeDefinition() function parseScalarTypeDefinition()
{ {
$start = $this->token->start; $start = $this->lexer->token;
$this->expectKeyword('scalar'); $this->expectKeyword('scalar');
$name = $this->parseName(); $name = $this->parseName();
$directives = $this->parseDirectives();
return new ScalarTypeDefinition([ return new ScalarTypeDefinition([
'name' => $name, 'name' => $name,
'directives' => $directives,
'loc' => $this->loc($start) 'loc' => $this->loc($start)
]); ]);
} }
@ -864,10 +909,12 @@ class Parser
*/ */
function parseObjectTypeDefinition() function parseObjectTypeDefinition()
{ {
$start = $this->token->start; $start = $this->lexer->token;
$this->expectKeyword('type'); $this->expectKeyword('type');
$name = $this->parseName(); $name = $this->parseName();
$interfaces = $this->parseImplementsInterfaces(); $interfaces = $this->parseImplementsInterfaces();
$directives = $this->parseDirectives();
$fields = $this->any( $fields = $this->any(
Token::BRACE_L, Token::BRACE_L,
[$this, 'parseFieldDefinition'], [$this, 'parseFieldDefinition'],
@ -877,6 +924,7 @@ class Parser
return new ObjectTypeDefinition([ return new ObjectTypeDefinition([
'name' => $name, 'name' => $name,
'interfaces' => $interfaces, 'interfaces' => $interfaces,
'directives' => $directives,
'fields' => $fields, 'fields' => $fields,
'loc' => $this->loc($start) 'loc' => $this->loc($start)
]); ]);
@ -888,11 +936,11 @@ class Parser
function parseImplementsInterfaces() function parseImplementsInterfaces()
{ {
$types = []; $types = [];
if ($this->token->value === 'implements') { if ($this->lexer->token->value === 'implements') {
$this->advance(); $this->lexer->advance();
do { do {
$types[] = $this->parseNamedType(); $types[] = $this->parseNamedType();
} while (!$this->peek(Token::BRACE_L)); } while ($this->peek(Token::NAME));
} }
return $types; return $types;
} }
@ -903,16 +951,18 @@ class Parser
*/ */
function parseFieldDefinition() function parseFieldDefinition()
{ {
$start = $this->token->start; $start = $this->lexer->token;
$name = $this->parseName(); $name = $this->parseName();
$args = $this->parseArgumentDefs(); $args = $this->parseArgumentDefs();
$this->expect(Token::COLON); $this->expect(Token::COLON);
$type = $this->parseType(); $type = $this->parseTypeReference();
$directives = $this->parseDirectives();
return new FieldDefinition([ return new FieldDefinition([
'name' => $name, 'name' => $name,
'arguments' => $args, 'arguments' => $args,
'type' => $type, 'type' => $type,
'directives' => $directives,
'loc' => $this->loc($start) 'loc' => $this->loc($start)
]); ]);
} }
@ -934,18 +984,20 @@ class Parser
*/ */
function parseInputValueDef() function parseInputValueDef()
{ {
$start = $this->token->start; $start = $this->lexer->token;
$name = $this->parseName(); $name = $this->parseName();
$this->expect(Token::COLON); $this->expect(Token::COLON);
$type = $this->parseType(); $type = $this->parseTypeReference();
$defaultValue = null; $defaultValue = null;
if ($this->skip(Token::EQUALS)) { if ($this->skip(Token::EQUALS)) {
$defaultValue = $this->parseConstValue(); $defaultValue = $this->parseConstValue();
} }
$directives = $this->parseDirectives();
return new InputValueDefinition([ return new InputValueDefinition([
'name' => $name, 'name' => $name,
'type' => $type, 'type' => $type,
'defaultValue' => $defaultValue, 'defaultValue' => $defaultValue,
'directives' => $directives,
'loc' => $this->loc($start) 'loc' => $this->loc($start)
]); ]);
} }
@ -956,9 +1008,10 @@ class Parser
*/ */
function parseInterfaceTypeDefinition() function parseInterfaceTypeDefinition()
{ {
$start = $this->token->start; $start = $this->lexer->token;
$this->expectKeyword('interface'); $this->expectKeyword('interface');
$name = $this->parseName(); $name = $this->parseName();
$directives = $this->parseDirectives();
$fields = $this->any( $fields = $this->any(
Token::BRACE_L, Token::BRACE_L,
[$this, 'parseFieldDefinition'], [$this, 'parseFieldDefinition'],
@ -967,6 +1020,7 @@ class Parser
return new InterfaceTypeDefinition([ return new InterfaceTypeDefinition([
'name' => $name, 'name' => $name,
'directives' => $directives,
'fields' => $fields, 'fields' => $fields,
'loc' => $this->loc($start) 'loc' => $this->loc($start)
]); ]);
@ -978,20 +1032,26 @@ class Parser
*/ */
function parseUnionTypeDefinition() function parseUnionTypeDefinition()
{ {
$start = $this->token->start; $start = $this->lexer->token;
$this->expectKeyword('union'); $this->expectKeyword('union');
$name = $this->parseName(); $name = $this->parseName();
$directives = $this->parseDirectives();
$this->expect(Token::EQUALS); $this->expect(Token::EQUALS);
$types = $this->parseUnionMembers(); $types = $this->parseUnionMembers();
return new UnionTypeDefinition([ return new UnionTypeDefinition([
'name' => $name, 'name' => $name,
'directives' => $directives,
'types' => $types, 'types' => $types,
'loc' => $this->loc($start) 'loc' => $this->loc($start)
]); ]);
} }
/** /**
* UnionMembers :
* - NamedType
* - UnionMembers | NamedType
*
* @return NamedType[] * @return NamedType[]
*/ */
function parseUnionMembers() function parseUnionMembers()
@ -1009,9 +1069,10 @@ class Parser
*/ */
function parseEnumTypeDefinition() function parseEnumTypeDefinition()
{ {
$start = $this->token->start; $start = $this->lexer->token;
$this->expectKeyword('enum'); $this->expectKeyword('enum');
$name = $this->parseName(); $name = $this->parseName();
$directives = $this->parseDirectives();
$values = $this->many( $values = $this->many(
Token::BRACE_L, Token::BRACE_L,
[$this, 'parseEnumValueDefinition'], [$this, 'parseEnumValueDefinition'],
@ -1020,6 +1081,7 @@ class Parser
return new EnumTypeDefinition([ return new EnumTypeDefinition([
'name' => $name, 'name' => $name,
'directives' => $directives,
'values' => $values, 'values' => $values,
'loc' => $this->loc($start) 'loc' => $this->loc($start)
]); ]);
@ -1030,11 +1092,13 @@ class Parser
*/ */
function parseEnumValueDefinition() function parseEnumValueDefinition()
{ {
$start = $this->token->start; $start = $this->lexer->token;
$name = $this->parseName(); $name = $this->parseName();
$directives = $this->parseDirectives();
return new EnumValueDefinition([ return new EnumValueDefinition([
'name' => $name, 'name' => $name,
'directives' => $directives,
'loc' => $this->loc($start) 'loc' => $this->loc($start)
]); ]);
} }
@ -1045,9 +1109,10 @@ class Parser
*/ */
function parseInputObjectTypeDefinition() function parseInputObjectTypeDefinition()
{ {
$start = $this->token->start; $start = $this->lexer->token;
$this->expectKeyword('input'); $this->expectKeyword('input');
$name = $this->parseName(); $name = $this->parseName();
$directives = $this->parseDirectives();
$fields = $this->any( $fields = $this->any(
Token::BRACE_L, Token::BRACE_L,
[$this, 'parseInputValueDef'], [$this, 'parseInputValueDef'],
@ -1056,6 +1121,7 @@ class Parser
return new InputObjectTypeDefinition([ return new InputObjectTypeDefinition([
'name' => $name, 'name' => $name,
'directives' => $directives,
'fields' => $fields, 'fields' => $fields,
'loc' => $this->loc($start) 'loc' => $this->loc($start)
]); ]);
@ -1067,7 +1133,7 @@ class Parser
*/ */
function parseTypeExtensionDefinition() function parseTypeExtensionDefinition()
{ {
$start = $this->token->start; $start = $this->lexer->token;
$this->expectKeyword('extend'); $this->expectKeyword('extend');
$definition = $this->parseObjectTypeDefinition(); $definition = $this->parseObjectTypeDefinition();
@ -1078,12 +1144,15 @@ class Parser
} }
/** /**
* DirectiveDefinition :
* - directive @ Name ArgumentsDefinition? on DirectiveLocations
*
* @return DirectiveDefinition * @return DirectiveDefinition
* @throws SyntaxError * @throws SyntaxError
*/ */
function parseDirectiveDefinition() function parseDirectiveDefinition()
{ {
$start = $this->token->start; $start = $this->lexer->token;
$this->expectKeyword('directive'); $this->expectKeyword('directive');
$this->expect(Token::AT); $this->expect(Token::AT);
$name = $this->parseName(); $name = $this->parseName();

View File

@ -115,33 +115,77 @@ class Printer
Node::NON_NULL_TYPE => function(NonNullType $node) {return $node->type . '!';}, Node::NON_NULL_TYPE => function(NonNullType $node) {return $node->type . '!';},
// Type System Definitions // 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::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) { Node::OBJECT_TYPE_DEFINITION => function(ObjectTypeDefinition $def) {
return 'type ' . $def->name . ' ' . return self::join([
self::wrap('implements ', self::join($def->interfaces, ', '), ' ') . 'type',
self::block($def->fields); $def->name,
self::wrap('implements ', self::join($def->interfaces, ', ')),
self::join($def->directives, ' '),
self::block($def->fields)
], ' ');
}, },
Node::FIELD_DEFINITION => function(FieldDefinition $def) { 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) { 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) { 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) { 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) { 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) { 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::TYPE_EXTENSION_DEFINITION => function(TypeExtensionDefinition $def) {return "extend {$def->definition}";},
Node::DIRECTIVE_DEFINITION => function(DirectiveDefinition $def) { 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 * Given array, print each item on its own line, wrapped in an
* print each item on it's own line, wrapped in an indented "{ }" block. * 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) public static function indent($maybeString)

View File

@ -64,19 +64,17 @@ class Visitor
Node::LIST_TYPE => ['type'], Node::LIST_TYPE => ['type'],
Node::NON_NULL_TYPE => ['type'], Node::NON_NULL_TYPE => ['type'],
Node::SCHEMA_DEFINITION => ['operationTypes'], Node::SCHEMA_DEFINITION => ['directives', 'operationTypes'],
Node::OPERATION_TYPE_DEFINITION => ['type'], Node::OPERATION_TYPE_DEFINITION => ['type'],
Node::SCALAR_TYPE_DEFINITION => ['name'], Node::SCALAR_TYPE_DEFINITION => ['name', 'directives'],
Node::OBJECT_TYPE_DEFINITION => ['name', 'interfaces', 'fields'], Node::OBJECT_TYPE_DEFINITION => ['name', 'interfaces', 'directives', 'fields'],
Node::FIELD_DEFINITION => ['name', 'arguments', 'type'], Node::FIELD_DEFINITION => ['name', 'arguments', 'type', 'directives'],
Node::INPUT_VALUE_DEFINITION => ['name', 'type', 'defaultValue'], Node::INPUT_VALUE_DEFINITION => ['name', 'type', 'defaultValue', 'directives'],
Node::INPUT_VALUE_DEFINITION => [ 'name', 'type', 'defaultValue' ], Node::INTERFACE_TYPE_DEFINITION => [ 'name', 'directives', 'fields' ],
Node::INTERFACE_TYPE_DEFINITION => [ 'name', 'fields' ], Node::UNION_TYPE_DEFINITION => [ 'name', 'directives', 'types' ],
Node::UNION_TYPE_DEFINITION => [ 'name', 'types' ], Node::ENUM_TYPE_DEFINITION => [ 'name', 'directives', 'values' ],
Node::ENUM_TYPE_DEFINITION => [ 'name', 'values' ], Node::ENUM_VALUE_DEFINITION => [ 'name', 'directives' ],
Node::INPUT_OBJECT_TYPE_DEFINITION => [ 'name', 'directives', 'fields' ],
Node::ENUM_VALUE_DEFINITION => [ 'name' ],
Node::INPUT_OBJECT_TYPE_DEFINITION => [ 'name', 'fields' ],
Node::TYPE_EXTENSION_DEFINITION => [ 'definition' ], Node::TYPE_EXTENSION_DEFINITION => [ 'definition' ],
Node::DIRECTIVE_DEFINITION => [ 'name', 'arguments', 'locations' ] Node::DIRECTIVE_DEFINITION => [ 'name', 'arguments', 'locations' ]
); );

View File

@ -7,6 +7,7 @@ use GraphQL\Language\AST\Field;
use GraphQL\Language\AST\IntValue; use GraphQL\Language\AST\IntValue;
use GraphQL\Language\AST\Location; use GraphQL\Language\AST\Location;
use GraphQL\Language\AST\Name; use GraphQL\Language\AST\Name;
use GraphQL\Language\AST\Node;
use GraphQL\Language\AST\OperationDefinition; use GraphQL\Language\AST\OperationDefinition;
use GraphQL\Language\AST\SelectionSet; use GraphQL\Language\AST\SelectionSet;
use GraphQL\Language\AST\StringValue; use GraphQL\Language\AST\StringValue;
@ -18,45 +19,6 @@ use GraphQL\Utils;
class ParserTest extends \PHPUnit_Framework_TestCase 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 * @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, $run(1,
'{ ...MissingOn } '{ ...MissingOn }
fragment MissingOn Type fragment MissingOn Type
@ -100,7 +62,7 @@ fragment MissingOn Type
Parser::parse(new Source('query', 'MyQuery.graphql')); Parser::parse(new Source('query', 'MyQuery.graphql'));
$this->fail('Expected exception not thrown'); $this->fail('Expected exception not thrown');
} catch (SyntaxError $e) { } 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() public function testParseCreatesAst()
{ {
@ -300,73 +262,267 @@ fragment $fragmentName on Type {
'); ');
$result = Parser::parse($source); $result = Parser::parse($source);
$expected = new Document(array( $loc = function($start, $end) use ($source) {
'loc' => new Location(0, 41, $source), return [
'definitions' => array( 'start' => $start,
new OperationDefinition(array( 'end' => $end
'loc' => new Location(0, 40, $source), ];
};
$expected = [
'kind' => Node::DOCUMENT,
'loc' => $loc(0, 41),
'definitions' => [
[
'kind' => Node::OPERATION_DEFINITION,
'loc' => $loc(0, 40),
'operation' => 'query', 'operation' => 'query',
'name' => null, 'name' => null,
'variableDefinitions' => null, 'variableDefinitions' => null,
'directives' => array(), 'directives' => [],
'selectionSet' => new SelectionSet(array( 'selectionSet' => [
'loc' => new Location(0, 40, $source), 'kind' => Node::SELECTION_SET,
'selections' => array( 'loc' => $loc(0, 40),
new Field(array( 'selections' => [
'loc' => new Location(4, 38, $source), [
'kind' => Node::FIELD,
'loc' => $loc(4, 38),
'alias' => null, 'alias' => null,
'name' => new Name(array( 'name' => [
'loc' => new Location(4, 8, $source), 'kind' => Node::NAME,
'loc' => $loc(4, 8),
'value' => 'node' 'value' => 'node'
)), ],
'arguments' => array( 'arguments' => [
new Argument(array( [
'name' => new Name(array( 'kind' => Node::ARGUMENT,
'loc' => new Location(9, 11, $source), 'name' => [
'kind' => Node::NAME,
'loc' => $loc(9, 11),
'value' => 'id' 'value' => 'id'
)), ],
'value' => new IntValue(array( 'value' => [
'loc' => new Location(13, 14, $source), 'kind' => Node::INT,
'loc' => $loc(13, 14),
'value' => '4' 'value' => '4'
)), ],
'loc' => new Location(9, 14, $source) 'loc' => $loc(9, 14, $source)
)) ]
), ],
'directives' => [], 'directives' => [],
'selectionSet' => new SelectionSet(array( 'selectionSet' => [
'loc' => new Location(16, 38, $source), 'kind' => Node::SELECTION_SET,
'selections' => array( 'loc' => $loc(16, 38),
new Field(array( 'selections' => [
'loc' => new Location(22, 24, $source), [
'kind' => Node::FIELD,
'loc' => $loc(22, 24),
'alias' => null, 'alias' => null,
'name' => new Name(array( 'name' => [
'loc' => new Location(22, 24, $source), 'kind' => Node::NAME,
'loc' => $loc(22, 24),
'value' => 'id' 'value' => 'id'
)), ],
'arguments' => [], 'arguments' => [],
'directives' => [], 'directives' => [],
'selectionSet' => null 'selectionSet' => null
)), ],
new Field(array( [
'loc' => new Location(30, 34, $source), 'kind' => Node::FIELD,
'loc' => $loc(30, 34),
'alias' => null, 'alias' => null,
'name' => new Name(array( 'name' => [
'loc' => new Location(30, 34, $source), 'kind' => Node::NAME,
'loc' => $loc(30, 34),
'value' => 'name' 'value' => 'name'
)), ],
'arguments' => [], 'arguments' => [],
'directives' => [], 'directives' => [],
'selectionSet' => null '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);
} }
} }

View File

@ -13,6 +13,7 @@ use GraphQL\Language\AST\ListType;
use GraphQL\Language\AST\Location; use GraphQL\Language\AST\Location;
use GraphQL\Language\AST\Name; use GraphQL\Language\AST\Name;
use GraphQL\Language\AST\NamedType; use GraphQL\Language\AST\NamedType;
use GraphQL\Language\AST\Node;
use GraphQL\Language\AST\NonNullType; use GraphQL\Language\AST\NonNullType;
use GraphQL\Language\AST\ObjectTypeDefinition; use GraphQL\Language\AST\ObjectTypeDefinition;
use GraphQL\Language\AST\ScalarTypeDefinition; use GraphQL\Language\AST\ScalarTypeDefinition;
@ -23,6 +24,8 @@ use GraphQL\Language\Source;
class SchemaParserTest extends \PHPUnit_Framework_TestCase class SchemaParserTest extends \PHPUnit_Framework_TestCase
{ {
// Describe: Schema Parser
/** /**
* @it Simple type * @it Simple type
*/ */
@ -33,13 +36,16 @@ type Hello {
world: String world: String
}'; }';
$doc = Parser::parse($body); $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' => [ 'definitions' => [
new ObjectTypeDefinition([ [
'kind' => Node::OBJECT_TYPE_DEFINITION,
'name' => $this->nameNode('Hello', $loc(6, 11)), 'name' => $this->nameNode('Hello', $loc(6, 11)),
'interfaces' => [], 'interfaces' => [],
'directives' => [],
'fields' => [ 'fields' => [
$this->fieldNode( $this->fieldNode(
$this->nameNode('world', $loc(16, 21)), $this->nameNode('world', $loc(16, 21)),
@ -48,11 +54,11 @@ type Hello {
) )
], ],
'loc' => $loc(1, 31) 'loc' => $loc(1, 31)
]) ]
], ],
'loc' => $loc(1, 31) 'loc' => $loc(0, 31)
]); ];
$this->assertEquals($doc, $expected); $this->assertEquals($expected, TestUtils::nodeToArray($doc));
} }
/** /**
@ -63,15 +69,22 @@ type Hello {
$body = ' $body = '
extend type Hello { extend type Hello {
world: String world: String
}'; }
';
$doc = Parser::parse($body); $doc = Parser::parse($body);
$loc = $this->createLocFn($body); $loc = function($start, $end) {
$expected = new Document([ return TestUtils::locArray($start, $end);
};
$expected = [
'kind' => Node::DOCUMENT,
'definitions' => [ 'definitions' => [
new TypeExtensionDefinition([ [
'definition' => new ObjectTypeDefinition([ 'kind' => Node::TYPE_EXTENSION_DEFINITION,
'definition' => [
'kind' => Node::OBJECT_TYPE_DEFINITION,
'name' => $this->nameNode('Hello', $loc(13, 18)), 'name' => $this->nameNode('Hello', $loc(13, 18)),
'interfaces' => [], 'interfaces' => [],
'directives' => [],
'fields' => [ 'fields' => [
$this->fieldNode( $this->fieldNode(
$this->nameNode('world', $loc(23, 28)), $this->nameNode('world', $loc(23, 28)),
@ -80,13 +93,13 @@ extend type Hello {
) )
], ],
'loc' => $loc(8, 38) 'loc' => $loc(8, 38)
]),
'loc' => $loc(1, 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 { type Hello {
world: String! world: String!
}'; }';
$loc = $this->createLocFn($body); $loc = function($start, $end) {
return TestUtils::locArray($start, $end);
};
$doc = Parser::parse($body); $doc = Parser::parse($body);
$expected = new Document([ $expected = [
'kind' => Node::DOCUMENT,
'definitions' => [ 'definitions' => [
new ObjectTypeDefinition([ [
'kind' => Node::OBJECT_TYPE_DEFINITION,
'name' => $this->nameNode('Hello', $loc(6,11)), 'name' => $this->nameNode('Hello', $loc(6,11)),
'interfaces' => [], 'interfaces' => [],
'directives' => [],
'fields' => [ 'fields' => [
$this->fieldNode( $this->fieldNode(
$this->nameNode('world', $loc(16, 21)), $this->nameNode('world', $loc(16, 21)),
new NonNullType([ [
'kind' => Node::NON_NULL_TYPE,
'type' => $this->typeNode('String', $loc(23, 29)), 'type' => $this->typeNode('String', $loc(23, 29)),
'loc' => $loc(23, 30) 'loc' => $loc(23, 30)
]), ],
$loc(16,30) $loc(16,30)
) )
], ],
'loc' => $loc(1,32) '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() public function testSimpleTypeInheritingInterface()
{ {
$body = 'type Hello implements World { }'; $body = 'type Hello implements World { }';
$loc = $this->createLocFn($body); $loc = function($start, $end) { return TestUtils::locArray($start, $end); };
$doc = Parser::parse($body); $doc = Parser::parse($body);
$expected = new Document([ $expected = [
'kind' => Node::DOCUMENT,
'definitions' => [ 'definitions' => [
new ObjectTypeDefinition([ [
'kind' => Node::OBJECT_TYPE_DEFINITION,
'name' => $this->nameNode('Hello', $loc(5, 10)), 'name' => $this->nameNode('Hello', $loc(5, 10)),
'interfaces' => [ 'interfaces' => [
$this->typeNode('World', $loc(22, 27)) $this->typeNode('World', $loc(22, 27))
], ],
'directives' => [],
'fields' => [], 'fields' => [],
'loc' => $loc(0,31) 'loc' => $loc(0,31)
]) ]
], ],
'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() public function testSimpleTypeInheritingMultipleInterfaces()
{ {
$body = 'type Hello implements Wo, rld { }'; $body = 'type Hello implements Wo, rld { }';
$loc = $this->createLocFn($body); $loc = function($start, $end) {return TestUtils::locArray($start, $end);};
$doc = Parser::parse($body); $doc = Parser::parse($body);
$expected = new Document([ $expected = [
'kind' => Node::DOCUMENT,
'definitions' => [ 'definitions' => [
new ObjectTypeDefinition([ [
'kind' => Node::OBJECT_TYPE_DEFINITION,
'name' => $this->nameNode('Hello', $loc(5, 10)), 'name' => $this->nameNode('Hello', $loc(5, 10)),
'interfaces' => [ 'interfaces' => [
$this->typeNode('Wo', $loc(22,24)), $this->typeNode('Wo', $loc(22,24)),
$this->typeNode('rld', $loc(26,29)) $this->typeNode('rld', $loc(26,29))
], ],
'directives' => [],
'fields' => [], 'fields' => [],
'loc' => $loc(0, 33) 'loc' => $loc(0, 33)
]) ]
], ],
'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() public function testSingleValueEnum()
{ {
$body = 'enum Hello { WORLD }'; $body = 'enum Hello { WORLD }';
$loc = $this->createLocFn($body); $loc = function($start, $end) {return TestUtils::locArray($start, $end);};
$doc = Parser::parse($body); $doc = Parser::parse($body);
$expected = new Document([ $expected = [
'kind' => Node::DOCUMENT,
'definitions' => [ 'definitions' => [
new EnumTypeDefinition([ [
'kind' => Node::ENUM_TYPE_DEFINITION,
'name' => $this->nameNode('Hello', $loc(5, 10)), 'name' => $this->nameNode('Hello', $loc(5, 10)),
'directives' => [],
'values' => [$this->enumValueNode('WORLD', $loc(13, 18))], 'values' => [$this->enumValueNode('WORLD', $loc(13, 18))],
'loc' => $loc(0, 20) 'loc' => $loc(0, 20)
]) ]
], ],
'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() public function testDoubleValueEnum()
{ {
$body = 'enum Hello { WO, RLD }'; $body = 'enum Hello { WO, RLD }';
$loc = $this->createLocFn($body); $loc = function($start, $end) {return TestUtils::locArray($start, $end);};
$doc = Parser::parse($body); $doc = Parser::parse($body);
$expected = new Document([ $expected = [
'kind' => Node::DOCUMENT,
'definitions' => [ 'definitions' => [
new EnumTypeDefinition([ [
'kind' => Node::ENUM_TYPE_DEFINITION,
'name' => $this->nameNode('Hello', $loc(5, 10)), 'name' => $this->nameNode('Hello', $loc(5, 10)),
'directives' => [],
'values' => [ 'values' => [
$this->enumValueNode('WO', $loc(13, 15)), $this->enumValueNode('WO', $loc(13, 15)),
$this->enumValueNode('RLD', $loc(17, 20)) $this->enumValueNode('RLD', $loc(17, 20))
], ],
'loc' => $loc(0, 22) 'loc' => $loc(0, 22)
]) ]
], ],
'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 world: String
}'; }';
$doc = Parser::parse($body); $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' => [ 'definitions' => [
new InterfaceTypeDefinition([ [
'kind' => Node::INTERFACE_TYPE_DEFINITION,
'name' => $this->nameNode('Hello', $loc(11, 16)), 'name' => $this->nameNode('Hello', $loc(11, 16)),
'directives' => [],
'fields' => [ 'fields' => [
$this->fieldNode( $this->fieldNode(
$this->nameNode('world', $loc(21, 26)), $this->nameNode('world', $loc(21, 26)),
@ -251,11 +285,11 @@ interface Hello {
) )
], ],
'loc' => $loc(1, 36) 'loc' => $loc(1, 36)
]) ]
], ],
'loc' => $loc(1,36) 'loc' => $loc(0,36)
]); ];
$this->assertEquals($expected, $doc); $this->assertEquals($expected, TestUtils::nodeToArray($doc));
} }
/** /**
@ -268,13 +302,16 @@ type Hello {
world(flag: Boolean): String world(flag: Boolean): String
}'; }';
$doc = Parser::parse($body); $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' => [ 'definitions' => [
new ObjectTypeDefinition([ [
'kind' => Node::OBJECT_TYPE_DEFINITION,
'name' => $this->nameNode('Hello', $loc(6, 11)), 'name' => $this->nameNode('Hello', $loc(6, 11)),
'interfaces' => [], 'interfaces' => [],
'directives' => [],
'fields' => [ 'fields' => [
$this->fieldNodeWithArgs( $this->fieldNodeWithArgs(
$this->nameNode('world', $loc(16, 21)), $this->nameNode('world', $loc(16, 21)),
@ -291,12 +328,12 @@ type Hello {
) )
], ],
'loc' => $loc(1, 46) '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 world(flag: Boolean = true): String
}'; }';
$doc = Parser::parse($body); $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' => [ 'definitions' => [
new ObjectTypeDefinition([ [
'kind' => Node::OBJECT_TYPE_DEFINITION,
'name' => $this->nameNode('Hello', $loc(6, 11)), 'name' => $this->nameNode('Hello', $loc(6, 11)),
'interfaces' => [], 'interfaces' => [],
'directives' => [],
'fields' => [ 'fields' => [
$this->fieldNodeWithArgs( $this->fieldNodeWithArgs(
$this->nameNode('world', $loc(16, 21)), $this->nameNode('world', $loc(16, 21)),
@ -324,7 +364,7 @@ type Hello {
$this->inputValueNode( $this->inputValueNode(
$this->nameNode('flag', $loc(22, 26)), $this->nameNode('flag', $loc(22, 26)),
$this->typeNode('Boolean', $loc(28, 35)), $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) $loc(22, 42)
) )
], ],
@ -332,11 +372,11 @@ type Hello {
) )
], ],
'loc' => $loc(1, 53) 'loc' => $loc(1, 53)
]) ]
], ],
'loc' => $loc(1, 53) 'loc' => $loc(0, 53)
]); ];
$this->assertEquals($expected, $doc); $this->assertEquals($expected, TestUtils::nodeToArray($doc));
} }
/** /**
@ -349,13 +389,16 @@ type Hello {
world(things: [String]): String world(things: [String]): String
}'; }';
$doc = Parser::parse($body); $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' => [ 'definitions' => [
new ObjectTypeDefinition([ [
'kind' => Node::OBJECT_TYPE_DEFINITION,
'name' => $this->nameNode('Hello', $loc(6, 11)), 'name' => $this->nameNode('Hello', $loc(6, 11)),
'interfaces' => [], 'interfaces' => [],
'directives' => [],
'fields' => [ 'fields' => [
$this->fieldNodeWithArgs( $this->fieldNodeWithArgs(
$this->nameNode('world', $loc(16, 21)), $this->nameNode('world', $loc(16, 21)),
@ -363,7 +406,7 @@ type Hello {
[ [
$this->inputValueNode( $this->inputValueNode(
$this->nameNode('things', $loc(22,28)), $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, null,
$loc(22, 38) $loc(22, 38)
) )
@ -372,12 +415,12 @@ type Hello {
) )
], ],
'loc' => $loc(1, 49) '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 world(argOne: Boolean, argTwo: Int): String
}'; }';
$doc = Parser::parse($body); $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' => [ 'definitions' => [
new ObjectTypeDefinition([ [
'kind' => Node::OBJECT_TYPE_DEFINITION,
'name' => $this->nameNode('Hello', $loc(6, 11)), 'name' => $this->nameNode('Hello', $loc(6, 11)),
'interfaces' => [], 'interfaces' => [],
'directives' => [],
'fields' => [ 'fields' => [
$this->fieldNodeWithArgs( $this->fieldNodeWithArgs(
$this->nameNode('world', $loc(16, 21)), $this->nameNode('world', $loc(16, 21)),
@ -419,12 +465,12 @@ type Hello {
) )
], ],
'loc' => $loc(1, 61) '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'; $body = 'union Hello = World';
$doc = Parser::parse($body); $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' => [ 'definitions' => [
new UnionTypeDefinition([ [
'kind' => Node::UNION_TYPE_DEFINITION,
'name' => $this->nameNode('Hello', $loc(6, 11)), 'name' => $this->nameNode('Hello', $loc(6, 11)),
'directives' => [],
'types' => [$this->typeNode('World', $loc(14, 19))], 'types' => [$this->typeNode('World', $loc(14, 19))],
'loc' => $loc(0, 19) 'loc' => $loc(0, 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'; $body = 'union Hello = Wo | Rld';
$doc = Parser::parse($body); $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' => [ 'definitions' => [
new UnionTypeDefinition([ [
'kind' => Node::UNION_TYPE_DEFINITION,
'name' => $this->nameNode('Hello', $loc(6, 11)), 'name' => $this->nameNode('Hello', $loc(6, 11)),
'directives' => [],
'types' => [ 'types' => [
$this->typeNode('Wo', $loc(14, 16)), $this->typeNode('Wo', $loc(14, 16)),
$this->typeNode('Rld', $loc(19, 22)) $this->typeNode('Rld', $loc(19, 22))
], ],
'loc' => $loc(0, 22) 'loc' => $loc(0, 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'; $body = 'scalar Hello';
$doc = Parser::parse($body); $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' => [ 'definitions' => [
new ScalarTypeDefinition([ [
'kind' => Node::SCALAR_TYPE_DEFINITION,
'name' => $this->nameNode('Hello', $loc(7, 12)), 'name' => $this->nameNode('Hello', $loc(7, 12)),
'directives' => [],
'loc' => $loc(0, 12) 'loc' => $loc(0, 12)
]) ]
], ],
'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 world: String
}'; }';
$doc = Parser::parse($body); $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' => [ 'definitions' => [
new InputObjectTypeDefinition([ [
'kind' => Node::INPUT_OBJECT_TYPE_DEFINITION,
'name' => $this->nameNode('Hello', $loc(7, 12)), 'name' => $this->nameNode('Hello', $loc(7, 12)),
'directives' => [],
'fields' => [ 'fields' => [
$this->inputValueNode( $this->inputValueNode(
$this->nameNode('world', $loc(17, 22)), $this->nameNode('world', $loc(17, 22)),
@ -519,11 +577,11 @@ input Hello {
) )
], ],
'loc' => $loc(1, 32) 'loc' => $loc(1, 32)
]) ]
], ],
'loc' => $loc(1, 32) 'loc' => $loc(0, 32)
]); ];
$this->assertEquals($expected, $doc); $this->assertEquals($expected, TestUtils::nodeToArray($doc));
} }
/** /**
@ -539,28 +597,22 @@ input Hello {
Parser::parse($body); 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) private function typeNode($name, $loc)
{ {
return new NamedType([ return [
'name' => new Name(['value' => $name, 'loc' => $loc]), 'kind' => Node::NAMED_TYPE,
'name' => ['kind' => Node::NAME, 'value' => $name, 'loc' => $loc],
'loc' => $loc 'loc' => $loc
]); ];
} }
private function nameNode($name, $loc) private function nameNode($name, $loc)
{ {
return new Name([ return [
'kind' => Node::NAME,
'value' => $name, 'value' => $name,
'loc' => $loc 'loc' => $loc
]); ];
} }
private function fieldNode($name, $type, $loc) private function fieldNode($name, $type, $loc)
@ -570,29 +622,35 @@ input Hello {
private function fieldNodeWithArgs($name, $type, $args, $loc) private function fieldNodeWithArgs($name, $type, $args, $loc)
{ {
return new FieldDefinition([ return [
'kind' => Node::FIELD_DEFINITION,
'name' => $name, 'name' => $name,
'arguments' => $args, 'arguments' => $args,
'type' => $type, 'type' => $type,
'directives' => [],
'loc' => $loc 'loc' => $loc
]); ];
} }
private function enumValueNode($name, $loc) private function enumValueNode($name, $loc)
{ {
return new EnumValueDefinition([ return [
'kind' => Node::ENUM_VALUE_DEFINITION,
'name' => $this->nameNode($name, $loc), 'name' => $this->nameNode($name, $loc),
'directives' => [],
'loc' => $loc 'loc' => $loc
]); ];
} }
private function inputValueNode($name, $type, $defaultValue, $loc) private function inputValueNode($name, $type, $defaultValue, $loc)
{ {
return new InputValueDefinition([ return [
'kind' => Node::INPUT_VALUE_DEFINITION,
'name' => $name, 'name' => $name,
'type' => $type, 'type' => $type,
'defaultValue' => $defaultValue, 'defaultValue' => $defaultValue,
'directives' => [],
'loc' => $loc 'loc' => $loc
]); ];
} }
} }

View File

@ -65,29 +65,54 @@ type Foo implements Bar {
six(argument: InputType = {key: "value"}): Type six(argument: InputType = {key: "value"}): Type
} }
type AnnotatedObject @onObject(arg: "value") {
annotatedField(arg: Type = "default" @onArg): Type @onField
}
interface Bar { interface Bar {
one: Type one: Type
four(argument: String = "string"): String four(argument: String = "string"): String
} }
interface AnnotatedInterface @onInterface {
annotatedField(arg: Type @onArg): Type @onField
}
union Feed = Story | Article | Advert union Feed = Story | Article | Advert
union AnnotatedUnion @onUnion = A | B
scalar CustomScalar scalar CustomScalar
scalar AnnotatedScalar @onScalar
enum Site { enum Site {
DESKTOP DESKTOP
MOBILE MOBILE
} }
enum AnnotatedEnum @onEnum {
ANNOTATED_VALUE @onEnumValue
OTHER_VALUE
}
input InputType { input InputType {
key: String! key: String!
answer: Int = 42 answer: Int = 42
} }
input AnnotatedInput @onInputObjectType {
annotatedField: Type @onField
}
extend type Foo { extend type Foo {
seven(argument: [String]): Type seven(argument: [String]): Type
} }
extend type Foo @onType {}
type NoFields {}
directive @skip(if: Boolean!) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT directive @skip(if: Boolean!) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT
directive @include(if: Boolean!) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT directive @include(if: Boolean!) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT

View 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];
}
}

View File

@ -19,29 +19,54 @@ type Foo implements Bar {
six(argument: InputType = {key: "value"}): Type six(argument: InputType = {key: "value"}): Type
} }
type AnnotatedObject @onObject(arg: "value") {
annotatedField(arg: Type = "default" @onArg): Type @onField
}
interface Bar { interface Bar {
one: Type one: Type
four(argument: String = "string"): String four(argument: String = "string"): String
} }
interface AnnotatedInterface @onInterface {
annotatedField(arg: Type @onArg): Type @onField
}
union Feed = Story | Article | Advert union Feed = Story | Article | Advert
union AnnotatedUnion @onUnion = A | B
scalar CustomScalar scalar CustomScalar
scalar AnnotatedScalar @onScalar
enum Site { enum Site {
DESKTOP DESKTOP
MOBILE MOBILE
} }
enum AnnotatedEnum @onEnum {
ANNOTATED_VALUE @onEnumValue
OTHER_VALUE
}
input InputType { input InputType {
key: String! key: String!
answer: Int = 42 answer: Int = 42
} }
input AnnotatedInput @onInputObjectType {
annotatedField: Type @onField
}
extend type Foo { extend type Foo {
seven(argument: [String]): Type seven(argument: [String]): Type
} }
extend type Foo @onType {}
type NoFields {}
directive @skip(if: Boolean!) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT directive @skip(if: Boolean!) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT
directive @include(if: Boolean!) directive @include(if: Boolean!)