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;
/**
* @var Directive[]
*/
public $directives;
/**
* @var EnumValueDefinition[]
*/

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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:

View File

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

View File

@ -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;
}

View File

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

View File

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

View File

@ -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();

View File

@ -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)

View File

@ -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' ]
);

View File

@ -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);
}
}

View File

@ -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
]);
];
}
}

View File

@ -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

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
}
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!)