1 | <?php |
||||
2 | |||||
3 | declare(strict_types=1); |
||||
4 | |||||
5 | namespace GraphQL\Language; |
||||
6 | |||||
7 | use GraphQL\Error\SyntaxError; |
||||
8 | use GraphQL\Language\AST\ArgumentNode; |
||||
9 | use GraphQL\Language\AST\BooleanValueNode; |
||||
10 | use GraphQL\Language\AST\DefinitionNode; |
||||
11 | use GraphQL\Language\AST\DirectiveDefinitionNode; |
||||
12 | use GraphQL\Language\AST\DirectiveNode; |
||||
13 | use GraphQL\Language\AST\DocumentNode; |
||||
14 | use GraphQL\Language\AST\EnumTypeDefinitionNode; |
||||
15 | use GraphQL\Language\AST\EnumTypeExtensionNode; |
||||
16 | use GraphQL\Language\AST\EnumValueDefinitionNode; |
||||
17 | use GraphQL\Language\AST\EnumValueNode; |
||||
18 | use GraphQL\Language\AST\ExecutableDefinitionNode; |
||||
19 | use GraphQL\Language\AST\FieldDefinitionNode; |
||||
20 | use GraphQL\Language\AST\FieldNode; |
||||
21 | use GraphQL\Language\AST\FloatValueNode; |
||||
22 | use GraphQL\Language\AST\FragmentDefinitionNode; |
||||
23 | use GraphQL\Language\AST\FragmentSpreadNode; |
||||
24 | use GraphQL\Language\AST\InlineFragmentNode; |
||||
25 | use GraphQL\Language\AST\InputObjectTypeDefinitionNode; |
||||
26 | use GraphQL\Language\AST\InputObjectTypeExtensionNode; |
||||
27 | use GraphQL\Language\AST\InputValueDefinitionNode; |
||||
28 | use GraphQL\Language\AST\InterfaceTypeDefinitionNode; |
||||
29 | use GraphQL\Language\AST\InterfaceTypeExtensionNode; |
||||
30 | use GraphQL\Language\AST\IntValueNode; |
||||
31 | use GraphQL\Language\AST\ListTypeNode; |
||||
32 | use GraphQL\Language\AST\ListValueNode; |
||||
33 | use GraphQL\Language\AST\Location; |
||||
34 | use GraphQL\Language\AST\NamedTypeNode; |
||||
35 | use GraphQL\Language\AST\NameNode; |
||||
36 | use GraphQL\Language\AST\Node; |
||||
37 | use GraphQL\Language\AST\NodeList; |
||||
38 | use GraphQL\Language\AST\NonNullTypeNode; |
||||
39 | use GraphQL\Language\AST\NullValueNode; |
||||
40 | use GraphQL\Language\AST\ObjectFieldNode; |
||||
41 | use GraphQL\Language\AST\ObjectTypeDefinitionNode; |
||||
42 | use GraphQL\Language\AST\ObjectTypeExtensionNode; |
||||
43 | use GraphQL\Language\AST\ObjectValueNode; |
||||
44 | use GraphQL\Language\AST\OperationDefinitionNode; |
||||
45 | use GraphQL\Language\AST\OperationTypeDefinitionNode; |
||||
46 | use GraphQL\Language\AST\ScalarTypeDefinitionNode; |
||||
47 | use GraphQL\Language\AST\ScalarTypeExtensionNode; |
||||
48 | use GraphQL\Language\AST\SchemaDefinitionNode; |
||||
49 | use GraphQL\Language\AST\SchemaTypeExtensionNode; |
||||
50 | use GraphQL\Language\AST\SelectionNode; |
||||
51 | use GraphQL\Language\AST\SelectionSetNode; |
||||
52 | use GraphQL\Language\AST\StringValueNode; |
||||
53 | use GraphQL\Language\AST\TypeExtensionNode; |
||||
54 | use GraphQL\Language\AST\TypeNode; |
||||
55 | use GraphQL\Language\AST\TypeSystemDefinitionNode; |
||||
56 | use GraphQL\Language\AST\UnionTypeDefinitionNode; |
||||
57 | use GraphQL\Language\AST\UnionTypeExtensionNode; |
||||
58 | use GraphQL\Language\AST\ValueNode; |
||||
59 | use GraphQL\Language\AST\VariableDefinitionNode; |
||||
60 | use GraphQL\Language\AST\VariableNode; |
||||
61 | use function count; |
||||
62 | use function sprintf; |
||||
63 | |||||
64 | /** |
||||
65 | * Parses string containing GraphQL query or [type definition](type-system/type-language.md) to Abstract Syntax Tree. |
||||
66 | * |
||||
67 | * @method static NameNode name(Source|string $source, bool[] $options = []) |
||||
68 | * @method static DocumentNode document(Source|string $source, bool[] $options = []) |
||||
69 | * @method static ExecutableDefinitionNode|TypeSystemDefinitionNode definition(Source|string $source, bool[] $options = []) |
||||
70 | * @method static ExecutableDefinitionNode executableDefinition(Source|string $source, bool[] $options = []) |
||||
71 | * @method static OperationDefinitionNode operationDefinition(Source|string $source, bool[] $options = []) |
||||
72 | * @method static string operationType(Source|string $source, bool[] $options = []) |
||||
73 | * @method static NodeList<VariableDefinitionNode> variableDefinitions(Source|string $source, bool[] $options = []) |
||||
74 | * @method static VariableDefinitionNode variableDefinition(Source|string $source, bool[] $options = []) |
||||
75 | * @method static VariableNode variable(Source|string $source, bool[] $options = []) |
||||
76 | * @method static SelectionSetNode selectionSet(Source|string $source, bool[] $options = []) |
||||
77 | * @method static mixed selection(Source|string $source, bool[] $options = []) |
||||
78 | * @method static FieldNode field(Source|string $source, bool[] $options = []) |
||||
79 | * @method static NodeList<ArgumentNode> arguments(Source|string $source, bool[] $options = []) |
||||
80 | * @method static ArgumentNode argument(Source|string $source, bool[] $options = []) |
||||
81 | * @method static ArgumentNode constArgument(Source|string $source, bool[] $options = []) |
||||
82 | * @method static FragmentSpreadNode|InlineFragmentNode fragment(Source|string $source, bool[] $options = []) |
||||
83 | * @method static FragmentDefinitionNode fragmentDefinition(Source|string $source, bool[] $options = []) |
||||
84 | * @method static NameNode fragmentName(Source|string $source, bool[] $options = []) |
||||
85 | * @method static BooleanValueNode|EnumValueNode|FloatValueNode|IntValueNode|ListValueNode|NullValueNode|ObjectValueNode|StringValueNode|VariableNode valueLiteral(Source|string $source, bool[] $options = []) |
||||
86 | * @method static StringValueNode stringLiteral(Source|string $source, bool[] $options = []) |
||||
87 | * @method static BooleanValueNode|EnumValueNode|FloatValueNode|IntValueNode|StringValueNode|VariableNode constValue(Source|string $source, bool[] $options = []) |
||||
88 | * @method static BooleanValueNode|EnumValueNode|FloatValueNode|IntValueNode|ListValueNode|ObjectValueNode|StringValueNode|VariableNode variableValue(Source|string $source, bool[] $options = []) |
||||
89 | * @method static ListValueNode array(Source|string $source, bool[] $options = []) |
||||
90 | * @method static ObjectValueNode object(Source|string $source, bool[] $options = []) |
||||
91 | * @method static ObjectFieldNode objectField(Source|string $source, bool[] $options = []) |
||||
92 | * @method static NodeList<DirectiveNode> directives(Source|string $source, bool[] $options = []) |
||||
93 | * @method static DirectiveNode directive(Source|string $source, bool[] $options = []) |
||||
94 | * @method static ListTypeNode|NameNode|NonNullTypeNode typeReference(Source|string $source, bool[] $options = []) |
||||
95 | * @method static NamedTypeNode namedType(Source|string $source, bool[] $options = []) |
||||
96 | * @method static TypeSystemDefinitionNode typeSystemDefinition(Source|string $source, bool[] $options = []) |
||||
97 | * @method static StringValueNode|null description(Source|string $source, bool[] $options = []) |
||||
98 | * @method static SchemaDefinitionNode schemaDefinition(Source|string $source, bool[] $options = []) |
||||
99 | * @method static OperationTypeDefinitionNode operationTypeDefinition(Source|string $source, bool[] $options = []) |
||||
100 | * @method static ScalarTypeDefinitionNode scalarTypeDefinition(Source|string $source, bool[] $options = []) |
||||
101 | * @method static ObjectTypeDefinitionNode objectTypeDefinition(Source|string $source, bool[] $options = []) |
||||
102 | * @method static NamedTypeNode[] implementsInterfaces(Source|string $source, bool[] $options = []) |
||||
103 | * @method static FieldDefinitionNode[] fieldsDefinition(Source|string $source, bool[] $options = []) |
||||
104 | * @method static FieldDefinitionNode fieldDefinition(Source|string $source, bool[] $options = []) |
||||
105 | * @method static InputValueDefinitionNode[] argumentsDefinition(Source|string $source, bool[] $options = []) |
||||
106 | * @method static InputValueDefinitionNode inputValueDefinition(Source|string $source, bool[] $options = []) |
||||
107 | * @method static InterfaceTypeDefinitionNode interfaceTypeDefinition(Source|string $source, bool[] $options = []) |
||||
108 | * @method static UnionTypeDefinitionNode unionTypeDefinition(Source|string $source, bool[] $options = []) |
||||
109 | * @method static NamedTypeNode[] unionMemberTypes(Source|string $source, bool[] $options = []) |
||||
110 | * @method static EnumTypeDefinitionNode enumTypeDefinition(Source|string $source, bool[] $options = []) |
||||
111 | * @method static EnumValueDefinitionNode[] enumValuesDefinition(Source|string $source, bool[] $options = []) |
||||
112 | * @method static EnumValueDefinitionNode enumValueDefinition(Source|string $source, bool[] $options = []) |
||||
113 | * @method static InputObjectTypeDefinitionNode inputObjectTypeDefinition(Source|string $source, bool[] $options = []) |
||||
114 | * @method static InputValueDefinitionNode[] inputFieldsDefinition(Source|string $source, bool[] $options = []) |
||||
115 | * @method static TypeExtensionNode typeExtension(Source|string $source, bool[] $options = []) |
||||
116 | * @method static SchemaTypeExtensionNode schemaTypeExtension(Source|string $source, bool[] $options = []) |
||||
117 | * @method static ScalarTypeExtensionNode scalarTypeExtension(Source|string $source, bool[] $options = []) |
||||
118 | * @method static ObjectTypeExtensionNode objectTypeExtension(Source|string $source, bool[] $options = []) |
||||
119 | * @method static InterfaceTypeExtensionNode interfaceTypeExtension(Source|string $source, bool[] $options = []) |
||||
120 | * @method static UnionTypeExtensionNode unionTypeExtension(Source|string $source, bool[] $options = []) |
||||
121 | * @method static EnumTypeExtensionNode enumTypeExtension(Source|string $source, bool[] $options = []) |
||||
122 | * @method static InputObjectTypeExtensionNode inputObjectTypeExtension(Source|string $source, bool[] $options = []) |
||||
123 | * @method static DirectiveDefinitionNode directiveDefinition(Source|string $source, bool[] $options = []) |
||||
124 | * @method static DirectiveLocation[] directiveLocations(Source|string $source, bool[] $options = []) |
||||
125 | * @method static DirectiveLocation directiveLocation(Source|string $source, bool[] $options = []) |
||||
126 | */ |
||||
127 | class Parser |
||||
128 | { |
||||
129 | /** |
||||
130 | * Given a GraphQL source, parses it into a `GraphQL\Language\AST\DocumentNode`. |
||||
131 | * Throws `GraphQL\Error\SyntaxError` if a syntax error is encountered. |
||||
132 | * |
||||
133 | * Available options: |
||||
134 | * |
||||
135 | * noLocation: boolean, |
||||
136 | * (By default, the parser creates AST nodes that know the location |
||||
137 | * in the source that they correspond to. This configuration flag |
||||
138 | * disables that behavior for performance or testing.) |
||||
139 | * |
||||
140 | * allowLegacySDLEmptyFields: boolean |
||||
141 | * If enabled, the parser will parse empty fields sets in the Schema |
||||
142 | * Definition Language. Otherwise, the parser will follow the current |
||||
143 | * specification. |
||||
144 | * |
||||
145 | * This option is provided to ease adoption of the final SDL specification |
||||
146 | * and will be removed in a future major release. |
||||
147 | * |
||||
148 | * allowLegacySDLImplementsInterfaces: boolean |
||||
149 | * If enabled, the parser will parse implemented interfaces with no `&` |
||||
150 | * character between each interface. Otherwise, the parser will follow the |
||||
151 | * current specification. |
||||
152 | * |
||||
153 | * This option is provided to ease adoption of the final SDL specification |
||||
154 | * and will be removed in a future major release. |
||||
155 | * |
||||
156 | * experimentalFragmentVariables: boolean, |
||||
157 | * (If enabled, the parser will understand and parse variable definitions |
||||
158 | * contained in a fragment definition. They'll be represented in the |
||||
159 | * `variableDefinitions` field of the FragmentDefinitionNode. |
||||
160 | * |
||||
161 | * The syntax is identical to normal, query-defined variables. For example: |
||||
162 | * |
||||
163 | * fragment A($var: Boolean = false) on T { |
||||
164 | * ... |
||||
165 | * } |
||||
166 | * |
||||
167 | * Note: this feature is experimental and may change or be removed in the |
||||
168 | * future.) |
||||
169 | * |
||||
170 | * @param Source|string $source |
||||
171 | * @param bool[] $options |
||||
172 | * |
||||
173 | * @return DocumentNode |
||||
174 | * |
||||
175 | * @throws SyntaxError |
||||
176 | * |
||||
177 | * @api |
||||
178 | */ |
||||
179 | 968 | public static function parse($source, array $options = []) |
|||
180 | { |
||||
181 | 968 | $parser = new self($source, $options); |
|||
182 | |||||
183 | 965 | return $parser->parseDocument(); |
|||
184 | } |
||||
185 | |||||
186 | /** |
||||
187 | * Given a string containing a GraphQL value (ex. `[42]`), parse the AST for |
||||
188 | * that value. |
||||
189 | * Throws `GraphQL\Error\SyntaxError` if a syntax error is encountered. |
||||
190 | * |
||||
191 | * This is useful within tools that operate upon GraphQL Values directly and |
||||
192 | * in isolation of complete GraphQL documents. |
||||
193 | * |
||||
194 | * Consider providing the results to the utility function: `GraphQL\Utils\AST::valueFromAST()`. |
||||
195 | * |
||||
196 | * @param Source|string $source |
||||
197 | * @param bool[] $options |
||||
198 | * |
||||
199 | * @return BooleanValueNode|EnumValueNode|FloatValueNode|IntValueNode|ListValueNode|ObjectValueNode|StringValueNode|VariableNode |
||||
200 | * |
||||
201 | * @api |
||||
202 | */ |
||||
203 | 21 | public static function parseValue($source, array $options = []) |
|||
204 | { |
||||
205 | 21 | $parser = new Parser($source, $options); |
|||
206 | 21 | $parser->expect(Token::SOF); |
|||
207 | 21 | $value = $parser->parseValueLiteral(false); |
|||
208 | 21 | $parser->expect(Token::EOF); |
|||
209 | |||||
210 | 21 | return $value; |
|||
211 | } |
||||
212 | |||||
213 | /** |
||||
214 | * Given a string containing a GraphQL Type (ex. `[Int!]`), parse the AST for |
||||
215 | * that type. |
||||
216 | * Throws `GraphQL\Error\SyntaxError` if a syntax error is encountered. |
||||
217 | * |
||||
218 | * This is useful within tools that operate upon GraphQL Types directly and |
||||
219 | * in isolation of complete GraphQL documents. |
||||
220 | * |
||||
221 | * Consider providing the results to the utility function: `GraphQL\Utils\AST::typeFromAST()`. |
||||
222 | * |
||||
223 | * @param Source|string $source |
||||
224 | * @param bool[] $options |
||||
225 | * |
||||
226 | * @return ListTypeNode|NameNode|NonNullTypeNode |
||||
227 | * |
||||
228 | * @api |
||||
229 | */ |
||||
230 | 5 | public static function parseType($source, array $options = []) |
|||
231 | { |
||||
232 | 5 | $parser = new Parser($source, $options); |
|||
233 | 5 | $parser->expect(Token::SOF); |
|||
234 | 5 | $type = $parser->parseTypeReference(); |
|||
235 | 5 | $parser->expect(Token::EOF); |
|||
236 | |||||
237 | 5 | return $type; |
|||
0 ignored issues
–
show
Bug
Best Practice
introduced
by
![]() |
|||||
238 | } |
||||
239 | |||||
240 | /** |
||||
241 | * Parse partial source by delegating calls to the internal parseX methods. |
||||
242 | * |
||||
243 | * @param bool[] $arguments |
||||
244 | * |
||||
245 | * @throws SyntaxError |
||||
246 | */ |
||||
247 | 1 | public static function __callStatic(string $name, array $arguments) : Node |
|||
248 | { |
||||
249 | 1 | $parser = new Parser(...$arguments); |
|||
0 ignored issues
–
show
$arguments is expanded, but the parameter $source of GraphQL\Language\Parser::__construct() does not expect variable arguments.
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||
250 | 1 | $parser->expect(Token::SOF); |
|||
251 | 1 | $type = $parser->{'parse' . $name}(); |
|||
252 | 1 | $parser->expect(Token::EOF); |
|||
253 | |||||
254 | 1 | return $type; |
|||
255 | } |
||||
256 | |||||
257 | /** @var Lexer */ |
||||
258 | private $lexer; |
||||
259 | |||||
260 | /** |
||||
261 | * @param Source|string $source |
||||
262 | * @param bool[] $options |
||||
263 | */ |
||||
264 | 995 | public function __construct($source, array $options = []) |
|||
265 | { |
||||
266 | 995 | $sourceObj = $source instanceof Source ? $source : new Source($source); |
|||
267 | 992 | $this->lexer = new Lexer($sourceObj, $options); |
|||
268 | 992 | } |
|||
269 | |||||
270 | /** |
||||
271 | * Returns a location object, used to identify the place in |
||||
272 | * the source that created a given parsed object. |
||||
273 | */ |
||||
274 | 984 | private function loc(Token $startToken) : ?Location |
|||
275 | { |
||||
276 | 984 | if (empty($this->lexer->options['noLocation'])) { |
|||
277 | 966 | return new Location($startToken, $this->lexer->lastToken, $this->lexer->source); |
|||
278 | } |
||||
279 | |||||
280 | 18 | return null; |
|||
281 | } |
||||
282 | |||||
283 | /** |
||||
284 | * Determines if the next token is of a given kind |
||||
285 | */ |
||||
286 | 966 | private function peek(string $kind) : bool |
|||
287 | { |
||||
288 | 966 | return $this->lexer->token->kind === $kind; |
|||
289 | } |
||||
290 | |||||
291 | /** |
||||
292 | * If the next token is of the given kind, return true after advancing |
||||
293 | * the parser. Otherwise, do not change the parser state and return false. |
||||
294 | */ |
||||
295 | 971 | private function skip(string $kind) : bool |
|||
296 | { |
||||
297 | 971 | $match = $this->lexer->token->kind === $kind; |
|||
298 | |||||
299 | 971 | if ($match) { |
|||
300 | 967 | $this->lexer->advance(); |
|||
301 | } |
||||
302 | |||||
303 | 971 | return $match; |
|||
304 | } |
||||
305 | |||||
306 | /** |
||||
307 | * If the next token is of the given kind, return that token after advancing |
||||
308 | * the parser. Otherwise, do not change the parser state and return false. |
||||
309 | * |
||||
310 | * @throws SyntaxError |
||||
311 | */ |
||||
312 | 992 | private function expect(string $kind) : Token |
|||
313 | { |
||||
314 | 992 | $token = $this->lexer->token; |
|||
315 | |||||
316 | 992 | if ($token->kind === $kind) { |
|||
317 | 992 | $this->lexer->advance(); |
|||
318 | |||||
319 | 992 | return $token; |
|||
320 | } |
||||
321 | |||||
322 | 12 | throw new SyntaxError( |
|||
323 | 12 | $this->lexer->source, |
|||
324 | 12 | $token->start, |
|||
325 | 12 | sprintf('Expected %s, found %s', $kind, $token->getDescription()) |
|||
326 | ); |
||||
327 | } |
||||
328 | |||||
329 | /** |
||||
330 | * If the next token is a keyword with the given value, return that token after |
||||
331 | * advancing the parser. Otherwise, do not change the parser state and return |
||||
332 | * false. |
||||
333 | * |
||||
334 | * @throws SyntaxError |
||||
335 | */ |
||||
336 | 463 | private function expectKeyword(string $value) : Token |
|||
337 | { |
||||
338 | 463 | $token = $this->lexer->token; |
|||
339 | |||||
340 | 463 | if ($token->kind === Token::NAME && $token->value === $value) { |
|||
341 | 463 | $this->lexer->advance(); |
|||
342 | |||||
343 | 463 | return $token; |
|||
344 | } |
||||
345 | 2 | throw new SyntaxError( |
|||
346 | 2 | $this->lexer->source, |
|||
347 | 2 | $token->start, |
|||
348 | 2 | 'Expected "' . $value . '", found ' . $token->getDescription() |
|||
349 | ); |
||||
350 | } |
||||
351 | |||||
352 | 9 | private function unexpected(?Token $atToken = null) : SyntaxError |
|||
353 | { |
||||
354 | 9 | $token = $atToken ?: $this->lexer->token; |
|||
355 | |||||
356 | 9 | return new SyntaxError($this->lexer->source, $token->start, 'Unexpected ' . $token->getDescription()); |
|||
357 | } |
||||
358 | |||||
359 | /** |
||||
360 | * Returns a possibly empty list of parse nodes, determined by |
||||
361 | * the parseFn. This list begins with a lex token of openKind |
||||
362 | * and ends with a lex token of closeKind. Advances the parser |
||||
363 | * to the next lex token after the closing token. |
||||
364 | * |
||||
365 | * @throws SyntaxError |
||||
366 | */ |
||||
367 | 34 | private function any(string $openKind, callable $parseFn, string $closeKind) : NodeList |
|||
368 | { |
||||
369 | 34 | $this->expect($openKind); |
|||
370 | |||||
371 | 34 | $nodes = []; |
|||
372 | 34 | while (! $this->skip($closeKind)) { |
|||
373 | 32 | $nodes[] = $parseFn($this); |
|||
374 | } |
||||
375 | |||||
376 | 33 | return new NodeList($nodes); |
|||
377 | } |
||||
378 | |||||
379 | /** |
||||
380 | * Returns a non-empty list of parse nodes, determined by |
||||
381 | * the parseFn. This list begins with a lex token of openKind |
||||
382 | * and ends with a lex token of closeKind. Advances the parser |
||||
383 | * to the next lex token after the closing token. |
||||
384 | * |
||||
385 | * @throws SyntaxError |
||||
386 | */ |
||||
387 | 966 | private function many(string $openKind, callable $parseFn, string $closeKind) : NodeList |
|||
388 | { |
||||
389 | 966 | $this->expect($openKind); |
|||
390 | |||||
391 | 966 | $nodes = [$parseFn($this)]; |
|||
392 | 947 | while (! $this->skip($closeKind)) { |
|||
393 | 541 | $nodes[] = $parseFn($this); |
|||
394 | } |
||||
395 | |||||
396 | 945 | return new NodeList($nodes); |
|||
397 | } |
||||
398 | |||||
399 | /** |
||||
400 | * Converts a name lex token into a name parse node. |
||||
401 | * |
||||
402 | * @throws SyntaxError |
||||
403 | */ |
||||
404 | 972 | private function parseName() : NameNode |
|||
405 | { |
||||
406 | 972 | $token = $this->expect(Token::NAME); |
|||
407 | |||||
408 | 970 | return new NameNode([ |
|||
409 | 970 | 'value' => $token->value, |
|||
410 | 970 | 'loc' => $this->loc($token), |
|||
411 | ]); |
||||
412 | } |
||||
413 | |||||
414 | /** |
||||
415 | * Implements the parsing rules in the Document section. |
||||
416 | * |
||||
417 | * @throws SyntaxError |
||||
418 | */ |
||||
419 | 965 | private function parseDocument() : DocumentNode |
|||
420 | { |
||||
421 | 965 | $start = $this->lexer->token; |
|||
422 | |||||
423 | 965 | return new DocumentNode([ |
|||
424 | 965 | 'definitions' => $this->many( |
|||
425 | 965 | Token::SOF, |
|||
426 | function () { |
||||
427 | 965 | return $this->parseDefinition(); |
|||
428 | 965 | }, |
|||
429 | 965 | Token::EOF |
|||
430 | ), |
||||
431 | 943 | 'loc' => $this->loc($start), |
|||
432 | ]); |
||||
433 | } |
||||
434 | |||||
435 | /** |
||||
436 | * @return ExecutableDefinitionNode|TypeSystemDefinitionNode |
||||
437 | * |
||||
438 | * @throws SyntaxError |
||||
439 | */ |
||||
440 | 965 | private function parseDefinition() : DefinitionNode |
|||
441 | { |
||||
442 | 965 | if ($this->peek(Token::NAME)) { |
|||
443 | 674 | switch ($this->lexer->token->value) { |
|||
444 | 674 | case 'query': |
|||
445 | 477 | case 'mutation': |
|||
446 | 466 | case 'subscription': |
|||
447 | 461 | case 'fragment': |
|||
448 | 431 | return $this->parseExecutableDefinition(); |
|||
449 | |||||
450 | // Note: The schema definition language is an experimental addition. |
||||
451 | 253 | case 'schema': |
|||
452 | 251 | case 'scalar': |
|||
453 | 250 | case 'type': |
|||
454 | 174 | case 'interface': |
|||
455 | 150 | case 'union': |
|||
456 | 134 | case 'enum': |
|||
457 | 124 | case 'input': |
|||
458 | 105 | case 'extend': |
|||
459 | 91 | case 'directive': |
|||
460 | // Note: The schema definition language is an experimental addition. |
||||
461 | 253 | return $this->parseTypeSystemDefinition(); |
|||
462 | } |
||||
463 | 339 | } elseif ($this->peek(Token::BRACE_L)) { |
|||
464 | 331 | return $this->parseExecutableDefinition(); |
|||
465 | 8 | } elseif ($this->peekDescription()) { |
|||
466 | // Note: The schema definition language is an experimental addition. |
||||
467 | 7 | return $this->parseTypeSystemDefinition(); |
|||
468 | } |
||||
469 | |||||
470 | 3 | throw $this->unexpected(); |
|||
471 | } |
||||
472 | |||||
473 | /** |
||||
474 | * @throws SyntaxError |
||||
475 | */ |
||||
476 | 728 | private function parseExecutableDefinition() : ExecutableDefinitionNode |
|||
477 | { |
||||
478 | 728 | if ($this->peek(Token::NAME)) { |
|||
479 | 431 | switch ($this->lexer->token->value) { |
|||
480 | 431 | case 'query': |
|||
481 | 228 | case 'mutation': |
|||
482 | 215 | case 'subscription': |
|||
483 | 288 | return $this->parseOperationDefinition(); |
|||
484 | 210 | case 'fragment': |
|||
485 | 210 | return $this->parseFragmentDefinition(); |
|||
486 | } |
||||
487 | 331 | } elseif ($this->peek(Token::BRACE_L)) { |
|||
488 | 331 | return $this->parseOperationDefinition(); |
|||
489 | } |
||||
490 | |||||
491 | throw $this->unexpected(); |
||||
492 | } |
||||
493 | |||||
494 | // Implements the parsing rules in the Operations section. |
||||
495 | |||||
496 | /** |
||||
497 | * @throws SyntaxError |
||||
498 | */ |
||||
499 | 607 | private function parseOperationDefinition() : OperationDefinitionNode |
|||
500 | { |
||||
501 | 607 | $start = $this->lexer->token; |
|||
502 | 607 | if ($this->peek(Token::BRACE_L)) { |
|||
503 | 331 | return new OperationDefinitionNode([ |
|||
504 | 331 | 'operation' => 'query', |
|||
505 | 'name' => null, |
||||
506 | 331 | 'variableDefinitions' => new NodeList([]), |
|||
507 | 331 | 'directives' => new NodeList([]), |
|||
508 | 331 | 'selectionSet' => $this->parseSelectionSet(), |
|||
509 | 327 | 'loc' => $this->loc($start), |
|||
510 | ]); |
||||
511 | } |
||||
512 | |||||
513 | 288 | $operation = $this->parseOperationType(); |
|||
514 | |||||
515 | 288 | $name = null; |
|||
516 | 288 | if ($this->peek(Token::NAME)) { |
|||
517 | 263 | $name = $this->parseName(); |
|||
518 | } |
||||
519 | |||||
520 | 288 | return new OperationDefinitionNode([ |
|||
521 | 288 | 'operation' => $operation, |
|||
522 | 288 | 'name' => $name, |
|||
523 | 288 | 'variableDefinitions' => $this->parseVariableDefinitions(), |
|||
524 | 287 | 'directives' => $this->parseDirectives(false), |
|||
525 | 287 | 'selectionSet' => $this->parseSelectionSet(), |
|||
526 | 286 | 'loc' => $this->loc($start), |
|||
527 | ]); |
||||
528 | } |
||||
529 | |||||
530 | /** |
||||
531 | * @throws SyntaxError |
||||
532 | */ |
||||
533 | 337 | private function parseOperationType() : string |
|||
534 | { |
||||
535 | 337 | $operationToken = $this->expect(Token::NAME); |
|||
536 | 337 | switch ($operationToken->value) { |
|||
537 | 337 | case 'query': |
|||
538 | 319 | return 'query'; |
|||
539 | 51 | case 'mutation': |
|||
540 | 43 | return 'mutation'; |
|||
541 | 24 | case 'subscription': |
|||
542 | 24 | return 'subscription'; |
|||
543 | } |
||||
544 | |||||
545 | throw $this->unexpected($operationToken); |
||||
546 | } |
||||
547 | |||||
548 | 292 | private function parseVariableDefinitions() : NodeList |
|||
549 | { |
||||
550 | 292 | return $this->peek(Token::PAREN_L) |
|||
551 | 134 | ? $this->many( |
|||
552 | 134 | Token::PAREN_L, |
|||
553 | function () { |
||||
554 | 134 | return $this->parseVariableDefinition(); |
|||
555 | 134 | }, |
|||
556 | 134 | Token::PAREN_R |
|||
557 | ) |
||||
558 | 291 | : new NodeList([]); |
|||
559 | } |
||||
560 | |||||
561 | /** |
||||
562 | * @throws SyntaxError |
||||
563 | */ |
||||
564 | 134 | private function parseVariableDefinition() : VariableDefinitionNode |
|||
565 | { |
||||
566 | 134 | $start = $this->lexer->token; |
|||
567 | 134 | $var = $this->parseVariable(); |
|||
568 | |||||
569 | 134 | $this->expect(Token::COLON); |
|||
570 | 134 | $type = $this->parseTypeReference(); |
|||
571 | |||||
572 | 134 | return new VariableDefinitionNode([ |
|||
573 | 134 | 'variable' => $var, |
|||
574 | 134 | 'type' => $type, |
|||
575 | 'defaultValue' => |
||||
576 | 134 | ($this->skip(Token::EQUALS) ? $this->parseValueLiteral(true) : null), |
|||
577 | 133 | 'directives' => $this->parseDirectives(true), |
|||
578 | 133 | 'loc' => $this->loc($start), |
|||
579 | ]); |
||||
580 | } |
||||
581 | |||||
582 | /** |
||||
583 | * @throws SyntaxError |
||||
584 | */ |
||||
585 | 143 | private function parseVariable() : VariableNode |
|||
586 | { |
||||
587 | 143 | $start = $this->lexer->token; |
|||
588 | 143 | $this->expect(Token::DOLLAR); |
|||
589 | |||||
590 | 143 | return new VariableNode([ |
|||
591 | 143 | 'name' => $this->parseName(), |
|||
592 | 143 | 'loc' => $this->loc($start), |
|||
593 | ]); |
||||
594 | } |
||||
595 | |||||
596 | 726 | private function parseSelectionSet() : SelectionSetNode |
|||
597 | { |
||||
598 | 726 | $start = $this->lexer->token; |
|||
599 | |||||
600 | 726 | return new SelectionSetNode( |
|||
601 | [ |
||||
602 | 726 | 'selections' => $this->many( |
|||
603 | 726 | Token::BRACE_L, |
|||
604 | function () { |
||||
605 | 725 | return $this->parseSelection(); |
|||
606 | 726 | }, |
|||
607 | 726 | Token::BRACE_R |
|||
608 | ), |
||||
609 | 720 | 'loc' => $this->loc($start), |
|||
610 | ] |
||||
611 | ); |
||||
612 | } |
||||
613 | |||||
614 | /** |
||||
615 | * Selection : |
||||
616 | * - Field |
||||
617 | * - FragmentSpread |
||||
618 | * - InlineFragment |
||||
619 | */ |
||||
620 | 725 | private function parseSelection() : SelectionNode |
|||
621 | { |
||||
622 | 725 | return $this->peek(Token::SPREAD) |
|||
623 | 197 | ? $this->parseFragment() |
|||
624 | 724 | : $this->parseField(); |
|||
625 | } |
||||
626 | |||||
627 | /** |
||||
628 | * @throws SyntaxError |
||||
629 | */ |
||||
630 | 713 | private function parseField() : FieldNode |
|||
631 | { |
||||
632 | 713 | $start = $this->lexer->token; |
|||
633 | 713 | $nameOrAlias = $this->parseName(); |
|||
634 | |||||
635 | 711 | if ($this->skip(Token::COLON)) { |
|||
636 | 63 | $alias = $nameOrAlias; |
|||
637 | 63 | $name = $this->parseName(); |
|||
638 | } else { |
||||
639 | 691 | $alias = null; |
|||
640 | 691 | $name = $nameOrAlias; |
|||
641 | } |
||||
642 | |||||
643 | 710 | return new FieldNode([ |
|||
644 | 710 | 'alias' => $alias, |
|||
645 | 710 | 'name' => $name, |
|||
646 | 710 | 'arguments' => $this->parseArguments(false), |
|||
647 | 710 | 'directives' => $this->parseDirectives(false), |
|||
648 | 710 | 'selectionSet' => $this->peek(Token::BRACE_L) ? $this->parseSelectionSet() : null, |
|||
649 | 710 | 'loc' => $this->loc($start), |
|||
650 | ]); |
||||
651 | } |
||||
652 | |||||
653 | /** |
||||
654 | * @throws SyntaxError |
||||
655 | */ |
||||
656 | 742 | private function parseArguments(bool $isConst) : NodeList |
|||
657 | { |
||||
658 | 742 | $parseFn = $isConst |
|||
659 | ? function () { |
||||
660 | 7 | return $this->parseConstArgument(); |
|||
661 | 36 | } |
|||
662 | : function () { |
||||
663 | 333 | return $this->parseArgument(); |
|||
664 | 742 | }; |
|||
665 | |||||
666 | 742 | return $this->peek(Token::PAREN_L) |
|||
667 | 340 | ? $this->many(Token::PAREN_L, $parseFn, Token::PAREN_R) |
|||
668 | 742 | : new NodeList([]); |
|||
669 | } |
||||
670 | |||||
671 | /** |
||||
672 | * @throws SyntaxError |
||||
673 | */ |
||||
674 | 333 | private function parseArgument() : ArgumentNode |
|||
675 | { |
||||
676 | 333 | $start = $this->lexer->token; |
|||
677 | 333 | $name = $this->parseName(); |
|||
678 | |||||
679 | 333 | $this->expect(Token::COLON); |
|||
680 | 333 | $value = $this->parseValueLiteral(false); |
|||
681 | |||||
682 | 333 | return new ArgumentNode([ |
|||
683 | 333 | 'name' => $name, |
|||
684 | 333 | 'value' => $value, |
|||
685 | 333 | 'loc' => $this->loc($start), |
|||
686 | ]); |
||||
687 | } |
||||
688 | |||||
689 | /** |
||||
690 | * @throws SyntaxError |
||||
691 | */ |
||||
692 | 7 | private function parseConstArgument() : ArgumentNode |
|||
693 | { |
||||
694 | 7 | $start = $this->lexer->token; |
|||
695 | 7 | $name = $this->parseName(); |
|||
696 | |||||
697 | 7 | $this->expect(Token::COLON); |
|||
698 | 7 | $value = $this->parseConstValue(); |
|||
699 | |||||
700 | 7 | return new ArgumentNode([ |
|||
701 | 7 | 'name' => $name, |
|||
702 | 7 | 'value' => $value, |
|||
703 | 7 | 'loc' => $this->loc($start), |
|||
704 | ]); |
||||
705 | } |
||||
706 | |||||
707 | // Implements the parsing rules in the Fragments section. |
||||
708 | |||||
709 | /** |
||||
710 | * @return FragmentSpreadNode|InlineFragmentNode |
||||
711 | * |
||||
712 | * @throws SyntaxError |
||||
713 | */ |
||||
714 | 197 | private function parseFragment() : SelectionNode |
|||
715 | { |
||||
716 | 197 | $start = $this->lexer->token; |
|||
717 | 197 | $this->expect(Token::SPREAD); |
|||
718 | |||||
719 | 197 | if ($this->peek(Token::NAME) && $this->lexer->token->value !== 'on') { |
|||
720 | 129 | return new FragmentSpreadNode([ |
|||
721 | 129 | 'name' => $this->parseFragmentName(), |
|||
722 | 129 | 'directives' => $this->parseDirectives(false), |
|||
723 | 129 | 'loc' => $this->loc($start), |
|||
724 | ]); |
||||
725 | } |
||||
726 | |||||
727 | 94 | $typeCondition = null; |
|||
728 | 94 | if ($this->lexer->token->value === 'on') { |
|||
729 | 90 | $this->lexer->advance(); |
|||
730 | 90 | $typeCondition = $this->parseNamedType(); |
|||
731 | } |
||||
732 | |||||
733 | 93 | return new InlineFragmentNode([ |
|||
734 | 93 | 'typeCondition' => $typeCondition, |
|||
735 | 93 | 'directives' => $this->parseDirectives(false), |
|||
736 | 93 | 'selectionSet' => $this->parseSelectionSet(), |
|||
737 | 93 | 'loc' => $this->loc($start), |
|||
738 | ]); |
||||
739 | } |
||||
740 | |||||
741 | /** |
||||
742 | * @throws SyntaxError |
||||
743 | */ |
||||
744 | 210 | private function parseFragmentDefinition() : FragmentDefinitionNode |
|||
745 | { |
||||
746 | 210 | $start = $this->lexer->token; |
|||
747 | 210 | $this->expectKeyword('fragment'); |
|||
748 | |||||
749 | 210 | $name = $this->parseFragmentName(); |
|||
750 | |||||
751 | // Experimental support for defining variables within fragments changes |
||||
752 | // the grammar of FragmentDefinition: |
||||
753 | // - fragment FragmentName VariableDefinitions? on TypeCondition Directives? SelectionSet |
||||
754 | 209 | $variableDefinitions = null; |
|||
755 | 209 | if (isset($this->lexer->options['experimentalFragmentVariables'])) { |
|||
756 | 4 | $variableDefinitions = $this->parseVariableDefinitions(); |
|||
757 | } |
||||
758 | 209 | $this->expectKeyword('on'); |
|||
759 | 208 | $typeCondition = $this->parseNamedType(); |
|||
760 | |||||
761 | 208 | return new FragmentDefinitionNode([ |
|||
762 | 208 | 'name' => $name, |
|||
763 | 208 | 'variableDefinitions' => $variableDefinitions, |
|||
764 | 208 | 'typeCondition' => $typeCondition, |
|||
765 | 208 | 'directives' => $this->parseDirectives(false), |
|||
766 | 208 | 'selectionSet' => $this->parseSelectionSet(), |
|||
767 | 207 | 'loc' => $this->loc($start), |
|||
768 | ]); |
||||
769 | } |
||||
770 | |||||
771 | /** |
||||
772 | * @throws SyntaxError |
||||
773 | */ |
||||
774 | 212 | private function parseFragmentName() : NameNode |
|||
775 | { |
||||
776 | 212 | if ($this->lexer->token->value === 'on') { |
|||
777 | 1 | throw $this->unexpected(); |
|||
778 | } |
||||
779 | |||||
780 | 211 | return $this->parseName(); |
|||
781 | } |
||||
782 | |||||
783 | // Implements the parsing rules in the Values section. |
||||
784 | |||||
785 | /** |
||||
786 | * Value[Const] : |
||||
787 | * - [~Const] Variable |
||||
788 | * - IntValue |
||||
789 | * - FloatValue |
||||
790 | * - StringValue |
||||
791 | * - BooleanValue |
||||
792 | * - NullValue |
||||
793 | * - EnumValue |
||||
794 | * - ListValue[?Const] |
||||
795 | * - ObjectValue[?Const] |
||||
796 | * |
||||
797 | * BooleanValue : one of `true` `false` |
||||
798 | * |
||||
799 | * NullValue : `null` |
||||
800 | * |
||||
801 | * EnumValue : Name but not `true`, `false` or `null` |
||||
802 | * |
||||
803 | * @return BooleanValueNode|EnumValueNode|FloatValueNode|IntValueNode|StringValueNode|VariableNode|ListValueNode|ObjectValueNode|NullValueNode |
||||
804 | * |
||||
805 | * @throws SyntaxError |
||||
806 | */ |
||||
807 | 380 | private function parseValueLiteral(bool $isConst) : ValueNode |
|||
808 | { |
||||
809 | 380 | $token = $this->lexer->token; |
|||
810 | 380 | switch ($token->kind) { |
|||
811 | 380 | case Token::BRACKET_L: |
|||
812 | 34 | return $this->parseArray($isConst); |
|||
813 | 379 | case Token::BRACE_L: |
|||
814 | 42 | return $this->parseObject($isConst); |
|||
815 | 379 | case Token::INT: |
|||
816 | 94 | $this->lexer->advance(); |
|||
817 | |||||
818 | 94 | return new IntValueNode([ |
|||
819 | 94 | 'value' => $token->value, |
|||
820 | 94 | 'loc' => $this->loc($token), |
|||
821 | ]); |
||||
822 | 323 | case Token::FLOAT: |
|||
823 | 14 | $this->lexer->advance(); |
|||
824 | |||||
825 | 14 | return new FloatValueNode([ |
|||
826 | 14 | 'value' => $token->value, |
|||
827 | 14 | 'loc' => $this->loc($token), |
|||
828 | ]); |
||||
829 | 315 | case Token::STRING: |
|||
830 | 241 | case Token::BLOCK_STRING: |
|||
831 | 115 | return $this->parseStringLiteral(); |
|||
832 | 237 | case Token::NAME: |
|||
833 | 132 | if ($token->value === 'true' || $token->value === 'false') { |
|||
834 | 86 | $this->lexer->advance(); |
|||
835 | |||||
836 | 86 | return new BooleanValueNode([ |
|||
837 | 86 | 'value' => $token->value === 'true', |
|||
838 | 86 | 'loc' => $this->loc($token), |
|||
839 | ]); |
||||
840 | } |
||||
841 | |||||
842 | 69 | if ($token->value === 'null') { |
|||
843 | 32 | $this->lexer->advance(); |
|||
844 | |||||
845 | 32 | return new NullValueNode([ |
|||
846 | 32 | 'loc' => $this->loc($token), |
|||
847 | ]); |
||||
848 | } else { |
||||
849 | 47 | $this->lexer->advance(); |
|||
850 | |||||
851 | 47 | return new EnumValueNode([ |
|||
852 | 47 | 'value' => $token->value, |
|||
853 | 47 | 'loc' => $this->loc($token), |
|||
854 | ]); |
||||
855 | } |
||||
856 | break; |
||||
857 | |||||
858 | 124 | case Token::DOLLAR: |
|||
859 | 124 | if (! $isConst) { |
|||
860 | 123 | return $this->parseVariable(); |
|||
861 | } |
||||
862 | 1 | break; |
|||
863 | } |
||||
864 | 1 | throw $this->unexpected(); |
|||
865 | } |
||||
866 | |||||
867 | 124 | private function parseStringLiteral() : StringValueNode |
|||
868 | { |
||||
869 | 124 | $token = $this->lexer->token; |
|||
870 | 124 | $this->lexer->advance(); |
|||
871 | |||||
872 | 124 | return new StringValueNode([ |
|||
873 | 124 | 'value' => $token->value, |
|||
874 | 124 | 'block' => $token->kind === Token::BLOCK_STRING, |
|||
875 | 124 | 'loc' => $this->loc($token), |
|||
876 | ]); |
||||
877 | } |
||||
878 | |||||
879 | /** |
||||
880 | * @return BooleanValueNode|EnumValueNode|FloatValueNode|IntValueNode|StringValueNode|VariableNode |
||||
881 | * |
||||
882 | * @throws SyntaxError |
||||
883 | */ |
||||
884 | 19 | private function parseConstValue() : ValueNode |
|||
885 | { |
||||
886 | 19 | return $this->parseValueLiteral(true); |
|||
887 | } |
||||
888 | |||||
889 | /** |
||||
890 | * @return BooleanValueNode|EnumValueNode|FloatValueNode|IntValueNode|ListValueNode|ObjectValueNode|StringValueNode|VariableNode |
||||
891 | */ |
||||
892 | 27 | private function parseVariableValue() : ValueNode |
|||
893 | { |
||||
894 | 27 | return $this->parseValueLiteral(false); |
|||
895 | } |
||||
896 | |||||
897 | 34 | private function parseArray(bool $isConst) : ListValueNode |
|||
898 | { |
||||
899 | 34 | $start = $this->lexer->token; |
|||
900 | $parseFn = $isConst ? function () { |
||||
901 | 5 | return $this->parseConstValue(); |
|||
902 | } : function () { |
||||
903 | 27 | return $this->parseVariableValue(); |
|||
904 | 34 | }; |
|||
905 | |||||
906 | 34 | return new ListValueNode( |
|||
907 | [ |
||||
908 | 34 | 'values' => $this->any(Token::BRACKET_L, $parseFn, Token::BRACKET_R), |
|||
909 | 33 | 'loc' => $this->loc($start), |
|||
910 | ] |
||||
911 | ); |
||||
912 | } |
||||
913 | |||||
914 | 42 | private function parseObject(bool $isConst) : ObjectValueNode |
|||
915 | { |
||||
916 | 42 | $start = $this->lexer->token; |
|||
917 | 42 | $this->expect(Token::BRACE_L); |
|||
918 | 42 | $fields = []; |
|||
919 | 42 | while (! $this->skip(Token::BRACE_R)) { |
|||
920 | 42 | $fields[] = $this->parseObjectField($isConst); |
|||
921 | } |
||||
922 | |||||
923 | 41 | return new ObjectValueNode([ |
|||
924 | 41 | 'fields' => new NodeList($fields), |
|||
925 | 41 | 'loc' => $this->loc($start), |
|||
926 | ]); |
||||
927 | } |
||||
928 | |||||
929 | 42 | private function parseObjectField(bool $isConst) : ObjectFieldNode |
|||
930 | { |
||||
931 | 42 | $start = $this->lexer->token; |
|||
932 | 42 | $name = $this->parseName(); |
|||
933 | |||||
934 | 42 | $this->expect(Token::COLON); |
|||
935 | |||||
936 | 42 | return new ObjectFieldNode([ |
|||
937 | 42 | 'name' => $name, |
|||
938 | 42 | 'value' => $this->parseValueLiteral($isConst), |
|||
939 | 41 | 'loc' => $this->loc($start), |
|||
940 | ]); |
||||
941 | } |
||||
942 | |||||
943 | // Implements the parsing rules in the Directives section. |
||||
944 | |||||
945 | /** |
||||
946 | * @throws SyntaxError |
||||
947 | */ |
||||
948 | 956 | private function parseDirectives(bool $isConst) : NodeList |
|||
949 | { |
||||
950 | 956 | $directives = []; |
|||
951 | 956 | while ($this->peek(Token::AT)) { |
|||
952 | 95 | $directives[] = $this->parseDirective($isConst); |
|||
953 | } |
||||
954 | |||||
955 | 956 | return new NodeList($directives); |
|||
956 | } |
||||
957 | |||||
958 | /** |
||||
959 | * @throws SyntaxError |
||||
960 | */ |
||||
961 | 95 | private function parseDirective(bool $isConst) : DirectiveNode |
|||
962 | { |
||||
963 | 95 | $start = $this->lexer->token; |
|||
964 | 95 | $this->expect(Token::AT); |
|||
965 | |||||
966 | 95 | return new DirectiveNode([ |
|||
967 | 95 | 'name' => $this->parseName(), |
|||
968 | 95 | 'arguments' => $this->parseArguments($isConst), |
|||
969 | 95 | 'loc' => $this->loc($start), |
|||
970 | ]); |
||||
971 | } |
||||
972 | |||||
973 | // Implements the parsing rules in the Types section. |
||||
974 | |||||
975 | /** |
||||
976 | * Handles the Type: TypeName, ListType, and NonNullType parsing rules. |
||||
977 | * |
||||
978 | * @return ListTypeNode|NameNode|NonNullTypeNode |
||||
979 | * |
||||
980 | * @throws SyntaxError |
||||
981 | */ |
||||
982 | 361 | private function parseTypeReference() : TypeNode |
|||
983 | { |
||||
984 | 361 | $start = $this->lexer->token; |
|||
985 | |||||
986 | 361 | if ($this->skip(Token::BRACKET_L)) { |
|||
987 | 91 | $type = $this->parseTypeReference(); |
|||
988 | 91 | $this->expect(Token::BRACKET_R); |
|||
989 | 91 | $type = new ListTypeNode([ |
|||
990 | 91 | 'type' => $type, |
|||
991 | 91 | 'loc' => $this->loc($start), |
|||
992 | ]); |
||||
993 | } else { |
||||
994 | 361 | $type = $this->parseNamedType(); |
|||
995 | } |
||||
996 | 361 | if ($this->skip(Token::BANG)) { |
|||
997 | 119 | return new NonNullTypeNode([ |
|||
998 | 119 | 'type' => $type, |
|||
999 | 119 | 'loc' => $this->loc($start), |
|||
1000 | ]); |
||||
1001 | } |
||||
1002 | |||||
1003 | 326 | return $type; |
|||
0 ignored issues
–
show
|
|||||
1004 | } |
||||
1005 | |||||
1006 | 596 | private function parseNamedType() : NamedTypeNode |
|||
1007 | { |
||||
1008 | 596 | $start = $this->lexer->token; |
|||
1009 | |||||
1010 | 596 | return new NamedTypeNode([ |
|||
1011 | 596 | 'name' => $this->parseName(), |
|||
1012 | 593 | 'loc' => $this->loc($start), |
|||
1013 | ]); |
||||
1014 | } |
||||
1015 | |||||
1016 | // Implements the parsing rules in the Type Definition section. |
||||
1017 | |||||
1018 | /** |
||||
1019 | * TypeSystemDefinition : |
||||
1020 | * - SchemaDefinition |
||||
1021 | * - TypeDefinition |
||||
1022 | * - TypeExtension |
||||
1023 | * - DirectiveDefinition |
||||
1024 | * |
||||
1025 | * TypeDefinition : |
||||
1026 | * - ScalarTypeDefinition |
||||
1027 | * - ObjectTypeDefinition |
||||
1028 | * - InterfaceTypeDefinition |
||||
1029 | * - UnionTypeDefinition |
||||
1030 | * - EnumTypeDefinition |
||||
1031 | * - InputObjectTypeDefinition |
||||
1032 | * |
||||
1033 | * @throws SyntaxError |
||||
1034 | */ |
||||
1035 | 256 | private function parseTypeSystemDefinition() : TypeSystemDefinitionNode |
|||
1036 | { |
||||
1037 | // Many definitions begin with a description and require a lookahead. |
||||
1038 | 256 | $keywordToken = $this->peekDescription() |
|||
1039 | 7 | ? $this->lexer->lookahead() |
|||
1040 | 256 | : $this->lexer->token; |
|||
1041 | |||||
1042 | 256 | if ($keywordToken->kind === Token::NAME) { |
|||
1043 | 256 | switch ($keywordToken->value) { |
|||
1044 | 256 | case 'schema': |
|||
1045 | 42 | return $this->parseSchemaDefinition(); |
|||
1046 | 254 | case 'scalar': |
|||
1047 | 68 | return $this->parseScalarTypeDefinition(); |
|||
1048 | 253 | case 'type': |
|||
1049 | 222 | return $this->parseObjectTypeDefinition(); |
|||
1050 | 174 | case 'interface': |
|||
1051 | 96 | return $this->parseInterfaceTypeDefinition(); |
|||
1052 | 150 | case 'union': |
|||
1053 | 85 | return $this->parseUnionTypeDefinition(); |
|||
1054 | 134 | case 'enum': |
|||
1055 | 76 | return $this->parseEnumTypeDefinition(); |
|||
1056 | 124 | case 'input': |
|||
1057 | 85 | return $this->parseInputObjectTypeDefinition(); |
|||
1058 | 105 | case 'extend': |
|||
1059 | 65 | return $this->parseTypeExtension(); |
|||
1060 | 90 | case 'directive': |
|||
1061 | 90 | return $this->parseDirectiveDefinition(); |
|||
1062 | } |
||||
1063 | } |
||||
1064 | |||||
1065 | throw $this->unexpected($keywordToken); |
||||
1066 | } |
||||
1067 | |||||
1068 | 258 | private function peekDescription() : bool |
|||
1069 | { |
||||
1070 | 258 | return $this->peek(Token::STRING) || $this->peek(Token::BLOCK_STRING); |
|||
1071 | } |
||||
1072 | |||||
1073 | 250 | private function parseDescription() : ?StringValueNode |
|||
1074 | { |
||||
1075 | 250 | if ($this->peekDescription()) { |
|||
1076 | 11 | return $this->parseStringLiteral(); |
|||
1077 | } |
||||
1078 | |||||
1079 | 250 | return null; |
|||
1080 | } |
||||
1081 | |||||
1082 | /** |
||||
1083 | * @throws SyntaxError |
||||
1084 | */ |
||||
1085 | 42 | private function parseSchemaDefinition() : SchemaDefinitionNode |
|||
1086 | { |
||||
1087 | 42 | $start = $this->lexer->token; |
|||
1088 | 42 | $this->expectKeyword('schema'); |
|||
1089 | 42 | $directives = $this->parseDirectives(true); |
|||
1090 | |||||
1091 | 42 | $operationTypes = $this->many( |
|||
1092 | 42 | Token::BRACE_L, |
|||
1093 | function () { |
||||
1094 | 42 | return $this->parseOperationTypeDefinition(); |
|||
1095 | 42 | }, |
|||
1096 | 42 | Token::BRACE_R |
|||
1097 | ); |
||||
1098 | |||||
1099 | 42 | return new SchemaDefinitionNode([ |
|||
1100 | 42 | 'directives' => $directives, |
|||
1101 | 42 | 'operationTypes' => $operationTypes, |
|||
1102 | 42 | 'loc' => $this->loc($start), |
|||
1103 | ]); |
||||
1104 | } |
||||
1105 | |||||
1106 | /** |
||||
1107 | * @throws SyntaxError |
||||
1108 | */ |
||||
1109 | 51 | private function parseOperationTypeDefinition() : OperationTypeDefinitionNode |
|||
1110 | { |
||||
1111 | 51 | $start = $this->lexer->token; |
|||
1112 | 51 | $operation = $this->parseOperationType(); |
|||
1113 | 51 | $this->expect(Token::COLON); |
|||
1114 | 51 | $type = $this->parseNamedType(); |
|||
1115 | |||||
1116 | 51 | return new OperationTypeDefinitionNode([ |
|||
1117 | 51 | 'operation' => $operation, |
|||
1118 | 51 | 'type' => $type, |
|||
1119 | 51 | 'loc' => $this->loc($start), |
|||
1120 | ]); |
||||
1121 | } |
||||
1122 | |||||
1123 | /** |
||||
1124 | * @throws SyntaxError |
||||
1125 | */ |
||||
1126 | 68 | private function parseScalarTypeDefinition() : ScalarTypeDefinitionNode |
|||
1127 | { |
||||
1128 | 68 | $start = $this->lexer->token; |
|||
1129 | 68 | $description = $this->parseDescription(); |
|||
1130 | 68 | $this->expectKeyword('scalar'); |
|||
1131 | 68 | $name = $this->parseName(); |
|||
1132 | 68 | $directives = $this->parseDirectives(true); |
|||
1133 | |||||
1134 | 68 | return new ScalarTypeDefinitionNode([ |
|||
1135 | 68 | 'name' => $name, |
|||
1136 | 68 | 'directives' => $directives, |
|||
1137 | 68 | 'loc' => $this->loc($start), |
|||
1138 | 68 | 'description' => $description, |
|||
1139 | ]); |
||||
1140 | } |
||||
1141 | |||||
1142 | /** |
||||
1143 | * @throws SyntaxError |
||||
1144 | */ |
||||
1145 | 223 | private function parseObjectTypeDefinition() : ObjectTypeDefinitionNode |
|||
1146 | { |
||||
1147 | 223 | $start = $this->lexer->token; |
|||
1148 | 223 | $description = $this->parseDescription(); |
|||
1149 | 223 | $this->expectKeyword('type'); |
|||
1150 | 223 | $name = $this->parseName(); |
|||
1151 | 223 | $interfaces = $this->parseImplementsInterfaces(); |
|||
1152 | 223 | $directives = $this->parseDirectives(true); |
|||
1153 | 223 | $fields = $this->parseFieldsDefinition(); |
|||
1154 | |||||
1155 | 222 | return new ObjectTypeDefinitionNode([ |
|||
1156 | 222 | 'name' => $name, |
|||
1157 | 222 | 'interfaces' => $interfaces, |
|||
1158 | 222 | 'directives' => $directives, |
|||
1159 | 222 | 'fields' => $fields, |
|||
1160 | 222 | 'loc' => $this->loc($start), |
|||
1161 | 222 | 'description' => $description, |
|||
1162 | ]); |
||||
1163 | } |
||||
1164 | |||||
1165 | /** |
||||
1166 | * ImplementsInterfaces : |
||||
1167 | * - implements `&`? NamedType |
||||
1168 | * - ImplementsInterfaces & NamedType |
||||
1169 | * |
||||
1170 | * @return NamedTypeNode[] |
||||
1171 | */ |
||||
1172 | 227 | private function parseImplementsInterfaces() : array |
|||
1173 | { |
||||
1174 | 227 | $types = []; |
|||
1175 | 227 | if ($this->lexer->token->value === 'implements') { |
|||
1176 | 100 | $this->lexer->advance(); |
|||
1177 | // Optional leading ampersand |
||||
1178 | 100 | $this->skip(Token::AMP); |
|||
1179 | do { |
||||
1180 | 100 | $types[] = $this->parseNamedType(); |
|||
1181 | 100 | } while ($this->skip(Token::AMP) || |
|||
1182 | // Legacy support for the SDL? |
||||
1183 | 100 | (! empty($this->lexer->options['allowLegacySDLImplementsInterfaces']) && $this->peek(Token::NAME)) |
|||
1184 | ); |
||||
1185 | } |
||||
1186 | |||||
1187 | 227 | return $types; |
|||
1188 | } |
||||
1189 | |||||
1190 | /** |
||||
1191 | * @throws SyntaxError |
||||
1192 | */ |
||||
1193 | 228 | private function parseFieldsDefinition() : NodeList |
|||
1194 | { |
||||
1195 | // Legacy support for the SDL? |
||||
1196 | 228 | if (! empty($this->lexer->options['allowLegacySDLEmptyFields']) |
|||
1197 | 228 | && $this->peek(Token::BRACE_L) |
|||
1198 | 228 | && $this->lexer->lookahead()->kind === Token::BRACE_R |
|||
1199 | ) { |
||||
1200 | 1 | $this->lexer->advance(); |
|||
1201 | 1 | $this->lexer->advance(); |
|||
1202 | |||||
1203 | /** @phpstan-var NodeList<FieldDefinitionNode&Node> $nodeList */ |
||||
1204 | 1 | $nodeList = new NodeList([]); |
|||
1205 | } else { |
||||
1206 | /** @phpstan-var NodeList<FieldDefinitionNode&Node> $nodeList */ |
||||
1207 | 227 | $nodeList = $this->peek(Token::BRACE_L) |
|||
1208 | 222 | ? $this->many( |
|||
1209 | 222 | Token::BRACE_L, |
|||
1210 | function () { |
||||
1211 | 222 | return $this->parseFieldDefinition(); |
|||
1212 | 222 | }, |
|||
1213 | 222 | Token::BRACE_R |
|||
1214 | ) |
||||
1215 | 226 | : new NodeList([]); |
|||
1216 | } |
||||
1217 | |||||
1218 | 227 | return $nodeList; |
|||
1219 | } |
||||
1220 | |||||
1221 | /** |
||||
1222 | * @throws SyntaxError |
||||
1223 | */ |
||||
1224 | 222 | private function parseFieldDefinition() : FieldDefinitionNode |
|||
1225 | { |
||||
1226 | 222 | $start = $this->lexer->token; |
|||
1227 | 222 | $description = $this->parseDescription(); |
|||
1228 | 222 | $name = $this->parseName(); |
|||
1229 | 221 | $args = $this->parseArgumentsDefinition(); |
|||
1230 | 221 | $this->expect(Token::COLON); |
|||
1231 | 221 | $type = $this->parseTypeReference(); |
|||
1232 | 221 | $directives = $this->parseDirectives(true); |
|||
1233 | |||||
1234 | 221 | return new FieldDefinitionNode([ |
|||
1235 | 221 | 'name' => $name, |
|||
1236 | 221 | 'arguments' => $args, |
|||
1237 | 221 | 'type' => $type, |
|||
1238 | 221 | 'directives' => $directives, |
|||
1239 | 221 | 'loc' => $this->loc($start), |
|||
1240 | 221 | 'description' => $description, |
|||
1241 | ]); |
||||
1242 | } |
||||
1243 | |||||
1244 | /** |
||||
1245 | * @throws SyntaxError |
||||
1246 | */ |
||||
1247 | 232 | private function parseArgumentsDefinition() : NodeList |
|||
1248 | { |
||||
1249 | /** @var NodeList<InputValueDefinitionNode&Node> $nodeList */ |
||||
1250 | 232 | $nodeList = $this->peek(Token::PAREN_L) |
|||
1251 | 110 | ? $this->many( |
|||
1252 | 110 | Token::PAREN_L, |
|||
1253 | function () { |
||||
1254 | 110 | return $this->parseInputValueDefinition(); |
|||
1255 | 110 | }, |
|||
1256 | 110 | Token::PAREN_R |
|||
1257 | ) |
||||
1258 | 232 | : new NodeList([]); |
|||
1259 | |||||
1260 | 232 | return $nodeList; |
|||
1261 | } |
||||
1262 | |||||
1263 | /** |
||||
1264 | * @throws SyntaxError |
||||
1265 | */ |
||||
1266 | 120 | private function parseInputValueDefinition() : InputValueDefinitionNode |
|||
1267 | { |
||||
1268 | 120 | $start = $this->lexer->token; |
|||
1269 | 120 | $description = $this->parseDescription(); |
|||
1270 | 120 | $name = $this->parseName(); |
|||
1271 | 120 | $this->expect(Token::COLON); |
|||
1272 | 119 | $type = $this->parseTypeReference(); |
|||
1273 | 119 | $defaultValue = null; |
|||
1274 | 119 | if ($this->skip(Token::EQUALS)) { |
|||
1275 | 11 | $defaultValue = $this->parseConstValue(); |
|||
1276 | } |
||||
1277 | 119 | $directives = $this->parseDirectives(true); |
|||
1278 | |||||
1279 | 119 | return new InputValueDefinitionNode([ |
|||
1280 | 119 | 'name' => $name, |
|||
1281 | 119 | 'type' => $type, |
|||
1282 | 119 | 'defaultValue' => $defaultValue, |
|||
1283 | 119 | 'directives' => $directives, |
|||
1284 | 119 | 'loc' => $this->loc($start), |
|||
1285 | 119 | 'description' => $description, |
|||
1286 | ]); |
||||
1287 | } |
||||
1288 | |||||
1289 | /** |
||||
1290 | * @throws SyntaxError |
||||
1291 | */ |
||||
1292 | 96 | private function parseInterfaceTypeDefinition() : InterfaceTypeDefinitionNode |
|||
1293 | { |
||||
1294 | 96 | $start = $this->lexer->token; |
|||
1295 | 96 | $description = $this->parseDescription(); |
|||
1296 | 96 | $this->expectKeyword('interface'); |
|||
1297 | 96 | $name = $this->parseName(); |
|||
1298 | 96 | $directives = $this->parseDirectives(true); |
|||
1299 | 96 | $fields = $this->parseFieldsDefinition(); |
|||
1300 | |||||
1301 | 96 | return new InterfaceTypeDefinitionNode([ |
|||
1302 | 96 | 'name' => $name, |
|||
1303 | 96 | 'directives' => $directives, |
|||
1304 | 96 | 'fields' => $fields, |
|||
1305 | 96 | 'loc' => $this->loc($start), |
|||
1306 | 96 | 'description' => $description, |
|||
1307 | ]); |
||||
1308 | } |
||||
1309 | |||||
1310 | /** |
||||
1311 | * UnionTypeDefinition : |
||||
1312 | * - Description? union Name Directives[Const]? UnionMemberTypes? |
||||
1313 | * |
||||
1314 | * @throws SyntaxError |
||||
1315 | */ |
||||
1316 | 85 | private function parseUnionTypeDefinition() : UnionTypeDefinitionNode |
|||
1317 | { |
||||
1318 | 85 | $start = $this->lexer->token; |
|||
1319 | 85 | $description = $this->parseDescription(); |
|||
1320 | 85 | $this->expectKeyword('union'); |
|||
1321 | 85 | $name = $this->parseName(); |
|||
1322 | 85 | $directives = $this->parseDirectives(true); |
|||
1323 | 85 | $types = $this->parseUnionMemberTypes(); |
|||
1324 | |||||
1325 | 81 | return new UnionTypeDefinitionNode([ |
|||
1326 | 81 | 'name' => $name, |
|||
1327 | 81 | 'directives' => $directives, |
|||
1328 | 81 | 'types' => $types, |
|||
1329 | 81 | 'loc' => $this->loc($start), |
|||
1330 | 81 | 'description' => $description, |
|||
1331 | ]); |
||||
1332 | } |
||||
1333 | |||||
1334 | /** |
||||
1335 | * UnionMemberTypes : |
||||
1336 | * - = `|`? NamedType |
||||
1337 | * - UnionMemberTypes | NamedType |
||||
1338 | * |
||||
1339 | * @return NamedTypeNode[] |
||||
1340 | */ |
||||
1341 | 85 | private function parseUnionMemberTypes() : array |
|||
1342 | { |
||||
1343 | 85 | $types = []; |
|||
1344 | 85 | if ($this->skip(Token::EQUALS)) { |
|||
1345 | // Optional leading pipe |
||||
1346 | 83 | $this->skip(Token::PIPE); |
|||
1347 | do { |
||||
1348 | 83 | $types[] = $this->parseNamedType(); |
|||
1349 | 81 | } while ($this->skip(Token::PIPE)); |
|||
1350 | } |
||||
1351 | |||||
1352 | 81 | return $types; |
|||
1353 | } |
||||
1354 | |||||
1355 | /** |
||||
1356 | * @throws SyntaxError |
||||
1357 | */ |
||||
1358 | 76 | private function parseEnumTypeDefinition() : EnumTypeDefinitionNode |
|||
1359 | { |
||||
1360 | 76 | $start = $this->lexer->token; |
|||
1361 | 76 | $description = $this->parseDescription(); |
|||
1362 | 76 | $this->expectKeyword('enum'); |
|||
1363 | 76 | $name = $this->parseName(); |
|||
1364 | 76 | $directives = $this->parseDirectives(true); |
|||
1365 | 76 | $values = $this->parseEnumValuesDefinition(); |
|||
1366 | |||||
1367 | 76 | return new EnumTypeDefinitionNode([ |
|||
1368 | 76 | 'name' => $name, |
|||
1369 | 76 | 'directives' => $directives, |
|||
1370 | 76 | 'values' => $values, |
|||
1371 | 76 | 'loc' => $this->loc($start), |
|||
1372 | 76 | 'description' => $description, |
|||
1373 | ]); |
||||
1374 | } |
||||
1375 | |||||
1376 | /** |
||||
1377 | * @throws SyntaxError |
||||
1378 | */ |
||||
1379 | 76 | private function parseEnumValuesDefinition() : NodeList |
|||
1380 | { |
||||
1381 | /** @var NodeList<EnumValueDefinitionNode&Node> $nodeList */ |
||||
1382 | 76 | $nodeList = $this->peek(Token::BRACE_L) |
|||
1383 | 75 | ? $this->many( |
|||
1384 | 75 | Token::BRACE_L, |
|||
1385 | function () { |
||||
1386 | 75 | return $this->parseEnumValueDefinition(); |
|||
1387 | 75 | }, |
|||
1388 | 75 | Token::BRACE_R |
|||
1389 | ) |
||||
1390 | 76 | : new NodeList([]); |
|||
1391 | |||||
1392 | 76 | return $nodeList; |
|||
1393 | } |
||||
1394 | |||||
1395 | /** |
||||
1396 | * @throws SyntaxError |
||||
1397 | */ |
||||
1398 | 75 | private function parseEnumValueDefinition() : EnumValueDefinitionNode |
|||
1399 | { |
||||
1400 | 75 | $start = $this->lexer->token; |
|||
1401 | 75 | $description = $this->parseDescription(); |
|||
1402 | 75 | $name = $this->parseName(); |
|||
1403 | 75 | $directives = $this->parseDirectives(true); |
|||
1404 | |||||
1405 | 75 | return new EnumValueDefinitionNode([ |
|||
1406 | 75 | 'name' => $name, |
|||
1407 | 75 | 'directives' => $directives, |
|||
1408 | 75 | 'loc' => $this->loc($start), |
|||
1409 | 75 | 'description' => $description, |
|||
1410 | ]); |
||||
1411 | } |
||||
1412 | |||||
1413 | /** |
||||
1414 | * @throws SyntaxError |
||||
1415 | */ |
||||
1416 | 85 | private function parseInputObjectTypeDefinition() : InputObjectTypeDefinitionNode |
|||
1417 | { |
||||
1418 | 85 | $start = $this->lexer->token; |
|||
1419 | 85 | $description = $this->parseDescription(); |
|||
1420 | 85 | $this->expectKeyword('input'); |
|||
1421 | 85 | $name = $this->parseName(); |
|||
1422 | 85 | $directives = $this->parseDirectives(true); |
|||
1423 | 85 | $fields = $this->parseInputFieldsDefinition(); |
|||
1424 | |||||
1425 | 84 | return new InputObjectTypeDefinitionNode([ |
|||
1426 | 84 | 'name' => $name, |
|||
1427 | 84 | 'directives' => $directives, |
|||
1428 | 84 | 'fields' => $fields, |
|||
1429 | 84 | 'loc' => $this->loc($start), |
|||
1430 | 84 | 'description' => $description, |
|||
1431 | ]); |
||||
1432 | } |
||||
1433 | |||||
1434 | /** |
||||
1435 | * @throws SyntaxError |
||||
1436 | */ |
||||
1437 | 85 | private function parseInputFieldsDefinition() : NodeList |
|||
1438 | { |
||||
1439 | /** @var NodeList<InputValueDefinitionNode&Node> $nodeList */ |
||||
1440 | 85 | $nodeList = $this->peek(Token::BRACE_L) |
|||
1441 | 83 | ? $this->many( |
|||
1442 | 83 | Token::BRACE_L, |
|||
1443 | function () { |
||||
1444 | 83 | return $this->parseInputValueDefinition(); |
|||
1445 | 83 | }, |
|||
1446 | 83 | Token::BRACE_R |
|||
1447 | ) |
||||
1448 | 84 | : new NodeList([]); |
|||
1449 | |||||
1450 | 84 | return $nodeList; |
|||
1451 | } |
||||
1452 | |||||
1453 | /** |
||||
1454 | * TypeExtension : |
||||
1455 | * - ScalarTypeExtension |
||||
1456 | * - ObjectTypeExtension |
||||
1457 | * - InterfaceTypeExtension |
||||
1458 | * - UnionTypeExtension |
||||
1459 | * - EnumTypeExtension |
||||
1460 | * - InputObjectTypeDefinition |
||||
1461 | * |
||||
1462 | * @throws SyntaxError |
||||
1463 | */ |
||||
1464 | 65 | private function parseTypeExtension() : TypeExtensionNode |
|||
1465 | { |
||||
1466 | 65 | $keywordToken = $this->lexer->lookahead(); |
|||
1467 | |||||
1468 | 65 | if ($keywordToken->kind === Token::NAME) { |
|||
1469 | 64 | switch ($keywordToken->value) { |
|||
1470 | 64 | case 'schema': |
|||
1471 | 12 | return $this->parseSchemaTypeExtension(); |
|||
1472 | 53 | case 'scalar': |
|||
1473 | 7 | return $this->parseScalarTypeExtension(); |
|||
1474 | 52 | case 'type': |
|||
1475 | 38 | return $this->parseObjectTypeExtension(); |
|||
1476 | 28 | case 'interface': |
|||
1477 | 15 | return $this->parseInterfaceTypeExtension(); |
|||
1478 | 22 | case 'union': |
|||
1479 | 14 | return $this->parseUnionTypeExtension(); |
|||
1480 | 17 | case 'enum': |
|||
1481 | 11 | return $this->parseEnumTypeExtension(); |
|||
1482 | 13 | case 'input': |
|||
1483 | 12 | return $this->parseInputObjectTypeExtension(); |
|||
1484 | } |
||||
1485 | } |
||||
1486 | |||||
1487 | 2 | throw $this->unexpected($keywordToken); |
|||
1488 | } |
||||
1489 | |||||
1490 | /** |
||||
1491 | * @throws SyntaxError |
||||
1492 | */ |
||||
1493 | 12 | private function parseSchemaTypeExtension() : SchemaTypeExtensionNode |
|||
1494 | { |
||||
1495 | 12 | $start = $this->lexer->token; |
|||
1496 | 12 | $this->expectKeyword('extend'); |
|||
1497 | 12 | $this->expectKeyword('schema'); |
|||
1498 | 12 | $directives = $this->parseDirectives(true); |
|||
1499 | 12 | $operationTypes = $this->peek(Token::BRACE_L) |
|||
1500 | 9 | ? $this->many( |
|||
1501 | 9 | Token::BRACE_L, |
|||
1502 | 9 | [$this, 'parseOperationTypeDefinition'], |
|||
1503 | 9 | Token::BRACE_R |
|||
1504 | ) |
||||
1505 | 12 | : []; |
|||
1506 | 12 | if (count($directives) === 0 && count($operationTypes) === 0) { |
|||
1507 | $this->unexpected(); |
||||
1508 | } |
||||
1509 | |||||
1510 | 12 | return new SchemaTypeExtensionNode([ |
|||
1511 | 12 | 'directives' => $directives, |
|||
1512 | 12 | 'operationTypes' => $operationTypes, |
|||
1513 | 12 | 'loc' => $this->loc($start), |
|||
1514 | ]); |
||||
1515 | } |
||||
1516 | |||||
1517 | /** |
||||
1518 | * @throws SyntaxError |
||||
1519 | */ |
||||
1520 | 7 | private function parseScalarTypeExtension() : ScalarTypeExtensionNode |
|||
1521 | { |
||||
1522 | 7 | $start = $this->lexer->token; |
|||
1523 | 7 | $this->expectKeyword('extend'); |
|||
1524 | 7 | $this->expectKeyword('scalar'); |
|||
1525 | 7 | $name = $this->parseName(); |
|||
1526 | 7 | $directives = $this->parseDirectives(true); |
|||
1527 | 7 | if (count($directives) === 0) { |
|||
1528 | throw $this->unexpected(); |
||||
1529 | } |
||||
1530 | |||||
1531 | 7 | return new ScalarTypeExtensionNode([ |
|||
1532 | 7 | 'name' => $name, |
|||
1533 | 7 | 'directives' => $directives, |
|||
1534 | 7 | 'loc' => $this->loc($start), |
|||
1535 | ]); |
||||
1536 | } |
||||
1537 | |||||
1538 | /** |
||||
1539 | * @throws SyntaxError |
||||
1540 | */ |
||||
1541 | 38 | private function parseObjectTypeExtension() : ObjectTypeExtensionNode |
|||
1542 | { |
||||
1543 | 38 | $start = $this->lexer->token; |
|||
1544 | 38 | $this->expectKeyword('extend'); |
|||
1545 | 38 | $this->expectKeyword('type'); |
|||
1546 | 38 | $name = $this->parseName(); |
|||
1547 | 38 | $interfaces = $this->parseImplementsInterfaces(); |
|||
1548 | 38 | $directives = $this->parseDirectives(true); |
|||
1549 | 38 | $fields = $this->parseFieldsDefinition(); |
|||
1550 | |||||
1551 | 38 | if (count($interfaces) === 0 && |
|||
1552 | 38 | count($directives) === 0 && |
|||
1553 | 38 | count($fields) === 0 |
|||
1554 | ) { |
||||
1555 | 1 | throw $this->unexpected(); |
|||
1556 | } |
||||
1557 | |||||
1558 | 37 | return new ObjectTypeExtensionNode([ |
|||
1559 | 37 | 'name' => $name, |
|||
1560 | 37 | 'interfaces' => $interfaces, |
|||
1561 | 37 | 'directives' => $directives, |
|||
1562 | 37 | 'fields' => $fields, |
|||
1563 | 37 | 'loc' => $this->loc($start), |
|||
1564 | ]); |
||||
1565 | } |
||||
1566 | |||||
1567 | /** |
||||
1568 | * @throws SyntaxError |
||||
1569 | */ |
||||
1570 | 15 | private function parseInterfaceTypeExtension() : InterfaceTypeExtensionNode |
|||
1571 | { |
||||
1572 | 15 | $start = $this->lexer->token; |
|||
1573 | 15 | $this->expectKeyword('extend'); |
|||
1574 | 15 | $this->expectKeyword('interface'); |
|||
1575 | 15 | $name = $this->parseName(); |
|||
1576 | 15 | $directives = $this->parseDirectives(true); |
|||
1577 | 15 | $fields = $this->parseFieldsDefinition(); |
|||
1578 | 15 | if (count($directives) === 0 && |
|||
1579 | 15 | count($fields) === 0 |
|||
1580 | ) { |
||||
1581 | throw $this->unexpected(); |
||||
1582 | } |
||||
1583 | |||||
1584 | 15 | return new InterfaceTypeExtensionNode([ |
|||
1585 | 15 | 'name' => $name, |
|||
1586 | 15 | 'directives' => $directives, |
|||
1587 | 15 | 'fields' => $fields, |
|||
1588 | 15 | 'loc' => $this->loc($start), |
|||
1589 | ]); |
||||
1590 | } |
||||
1591 | |||||
1592 | /** |
||||
1593 | * UnionTypeExtension : |
||||
1594 | * - extend union Name Directives[Const]? UnionMemberTypes |
||||
1595 | * - extend union Name Directives[Const] |
||||
1596 | * |
||||
1597 | * @throws SyntaxError |
||||
1598 | */ |
||||
1599 | 14 | private function parseUnionTypeExtension() : UnionTypeExtensionNode |
|||
1600 | { |
||||
1601 | 14 | $start = $this->lexer->token; |
|||
1602 | 14 | $this->expectKeyword('extend'); |
|||
1603 | 14 | $this->expectKeyword('union'); |
|||
1604 | 14 | $name = $this->parseName(); |
|||
1605 | 14 | $directives = $this->parseDirectives(true); |
|||
1606 | 14 | $types = $this->parseUnionMemberTypes(); |
|||
1607 | 14 | if (count($directives) === 0 && count($types) === 0) { |
|||
1608 | throw $this->unexpected(); |
||||
1609 | } |
||||
1610 | |||||
1611 | 14 | return new UnionTypeExtensionNode([ |
|||
1612 | 14 | 'name' => $name, |
|||
1613 | 14 | 'directives' => $directives, |
|||
1614 | 14 | 'types' => $types, |
|||
1615 | 14 | 'loc' => $this->loc($start), |
|||
1616 | ]); |
||||
1617 | } |
||||
1618 | |||||
1619 | /** |
||||
1620 | * @throws SyntaxError |
||||
1621 | */ |
||||
1622 | 11 | private function parseEnumTypeExtension() : EnumTypeExtensionNode |
|||
1623 | { |
||||
1624 | 11 | $start = $this->lexer->token; |
|||
1625 | 11 | $this->expectKeyword('extend'); |
|||
1626 | 11 | $this->expectKeyword('enum'); |
|||
1627 | 11 | $name = $this->parseName(); |
|||
1628 | 11 | $directives = $this->parseDirectives(true); |
|||
1629 | 11 | $values = $this->parseEnumValuesDefinition(); |
|||
1630 | 11 | if (count($directives) === 0 && |
|||
1631 | 11 | count($values) === 0 |
|||
1632 | ) { |
||||
1633 | throw $this->unexpected(); |
||||
1634 | } |
||||
1635 | |||||
1636 | 11 | return new EnumTypeExtensionNode([ |
|||
1637 | 11 | 'name' => $name, |
|||
1638 | 11 | 'directives' => $directives, |
|||
1639 | 11 | 'values' => $values, |
|||
1640 | 11 | 'loc' => $this->loc($start), |
|||
1641 | ]); |
||||
1642 | } |
||||
1643 | |||||
1644 | /** |
||||
1645 | * @throws SyntaxError |
||||
1646 | */ |
||||
1647 | 12 | private function parseInputObjectTypeExtension() : InputObjectTypeExtensionNode |
|||
1648 | { |
||||
1649 | 12 | $start = $this->lexer->token; |
|||
1650 | 12 | $this->expectKeyword('extend'); |
|||
1651 | 12 | $this->expectKeyword('input'); |
|||
1652 | 12 | $name = $this->parseName(); |
|||
1653 | 12 | $directives = $this->parseDirectives(true); |
|||
1654 | 12 | $fields = $this->parseInputFieldsDefinition(); |
|||
1655 | 12 | if (count($directives) === 0 && |
|||
1656 | 12 | count($fields) === 0 |
|||
1657 | ) { |
||||
1658 | throw $this->unexpected(); |
||||
1659 | } |
||||
1660 | |||||
1661 | 12 | return new InputObjectTypeExtensionNode([ |
|||
1662 | 12 | 'name' => $name, |
|||
1663 | 12 | 'directives' => $directives, |
|||
1664 | 12 | 'fields' => $fields, |
|||
1665 | 12 | 'loc' => $this->loc($start), |
|||
1666 | ]); |
||||
1667 | } |
||||
1668 | |||||
1669 | /** |
||||
1670 | * DirectiveDefinition : |
||||
1671 | * - directive @ Name ArgumentsDefinition? on DirectiveLocations |
||||
1672 | * |
||||
1673 | * @throws SyntaxError |
||||
1674 | */ |
||||
1675 | 90 | private function parseDirectiveDefinition() : DirectiveDefinitionNode |
|||
1676 | { |
||||
1677 | 90 | $start = $this->lexer->token; |
|||
1678 | 90 | $description = $this->parseDescription(); |
|||
1679 | 90 | $this->expectKeyword('directive'); |
|||
1680 | 90 | $this->expect(Token::AT); |
|||
1681 | 90 | $name = $this->parseName(); |
|||
1682 | 90 | $args = $this->parseArgumentsDefinition(); |
|||
1683 | 90 | $this->expectKeyword('on'); |
|||
1684 | 90 | $locations = $this->parseDirectiveLocations(); |
|||
1685 | |||||
1686 | 89 | return new DirectiveDefinitionNode([ |
|||
1687 | 89 | 'name' => $name, |
|||
1688 | 89 | 'arguments' => $args, |
|||
1689 | 89 | 'locations' => $locations, |
|||
1690 | 89 | 'loc' => $this->loc($start), |
|||
1691 | 89 | 'description' => $description, |
|||
1692 | ]); |
||||
1693 | } |
||||
1694 | |||||
1695 | /** |
||||
1696 | * @return NameNode[] |
||||
1697 | * |
||||
1698 | * @throws SyntaxError |
||||
1699 | */ |
||||
1700 | 90 | private function parseDirectiveLocations() : array |
|||
1701 | { |
||||
1702 | // Optional leading pipe |
||||
1703 | 90 | $this->skip(Token::PIPE); |
|||
1704 | 90 | $locations = []; |
|||
1705 | do { |
||||
1706 | 90 | $locations[] = $this->parseDirectiveLocation(); |
|||
1707 | 90 | } while ($this->skip(Token::PIPE)); |
|||
1708 | |||||
1709 | 89 | return $locations; |
|||
1710 | } |
||||
1711 | |||||
1712 | /** |
||||
1713 | * @throws SyntaxError |
||||
1714 | */ |
||||
1715 | 90 | private function parseDirectiveLocation() : NameNode |
|||
1716 | { |
||||
1717 | 90 | $start = $this->lexer->token; |
|||
1718 | 90 | $name = $this->parseName(); |
|||
1719 | 90 | if (DirectiveLocation::has($name->value)) { |
|||
1720 | 90 | return $name; |
|||
1721 | } |
||||
1722 | |||||
1723 | 1 | throw $this->unexpected($start); |
|||
1724 | } |
||||
1725 | } |
||||
1726 |