mirror of
https://github.com/retailcrm/graphql-php.git
synced 2025-02-06 07:49:24 +03:00
Add experimental support for parsing variable definitions in fragments
ref: graphql/graphql-js#1141
This commit is contained in:
parent
fde7df534d
commit
949b853678
@ -10,13 +10,21 @@ class FragmentDefinitionNode extends Node implements ExecutableDefinitionNode, H
|
|||||||
*/
|
*/
|
||||||
public $name;
|
public $name;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Note: fragment variable definitions are experimental and may be changed
|
||||||
|
* or removed in the future.
|
||||||
|
*
|
||||||
|
* @var VariableDefinitionNode[]|NodeList
|
||||||
|
*/
|
||||||
|
public $variableDefinitions;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var NamedTypeNode
|
* @var NamedTypeNode
|
||||||
*/
|
*/
|
||||||
public $typeCondition;
|
public $typeCondition;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var DirectiveNode[]
|
* @var DirectiveNode[]|NodeList
|
||||||
*/
|
*/
|
||||||
public $directives;
|
public $directives;
|
||||||
|
|
||||||
|
@ -49,7 +49,6 @@ use GraphQL\Language\AST\UnionTypeExtensionNode;
|
|||||||
use GraphQL\Language\AST\VariableNode;
|
use GraphQL\Language\AST\VariableNode;
|
||||||
use GraphQL\Language\AST\VariableDefinitionNode;
|
use GraphQL\Language\AST\VariableDefinitionNode;
|
||||||
use GraphQL\Error\SyntaxError;
|
use GraphQL\Error\SyntaxError;
|
||||||
use GraphQL\Type\TypeKind;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parses string containing GraphQL query or [type definition](type-system/type-language.md) to Abstract Syntax Tree.
|
* Parses string containing GraphQL query or [type definition](type-system/type-language.md) to Abstract Syntax Tree.
|
||||||
@ -67,10 +66,25 @@ class Parser
|
|||||||
* in the source that they correspond to. This configuration flag
|
* in the source that they correspond to. This configuration flag
|
||||||
* disables that behavior for performance or testing.)
|
* disables that behavior for performance or testing.)
|
||||||
*
|
*
|
||||||
|
* experimentalFragmentVariables: boolean,
|
||||||
|
* (If enabled, the parser will understand and parse variable definitions
|
||||||
|
* contained in a fragment definition. They'll be represented in the
|
||||||
|
* `variableDefinitions` field of the FragmentDefinitionNode.
|
||||||
|
*
|
||||||
|
* The syntax is identical to normal, query-defined variables. For example:
|
||||||
|
*
|
||||||
|
* fragment A($var: Boolean = false) on T {
|
||||||
|
* ...
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* Note: this feature is experimental and may change or be removed in the
|
||||||
|
* future.)
|
||||||
|
*
|
||||||
* @api
|
* @api
|
||||||
* @param Source|string $source
|
* @param Source|string $source
|
||||||
* @param array $options
|
* @param array $options
|
||||||
* @return DocumentNode
|
* @return DocumentNode
|
||||||
|
* @throws SyntaxError
|
||||||
*/
|
*/
|
||||||
public static function parse($source, array $options = [])
|
public static function parse($source, array $options = [])
|
||||||
{
|
{
|
||||||
@ -639,11 +653,19 @@ class Parser
|
|||||||
$this->expectKeyword('fragment');
|
$this->expectKeyword('fragment');
|
||||||
|
|
||||||
$name = $this->parseFragmentName();
|
$name = $this->parseFragmentName();
|
||||||
|
|
||||||
|
// Experimental support for defining variables within fragments changes
|
||||||
|
// the grammar of FragmentDefinition:
|
||||||
|
// - fragment FragmentName VariableDefinitions? on TypeCondition Directives? SelectionSet
|
||||||
|
$variableDefinitions = null;
|
||||||
|
if (isset($this->lexer->options['experimentalFragmentVariables'])) {
|
||||||
|
$variableDefinitions = $this->parseVariableDefinitions();
|
||||||
|
}
|
||||||
$this->expectKeyword('on');
|
$this->expectKeyword('on');
|
||||||
$typeCondition = $this->parseNamedType();
|
$typeCondition = $this->parseNamedType();
|
||||||
|
|
||||||
return new FragmentDefinitionNode([
|
return new FragmentDefinitionNode([
|
||||||
'name' => $name,
|
'name' => $name,
|
||||||
|
'variableDefinitions' => $variableDefinitions,
|
||||||
'typeCondition' => $typeCondition,
|
'typeCondition' => $typeCondition,
|
||||||
'directives' => $this->parseDirectives(false),
|
'directives' => $this->parseDirectives(false),
|
||||||
'selectionSet' => $this->parseSelectionSet(),
|
'selectionSet' => $this->parseSelectionSet(),
|
||||||
|
@ -131,7 +131,11 @@ class Printer
|
|||||||
], ' ');
|
], ' ');
|
||||||
},
|
},
|
||||||
NodeKind::FRAGMENT_DEFINITION => function(FragmentDefinitionNode $node) {
|
NodeKind::FRAGMENT_DEFINITION => function(FragmentDefinitionNode $node) {
|
||||||
return "fragment {$node->name} on {$node->typeCondition} "
|
// Note: fragment variable definitions are experimental and may be changed
|
||||||
|
// or removed in the future.
|
||||||
|
return "fragment {$node->name}"
|
||||||
|
. $this->wrap('(', $this->join($node->variableDefinitions, ', '), ')')
|
||||||
|
. " on {$node->typeCondition} "
|
||||||
. $this->wrap('', $this->join($node->directives, ' '), ' ')
|
. $this->wrap('', $this->join($node->directives, ' '), ' ')
|
||||||
. $node->selectionSet;
|
. $node->selectionSet;
|
||||||
},
|
},
|
||||||
|
@ -115,7 +115,15 @@ class Visitor
|
|||||||
NodeKind::ARGUMENT => ['name', 'value'],
|
NodeKind::ARGUMENT => ['name', 'value'],
|
||||||
NodeKind::FRAGMENT_SPREAD => ['name', 'directives'],
|
NodeKind::FRAGMENT_SPREAD => ['name', 'directives'],
|
||||||
NodeKind::INLINE_FRAGMENT => ['typeCondition', 'directives', 'selectionSet'],
|
NodeKind::INLINE_FRAGMENT => ['typeCondition', 'directives', 'selectionSet'],
|
||||||
NodeKind::FRAGMENT_DEFINITION => ['name', 'typeCondition', 'directives', 'selectionSet'],
|
NodeKind::FRAGMENT_DEFINITION => [
|
||||||
|
'name',
|
||||||
|
// Note: fragment variable definitions are experimental and may be changed
|
||||||
|
// or removed in the future.
|
||||||
|
'variableDefinitions',
|
||||||
|
'typeCondition',
|
||||||
|
'directives',
|
||||||
|
'selectionSet'
|
||||||
|
],
|
||||||
|
|
||||||
NodeKind::INT => [],
|
NodeKind::INT => [],
|
||||||
NodeKind::FLOAT => [],
|
NodeKind::FLOAT => [],
|
||||||
|
@ -453,10 +453,23 @@ fragment $fragmentName on Type {
|
|||||||
$this->assertEquals(null, $result->loc);
|
$this->assertEquals(null, $result->loc);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @it Experimental: allows parsing fragment defined variables
|
||||||
|
*/
|
||||||
|
public function testExperimentalAllowsParsingFragmentDefinedVariables()
|
||||||
|
{
|
||||||
|
$source = new Source('fragment a($v: Boolean = false) on t { f(v: $v) }');
|
||||||
|
// not throw
|
||||||
|
Parser::parse($source, ['experimentalFragmentVariables' => true]);
|
||||||
|
|
||||||
|
$this->setExpectedException(SyntaxError::class);
|
||||||
|
Parser::parse($source);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @it contains location information that only stringifys start/end
|
* @it contains location information that only stringifys start/end
|
||||||
*/
|
*/
|
||||||
public function testConvertToArray()
|
public function testContainsLocationInformationThatOnlyStringifysStartEnd()
|
||||||
{
|
{
|
||||||
$source = new Source('{ id }');
|
$source = new Source('{ id }');
|
||||||
$result = Parser::parse($source);
|
$result = Parser::parse($source);
|
||||||
|
@ -132,6 +132,28 @@ class PrinterTest extends \PHPUnit_Framework_TestCase
|
|||||||
$this->assertEquals($expected, Printer::doPrint($mutationAstWithArtifacts));
|
$this->assertEquals($expected, Printer::doPrint($mutationAstWithArtifacts));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @it Experimental: correctly prints fragment defined variables
|
||||||
|
*/
|
||||||
|
public function testExperimentalCorrectlyPrintsFragmentDefinedVariables()
|
||||||
|
{
|
||||||
|
$fragmentWithVariable = Parser::parse('
|
||||||
|
fragment Foo($a: ComplexType, $b: Boolean = false) on TestType {
|
||||||
|
id
|
||||||
|
}
|
||||||
|
',
|
||||||
|
['experimentalFragmentVariables' => true]
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->assertEquals(
|
||||||
|
Printer::doPrint($fragmentWithVariable),
|
||||||
|
'fragment Foo($a: ComplexType, $b: Boolean = false) on TestType {
|
||||||
|
id
|
||||||
|
}
|
||||||
|
'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @it correctly prints single-line with leading space and quotation
|
* @it correctly prints single-line with leading space and quotation
|
||||||
*/
|
*/
|
||||||
|
@ -326,6 +326,60 @@ class VisitorTest extends \PHPUnit_Framework_TestCase
|
|||||||
$this->assertEquals($expected, $visited);
|
$this->assertEquals($expected, $visited);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @it Experimental: visits variables defined in fragments
|
||||||
|
*/
|
||||||
|
public function testExperimentalVisitsVariablesDefinedInFragments()
|
||||||
|
{
|
||||||
|
$ast = Parser::parse(
|
||||||
|
'fragment a($v: Boolean = false) on t { f }',
|
||||||
|
['experimentalFragmentVariables' => true]
|
||||||
|
);
|
||||||
|
$visited = [];
|
||||||
|
|
||||||
|
Visitor::visit($ast, [
|
||||||
|
'enter' => function($node) use (&$visited) {
|
||||||
|
$visited[] = ['enter', $node->kind, isset($node->value) ? $node->value : null];
|
||||||
|
},
|
||||||
|
'leave' => function($node) use (&$visited) {
|
||||||
|
$visited[] = ['leave', $node->kind, isset($node->value) ? $node->value : null];
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
$expected = [
|
||||||
|
['enter', 'Document', null],
|
||||||
|
['enter', 'FragmentDefinition', null],
|
||||||
|
['enter', 'Name', 'a'],
|
||||||
|
['leave', 'Name', 'a'],
|
||||||
|
['enter', 'VariableDefinition', null],
|
||||||
|
['enter', 'Variable', null],
|
||||||
|
['enter', 'Name', 'v'],
|
||||||
|
['leave', 'Name', 'v'],
|
||||||
|
['leave', 'Variable', null],
|
||||||
|
['enter', 'NamedType', null],
|
||||||
|
['enter', 'Name', 'Boolean'],
|
||||||
|
['leave', 'Name', 'Boolean'],
|
||||||
|
['leave', 'NamedType', null],
|
||||||
|
['enter', 'BooleanValue', false],
|
||||||
|
['leave', 'BooleanValue', false],
|
||||||
|
['leave', 'VariableDefinition', null],
|
||||||
|
['enter', 'NamedType', null],
|
||||||
|
['enter', 'Name', 't'],
|
||||||
|
['leave', 'Name', 't'],
|
||||||
|
['leave', 'NamedType', null],
|
||||||
|
['enter', 'SelectionSet', null],
|
||||||
|
['enter', 'Field', null],
|
||||||
|
['enter', 'Name', 'f'],
|
||||||
|
['leave', 'Name', 'f'],
|
||||||
|
['leave', 'Field', null],
|
||||||
|
['leave', 'SelectionSet', null],
|
||||||
|
['leave', 'FragmentDefinition', null],
|
||||||
|
['leave', 'Document', null],
|
||||||
|
];
|
||||||
|
|
||||||
|
$this->assertEquals($expected, $visited);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @it visits kitchen sink
|
* @it visits kitchen sink
|
||||||
*/
|
*/
|
||||||
|
Loading…
x
Reference in New Issue
Block a user