mirror of
https://github.com/retailcrm/graphql-php.git
synced 2024-11-25 14:26:08 +03:00
AST: new NodeList
class for collections of nodes (vs array) to enable effective conversion of libgraphqlparser output to our AST tree
This commit is contained in:
parent
e04d3300a7
commit
1af902865b
@ -11,6 +11,7 @@ use GraphQL\Language\AST\FieldDefinitionNode;
|
||||
use GraphQL\Language\AST\FieldNode;
|
||||
use GraphQL\Language\AST\FragmentSpreadNode;
|
||||
use GraphQL\Language\AST\InlineFragmentNode;
|
||||
use GraphQL\Language\AST\NodeList;
|
||||
use GraphQL\Language\AST\VariableNode;
|
||||
use GraphQL\Language\AST\VariableDefinitionNode;
|
||||
use GraphQL\Language\Printer;
|
||||
@ -180,7 +181,7 @@ class Values
|
||||
*/
|
||||
public static function getDirectiveValues(Directive $directiveDef, $node, $variableValues = null)
|
||||
{
|
||||
if (isset($node->directives) && is_array($node->directives)) {
|
||||
if (isset($node->directives) && $node->directives instanceof NodeList) {
|
||||
$directiveNode = Utils::find($node->directives, function(DirectiveNode $directive) use ($directiveDef) {
|
||||
return $directive->name->value === $directiveDef->name;
|
||||
});
|
||||
|
@ -45,12 +45,28 @@ class Location
|
||||
*/
|
||||
public $source;
|
||||
|
||||
public function __construct(Token $startToken, Token $endToken, Source $source = null)
|
||||
/**
|
||||
* @param $start
|
||||
* @param $end
|
||||
* @return static
|
||||
*/
|
||||
public static function create($start, $end)
|
||||
{
|
||||
$tmp = new static();
|
||||
$tmp->start = $start;
|
||||
$tmp->end = $end;
|
||||
return $tmp;
|
||||
}
|
||||
|
||||
public function __construct(Token $startToken = null, Token $endToken = null, Source $source = null)
|
||||
{
|
||||
$this->startToken = $startToken;
|
||||
$this->endToken = $endToken;
|
||||
$this->source = $source;
|
||||
|
||||
if ($startToken && $endToken) {
|
||||
$this->start = $startToken->start;
|
||||
$this->end = $endToken->end;
|
||||
$this->source = $source;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
<?php
|
||||
namespace GraphQL\Language\AST;
|
||||
|
||||
use GraphQL\Error\InvariantViolation;
|
||||
use GraphQL\Utils\Utils;
|
||||
|
||||
abstract class Node
|
||||
@ -37,13 +38,70 @@ abstract class Node
|
||||
*/
|
||||
public $loc;
|
||||
|
||||
/**
|
||||
* Converts representation of AST as associative array to Node instance.
|
||||
*
|
||||
* For example:
|
||||
*
|
||||
* ```php
|
||||
* Node::fromArray([
|
||||
* 'kind' => 'ListValue',
|
||||
* 'values' => [
|
||||
* ['kind' => 'StringValue', 'value' => 'my str'],
|
||||
* ['kind' => 'StringValue', 'value' => 'my other str']
|
||||
* ],
|
||||
* 'loc' => ['start' => 21, 'end' => 25]
|
||||
* ]);
|
||||
* ```
|
||||
*
|
||||
* Will produce instance of `ListValueNode` where `values` prop is a lazily-evaluated `NodeList`
|
||||
* returning instances of `StringValueNode` on access.
|
||||
*
|
||||
* This is a reverse operation for $node->toArray(true)
|
||||
*
|
||||
* @param array $node
|
||||
* @return EnumValueDefinitionNode
|
||||
*/
|
||||
public static function fromArray(array $node)
|
||||
{
|
||||
if (!isset($node['kind']) || !isset(NodeKind::$classMap[$node['kind']])) {
|
||||
throw new InvariantViolation("Unexpected node structure: " . Utils::printSafeJson($node));
|
||||
}
|
||||
|
||||
$kind = isset($node['kind']) ? $node['kind'] : null;
|
||||
$class = NodeKind::$classMap[$kind];
|
||||
$instance = new $class([]);
|
||||
|
||||
if (isset($node['loc'], $node['loc']['start'], $node['loc']['end'])) {
|
||||
$instance->loc = Location::create($node['loc']['start'], $node['loc']['end']);
|
||||
}
|
||||
|
||||
|
||||
foreach ($node as $key => $value) {
|
||||
if ('loc' === $key || 'kind' === $key) {
|
||||
continue ;
|
||||
}
|
||||
if (is_array($value)) {
|
||||
if (isset($value[0]) || empty($value)) {
|
||||
$value = new NodeList($value);
|
||||
} else {
|
||||
$value = self::fromArray($value);
|
||||
}
|
||||
}
|
||||
$instance->{$key} = $value;
|
||||
}
|
||||
return $instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $vars
|
||||
*/
|
||||
public function __construct(array $vars)
|
||||
{
|
||||
if (!empty($vars)) {
|
||||
Utils::assign($this, $vars);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return $this
|
||||
@ -91,6 +149,9 @@ abstract class Node
|
||||
*/
|
||||
public function toArray($recursive = false)
|
||||
{
|
||||
if ($recursive) {
|
||||
return $this->recursiveToArray($this);
|
||||
} else {
|
||||
$tmp = (array) $this;
|
||||
|
||||
$tmp['loc'] = [
|
||||
@ -98,27 +159,43 @@ abstract class Node
|
||||
'end' => $this->loc->end
|
||||
];
|
||||
|
||||
if ($recursive) {
|
||||
$this->recursiveToArray($tmp);
|
||||
}
|
||||
|
||||
return $tmp;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $object
|
||||
* @param Node $node
|
||||
* @return array
|
||||
*/
|
||||
public function recursiveToArray(&$object)
|
||||
private function recursiveToArray(Node $node)
|
||||
{
|
||||
if ($object instanceof Node) {
|
||||
/** @var Node $object */
|
||||
$object = $object->toArray(true);
|
||||
} elseif (is_object($object)) {
|
||||
$object = (array) $object;
|
||||
} elseif (is_array($object)) {
|
||||
foreach ($object as &$o) {
|
||||
$this->recursiveToArray($o);
|
||||
}
|
||||
}
|
||||
$result = [
|
||||
'kind' => $node->kind,
|
||||
'loc' => [
|
||||
'start' => $node->loc->start,
|
||||
'end' => $node->loc->end
|
||||
]
|
||||
];
|
||||
|
||||
foreach (get_object_vars($node) as $prop => $propValue) {
|
||||
if (isset($result[$prop]))
|
||||
continue;
|
||||
|
||||
if (is_array($propValue) || $propValue instanceof NodeList) {
|
||||
$tmp = [];
|
||||
foreach ($propValue as $tmp1) {
|
||||
$tmp[] = $tmp1 instanceof Node ? $this->recursiveToArray($tmp1) : (array) $tmp1;
|
||||
}
|
||||
} else if ($propValue instanceof Node) {
|
||||
$tmp = $this->recursiveToArray($propValue);
|
||||
} else if (is_scalar($propValue) || null === $propValue) {
|
||||
$tmp = $propValue;
|
||||
} else {
|
||||
$tmp = null;
|
||||
}
|
||||
|
||||
$result[$prop] = $tmp;
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
|
@ -70,4 +70,66 @@ class NodeKind
|
||||
// Directive Definitions
|
||||
|
||||
const DIRECTIVE_DEFINITION = 'DirectiveDefinition';
|
||||
|
||||
/**
|
||||
* @todo conver to const array when moving to PHP5.6
|
||||
* @var array
|
||||
*/
|
||||
public static $classMap = [
|
||||
NodeKind::NAME => NameNode::class,
|
||||
|
||||
// Document
|
||||
NodeKind::DOCUMENT => DocumentNode::class,
|
||||
NodeKind::OPERATION_DEFINITION => OperationDefinitionNode::class,
|
||||
NodeKind::VARIABLE_DEFINITION => VariableDefinitionNode::class,
|
||||
NodeKind::VARIABLE => VariableNode::class,
|
||||
NodeKind::SELECTION_SET => SelectionSetNode::class,
|
||||
NodeKind::FIELD => FieldNode::class,
|
||||
NodeKind::ARGUMENT => ArgumentNode::class,
|
||||
|
||||
// Fragments
|
||||
NodeKind::FRAGMENT_SPREAD => FragmentSpreadNode::class,
|
||||
NodeKind::INLINE_FRAGMENT => InlineFragmentNode::class,
|
||||
NodeKind::FRAGMENT_DEFINITION => FragmentDefinitionNode::class,
|
||||
|
||||
// Values
|
||||
NodeKind::INT => IntValueNode::class,
|
||||
NodeKind::FLOAT => FloatValueNode::class,
|
||||
NodeKind::STRING => StringValueNode::class,
|
||||
NodeKind::BOOLEAN => BooleanValueNode::class,
|
||||
NodeKind::ENUM => EnumValueNode::class,
|
||||
NodeKind::NULL => NullValueNode::class,
|
||||
NodeKind::LST => ListValueNode::class,
|
||||
NodeKind::OBJECT => ObjectValueNode::class,
|
||||
NodeKind::OBJECT_FIELD => ObjectFieldNode::class,
|
||||
|
||||
// Directives
|
||||
NodeKind::DIRECTIVE => DirectiveNode::class,
|
||||
|
||||
// Types
|
||||
NodeKind::NAMED_TYPE => NamedTypeNode::class,
|
||||
NodeKind::LIST_TYPE => ListTypeNode::class,
|
||||
NodeKind::NON_NULL_TYPE => NonNullTypeNode::class,
|
||||
|
||||
// Type System Definitions
|
||||
NodeKind::SCHEMA_DEFINITION => SchemaDefinitionNode::class,
|
||||
NodeKind::OPERATION_TYPE_DEFINITION => OperationTypeDefinitionNode::class,
|
||||
|
||||
// Type Definitions
|
||||
NodeKind::SCALAR_TYPE_DEFINITION => ScalarTypeDefinitionNode::class,
|
||||
NodeKind::OBJECT_TYPE_DEFINITION => ObjectTypeDefinitionNode::class,
|
||||
NodeKind::FIELD_DEFINITION => FieldDefinitionNode::class,
|
||||
NodeKind::INPUT_VALUE_DEFINITION => InputValueDefinitionNode::class,
|
||||
NodeKind::INTERFACE_TYPE_DEFINITION => InterfaceTypeDefinitionNode::class,
|
||||
NodeKind::UNION_TYPE_DEFINITION => UnionTypeDefinitionNode::class,
|
||||
NodeKind::ENUM_TYPE_DEFINITION => EnumTypeDefinitionNode::class,
|
||||
NodeKind::ENUM_VALUE_DEFINITION => EnumValueDefinitionNode::class,
|
||||
NodeKind::INPUT_OBJECT_TYPE_DEFINITION =>InputObjectTypeDefinitionNode::class,
|
||||
|
||||
// Type Extensions
|
||||
NodeKind::TYPE_EXTENSION_DEFINITION => TypeExtensionDefinitionNode::class,
|
||||
|
||||
// Directive Definitions
|
||||
NodeKind::DIRECTIVE_DEFINITION => DirectiveDefinitionNode::class
|
||||
];
|
||||
}
|
||||
|
119
src/Language/AST/NodeList.php
Normal file
119
src/Language/AST/NodeList.php
Normal file
@ -0,0 +1,119 @@
|
||||
<?php
|
||||
namespace GraphQL\Language\AST;
|
||||
|
||||
/**
|
||||
* Class NodeList
|
||||
*
|
||||
* @package GraphQL\Utils
|
||||
*/
|
||||
class NodeList implements \ArrayAccess, \IteratorAggregate, \Countable
|
||||
{
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
private $nodes;
|
||||
|
||||
/**
|
||||
* @param array $nodes
|
||||
* @return static
|
||||
*/
|
||||
public static function create(array $nodes)
|
||||
{
|
||||
return new static($nodes);
|
||||
}
|
||||
|
||||
/**
|
||||
* NodeList constructor.
|
||||
* @param array $nodes
|
||||
*/
|
||||
public function __construct(array $nodes)
|
||||
{
|
||||
$this->nodes = $nodes;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $offset
|
||||
* @return bool
|
||||
*/
|
||||
public function offsetExists($offset)
|
||||
{
|
||||
return isset($this->nodes[$offset]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $offset
|
||||
* @return mixed
|
||||
*/
|
||||
public function offsetGet($offset)
|
||||
{
|
||||
$item = $this->nodes[$offset];
|
||||
|
||||
if (is_array($item) && isset($item['kind'])) {
|
||||
$this->nodes[$offset] = $item = Node::fromArray($item);
|
||||
}
|
||||
|
||||
return $item;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $offset
|
||||
* @param mixed $value
|
||||
*/
|
||||
public function offsetSet($offset, $value)
|
||||
{
|
||||
if (is_array($value) && isset($value['kind'])) {
|
||||
$value = Node::fromArray($value);
|
||||
}
|
||||
$this->nodes[$offset] = $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $offset
|
||||
*/
|
||||
public function offsetUnset($offset)
|
||||
{
|
||||
unset($this->nodes[$offset]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $offset
|
||||
* @param int $length
|
||||
* @param mixed $replacement
|
||||
* @return NodeList
|
||||
*/
|
||||
public function splice($offset, $length, $replacement = null)
|
||||
{
|
||||
return new NodeList(array_splice($this->nodes, $offset, $length, $replacement));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $list
|
||||
* @return NodeList
|
||||
*/
|
||||
public function merge($list)
|
||||
{
|
||||
if ($list instanceof NodeList) {
|
||||
$list = $list->nodes;
|
||||
}
|
||||
return new NodeList(array_merge($this->nodes, $list));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \Generator
|
||||
*/
|
||||
public function getIterator()
|
||||
{
|
||||
$count = count($this->nodes);
|
||||
for ($i = 0; $i < $count; $i++) {
|
||||
yield $this->offsetGet($i);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function count()
|
||||
{
|
||||
return count($this->nodes);
|
||||
}
|
||||
}
|
@ -26,6 +26,7 @@ use GraphQL\Language\AST\ListTypeNode;
|
||||
use GraphQL\Language\AST\Location;
|
||||
use GraphQL\Language\AST\NameNode;
|
||||
use GraphQL\Language\AST\NamedTypeNode;
|
||||
use GraphQL\Language\AST\NodeList;
|
||||
use GraphQL\Language\AST\NonNullTypeNode;
|
||||
use GraphQL\Language\AST\NullValueNode;
|
||||
use GraphQL\Language\AST\ObjectFieldNode;
|
||||
@ -248,7 +249,7 @@ class Parser
|
||||
while (!$this->skip($closeKind)) {
|
||||
$nodes[] = $parseFn($this);
|
||||
}
|
||||
return $nodes;
|
||||
return new NodeList($nodes);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -260,7 +261,7 @@ class Parser
|
||||
* @param $openKind
|
||||
* @param $parseFn
|
||||
* @param $closeKind
|
||||
* @return array
|
||||
* @return NodeList
|
||||
* @throws SyntaxError
|
||||
*/
|
||||
function many($openKind, $parseFn, $closeKind)
|
||||
@ -271,7 +272,7 @@ class Parser
|
||||
while (!$this->skip($closeKind)) {
|
||||
$nodes[] = $parseFn($this);
|
||||
}
|
||||
return $nodes;
|
||||
return new NodeList($nodes);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -307,7 +308,7 @@ class Parser
|
||||
} while (!$this->skip(Token::EOF));
|
||||
|
||||
return new DocumentNode([
|
||||
'definitions' => $definitions,
|
||||
'definitions' => new NodeList($definitions),
|
||||
'loc' => $this->loc($start)
|
||||
]);
|
||||
}
|
||||
@ -363,7 +364,7 @@ class Parser
|
||||
'operation' => 'query',
|
||||
'name' => null,
|
||||
'variableDefinitions' => null,
|
||||
'directives' => [],
|
||||
'directives' => new NodeList([]),
|
||||
'selectionSet' => $this->parseSelectionSet(),
|
||||
'loc' => $this->loc($start)
|
||||
]);
|
||||
@ -414,7 +415,7 @@ class Parser
|
||||
[$this, 'parseVariableDefinition'],
|
||||
Token::PAREN_R
|
||||
) :
|
||||
[];
|
||||
new NodeList([]);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -513,7 +514,7 @@ class Parser
|
||||
{
|
||||
return $this->peek(Token::PAREN_L) ?
|
||||
$this->many(Token::PAREN_L, [$this, 'parseArgument'], Token::PAREN_R) :
|
||||
[];
|
||||
new NodeList([]);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -726,7 +727,7 @@ class Parser
|
||||
$fields[] = $this->parseObjectField($isConst);
|
||||
}
|
||||
return new ObjectValueNode([
|
||||
'fields' => $fields,
|
||||
'fields' => new NodeList($fields),
|
||||
'loc' => $this->loc($start)
|
||||
]);
|
||||
}
|
||||
@ -760,7 +761,7 @@ class Parser
|
||||
while ($this->peek(Token::AT)) {
|
||||
$directives[] = $this->parseDirective();
|
||||
}
|
||||
return $directives;
|
||||
return new NodeList($directives);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -38,6 +38,7 @@ use GraphQL\Language\AST\StringValueNode;
|
||||
use GraphQL\Language\AST\TypeExtensionDefinitionNode;
|
||||
use GraphQL\Language\AST\UnionTypeDefinitionNode;
|
||||
use GraphQL\Language\AST\VariableDefinitionNode;
|
||||
use GraphQL\Utils\Utils;
|
||||
|
||||
class Printer
|
||||
{
|
||||
@ -280,7 +281,7 @@ class Printer
|
||||
return $maybeArray
|
||||
? implode(
|
||||
$separator,
|
||||
array_filter(
|
||||
Utils::filter(
|
||||
$maybeArray,
|
||||
function($x) { return !!$x;}
|
||||
)
|
||||
|
@ -3,6 +3,7 @@ namespace GraphQL\Language;
|
||||
|
||||
use GraphQL\Language\AST\Node;
|
||||
use GraphQL\Language\AST\NodeKind;
|
||||
use GraphQL\Language\AST\NodeList;
|
||||
use GraphQL\Utils\TypeInfo;
|
||||
|
||||
class VisitorOperation
|
||||
@ -180,7 +181,7 @@ class Visitor
|
||||
$visitorKeys = $keyMap ?: self::$visitorKeys;
|
||||
|
||||
$stack = null;
|
||||
$inArray = is_array($root);
|
||||
$inArray = $root instanceof NodeList || is_array($root);
|
||||
$keys = [$root];
|
||||
$index = -1;
|
||||
$edits = [];
|
||||
@ -206,6 +207,9 @@ class Visitor
|
||||
if ($isEdited) {
|
||||
if ($inArray) {
|
||||
// $node = $node; // arrays are value types in PHP
|
||||
if ($node instanceof NodeList) {
|
||||
$node = clone $node;
|
||||
}
|
||||
} else {
|
||||
$node = clone $node;
|
||||
}
|
||||
@ -218,10 +222,14 @@ class Visitor
|
||||
$editKey -= $editOffset;
|
||||
}
|
||||
if ($inArray && $editValue === null) {
|
||||
if ($node instanceof NodeList) {
|
||||
$node->splice($editKey, 1);
|
||||
} else {
|
||||
array_splice($node, $editKey, 1);
|
||||
}
|
||||
$editOffset++;
|
||||
} else {
|
||||
if (is_array($node)) {
|
||||
if ($node instanceof NodeList || is_array($node)) {
|
||||
$node[$editKey] = $editValue;
|
||||
} else {
|
||||
$node->{$editKey} = $editValue;
|
||||
@ -236,7 +244,7 @@ class Visitor
|
||||
$stack = $stack['prev'];
|
||||
} else {
|
||||
$key = $parent ? ($inArray ? $index : $keys[$index]) : $UNDEFINED;
|
||||
$node = $parent ? (is_array($parent) ? $parent[$key] : $parent->{$key}) : $newRoot;
|
||||
$node = $parent ? (($parent instanceof NodeList || is_array($parent)) ? $parent[$key] : $parent->{$key}) : $newRoot;
|
||||
if ($node === null || $node === $UNDEFINED) {
|
||||
continue;
|
||||
}
|
||||
@ -246,7 +254,7 @@ class Visitor
|
||||
}
|
||||
|
||||
$result = null;
|
||||
if (!is_array($node)) {
|
||||
if (!$node instanceof NodeList && !is_array($node)) {
|
||||
if (!($node instanceof Node)) {
|
||||
throw new \Exception('Invalid AST Node: ' . json_encode($node));
|
||||
}
|
||||
@ -297,7 +305,7 @@ class Visitor
|
||||
'edits' => $edits,
|
||||
'prev' => $stack
|
||||
];
|
||||
$inArray = is_array($node);
|
||||
$inArray = $node instanceof NodeList || is_array($node);
|
||||
|
||||
$keys = ($inArray ? $node : $visitorKeys[$node->kind]) ?: [];
|
||||
$index = -1;
|
||||
|
@ -183,12 +183,19 @@ class Utils
|
||||
return $grouped;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array|Traversable $traversable
|
||||
* @param callable $keyFn
|
||||
* @param callable $valFn
|
||||
* @return array
|
||||
*/
|
||||
public static function keyValMap($traversable, callable $keyFn, callable $valFn)
|
||||
{
|
||||
return array_reduce($traversable, function ($map, $item) use ($keyFn, $valFn) {
|
||||
$map = [];
|
||||
foreach ($traversable as $item) {
|
||||
$map[$keyFn($item)] = $valFn($item);
|
||||
}
|
||||
return $map;
|
||||
}, []);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -379,7 +379,7 @@ class OverlappingFieldsCanBeMerged
|
||||
*
|
||||
* @return bool|string
|
||||
*/
|
||||
private function sameArguments(array $arguments1, array $arguments2)
|
||||
private function sameArguments($arguments1, $arguments2)
|
||||
{
|
||||
if (count($arguments1) !== count($arguments2)) {
|
||||
return false;
|
||||
|
@ -131,13 +131,13 @@ class ValidationContext
|
||||
{
|
||||
$fragments = $this->fragments;
|
||||
if (!$fragments) {
|
||||
$this->fragments = $fragments =
|
||||
array_reduce($this->getDocument()->definitions, function($frags, $statement) {
|
||||
$fragments = [];
|
||||
foreach ($this->getDocument()->definitions as $statement) {
|
||||
if ($statement->kind === NodeKind::FRAGMENT_DEFINITION) {
|
||||
$frags[$statement->name->value] = $statement;
|
||||
$fragments[$statement->name->value] = $statement;
|
||||
}
|
||||
return $frags;
|
||||
}, []);
|
||||
}
|
||||
$this->fragments = $fragments;
|
||||
}
|
||||
return isset($fragments[$name]) ? $fragments[$name] : null;
|
||||
}
|
||||
|
@ -7,6 +7,7 @@ use GraphQL\Language\AST\FieldNode;
|
||||
use GraphQL\Language\AST\NameNode;
|
||||
use GraphQL\Language\AST\Node;
|
||||
use GraphQL\Language\AST\NodeKind;
|
||||
use GraphQL\Language\AST\NodeList;
|
||||
use GraphQL\Language\AST\SelectionSetNode;
|
||||
use GraphQL\Language\AST\StringValueNode;
|
||||
use GraphQL\Language\Parser;
|
||||
@ -150,20 +151,20 @@ HEREDOC;
|
||||
$result = Parser::parse($query, ['noLocation' => true]);
|
||||
|
||||
$expected = new SelectionSetNode([
|
||||
'selections' => [
|
||||
'selections' => new NodeList([
|
||||
new FieldNode([
|
||||
'name' => new NameNode(['value' => 'field']),
|
||||
'arguments' => [
|
||||
'arguments' => new NodeList([
|
||||
new ArgumentNode([
|
||||
'name' => new NameNode(['value' => 'arg']),
|
||||
'value' => new StringValueNode([
|
||||
'value' => "Has a $char multi-byte character."
|
||||
])
|
||||
])
|
||||
],
|
||||
'directives' => []
|
||||
]),
|
||||
'directives' => new NodeList([])
|
||||
])
|
||||
])
|
||||
]
|
||||
]);
|
||||
|
||||
$this->assertEquals($expected, $result->definitions[0]->selectionSet);
|
||||
|
73
tests/Language/SerializationTest.php
Normal file
73
tests/Language/SerializationTest.php
Normal file
@ -0,0 +1,73 @@
|
||||
<?php
|
||||
namespace GraphQL\Tests;
|
||||
|
||||
use GraphQL\Language\AST\Location;
|
||||
use GraphQL\Language\AST\Node;
|
||||
use GraphQL\Language\AST\NodeList;
|
||||
use GraphQL\Language\Parser;
|
||||
|
||||
class SerializationTest extends \PHPUnit_Framework_TestCase
|
||||
{
|
||||
public function testSerializesAst()
|
||||
{
|
||||
$kitchenSink = file_get_contents(__DIR__ . '/kitchen-sink.graphql');
|
||||
$ast = Parser::parse($kitchenSink);
|
||||
$expectedAst = json_decode(file_get_contents(__DIR__ . '/kitchen-sink.ast'), true);
|
||||
$this->assertEquals($expectedAst, $ast->toArray(true));
|
||||
}
|
||||
|
||||
public function testUnserializesAst()
|
||||
{
|
||||
$kitchenSink = file_get_contents(__DIR__ . '/kitchen-sink.graphql');
|
||||
$serializedAst = json_decode(file_get_contents(__DIR__ . '/kitchen-sink.ast'), true);
|
||||
$actualAst = Node::fromArray($serializedAst);
|
||||
$parsedAst = Parser::parse($kitchenSink);
|
||||
$this->assertNodesAreEqual($parsedAst, $actualAst);
|
||||
}
|
||||
|
||||
/**
|
||||
* Compares two nodes by actually iterating over all NodeLists, properly comparing locations (ignoring tokens), etc
|
||||
*
|
||||
* @param $expected
|
||||
* @param $actual
|
||||
* @param array $path
|
||||
*/
|
||||
private function assertNodesAreEqual($expected, $actual, $path = [])
|
||||
{
|
||||
$err = "Mismatch at AST path: " . implode(', ', $path);
|
||||
|
||||
$this->assertInstanceOf(Node::class, $actual, $err);
|
||||
$this->assertEquals(get_class($expected), get_class($actual), $err);
|
||||
|
||||
$expectedVars = get_object_vars($expected);
|
||||
$actualVars = get_object_vars($actual);
|
||||
$this->assertSame(count($expectedVars), count($actualVars), $err);
|
||||
$this->assertEquals(array_keys($expectedVars), array_keys($actualVars), $err);
|
||||
|
||||
foreach ($expectedVars as $name => $expectedValue) {
|
||||
$actualValue = $actualVars[$name];
|
||||
$tmpPath = $path;
|
||||
$tmpPath[] = $name;
|
||||
$err = "Mismatch at AST path: " . implode(', ', $tmpPath);
|
||||
|
||||
if ($expectedValue instanceof Node) {
|
||||
$this->assertNodesAreEqual($expectedValue, $actualValue, $tmpPath);
|
||||
} else if ($expectedValue instanceof NodeList) {
|
||||
$this->assertEquals(count($expectedValue), count($actualValue), $err);
|
||||
$this->assertInstanceOf(NodeList::class, $actualValue, $err);
|
||||
|
||||
foreach ($expectedValue as $index => $listNode) {
|
||||
$tmpPath2 = $tmpPath;
|
||||
$tmpPath2 [] = $index;
|
||||
$this->assertNodesAreEqual($listNode, $actualValue[$index], $tmpPath2);
|
||||
}
|
||||
} else if ($expectedValue instanceof Location) {
|
||||
$this->assertInstanceOf(Location::class, $actualValue, $err);
|
||||
$this->assertSame($expectedValue->start, $actualValue->start, $err);
|
||||
$this->assertSame($expectedValue->end, $actualValue->end, $err);
|
||||
} else {
|
||||
$this->assertEquals($expectedValue, $actualValue, $err);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -4,6 +4,7 @@ namespace GraphQL\Tests\Language;
|
||||
|
||||
use GraphQL\Language\AST\Location;
|
||||
use GraphQL\Language\AST\Node;
|
||||
use GraphQL\Language\AST\NodeList;
|
||||
|
||||
class TestUtils
|
||||
{
|
||||
@ -22,7 +23,7 @@ class TestUtils
|
||||
if (isset($result[$prop]))
|
||||
continue;
|
||||
|
||||
if (is_array($propValue)) {
|
||||
if (is_array($propValue) || $propValue instanceof NodeList) {
|
||||
$tmp = [];
|
||||
foreach ($propValue as $tmp1) {
|
||||
$tmp[] = $tmp1 instanceof Node ? self::nodeToArray($tmp1) : (array) $tmp1;
|
||||
|
@ -6,6 +6,7 @@ use GraphQL\Language\AST\FieldNode;
|
||||
use GraphQL\Language\AST\NameNode;
|
||||
use GraphQL\Language\AST\Node;
|
||||
use GraphQL\Language\AST\NodeKind;
|
||||
use GraphQL\Language\AST\NodeList;
|
||||
use GraphQL\Language\AST\OperationDefinitionNode;
|
||||
use GraphQL\Language\AST\SelectionSetNode;
|
||||
use GraphQL\Language\Parser;
|
||||
@ -157,7 +158,7 @@ class VisitorTest extends \PHPUnit_Framework_TestCase
|
||||
if ($node instanceof FieldNode && $node->name->value === 'a') {
|
||||
return new FieldNode([
|
||||
'selectionSet' => new SelectionSetNode(array(
|
||||
'selections' => array_merge([$addedField], $node->selectionSet->selections)
|
||||
'selections' => NodeList::create([$addedField])->merge($node->selectionSet->selections)
|
||||
))
|
||||
]);
|
||||
}
|
||||
|
1280
tests/Language/kitchen-sink.ast
Normal file
1280
tests/Language/kitchen-sink.ast
Normal file
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user