diff --git a/src/Executor/Values.php b/src/Executor/Values.php index e30f0c2..f57d217 100644 --- a/src/Executor/Values.php +++ b/src/Executor/Values.php @@ -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; }); diff --git a/src/Language/AST/Location.php b/src/Language/AST/Location.php index 3cd5ad5..4f0581e 100644 --- a/src/Language/AST/Location.php +++ b/src/Language/AST/Location.php @@ -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->start = $startToken->start; - $this->end = $endToken->end; $this->source = $source; + + if ($startToken && $endToken) { + $this->start = $startToken->start; + $this->end = $endToken->end; + } } } diff --git a/src/Language/AST/Node.php b/src/Language/AST/Node.php index 0ea1b5b..9bf0936 100644 --- a/src/Language/AST/Node.php +++ b/src/Language/AST/Node.php @@ -1,6 +1,7 @@ '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) { - Utils::assign($this, $vars); + if (!empty($vars)) { + Utils::assign($this, $vars); + } } /** @@ -91,34 +149,53 @@ abstract class Node */ public function toArray($recursive = false) { - $tmp = (array) $this; - - $tmp['loc'] = [ - 'start' => $this->loc->start, - 'end' => $this->loc->end - ]; - if ($recursive) { - $this->recursiveToArray($tmp); - } + return $this->recursiveToArray($this); + } else { + $tmp = (array) $this; - return $tmp; + $tmp['loc'] = [ + 'start' => $this->loc->start, + 'end' => $this->loc->end + ]; + + 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; } } diff --git a/src/Language/AST/NodeKind.php b/src/Language/AST/NodeKind.php index f3fc465..e797618 100644 --- a/src/Language/AST/NodeKind.php +++ b/src/Language/AST/NodeKind.php @@ -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 + ]; } diff --git a/src/Language/AST/NodeList.php b/src/Language/AST/NodeList.php new file mode 100644 index 0000000..e470ec7 --- /dev/null +++ b/src/Language/AST/NodeList.php @@ -0,0 +1,119 @@ +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); + } +} diff --git a/src/Language/Parser.php b/src/Language/Parser.php index 76c5d1b..b87d434 100644 --- a/src/Language/Parser.php +++ b/src/Language/Parser.php @@ -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) ]); @@ -408,13 +409,13 @@ class Parser */ function parseVariableDefinitions() { - return $this->peek(Token::PAREN_L) ? - $this->many( - Token::PAREN_L, - [$this, 'parseVariableDefinition'], - Token::PAREN_R - ) : - []; + return $this->peek(Token::PAREN_L) ? + $this->many( + Token::PAREN_L, + [$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); } /** diff --git a/src/Language/Printer.php b/src/Language/Printer.php index 9553273..ac4d7f7 100644 --- a/src/Language/Printer.php +++ b/src/Language/Printer.php @@ -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;} ) diff --git a/src/Language/Visitor.php b/src/Language/Visitor.php index 56a0ad8..b7b3743 100644 --- a/src/Language/Visitor.php +++ b/src/Language/Visitor.php @@ -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) { - array_splice($node, $editKey, 1); + 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; diff --git a/src/Utils/Utils.php b/src/Utils/Utils.php index 5913562..ee518c6 100644 --- a/src/Utils/Utils.php +++ b/src/Utils/Utils.php @@ -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; - }, []); + } + return $map; } /** diff --git a/src/Validator/Rules/OverlappingFieldsCanBeMerged.php b/src/Validator/Rules/OverlappingFieldsCanBeMerged.php index 8bc52d0..db907c9 100644 --- a/src/Validator/Rules/OverlappingFieldsCanBeMerged.php +++ b/src/Validator/Rules/OverlappingFieldsCanBeMerged.php @@ -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; diff --git a/src/Validator/ValidationContext.php b/src/Validator/ValidationContext.php index 0e20399..7e94707 100644 --- a/src/Validator/ValidationContext.php +++ b/src/Validator/ValidationContext.php @@ -131,13 +131,13 @@ class ValidationContext { $fragments = $this->fragments; if (!$fragments) { - $this->fragments = $fragments = - array_reduce($this->getDocument()->definitions, function($frags, $statement) { - if ($statement->kind === NodeKind::FRAGMENT_DEFINITION) { - $frags[$statement->name->value] = $statement; - } - return $frags; - }, []); + $fragments = []; + foreach ($this->getDocument()->definitions as $statement) { + if ($statement->kind === NodeKind::FRAGMENT_DEFINITION) { + $fragments[$statement->name->value] = $statement; + } + } + $this->fragments = $fragments; } return isset($fragments[$name]) ? $fragments[$name] : null; } diff --git a/tests/Language/ParserTest.php b/tests/Language/ParserTest.php index d829efc..4b2f01c 100644 --- a/tests/Language/ParserTest.php +++ b/tests/Language/ParserTest.php @@ -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); diff --git a/tests/Language/SerializationTest.php b/tests/Language/SerializationTest.php new file mode 100644 index 0000000..122423b --- /dev/null +++ b/tests/Language/SerializationTest.php @@ -0,0 +1,73 @@ +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); + } + } + } +} diff --git a/tests/Language/TestUtils.php b/tests/Language/TestUtils.php index 96999ad..b36377c 100644 --- a/tests/Language/TestUtils.php +++ b/tests/Language/TestUtils.php @@ -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; diff --git a/tests/Language/VisitorTest.php b/tests/Language/VisitorTest.php index 162e166..2a85663 100644 --- a/tests/Language/VisitorTest.php +++ b/tests/Language/VisitorTest.php @@ -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) )) ]); } diff --git a/tests/Language/kitchen-sink.ast b/tests/Language/kitchen-sink.ast new file mode 100644 index 0000000..2d24cb0 --- /dev/null +++ b/tests/Language/kitchen-sink.ast @@ -0,0 +1,1280 @@ +{ + "kind": "Document", + "loc": { + "start": 0, + "end": 1087 + }, + "definitions": [ + { + "kind": "OperationDefinition", + "loc": { + "start": 288, + "end": 645 + }, + "name": { + "kind": "Name", + "loc": { + "start": 294, + "end": 303 + }, + "value": "queryName" + }, + "operation": "query", + "variableDefinitions": [ + { + "kind": "VariableDefinition", + "loc": { + "start": 304, + "end": 321 + }, + "variable": { + "kind": "Variable", + "loc": { + "start": 304, + "end": 308 + }, + "name": { + "kind": "Name", + "loc": { + "start": 305, + "end": 308 + }, + "value": "foo" + } + }, + "type": { + "kind": "NamedType", + "loc": { + "start": 310, + "end": 321 + }, + "name": { + "kind": "Name", + "loc": { + "start": 310, + "end": 321 + }, + "value": "ComplexType" + } + }, + "defaultValue": null + }, + { + "kind": "VariableDefinition", + "loc": { + "start": 323, + "end": 343 + }, + "variable": { + "kind": "Variable", + "loc": { + "start": 323, + "end": 328 + }, + "name": { + "kind": "Name", + "loc": { + "start": 324, + "end": 328 + }, + "value": "site" + } + }, + "type": { + "kind": "NamedType", + "loc": { + "start": 330, + "end": 334 + }, + "name": { + "kind": "Name", + "loc": { + "start": 330, + "end": 334 + }, + "value": "Site" + } + }, + "defaultValue": { + "kind": "EnumValue", + "loc": { + "start": 337, + "end": 343 + }, + "value": "MOBILE" + } + } + ], + "directives": [], + "selectionSet": { + "kind": "SelectionSet", + "loc": { + "start": 345, + "end": 645 + }, + "selections": [ + { + "kind": "Field", + "loc": { + "start": 349, + "end": 643 + }, + "name": { + "kind": "Name", + "loc": { + "start": 363, + "end": 367 + }, + "value": "node" + }, + "alias": { + "kind": "Name", + "loc": { + "start": 349, + "end": 361 + }, + "value": "whoever123is" + }, + "arguments": [ + { + "kind": "Argument", + "loc": { + "start": 368, + "end": 382 + }, + "value": { + "kind": "ListValue", + "loc": { + "start": 372, + "end": 382 + }, + "values": [ + { + "kind": "IntValue", + "loc": { + "start": 373, + "end": 376 + }, + "value": "123" + }, + { + "kind": "IntValue", + "loc": { + "start": 378, + "end": 381 + }, + "value": "456" + } + ] + }, + "name": { + "kind": "Name", + "loc": { + "start": 368, + "end": 370 + }, + "value": "id" + } + } + ], + "directives": [], + "selectionSet": { + "kind": "SelectionSet", + "loc": { + "start": 384, + "end": 643 + }, + "selections": [ + { + "kind": "Field", + "loc": { + "start": 390, + "end": 392 + }, + "name": { + "kind": "Name", + "loc": { + "start": 390, + "end": 392 + }, + "value": "id" + }, + "alias": null, + "arguments": [], + "directives": [], + "selectionSet": null + }, + { + "kind": "InlineFragment", + "loc": { + "start": 399, + "end": 569 + }, + "typeCondition": { + "kind": "NamedType", + "loc": { + "start": 406, + "end": 410 + }, + "name": { + "kind": "Name", + "loc": { + "start": 406, + "end": 410 + }, + "value": "User" + } + }, + "directives": [ + { + "kind": "Directive", + "loc": { + "start": 411, + "end": 417 + }, + "name": { + "kind": "Name", + "loc": { + "start": 412, + "end": 417 + }, + "value": "defer" + }, + "arguments": [] + } + ], + "selectionSet": { + "kind": "SelectionSet", + "loc": { + "start": 418, + "end": 569 + }, + "selections": [ + { + "kind": "Field", + "loc": { + "start": 426, + "end": 563 + }, + "name": { + "kind": "Name", + "loc": { + "start": 426, + "end": 432 + }, + "value": "field2" + }, + "alias": null, + "arguments": [], + "directives": [], + "selectionSet": { + "kind": "SelectionSet", + "loc": { + "start": 433, + "end": 563 + }, + "selections": [ + { + "kind": "Field", + "loc": { + "start": 443, + "end": 445 + }, + "name": { + "kind": "Name", + "loc": { + "start": 443, + "end": 445 + }, + "value": "id" + }, + "alias": null, + "arguments": [], + "directives": [], + "selectionSet": null + }, + { + "kind": "Field", + "loc": { + "start": 456, + "end": 555 + }, + "name": { + "kind": "Name", + "loc": { + "start": 463, + "end": 469 + }, + "value": "field1" + }, + "alias": { + "kind": "Name", + "loc": { + "start": 456, + "end": 461 + }, + "value": "alias" + }, + "arguments": [ + { + "kind": "Argument", + "loc": { + "start": 470, + "end": 478 + }, + "value": { + "kind": "IntValue", + "loc": { + "start": 476, + "end": 478 + }, + "value": "10" + }, + "name": { + "kind": "Name", + "loc": { + "start": 470, + "end": 475 + }, + "value": "first" + } + }, + { + "kind": "Argument", + "loc": { + "start": 480, + "end": 490 + }, + "value": { + "kind": "Variable", + "loc": { + "start": 486, + "end": 490 + }, + "name": { + "kind": "Name", + "loc": { + "start": 487, + "end": 490 + }, + "value": "foo" + } + }, + "name": { + "kind": "Name", + "loc": { + "start": 480, + "end": 485 + }, + "value": "after" + } + } + ], + "directives": [ + { + "kind": "Directive", + "loc": { + "start": 493, + "end": 511 + }, + "name": { + "kind": "Name", + "loc": { + "start": 494, + "end": 501 + }, + "value": "include" + }, + "arguments": [ + { + "kind": "Argument", + "loc": { + "start": 502, + "end": 510 + }, + "value": { + "kind": "Variable", + "loc": { + "start": 506, + "end": 510 + }, + "name": { + "kind": "Name", + "loc": { + "start": 507, + "end": 510 + }, + "value": "foo" + } + }, + "name": { + "kind": "Name", + "loc": { + "start": 502, + "end": 504 + }, + "value": "if" + } + } + ] + } + ], + "selectionSet": { + "kind": "SelectionSet", + "loc": { + "start": 512, + "end": 555 + }, + "selections": [ + { + "kind": "Field", + "loc": { + "start": 524, + "end": 526 + }, + "name": { + "kind": "Name", + "loc": { + "start": 524, + "end": 526 + }, + "value": "id" + }, + "alias": null, + "arguments": [], + "directives": [], + "selectionSet": null + }, + { + "kind": "FragmentSpread", + "loc": { + "start": 538, + "end": 545 + }, + "name": { + "kind": "Name", + "loc": { + "start": 541, + "end": 545 + }, + "value": "frag" + }, + "directives": [] + } + ] + } + } + ] + } + } + ] + } + }, + { + "kind": "InlineFragment", + "loc": { + "start": 574, + "end": 614 + }, + "typeCondition": null, + "directives": [ + { + "kind": "Directive", + "loc": { + "start": 578, + "end": 597 + }, + "name": { + "kind": "Name", + "loc": { + "start": 579, + "end": 583 + }, + "value": "skip" + }, + "arguments": [ + { + "kind": "Argument", + "loc": { + "start": 584, + "end": 596 + }, + "value": { + "kind": "Variable", + "loc": { + "start": 592, + "end": 596 + }, + "name": { + "kind": "Name", + "loc": { + "start": 593, + "end": 596 + }, + "value": "foo" + } + }, + "name": { + "kind": "Name", + "loc": { + "start": 584, + "end": 590 + }, + "value": "unless" + } + } + ] + } + ], + "selectionSet": { + "kind": "SelectionSet", + "loc": { + "start": 598, + "end": 614 + }, + "selections": [ + { + "kind": "Field", + "loc": { + "start": 606, + "end": 608 + }, + "name": { + "kind": "Name", + "loc": { + "start": 606, + "end": 608 + }, + "value": "id" + }, + "alias": null, + "arguments": [], + "directives": [], + "selectionSet": null + } + ] + } + }, + { + "kind": "InlineFragment", + "loc": { + "start": 619, + "end": 639 + }, + "typeCondition": null, + "directives": [], + "selectionSet": { + "kind": "SelectionSet", + "loc": { + "start": 623, + "end": 639 + }, + "selections": [ + { + "kind": "Field", + "loc": { + "start": 631, + "end": 633 + }, + "name": { + "kind": "Name", + "loc": { + "start": 631, + "end": 633 + }, + "value": "id" + }, + "alias": null, + "arguments": [], + "directives": [], + "selectionSet": null + } + ] + } + } + ] + } + } + ] + } + }, + { + "kind": "OperationDefinition", + "loc": { + "start": 647, + "end": 728 + }, + "name": { + "kind": "Name", + "loc": { + "start": 656, + "end": 665 + }, + "value": "likeStory" + }, + "operation": "mutation", + "variableDefinitions": [], + "directives": [], + "selectionSet": { + "kind": "SelectionSet", + "loc": { + "start": 666, + "end": 728 + }, + "selections": [ + { + "kind": "Field", + "loc": { + "start": 670, + "end": 726 + }, + "name": { + "kind": "Name", + "loc": { + "start": 670, + "end": 674 + }, + "value": "like" + }, + "alias": null, + "arguments": [ + { + "kind": "Argument", + "loc": { + "start": 675, + "end": 685 + }, + "value": { + "kind": "IntValue", + "loc": { + "start": 682, + "end": 685 + }, + "value": "123" + }, + "name": { + "kind": "Name", + "loc": { + "start": 675, + "end": 680 + }, + "value": "story" + } + } + ], + "directives": [ + { + "kind": "Directive", + "loc": { + "start": 687, + "end": 693 + }, + "name": { + "kind": "Name", + "loc": { + "start": 688, + "end": 693 + }, + "value": "defer" + }, + "arguments": [] + } + ], + "selectionSet": { + "kind": "SelectionSet", + "loc": { + "start": 694, + "end": 726 + }, + "selections": [ + { + "kind": "Field", + "loc": { + "start": 700, + "end": 722 + }, + "name": { + "kind": "Name", + "loc": { + "start": 700, + "end": 705 + }, + "value": "story" + }, + "alias": null, + "arguments": [], + "directives": [], + "selectionSet": { + "kind": "SelectionSet", + "loc": { + "start": 706, + "end": 722 + }, + "selections": [ + { + "kind": "Field", + "loc": { + "start": 714, + "end": 716 + }, + "name": { + "kind": "Name", + "loc": { + "start": 714, + "end": 716 + }, + "value": "id" + }, + "alias": null, + "arguments": [], + "directives": [], + "selectionSet": null + } + ] + } + } + ] + } + } + ] + } + }, + { + "kind": "OperationDefinition", + "loc": { + "start": 730, + "end": 940 + }, + "name": { + "kind": "Name", + "loc": { + "start": 743, + "end": 764 + }, + "value": "StoryLikeSubscription" + }, + "operation": "subscription", + "variableDefinitions": [ + { + "kind": "VariableDefinition", + "loc": { + "start": 765, + "end": 796 + }, + "variable": { + "kind": "Variable", + "loc": { + "start": 765, + "end": 771 + }, + "name": { + "kind": "Name", + "loc": { + "start": 766, + "end": 771 + }, + "value": "input" + } + }, + "type": { + "kind": "NamedType", + "loc": { + "start": 773, + "end": 796 + }, + "name": { + "kind": "Name", + "loc": { + "start": 773, + "end": 796 + }, + "value": "StoryLikeSubscribeInput" + } + }, + "defaultValue": null + } + ], + "directives": [], + "selectionSet": { + "kind": "SelectionSet", + "loc": { + "start": 798, + "end": 940 + }, + "selections": [ + { + "kind": "Field", + "loc": { + "start": 802, + "end": 938 + }, + "name": { + "kind": "Name", + "loc": { + "start": 802, + "end": 820 + }, + "value": "storyLikeSubscribe" + }, + "alias": null, + "arguments": [ + { + "kind": "Argument", + "loc": { + "start": 821, + "end": 834 + }, + "value": { + "kind": "Variable", + "loc": { + "start": 828, + "end": 834 + }, + "name": { + "kind": "Name", + "loc": { + "start": 829, + "end": 834 + }, + "value": "input" + } + }, + "name": { + "kind": "Name", + "loc": { + "start": 821, + "end": 826 + }, + "value": "input" + } + } + ], + "directives": [], + "selectionSet": { + "kind": "SelectionSet", + "loc": { + "start": 836, + "end": 938 + }, + "selections": [ + { + "kind": "Field", + "loc": { + "start": 842, + "end": 934 + }, + "name": { + "kind": "Name", + "loc": { + "start": 842, + "end": 847 + }, + "value": "story" + }, + "alias": null, + "arguments": [], + "directives": [], + "selectionSet": { + "kind": "SelectionSet", + "loc": { + "start": 848, + "end": 934 + }, + "selections": [ + { + "kind": "Field", + "loc": { + "start": 856, + "end": 886 + }, + "name": { + "kind": "Name", + "loc": { + "start": 856, + "end": 862 + }, + "value": "likers" + }, + "alias": null, + "arguments": [], + "directives": [], + "selectionSet": { + "kind": "SelectionSet", + "loc": { + "start": 863, + "end": 886 + }, + "selections": [ + { + "kind": "Field", + "loc": { + "start": 873, + "end": 878 + }, + "name": { + "kind": "Name", + "loc": { + "start": 873, + "end": 878 + }, + "value": "count" + }, + "alias": null, + "arguments": [], + "directives": [], + "selectionSet": null + } + ] + } + }, + { + "kind": "Field", + "loc": { + "start": 893, + "end": 928 + }, + "name": { + "kind": "Name", + "loc": { + "start": 893, + "end": 905 + }, + "value": "likeSentence" + }, + "alias": null, + "arguments": [], + "directives": [], + "selectionSet": { + "kind": "SelectionSet", + "loc": { + "start": 906, + "end": 928 + }, + "selections": [ + { + "kind": "Field", + "loc": { + "start": 916, + "end": 920 + }, + "name": { + "kind": "Name", + "loc": { + "start": 916, + "end": 920 + }, + "value": "text" + }, + "alias": null, + "arguments": [], + "directives": [], + "selectionSet": null + } + ] + } + } + ] + } + } + ] + } + } + ] + } + }, + { + "kind": "FragmentDefinition", + "loc": { + "start": 942, + "end": 1018 + }, + "name": { + "kind": "Name", + "loc": { + "start": 951, + "end": 955 + }, + "value": "frag" + }, + "typeCondition": { + "kind": "NamedType", + "loc": { + "start": 959, + "end": 965 + }, + "name": { + "kind": "Name", + "loc": { + "start": 959, + "end": 965 + }, + "value": "Friend" + } + }, + "directives": [], + "selectionSet": { + "kind": "SelectionSet", + "loc": { + "start": 966, + "end": 1018 + }, + "selections": [ + { + "kind": "Field", + "loc": { + "start": 970, + "end": 1016 + }, + "name": { + "kind": "Name", + "loc": { + "start": 970, + "end": 973 + }, + "value": "foo" + }, + "alias": null, + "arguments": [ + { + "kind": "Argument", + "loc": { + "start": 974, + "end": 985 + }, + "value": { + "kind": "Variable", + "loc": { + "start": 980, + "end": 985 + }, + "name": { + "kind": "Name", + "loc": { + "start": 981, + "end": 985 + }, + "value": "size" + } + }, + "name": { + "kind": "Name", + "loc": { + "start": 974, + "end": 978 + }, + "value": "size" + } + }, + { + "kind": "Argument", + "loc": { + "start": 987, + "end": 994 + }, + "value": { + "kind": "Variable", + "loc": { + "start": 992, + "end": 994 + }, + "name": { + "kind": "Name", + "loc": { + "start": 993, + "end": 994 + }, + "value": "b" + } + }, + "name": { + "kind": "Name", + "loc": { + "start": 987, + "end": 990 + }, + "value": "bar" + } + }, + { + "kind": "Argument", + "loc": { + "start": 996, + "end": 1015 + }, + "value": { + "kind": "ObjectValue", + "loc": { + "start": 1001, + "end": 1015 + }, + "fields": [ + { + "kind": "ObjectField", + "loc": { + "start": 1002, + "end": 1014 + }, + "name": { + "kind": "Name", + "loc": { + "start": 1002, + "end": 1005 + }, + "value": "key" + }, + "value": { + "kind": "StringValue", + "loc": { + "start": 1007, + "end": 1014 + }, + "value": "value" + } + } + ] + }, + "name": { + "kind": "Name", + "loc": { + "start": 996, + "end": 999 + }, + "value": "obj" + } + } + ], + "directives": [], + "selectionSet": null + } + ] + } + }, + { + "kind": "OperationDefinition", + "loc": { + "start": 1020, + "end": 1086 + }, + "name": null, + "operation": "query", + "variableDefinitions": null, + "directives": [], + "selectionSet": { + "kind": "SelectionSet", + "loc": { + "start": 1020, + "end": 1086 + }, + "selections": [ + { + "kind": "Field", + "loc": { + "start": 1024, + "end": 1075 + }, + "name": { + "kind": "Name", + "loc": { + "start": 1024, + "end": 1031 + }, + "value": "unnamed" + }, + "alias": null, + "arguments": [ + { + "kind": "Argument", + "loc": { + "start": 1032, + "end": 1044 + }, + "value": { + "kind": "BooleanValue", + "loc": { + "start": 1040, + "end": 1044 + }, + "value": true + }, + "name": { + "kind": "Name", + "loc": { + "start": 1032, + "end": 1038 + }, + "value": "truthy" + } + }, + { + "kind": "Argument", + "loc": { + "start": 1046, + "end": 1059 + }, + "value": { + "kind": "BooleanValue", + "loc": { + "start": 1054, + "end": 1059 + }, + "value": false + }, + "name": { + "kind": "Name", + "loc": { + "start": 1046, + "end": 1052 + }, + "value": "falsey" + } + }, + { + "kind": "Argument", + "loc": { + "start": 1061, + "end": 1074 + }, + "value": { + "kind": "NullValue", + "loc": { + "start": 1070, + "end": 1074 + } + }, + "name": { + "kind": "Name", + "loc": { + "start": 1061, + "end": 1068 + }, + "value": "nullish" + } + } + ], + "directives": [], + "selectionSet": null + }, + { + "kind": "Field", + "loc": { + "start": 1079, + "end": 1084 + }, + "name": { + "kind": "Name", + "loc": { + "start": 1079, + "end": 1084 + }, + "value": "query" + }, + "alias": null, + "arguments": [], + "directives": [], + "selectionSet": null + } + ] + } + } + ] +} \ No newline at end of file