Failed Conditions
Push — master ( cc39b3...a3ef1b )
by Šimon
10s
created

VisitorTest::testAllowsSkippingASubTree()   A

Complexity

Conditions 3
Paths 1

Size

Total Lines 38
Code Lines 28

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 28
dl 0
loc 38
rs 9.472
c 0
b 0
f 0
cc 3
nc 1
nop 0
1
<?php
2
3
declare(strict_types=1);
4
5
namespace GraphQL\Tests\Language;
6
7
use GraphQL\Language\AST\DocumentNode;
8
use GraphQL\Language\AST\FieldNode;
9
use GraphQL\Language\AST\NameNode;
10
use GraphQL\Language\AST\Node;
11
use GraphQL\Language\AST\NodeKind;
12
use GraphQL\Language\AST\NodeList;
13
use GraphQL\Language\AST\OperationDefinitionNode;
14
use GraphQL\Language\AST\SelectionSetNode;
15
use GraphQL\Language\Parser;
16
use GraphQL\Language\Printer;
17
use GraphQL\Language\Visitor;
18
use GraphQL\Tests\Validator\ValidatorTestCase;
19
use GraphQL\Type\Definition\Type;
20
use GraphQL\Utils\TypeInfo;
21
use function array_keys;
22
use function array_slice;
23
use function count;
24
use function file_get_contents;
25
use function func_get_args;
26
use function gettype;
27
use function is_array;
28
use function iterator_to_array;
29
30
class VisitorTest extends ValidatorTestCase
31
{
32
    private function getNodeByPath(DocumentNode $ast, $path)
33
    {
34
        $result = $ast;
35
        foreach ($path as $key) {
36
            $resultArray = $result instanceof NodeList ? iterator_to_array($result) : $result->toArray();
37
            $this->assertArrayHasKey($key, $resultArray);
38
            $result = $resultArray[$key];
39
        }
40
        return $result;
41
    }
42
43
    private function checkVisitorFnArgs($ast, $args, $isEdited = false)
44
    {
45
        /** @var Node $node */
46
        list($node, $key, $parent, $path, $ancestors) = $args;
47
48
        $parentArray = $parent && ! is_array($parent) ? ($parent instanceof NodeList ? iterator_to_array($parent) : $parent->toArray()) : $parent;
49
50
        $this->assertInstanceOf(Node::class, $node);
51
        $this->assertContains($node->kind, array_keys(NodeKind::$classMap));
52
53
        $isRoot = $key === null;
54
        if ($isRoot) {
55
            if (! $isEdited) {
56
                $this->assertEquals($ast, $node);
57
            }
58
            $this->assertEquals(null, $parent);
59
            $this->assertEquals([], $path);
60
            $this->assertEquals([], $ancestors);
61
            return;
62
        }
63
64
        $this->assertContains(gettype($key), ['integer', 'string']);
65
66
        $this->assertArrayHasKey($key, $parentArray);
67
68
        $this->assertInternalType('array', $path);
69
        $this->assertEquals($key, $path[count($path) - 1]);
70
71
        $this->assertInternalType('array', $ancestors);
72
        $this->assertCount(count($path) - 1, $ancestors);
73
74
        if ($isEdited) {
75
            return;
76
        }
77
78
        $this->assertEquals($node, $parentArray[$key]);
79
        $this->assertEquals($node, $this->getNodeByPath($ast, $path));
80
        $ancestorsLength = count($ancestors);
81
        for ($i = 0; $i < $ancestorsLength; ++$i) {
82
            $ancestorPath = array_slice($path, 0, $i);
83
            $this->assertEquals($ancestors[$i], $this->getNodeByPath($ast, $ancestorPath));
84
        }
85
    }
86
87
    public function testValidatesPathArgument()
88
    {
89
        $visited = [];
90
91
        $ast = Parser::parse('{ a }', ['noLocation' => true]);
92
93
        Visitor::visit($ast, [
94
           'enter' => function ($node, $key, $parent, $path) use ($ast, &$visited) {
95
                $this->checkVisitorFnArgs($ast, func_get_args());
96
                $visited[] = ['enter', $path];
97
           },
98
            'leave' => function ($node, $key, $parent, $path) use ($ast, &$visited) {
99
                $this->checkVisitorFnArgs($ast, func_get_args());
100
                $visited[] = ['leave', $path];
101
            },
102
        ]);
103
104
        $expected = [
105
            ['enter', []],
106
            ['enter', ['definitions', 0]],
107
            ['enter', ['definitions', 0, 'selectionSet']],
108
            ['enter', ['definitions', 0, 'selectionSet', 'selections', 0]],
109
            ['enter', ['definitions', 0, 'selectionSet', 'selections', 0, 'name']],
110
            ['leave', ['definitions', 0, 'selectionSet', 'selections', 0, 'name']],
111
            ['leave', ['definitions', 0, 'selectionSet', 'selections', 0]],
112
            ['leave', ['definitions', 0, 'selectionSet']],
113
            ['leave', ['definitions', 0]],
114
            ['leave', []],
115
        ];
116
117
        $this->assertEquals($expected, $visited);
118
    }
119
120
    public function testAllowsEditingNodeOnEnterAndOnLeave()
121
    {
122
        $ast = Parser::parse('{ a, b, c { a, b, c } }', [ 'noLocation' => true ]);
123
124
        $selectionSet = null;
125
        $editedAst    = Visitor::visit($ast, [
126
            NodeKind::OPERATION_DEFINITION => [
127
                'enter' => function (OperationDefinitionNode $node) use (&$selectionSet, $ast) {
128
                    $this->checkVisitorFnArgs($ast, func_get_args());
129
                    $selectionSet = $node->selectionSet;
130
131
                    $newNode               = clone $node;
132
                    $newNode->selectionSet = new SelectionSetNode([
133
                        'selections' => [],
134
                    ]);
135
                    $newNode->didEnter     = true;
0 ignored issues
show
Bug introduced by
The property didEnter does not seem to exist on GraphQL\Language\AST\OperationDefinitionNode.
Loading history...
136
                    return $newNode;
137
                },
138
                'leave' => function (OperationDefinitionNode $node) use (&$selectionSet, $ast) {
139
                    $this->checkVisitorFnArgs($ast, func_get_args(), true);
140
                    $newNode               = clone $node;
141
                    $newNode->selectionSet = $selectionSet;
142
                    $newNode->didLeave     = true;
0 ignored issues
show
Bug introduced by
The property didLeave does not seem to exist on GraphQL\Language\AST\OperationDefinitionNode.
Loading history...
143
                    return $newNode;
144
                },
145
            ],
146
        ]);
147
148
        $this->assertNotEquals($ast, $editedAst);
149
150
        $expected                           = $ast->cloneDeep();
151
        $expected->definitions[0]->didEnter = true;
152
        $expected->definitions[0]->didLeave = true;
153
154
        $this->assertEquals($expected, $editedAst);
155
    }
156
157
    public function testAllowsEditingRootNodeOnEnterAndLeave()
158
    {
159
        $ast         = Parser::parse('{ a, b, c { a, b, c } }', [ 'noLocation' => true ]);
160
        $definitions = $ast->definitions;
161
162
        $editedAst = Visitor::visit($ast, [
163
            NodeKind::DOCUMENT => [
164
                'enter' => function (DocumentNode $node) use ($ast) {
165
                    $this->checkVisitorFnArgs($ast, func_get_args());
166
                    $tmp              = clone $node;
167
                    $tmp->definitions = [];
168
                    $tmp->didEnter    = true;
0 ignored issues
show
Bug introduced by
The property didEnter does not seem to exist on GraphQL\Language\AST\DocumentNode.
Loading history...
169
                    return $tmp;
170
                },
171
                'leave' => function (DocumentNode $node) use ($definitions, $ast) {
172
                    $this->checkVisitorFnArgs($ast, func_get_args(), true);
173
                    $node->definitions = $definitions;
174
                    $node->didLeave    = true;
0 ignored issues
show
Bug introduced by
The property didLeave does not seem to exist on GraphQL\Language\AST\DocumentNode.
Loading history...
175
                },
176
            ],
177
        ]);
178
179
        $this->assertNotEquals($ast, $editedAst);
180
181
        $tmp           = $ast->cloneDeep();
182
        $tmp->didEnter = true;
0 ignored issues
show
Bug introduced by
The property didEnter does not seem to exist on GraphQL\Language\AST\Node.
Loading history...
183
        $tmp->didLeave = true;
0 ignored issues
show
Bug introduced by
The property didLeave does not seem to exist on GraphQL\Language\AST\Node.
Loading history...
184
185
        $this->assertEquals($tmp, $editedAst);
186
    }
187
188
    public function testAllowsForEditingOnEnter()
189
    {
190
        $ast       = Parser::parse('{ a, b, c { a, b, c } }', ['noLocation' => true]);
191
        $editedAst = Visitor::visit($ast, [
192
            'enter' => function ($node) use ($ast) {
193
                $this->checkVisitorFnArgs($ast, func_get_args());
194
                if ($node instanceof FieldNode && $node->name->value === 'b') {
195
                    return Visitor::removeNode();
196
                }
197
            },
198
        ]);
199
200
        $this->assertEquals(
201
            Parser::parse('{ a, b, c { a, b, c } }', ['noLocation' => true]),
202
            $ast
203
        );
204
        $this->assertEquals(
205
            Parser::parse('{ a,    c { a,    c } }', ['noLocation' => true]),
206
            $editedAst
207
        );
208
    }
209
210
    public function testAllowsForEditingOnLeave()
211
    {
212
        $ast       = Parser::parse('{ a, b, c { a, b, c } }', ['noLocation' => true]);
213
        $editedAst = Visitor::visit($ast, [
214
            'leave' => function ($node) use ($ast) {
215
                $this->checkVisitorFnArgs($ast, func_get_args(), true);
216
                if ($node instanceof FieldNode && $node->name->value === 'b') {
217
                    return Visitor::removeNode();
218
                }
219
            },
220
        ]);
221
222
        $this->assertEquals(
223
            Parser::parse('{ a, b, c { a, b, c } }', ['noLocation' => true]),
224
            $ast
225
        );
226
227
        $this->assertEquals(
228
            Parser::parse('{ a,    c { a,    c } }', ['noLocation' => true]),
229
            $editedAst
230
        );
231
    }
232
233
    public function testVisitsEditedNode()
234
    {
235
        $addedField = new FieldNode([
236
            'name' => new NameNode(['value' => '__typename']),
237
        ]);
238
239
        $didVisitAddedField = false;
240
241
        $ast = Parser::parse('{ a { x } }', ['noLocation' => true]);
242
243
        Visitor::visit($ast, [
244
            'enter' => function ($node) use ($addedField, &$didVisitAddedField, $ast) {
245
                $this->checkVisitorFnArgs($ast, func_get_args(), true);
246
                if ($node instanceof FieldNode && $node->name->value === 'a') {
247
                    return new FieldNode([
248
                        'selectionSet' => new SelectionSetNode([
249
                            'selections' => NodeList::create([$addedField])->merge($node->selectionSet->selections),
250
                        ]),
251
                    ]);
252
                }
253
                if ($node !== $addedField) {
254
                    return;
255
                }
256
257
                $didVisitAddedField = true;
258
            },
259
        ]);
260
261
        $this->assertTrue($didVisitAddedField);
262
    }
263
264
    public function testAllowsSkippingASubTree()
265
    {
266
        $visited = [];
267
        $ast     = Parser::parse('{ a, b { x }, c }', ['noLocation' => true]);
268
269
        Visitor::visit($ast, [
270
            'enter' => function (Node $node) use (&$visited, $ast) {
271
                $this->checkVisitorFnArgs($ast, func_get_args());
272
                $visited[] = ['enter', $node->kind, $node->value ?? null];
273
                if ($node instanceof FieldNode && $node->name->value === 'b') {
274
                    return Visitor::skipNode();
275
                }
276
            },
277
            'leave' => function (Node $node) use (&$visited, $ast) {
278
                $this->checkVisitorFnArgs($ast, func_get_args());
279
                $visited[] = ['leave', $node->kind, $node->value ?? null];
280
            },
281
        ]);
282
283
        $expected = [
284
            [ 'enter', 'Document', null ],
285
            [ 'enter', 'OperationDefinition', null ],
286
            [ 'enter', 'SelectionSet', null ],
287
            [ 'enter', 'Field', null ],
288
            [ 'enter', 'Name', 'a' ],
289
            [ 'leave', 'Name', 'a' ],
290
            [ 'leave', 'Field', null ],
291
            [ 'enter', 'Field', null ],
292
            [ 'enter', 'Field', null ],
293
            [ 'enter', 'Name', 'c' ],
294
            [ 'leave', 'Name', 'c' ],
295
            [ 'leave', 'Field', null ],
296
            [ 'leave', 'SelectionSet', null ],
297
            [ 'leave', 'OperationDefinition', null ],
298
            [ 'leave', 'Document', null ],
299
        ];
300
301
        $this->assertEquals($expected, $visited);
302
    }
303
304
    public function testAllowsEarlyExitWhileVisiting()
305
    {
306
        $visited = [];
307
        $ast     = Parser::parse('{ a, b { x }, c }', ['noLocation' => true]);
308
309
        Visitor::visit($ast, [
310
            'enter' => function (Node $node) use (&$visited, $ast) {
311
                $this->checkVisitorFnArgs($ast, func_get_args());
312
                $visited[] = ['enter', $node->kind, $node->value ?? null];
313
                if ($node instanceof NameNode && $node->value === 'x') {
314
                    return Visitor::stop();
315
                }
316
            },
317
            'leave' => function (Node $node) use (&$visited, $ast) {
318
                $this->checkVisitorFnArgs($ast, func_get_args());
319
                $visited[] = ['leave', $node->kind, $node->value ?? null];
320
            },
321
        ]);
322
323
        $expected = [
324
            [ 'enter', 'Document', null ],
325
            [ 'enter', 'OperationDefinition', null ],
326
            [ 'enter', 'SelectionSet', null ],
327
            [ 'enter', 'Field', null ],
328
            [ 'enter', 'Name', 'a' ],
329
            [ 'leave', 'Name', 'a' ],
330
            [ 'leave', 'Field', null ],
331
            [ 'enter', 'Field', null ],
332
            [ 'enter', 'Name', 'b' ],
333
            [ 'leave', 'Name', 'b' ],
334
            [ 'enter', 'SelectionSet', null ],
335
            [ 'enter', 'Field', null ],
336
            [ 'enter', 'Name', 'x' ],
337
        ];
338
339
        $this->assertEquals($expected, $visited);
340
    }
341
342
    public function testAllowsEarlyExitWhileLeaving()
343
    {
344
        $visited = [];
345
346
        $ast = Parser::parse('{ a, b { x }, c }', ['noLocation' => true]);
347
        Visitor::visit($ast, [
348
            'enter' => function ($node) use (&$visited, $ast) {
349
                $this->checkVisitorFnArgs($ast, func_get_args());
350
                $visited[] = ['enter', $node->kind, $node->value ?? null];
351
            },
352
            'leave' => function ($node) use (&$visited, $ast) {
353
                $this->checkVisitorFnArgs($ast, func_get_args());
354
                $visited[] = ['leave', $node->kind, $node->value ?? null];
355
356
                if ($node->kind === NodeKind::NAME && $node->value === 'x') {
357
                    return Visitor::stop();
358
                }
359
            },
360
        ]);
361
362
        $this->assertEquals($visited, [
363
            [ 'enter', 'Document', null ],
364
            [ 'enter', 'OperationDefinition', null ],
365
            [ 'enter', 'SelectionSet', null ],
366
            [ 'enter', 'Field', null ],
367
            [ 'enter', 'Name', 'a' ],
368
            [ 'leave', 'Name', 'a' ],
369
            [ 'leave', 'Field', null ],
370
            [ 'enter', 'Field', null ],
371
            [ 'enter', 'Name', 'b' ],
372
            [ 'leave', 'Name', 'b' ],
373
            [ 'enter', 'SelectionSet', null ],
374
            [ 'enter', 'Field', null ],
375
            [ 'enter', 'Name', 'x' ],
376
            [ 'leave', 'Name', 'x' ],
377
        ]);
378
    }
379
380
    public function testAllowsANamedFunctionsVisitorAPI()
381
    {
382
        $visited = [];
383
        $ast     = Parser::parse('{ a, b { x }, c }', ['noLocation' => true]);
384
385
        Visitor::visit($ast, [
386
            NodeKind::NAME => function (NameNode $node) use (&$visited, $ast) {
387
                $this->checkVisitorFnArgs($ast, func_get_args());
388
                $visited[] = ['enter', $node->kind, $node->value];
389
            },
390
            NodeKind::SELECTION_SET => [
391
                'enter' => function (SelectionSetNode $node) use (&$visited, $ast) {
392
                    $this->checkVisitorFnArgs($ast, func_get_args());
393
                    $visited[] = ['enter', $node->kind, null];
394
                },
395
                'leave' => function (SelectionSetNode $node) use (&$visited, $ast) {
396
                    $this->checkVisitorFnArgs($ast, func_get_args());
397
                    $visited[] = ['leave', $node->kind, null];
398
                },
399
            ],
400
        ]);
401
402
        $expected = [
403
            [ 'enter', 'SelectionSet', null ],
404
            [ 'enter', 'Name', 'a' ],
405
            [ 'enter', 'Name', 'b' ],
406
            [ 'enter', 'SelectionSet', null ],
407
            [ 'enter', 'Name', 'x' ],
408
            [ 'leave', 'SelectionSet', null ],
409
            [ 'enter', 'Name', 'c' ],
410
            [ 'leave', 'SelectionSet', null ],
411
        ];
412
413
        $this->assertEquals($expected, $visited);
414
    }
415
416
    public function testExperimentalVisitsVariablesDefinedInFragments()
417
    {
418
        $ast     = Parser::parse(
419
            'fragment a($v: Boolean = false) on t { f }',
420
            [
421
                'noLocation' => true,
422
                'experimentalFragmentVariables' => true,
423
            ]
424
        );
425
        $visited = [];
426
427
        Visitor::visit($ast, [
428
            'enter' => function ($node) use (&$visited, $ast) {
429
                $this->checkVisitorFnArgs($ast, func_get_args());
430
                $visited[] = ['enter', $node->kind, $node->value ?? null];
431
            },
432
            'leave' => function ($node) use (&$visited, $ast) {
433
                $this->checkVisitorFnArgs($ast, func_get_args());
434
                $visited[] = ['leave', $node->kind, $node->value ?? null];
435
            },
436
        ]);
437
438
        $expected = [
439
            ['enter', 'Document', null],
440
            ['enter', 'FragmentDefinition', null],
441
            ['enter', 'Name', 'a'],
442
            ['leave', 'Name', 'a'],
443
            ['enter', 'VariableDefinition', null],
444
            ['enter', 'Variable', null],
445
            ['enter', 'Name', 'v'],
446
            ['leave', 'Name', 'v'],
447
            ['leave', 'Variable', null],
448
            ['enter', 'NamedType', null],
449
            ['enter', 'Name', 'Boolean'],
450
            ['leave', 'Name', 'Boolean'],
451
            ['leave', 'NamedType', null],
452
            ['enter', 'BooleanValue', false],
453
            ['leave', 'BooleanValue', false],
454
            ['leave', 'VariableDefinition', null],
455
            ['enter', 'NamedType', null],
456
            ['enter', 'Name', 't'],
457
            ['leave', 'Name', 't'],
458
            ['leave', 'NamedType', null],
459
            ['enter', 'SelectionSet', null],
460
            ['enter', 'Field', null],
461
            ['enter', 'Name', 'f'],
462
            ['leave', 'Name', 'f'],
463
            ['leave', 'Field', null],
464
            ['leave', 'SelectionSet', null],
465
            ['leave', 'FragmentDefinition', null],
466
            ['leave', 'Document', null],
467
        ];
468
469
        $this->assertEquals($expected, $visited);
470
    }
471
472
    public function testVisitsKitchenSink()
473
    {
474
        $kitchenSink = file_get_contents(__DIR__ . '/kitchen-sink.graphql');
475
        $ast         = Parser::parse($kitchenSink);
476
477
        $visited = [];
478
        Visitor::visit($ast, [
479
            'enter' => function (Node $node, $key, $parent) use (&$visited, $ast) {
480
                $this->checkVisitorFnArgs($ast, func_get_args());
481
                $r         = ['enter', $node->kind, $key, $parent instanceof Node ? $parent->kind : null];
482
                $visited[] = $r;
483
            },
484
            'leave' => function (Node $node, $key, $parent) use (&$visited, $ast) {
485
                $this->checkVisitorFnArgs($ast, func_get_args());
486
                $r         = ['leave', $node->kind, $key, $parent instanceof Node ? $parent->kind : null];
487
                $visited[] = $r;
488
            },
489
        ]);
490
491
        $expected = [
492
            [ 'enter', 'Document', null, null ],
493
            [ 'enter', 'OperationDefinition', 0, null ],
494
            [ 'enter', 'Name', 'name', 'OperationDefinition' ],
495
            [ 'leave', 'Name', 'name', 'OperationDefinition' ],
496
            [ 'enter', 'VariableDefinition', 0, null ],
497
            [ 'enter', 'Variable', 'variable', 'VariableDefinition' ],
498
            [ 'enter', 'Name', 'name', 'Variable' ],
499
            [ 'leave', 'Name', 'name', 'Variable' ],
500
            [ 'leave', 'Variable', 'variable', 'VariableDefinition' ],
501
            [ 'enter', 'NamedType', 'type', 'VariableDefinition' ],
502
            [ 'enter', 'Name', 'name', 'NamedType' ],
503
            [ 'leave', 'Name', 'name', 'NamedType' ],
504
            [ 'leave', 'NamedType', 'type', 'VariableDefinition' ],
505
            [ 'leave', 'VariableDefinition', 0, null ],
506
            [ 'enter', 'VariableDefinition', 1, null ],
507
            [ 'enter', 'Variable', 'variable', 'VariableDefinition' ],
508
            [ 'enter', 'Name', 'name', 'Variable' ],
509
            [ 'leave', 'Name', 'name', 'Variable' ],
510
            [ 'leave', 'Variable', 'variable', 'VariableDefinition' ],
511
            [ 'enter', 'NamedType', 'type', 'VariableDefinition' ],
512
            [ 'enter', 'Name', 'name', 'NamedType' ],
513
            [ 'leave', 'Name', 'name', 'NamedType' ],
514
            [ 'leave', 'NamedType', 'type', 'VariableDefinition' ],
515
            [ 'enter', 'EnumValue', 'defaultValue', 'VariableDefinition' ],
516
            [ 'leave', 'EnumValue', 'defaultValue', 'VariableDefinition' ],
517
            [ 'leave', 'VariableDefinition', 1, null ],
518
            [ 'enter', 'SelectionSet', 'selectionSet', 'OperationDefinition' ],
519
            [ 'enter', 'Field', 0, null ],
520
            [ 'enter', 'Name', 'alias', 'Field' ],
521
            [ 'leave', 'Name', 'alias', 'Field' ],
522
            [ 'enter', 'Name', 'name', 'Field' ],
523
            [ 'leave', 'Name', 'name', 'Field' ],
524
            [ 'enter', 'Argument', 0, null ],
525
            [ 'enter', 'Name', 'name', 'Argument' ],
526
            [ 'leave', 'Name', 'name', 'Argument' ],
527
            [ 'enter', 'ListValue', 'value', 'Argument' ],
528
            [ 'enter', 'IntValue', 0, null ],
529
            [ 'leave', 'IntValue', 0, null ],
530
            [ 'enter', 'IntValue', 1, null ],
531
            [ 'leave', 'IntValue', 1, null ],
532
            [ 'leave', 'ListValue', 'value', 'Argument' ],
533
            [ 'leave', 'Argument', 0, null ],
534
            [ 'enter', 'SelectionSet', 'selectionSet', 'Field' ],
535
            [ 'enter', 'Field', 0, null ],
536
            [ 'enter', 'Name', 'name', 'Field' ],
537
            [ 'leave', 'Name', 'name', 'Field' ],
538
            [ 'leave', 'Field', 0, null ],
539
            [ 'enter', 'InlineFragment', 1, null ],
540
            [ 'enter', 'NamedType', 'typeCondition', 'InlineFragment' ],
541
            [ 'enter', 'Name', 'name', 'NamedType' ],
542
            [ 'leave', 'Name', 'name', 'NamedType' ],
543
            [ 'leave', 'NamedType', 'typeCondition', 'InlineFragment' ],
544
            [ 'enter', 'Directive', 0, null ],
545
            [ 'enter', 'Name', 'name', 'Directive' ],
546
            [ 'leave', 'Name', 'name', 'Directive' ],
547
            [ 'leave', 'Directive', 0, null ],
548
            [ 'enter', 'SelectionSet', 'selectionSet', 'InlineFragment' ],
549
            [ 'enter', 'Field', 0, null ],
550
            [ 'enter', 'Name', 'name', 'Field' ],
551
            [ 'leave', 'Name', 'name', 'Field' ],
552
            [ 'enter', 'SelectionSet', 'selectionSet', 'Field' ],
553
            [ 'enter', 'Field', 0, null ],
554
            [ 'enter', 'Name', 'name', 'Field' ],
555
            [ 'leave', 'Name', 'name', 'Field' ],
556
            [ 'leave', 'Field', 0, null ],
557
            [ 'enter', 'Field', 1, null ],
558
            [ 'enter', 'Name', 'alias', 'Field' ],
559
            [ 'leave', 'Name', 'alias', 'Field' ],
560
            [ 'enter', 'Name', 'name', 'Field' ],
561
            [ 'leave', 'Name', 'name', 'Field' ],
562
            [ 'enter', 'Argument', 0, null ],
563
            [ 'enter', 'Name', 'name', 'Argument' ],
564
            [ 'leave', 'Name', 'name', 'Argument' ],
565
            [ 'enter', 'IntValue', 'value', 'Argument' ],
566
            [ 'leave', 'IntValue', 'value', 'Argument' ],
567
            [ 'leave', 'Argument', 0, null ],
568
            [ 'enter', 'Argument', 1, null ],
569
            [ 'enter', 'Name', 'name', 'Argument' ],
570
            [ 'leave', 'Name', 'name', 'Argument' ],
571
            [ 'enter', 'Variable', 'value', 'Argument' ],
572
            [ 'enter', 'Name', 'name', 'Variable' ],
573
            [ 'leave', 'Name', 'name', 'Variable' ],
574
            [ 'leave', 'Variable', 'value', 'Argument' ],
575
            [ 'leave', 'Argument', 1, null ],
576
            [ 'enter', 'Directive', 0, null ],
577
            [ 'enter', 'Name', 'name', 'Directive' ],
578
            [ 'leave', 'Name', 'name', 'Directive' ],
579
            [ 'enter', 'Argument', 0, null ],
580
            [ 'enter', 'Name', 'name', 'Argument' ],
581
            [ 'leave', 'Name', 'name', 'Argument' ],
582
            [ 'enter', 'Variable', 'value', 'Argument' ],
583
            [ 'enter', 'Name', 'name', 'Variable' ],
584
            [ 'leave', 'Name', 'name', 'Variable' ],
585
            [ 'leave', 'Variable', 'value', 'Argument' ],
586
            [ 'leave', 'Argument', 0, null ],
587
            [ 'leave', 'Directive', 0, null ],
588
            [ 'enter', 'SelectionSet', 'selectionSet', 'Field' ],
589
            [ 'enter', 'Field', 0, null ],
590
            [ 'enter', 'Name', 'name', 'Field' ],
591
            [ 'leave', 'Name', 'name', 'Field' ],
592
            [ 'leave', 'Field', 0, null ],
593
            [ 'enter', 'FragmentSpread', 1, null ],
594
            [ 'enter', 'Name', 'name', 'FragmentSpread' ],
595
            [ 'leave', 'Name', 'name', 'FragmentSpread' ],
596
            [ 'leave', 'FragmentSpread', 1, null ],
597
            [ 'leave', 'SelectionSet', 'selectionSet', 'Field' ],
598
            [ 'leave', 'Field', 1, null ],
599
            [ 'leave', 'SelectionSet', 'selectionSet', 'Field' ],
600
            [ 'leave', 'Field', 0, null ],
601
            [ 'leave', 'SelectionSet', 'selectionSet', 'InlineFragment' ],
602
            [ 'leave', 'InlineFragment', 1, null ],
603
            [ 'enter', 'InlineFragment', 2, null ],
604
            [ 'enter', 'Directive', 0, null ],
605
            [ 'enter', 'Name', 'name', 'Directive' ],
606
            [ 'leave', 'Name', 'name', 'Directive' ],
607
            [ 'enter', 'Argument', 0, null ],
608
            [ 'enter', 'Name', 'name', 'Argument' ],
609
            [ 'leave', 'Name', 'name', 'Argument' ],
610
            [ 'enter', 'Variable', 'value', 'Argument' ],
611
            [ 'enter', 'Name', 'name', 'Variable' ],
612
            [ 'leave', 'Name', 'name', 'Variable' ],
613
            [ 'leave', 'Variable', 'value', 'Argument' ],
614
            [ 'leave', 'Argument', 0, null ],
615
            [ 'leave', 'Directive', 0, null ],
616
            [ 'enter', 'SelectionSet', 'selectionSet', 'InlineFragment' ],
617
            [ 'enter', 'Field', 0, null ],
618
            [ 'enter', 'Name', 'name', 'Field' ],
619
            [ 'leave', 'Name', 'name', 'Field' ],
620
            [ 'leave', 'Field', 0, null ],
621
            [ 'leave', 'SelectionSet', 'selectionSet', 'InlineFragment' ],
622
            [ 'leave', 'InlineFragment', 2, null ],
623
            [ 'enter', 'InlineFragment', 3, null ],
624
            [ 'enter', 'SelectionSet', 'selectionSet', 'InlineFragment' ],
625
            [ 'enter', 'Field', 0, null ],
626
            [ 'enter', 'Name', 'name', 'Field' ],
627
            [ 'leave', 'Name', 'name', 'Field' ],
628
            [ 'leave', 'Field', 0, null ],
629
            [ 'leave', 'SelectionSet', 'selectionSet', 'InlineFragment' ],
630
            [ 'leave', 'InlineFragment', 3, null ],
631
            [ 'leave', 'SelectionSet', 'selectionSet', 'Field' ],
632
            [ 'leave', 'Field', 0, null ],
633
            [ 'leave', 'SelectionSet', 'selectionSet', 'OperationDefinition' ],
634
            [ 'leave', 'OperationDefinition', 0, null ],
635
            [ 'enter', 'OperationDefinition', 1, null ],
636
            [ 'enter', 'Name', 'name', 'OperationDefinition' ],
637
            [ 'leave', 'Name', 'name', 'OperationDefinition' ],
638
            [ 'enter', 'SelectionSet', 'selectionSet', 'OperationDefinition' ],
639
            [ 'enter', 'Field', 0, null ],
640
            [ 'enter', 'Name', 'name', 'Field' ],
641
            [ 'leave', 'Name', 'name', 'Field' ],
642
            [ 'enter', 'Argument', 0, null ],
643
            [ 'enter', 'Name', 'name', 'Argument' ],
644
            [ 'leave', 'Name', 'name', 'Argument' ],
645
            [ 'enter', 'IntValue', 'value', 'Argument' ],
646
            [ 'leave', 'IntValue', 'value', 'Argument' ],
647
            [ 'leave', 'Argument', 0, null ],
648
            [ 'enter', 'Directive', 0, null ],
649
            [ 'enter', 'Name', 'name', 'Directive' ],
650
            [ 'leave', 'Name', 'name', 'Directive' ],
651
            [ 'leave', 'Directive', 0, null ],
652
            [ 'enter', 'SelectionSet', 'selectionSet', 'Field' ],
653
            [ 'enter', 'Field', 0, null ],
654
            [ 'enter', 'Name', 'name', 'Field' ],
655
            [ 'leave', 'Name', 'name', 'Field' ],
656
            [ 'enter', 'SelectionSet', 'selectionSet', 'Field' ],
657
            [ 'enter', 'Field', 0, null ],
658
            [ 'enter', 'Name', 'name', 'Field' ],
659
            [ 'leave', 'Name', 'name', 'Field' ],
660
            [ 'leave', 'Field', 0, null ],
661
            [ 'leave', 'SelectionSet', 'selectionSet', 'Field' ],
662
            [ 'leave', 'Field', 0, null ],
663
            [ 'leave', 'SelectionSet', 'selectionSet', 'Field' ],
664
            [ 'leave', 'Field', 0, null ],
665
            [ 'leave', 'SelectionSet', 'selectionSet', 'OperationDefinition' ],
666
            [ 'leave', 'OperationDefinition', 1, null ],
667
            [ 'enter', 'OperationDefinition', 2, null ],
668
            [ 'enter', 'Name', 'name', 'OperationDefinition' ],
669
            [ 'leave', 'Name', 'name', 'OperationDefinition' ],
670
            [ 'enter', 'VariableDefinition', 0, null ],
671
            [ 'enter', 'Variable', 'variable', 'VariableDefinition' ],
672
            [ 'enter', 'Name', 'name', 'Variable' ],
673
            [ 'leave', 'Name', 'name', 'Variable' ],
674
            [ 'leave', 'Variable', 'variable', 'VariableDefinition' ],
675
            [ 'enter', 'NamedType', 'type', 'VariableDefinition' ],
676
            [ 'enter', 'Name', 'name', 'NamedType' ],
677
            [ 'leave', 'Name', 'name', 'NamedType' ],
678
            [ 'leave', 'NamedType', 'type', 'VariableDefinition' ],
679
            [ 'leave', 'VariableDefinition', 0, null ],
680
            [ 'enter', 'SelectionSet', 'selectionSet', 'OperationDefinition' ],
681
            [ 'enter', 'Field', 0, null ],
682
            [ 'enter', 'Name', 'name', 'Field' ],
683
            [ 'leave', 'Name', 'name', 'Field' ],
684
            [ 'enter', 'Argument', 0, null ],
685
            [ 'enter', 'Name', 'name', 'Argument' ],
686
            [ 'leave', 'Name', 'name', 'Argument' ],
687
            [ 'enter', 'Variable', 'value', 'Argument' ],
688
            [ 'enter', 'Name', 'name', 'Variable' ],
689
            [ 'leave', 'Name', 'name', 'Variable' ],
690
            [ 'leave', 'Variable', 'value', 'Argument' ],
691
            [ 'leave', 'Argument', 0, null ],
692
            [ 'enter', 'SelectionSet', 'selectionSet', 'Field' ],
693
            [ 'enter', 'Field', 0, null ],
694
            [ 'enter', 'Name', 'name', 'Field' ],
695
            [ 'leave', 'Name', 'name', 'Field' ],
696
            [ 'enter', 'SelectionSet', 'selectionSet', 'Field' ],
697
            [ 'enter', 'Field', 0, null ],
698
            [ 'enter', 'Name', 'name', 'Field' ],
699
            [ 'leave', 'Name', 'name', 'Field' ],
700
            [ 'enter', 'SelectionSet', 'selectionSet', 'Field' ],
701
            [ 'enter', 'Field', 0, null ],
702
            [ 'enter', 'Name', 'name', 'Field' ],
703
            [ 'leave', 'Name', 'name', 'Field' ],
704
            [ 'leave', 'Field', 0, null ],
705
            [ 'leave', 'SelectionSet', 'selectionSet', 'Field' ],
706
            [ 'leave', 'Field', 0, null ],
707
            [ 'enter', 'Field', 1, null ],
708
            [ 'enter', 'Name', 'name', 'Field' ],
709
            [ 'leave', 'Name', 'name', 'Field' ],
710
            [ 'enter', 'SelectionSet', 'selectionSet', 'Field' ],
711
            [ 'enter', 'Field', 0, null ],
712
            [ 'enter', 'Name', 'name', 'Field' ],
713
            [ 'leave', 'Name', 'name', 'Field' ],
714
            [ 'leave', 'Field', 0, null ],
715
            [ 'leave', 'SelectionSet', 'selectionSet', 'Field' ],
716
            [ 'leave', 'Field', 1, null ],
717
            [ 'leave', 'SelectionSet', 'selectionSet', 'Field' ],
718
            [ 'leave', 'Field', 0, null ],
719
            [ 'leave', 'SelectionSet', 'selectionSet', 'Field' ],
720
            [ 'leave', 'Field', 0, null ],
721
            [ 'leave', 'SelectionSet', 'selectionSet', 'OperationDefinition' ],
722
            [ 'leave', 'OperationDefinition', 2, null ],
723
            [ 'enter', 'FragmentDefinition', 3, null ],
724
            [ 'enter', 'Name', 'name', 'FragmentDefinition' ],
725
            [ 'leave', 'Name', 'name', 'FragmentDefinition' ],
726
            [ 'enter', 'NamedType', 'typeCondition', 'FragmentDefinition' ],
727
            [ 'enter', 'Name', 'name', 'NamedType' ],
728
            [ 'leave', 'Name', 'name', 'NamedType' ],
729
            [ 'leave', 'NamedType', 'typeCondition', 'FragmentDefinition' ],
730
            [ 'enter', 'SelectionSet', 'selectionSet', 'FragmentDefinition' ],
731
            [ 'enter', 'Field', 0, null ],
732
            [ 'enter', 'Name', 'name', 'Field' ],
733
            [ 'leave', 'Name', 'name', 'Field' ],
734
            [ 'enter', 'Argument', 0, null ],
735
            [ 'enter', 'Name', 'name', 'Argument' ],
736
            [ 'leave', 'Name', 'name', 'Argument' ],
737
            [ 'enter', 'Variable', 'value', 'Argument' ],
738
            [ 'enter', 'Name', 'name', 'Variable' ],
739
            [ 'leave', 'Name', 'name', 'Variable' ],
740
            [ 'leave', 'Variable', 'value', 'Argument' ],
741
            [ 'leave', 'Argument', 0, null ],
742
            [ 'enter', 'Argument', 1, null ],
743
            [ 'enter', 'Name', 'name', 'Argument' ],
744
            [ 'leave', 'Name', 'name', 'Argument' ],
745
            [ 'enter', 'Variable', 'value', 'Argument' ],
746
            [ 'enter', 'Name', 'name', 'Variable' ],
747
            [ 'leave', 'Name', 'name', 'Variable' ],
748
            [ 'leave', 'Variable', 'value', 'Argument' ],
749
            [ 'leave', 'Argument', 1, null ],
750
            [ 'enter', 'Argument', 2, null ],
751
            [ 'enter', 'Name', 'name', 'Argument' ],
752
            [ 'leave', 'Name', 'name', 'Argument' ],
753
            [ 'enter', 'ObjectValue', 'value', 'Argument' ],
754
            [ 'enter', 'ObjectField', 0, null ],
755
            [ 'enter', 'Name', 'name', 'ObjectField' ],
756
            [ 'leave', 'Name', 'name', 'ObjectField' ],
757
            [ 'enter', 'StringValue', 'value', 'ObjectField' ],
758
            [ 'leave', 'StringValue', 'value', 'ObjectField' ],
759
            [ 'leave', 'ObjectField', 0, null ],
760
            [ 'enter', 'ObjectField', 1, null ],
761
            [ 'enter', 'Name', 'name', 'ObjectField' ],
762
            [ 'leave', 'Name', 'name', 'ObjectField' ],
763
            [ 'enter', 'StringValue', 'value', 'ObjectField' ],
764
            [ 'leave', 'StringValue', 'value', 'ObjectField' ],
765
            [ 'leave', 'ObjectField', 1, null ],
766
            [ 'leave', 'ObjectValue', 'value', 'Argument' ],
767
            [ 'leave', 'Argument', 2, null ],
768
            [ 'leave', 'Field', 0, null ],
769
            [ 'leave', 'SelectionSet', 'selectionSet', 'FragmentDefinition' ],
770
            [ 'leave', 'FragmentDefinition', 3, null ],
771
            [ 'enter', 'OperationDefinition', 4, null ],
772
            [ 'enter', 'SelectionSet', 'selectionSet', 'OperationDefinition' ],
773
            [ 'enter', 'Field', 0, null ],
774
            [ 'enter', 'Name', 'name', 'Field' ],
775
            [ 'leave', 'Name', 'name', 'Field' ],
776
            [ 'enter', 'Argument', 0, null ],
777
            [ 'enter', 'Name', 'name', 'Argument' ],
778
            [ 'leave', 'Name', 'name', 'Argument' ],
779
            [ 'enter', 'BooleanValue', 'value', 'Argument' ],
780
            [ 'leave', 'BooleanValue', 'value', 'Argument' ],
781
            [ 'leave', 'Argument', 0, null ],
782
            [ 'enter', 'Argument', 1, null ],
783
            [ 'enter', 'Name', 'name', 'Argument' ],
784
            [ 'leave', 'Name', 'name', 'Argument' ],
785
            [ 'enter', 'BooleanValue', 'value', 'Argument' ],
786
            [ 'leave', 'BooleanValue', 'value', 'Argument' ],
787
            [ 'leave', 'Argument', 1, null ],
788
            [ 'enter', 'Argument', 2, null ],
789
            [ 'enter', 'Name', 'name', 'Argument' ],
790
            [ 'leave', 'Name', 'name', 'Argument' ],
791
            [ 'enter', 'NullValue', 'value', 'Argument' ],
792
            [ 'leave', 'NullValue', 'value', 'Argument' ],
793
            [ 'leave', 'Argument', 2, null ],
794
            [ 'leave', 'Field', 0, null ],
795
            [ 'enter', 'Field', 1, null ],
796
            [ 'enter', 'Name', 'name', 'Field' ],
797
            [ 'leave', 'Name', 'name', 'Field' ],
798
            [ 'leave', 'Field', 1, null ],
799
            [ 'leave', 'SelectionSet', 'selectionSet', 'OperationDefinition' ],
800
            [ 'leave', 'OperationDefinition', 4, null ],
801
            [ 'leave', 'Document', null, null ],
802
        ];
803
804
        $this->assertEquals($expected, $visited);
805
    }
806
807
    /**
808
     * Describe: visitInParallel
809
     * Note: nearly identical to the above test of the same test but using visitInParallel.
810
     */
811
    public function testAllowsSkippingSubTree()
812
    {
813
        $visited = [];
814
815
        $ast = Parser::parse('{ a, b { x }, c }');
816
        Visitor::visit($ast, Visitor::visitInParallel([
817
            [
818
                'enter' => function ($node) use (&$visited, $ast) {
819
                    $this->checkVisitorFnArgs($ast, func_get_args());
820
                    $visited[] = [ 'enter', $node->kind, $node->value ?? null];
821
822
                    if ($node->kind === 'Field' && isset($node->name->value) && $node->name->value === 'b') {
823
                        return Visitor::skipNode();
824
                    }
825
                },
826
827
                'leave' => function ($node) use (&$visited, $ast) {
828
                    $this->checkVisitorFnArgs($ast, func_get_args());
829
                    $visited[] = ['leave', $node->kind, $node->value ?? null];
830
                },
831
            ],
832
        ]));
833
834
        $this->assertEquals([
835
            [ 'enter', 'Document', null ],
836
            [ 'enter', 'OperationDefinition', null ],
837
            [ 'enter', 'SelectionSet', null ],
838
            [ 'enter', 'Field', null ],
839
            [ 'enter', 'Name', 'a' ],
840
            [ 'leave', 'Name', 'a' ],
841
            [ 'leave', 'Field', null ],
842
            [ 'enter', 'Field', null ],
843
            [ 'enter', 'Field', null ],
844
            [ 'enter', 'Name', 'c' ],
845
            [ 'leave', 'Name', 'c' ],
846
            [ 'leave', 'Field', null ],
847
            [ 'leave', 'SelectionSet', null ],
848
            [ 'leave', 'OperationDefinition', null ],
849
            [ 'leave', 'Document', null ],
850
        ], $visited);
851
    }
852
853
    public function testAllowsSkippingDifferentSubTrees()
854
    {
855
        $visited = [];
856
857
        $ast = Parser::parse('{ a { x }, b { y} }');
858
        Visitor::visit($ast, Visitor::visitInParallel([
859
        [
860
            'enter' => function ($node) use (&$visited, $ast) {
861
                $this->checkVisitorFnArgs($ast, func_get_args());
862
                $visited[] = ['no-a', 'enter', $node->kind, $node->value ?? null];
863
                if ($node->kind === 'Field' && isset($node->name->value) && $node->name->value === 'a') {
864
                    return Visitor::skipNode();
865
                }
866
            },
867
            'leave' => function ($node) use (&$visited, $ast) {
868
                $this->checkVisitorFnArgs($ast, func_get_args());
869
                $visited[] = [ 'no-a', 'leave', $node->kind, $node->value ?? null ];
870
            },
871
        ],
872
        [
873
            'enter' => function ($node) use (&$visited, $ast) {
874
                $this->checkVisitorFnArgs($ast, func_get_args());
875
                $visited[] = ['no-b', 'enter', $node->kind, $node->value ?? null];
876
                if ($node->kind === 'Field' && isset($node->name->value) && $node->name->value === 'b') {
877
                    return Visitor::skipNode();
878
                }
879
            },
880
            'leave' => function ($node) use (&$visited, $ast) {
881
                $this->checkVisitorFnArgs($ast, func_get_args());
882
                $visited[] = ['no-b', 'leave', $node->kind, $node->value ?? null];
883
            },
884
        ],
885
        ]));
886
887
        $this->assertEquals([
888
            [ 'no-a', 'enter', 'Document', null ],
889
            [ 'no-b', 'enter', 'Document', null ],
890
            [ 'no-a', 'enter', 'OperationDefinition', null ],
891
            [ 'no-b', 'enter', 'OperationDefinition', null ],
892
            [ 'no-a', 'enter', 'SelectionSet', null ],
893
            [ 'no-b', 'enter', 'SelectionSet', null ],
894
            [ 'no-a', 'enter', 'Field', null ],
895
            [ 'no-b', 'enter', 'Field', null ],
896
            [ 'no-b', 'enter', 'Name', 'a' ],
897
            [ 'no-b', 'leave', 'Name', 'a' ],
898
            [ 'no-b', 'enter', 'SelectionSet', null ],
899
            [ 'no-b', 'enter', 'Field', null ],
900
            [ 'no-b', 'enter', 'Name', 'x' ],
901
            [ 'no-b', 'leave', 'Name', 'x' ],
902
            [ 'no-b', 'leave', 'Field', null ],
903
            [ 'no-b', 'leave', 'SelectionSet', null ],
904
            [ 'no-b', 'leave', 'Field', null ],
905
            [ 'no-a', 'enter', 'Field', null ],
906
            [ 'no-b', 'enter', 'Field', null ],
907
            [ 'no-a', 'enter', 'Name', 'b' ],
908
            [ 'no-a', 'leave', 'Name', 'b' ],
909
            [ 'no-a', 'enter', 'SelectionSet', null ],
910
            [ 'no-a', 'enter', 'Field', null ],
911
            [ 'no-a', 'enter', 'Name', 'y' ],
912
            [ 'no-a', 'leave', 'Name', 'y' ],
913
            [ 'no-a', 'leave', 'Field', null ],
914
            [ 'no-a', 'leave', 'SelectionSet', null ],
915
            [ 'no-a', 'leave', 'Field', null ],
916
            [ 'no-a', 'leave', 'SelectionSet', null ],
917
            [ 'no-b', 'leave', 'SelectionSet', null ],
918
            [ 'no-a', 'leave', 'OperationDefinition', null ],
919
            [ 'no-b', 'leave', 'OperationDefinition', null ],
920
            [ 'no-a', 'leave', 'Document', null ],
921
            [ 'no-b', 'leave', 'Document', null ],
922
        ], $visited);
923
    }
924
925
    public function testAllowsEarlyExitWhileVisiting2()
926
    {
927
        $visited = [];
928
929
        $ast = Parser::parse('{ a, b { x }, c }');
930
        Visitor::visit($ast, Visitor::visitInParallel([ [
931
            'enter' => function ($node) use (&$visited, $ast) {
932
                $this->checkVisitorFnArgs($ast, func_get_args());
933
                $value     = $node->value ?? null;
934
                $visited[] = ['enter', $node->kind, $value];
935
                if ($node->kind === 'Name' && $value === 'x') {
936
                    return Visitor::stop();
937
                }
938
            },
939
            'leave' => function ($node) use (&$visited, $ast) {
940
                $this->checkVisitorFnArgs($ast, func_get_args());
941
                $visited[] = ['leave', $node->kind, $node->value ?? null];
942
            },
943
        ],
944
        ]));
945
946
        $this->assertEquals([
947
            [ 'enter', 'Document', null ],
948
            [ 'enter', 'OperationDefinition', null ],
949
            [ 'enter', 'SelectionSet', null ],
950
            [ 'enter', 'Field', null ],
951
            [ 'enter', 'Name', 'a' ],
952
            [ 'leave', 'Name', 'a' ],
953
            [ 'leave', 'Field', null ],
954
            [ 'enter', 'Field', null ],
955
            [ 'enter', 'Name', 'b' ],
956
            [ 'leave', 'Name', 'b' ],
957
            [ 'enter', 'SelectionSet', null ],
958
            [ 'enter', 'Field', null ],
959
            [ 'enter', 'Name', 'x' ],
960
        ], $visited);
961
    }
962
963
    public function testAllowsEarlyExitFromDifferentPoints()
964
    {
965
        $visited = [];
966
967
        $ast = Parser::parse('{ a { y }, b { x } }');
968
        Visitor::visit($ast, Visitor::visitInParallel([
969
        [
970
            'enter' => function ($node) use (&$visited, $ast) {
971
                $this->checkVisitorFnArgs($ast, func_get_args());
972
                $value     = $node->value ?? null;
973
                $visited[] = ['break-a', 'enter', $node->kind, $value];
974
                if ($node->kind === 'Name' && $value === 'a') {
975
                    return Visitor::stop();
976
                }
977
            },
978
            'leave' => function ($node) use (&$visited, $ast) {
979
                $this->checkVisitorFnArgs($ast, func_get_args());
980
                $visited[] = [ 'break-a', 'leave', $node->kind, $node->value ?? null ];
981
            },
982
        ],
983
        [
984
            'enter' => function ($node) use (&$visited, $ast) {
985
                $this->checkVisitorFnArgs($ast, func_get_args());
986
                $value     = $node->value ?? null;
987
                $visited[] = ['break-b', 'enter', $node->kind, $value];
988
                if ($node->kind === 'Name' && $value === 'b') {
989
                    return Visitor::stop();
990
                }
991
            },
992
            'leave' => function ($node) use (&$visited, $ast) {
993
                $this->checkVisitorFnArgs($ast, func_get_args());
994
                $visited[] = ['break-b', 'leave', $node->kind, $node->value ?? null];
995
            },
996
        ],
997
        ]));
998
999
        $this->assertEquals([
1000
            [ 'break-a', 'enter', 'Document', null ],
1001
            [ 'break-b', 'enter', 'Document', null ],
1002
            [ 'break-a', 'enter', 'OperationDefinition', null ],
1003
            [ 'break-b', 'enter', 'OperationDefinition', null ],
1004
            [ 'break-a', 'enter', 'SelectionSet', null ],
1005
            [ 'break-b', 'enter', 'SelectionSet', null ],
1006
            [ 'break-a', 'enter', 'Field', null ],
1007
            [ 'break-b', 'enter', 'Field', null ],
1008
            [ 'break-a', 'enter', 'Name', 'a' ],
1009
            [ 'break-b', 'enter', 'Name', 'a' ],
1010
            [ 'break-b', 'leave', 'Name', 'a' ],
1011
            [ 'break-b', 'enter', 'SelectionSet', null ],
1012
            [ 'break-b', 'enter', 'Field', null ],
1013
            [ 'break-b', 'enter', 'Name', 'y' ],
1014
            [ 'break-b', 'leave', 'Name', 'y' ],
1015
            [ 'break-b', 'leave', 'Field', null ],
1016
            [ 'break-b', 'leave', 'SelectionSet', null ],
1017
            [ 'break-b', 'leave', 'Field', null ],
1018
            [ 'break-b', 'enter', 'Field', null ],
1019
            [ 'break-b', 'enter', 'Name', 'b' ],
1020
        ], $visited);
1021
    }
1022
1023
    public function testAllowsEarlyExitWhileLeaving2()
1024
    {
1025
        $visited = [];
1026
1027
        $ast = Parser::parse('{ a, b { x }, c }');
1028
        Visitor::visit($ast, Visitor::visitInParallel([ [
1029
            'enter' => function ($node) use (&$visited, $ast) {
1030
                $this->checkVisitorFnArgs($ast, func_get_args());
1031
                $visited[] = ['enter', $node->kind, $node->value ?? null];
1032
            },
1033
            'leave' => function ($node) use (&$visited, $ast) {
1034
                $this->checkVisitorFnArgs($ast, func_get_args());
1035
                $value     = $node->value ?? null;
1036
                $visited[] = ['leave', $node->kind, $value];
1037
                if ($node->kind === 'Name' && $value === 'x') {
1038
                    return Visitor::stop();
1039
                }
1040
            },
1041
        ],
1042
        ]));
1043
1044
        $this->assertEquals([
1045
            [ 'enter', 'Document', null ],
1046
            [ 'enter', 'OperationDefinition', null ],
1047
            [ 'enter', 'SelectionSet', null ],
1048
            [ 'enter', 'Field', null ],
1049
            [ 'enter', 'Name', 'a' ],
1050
            [ 'leave', 'Name', 'a' ],
1051
            [ 'leave', 'Field', null ],
1052
            [ 'enter', 'Field', null ],
1053
            [ 'enter', 'Name', 'b' ],
1054
            [ 'leave', 'Name', 'b' ],
1055
            [ 'enter', 'SelectionSet', null ],
1056
            [ 'enter', 'Field', null ],
1057
            [ 'enter', 'Name', 'x' ],
1058
            [ 'leave', 'Name', 'x' ],
1059
        ], $visited);
1060
    }
1061
1062
    public function testAllowsEarlyExitFromLeavingDifferentPoints()
1063
    {
1064
        $visited = [];
1065
1066
        $ast = Parser::parse('{ a { y }, b { x } }');
1067
        Visitor::visit($ast, Visitor::visitInParallel([
1068
            [
1069
                'enter' => function ($node) use (&$visited, $ast) {
1070
                    $this->checkVisitorFnArgs($ast, func_get_args());
1071
                    $visited[] = ['break-a', 'enter', $node->kind, $node->value ?? null];
1072
                },
1073
                'leave' => function ($node) use (&$visited, $ast) {
1074
                    $this->checkVisitorFnArgs($ast, func_get_args());
1075
                    $visited[] = ['break-a', 'leave', $node->kind, $node->value ?? null];
1076
                    if ($node->kind === 'Field' && isset($node->name->value) && $node->name->value === 'a') {
1077
                        return Visitor::stop();
1078
                    }
1079
                },
1080
            ],
1081
            [
1082
                'enter' => function ($node) use (&$visited, $ast) {
1083
                    $this->checkVisitorFnArgs($ast, func_get_args());
1084
                    $visited[] = ['break-b', 'enter', $node->kind, $node->value ?? null];
1085
                },
1086
                'leave' => function ($node) use (&$visited, $ast) {
1087
                    $this->checkVisitorFnArgs($ast, func_get_args());
1088
                    $visited[] = ['break-b', 'leave', $node->kind, $node->value ?? null];
1089
                    if ($node->kind === 'Field' && isset($node->name->value) && $node->name->value === 'b') {
1090
                        return Visitor::stop();
1091
                    }
1092
                },
1093
            ],
1094
        ]));
1095
1096
        $this->assertEquals([
1097
            [ 'break-a', 'enter', 'Document', null ],
1098
            [ 'break-b', 'enter', 'Document', null ],
1099
            [ 'break-a', 'enter', 'OperationDefinition', null ],
1100
            [ 'break-b', 'enter', 'OperationDefinition', null ],
1101
            [ 'break-a', 'enter', 'SelectionSet', null ],
1102
            [ 'break-b', 'enter', 'SelectionSet', null ],
1103
            [ 'break-a', 'enter', 'Field', null ],
1104
            [ 'break-b', 'enter', 'Field', null ],
1105
            [ 'break-a', 'enter', 'Name', 'a' ],
1106
            [ 'break-b', 'enter', 'Name', 'a' ],
1107
            [ 'break-a', 'leave', 'Name', 'a' ],
1108
            [ 'break-b', 'leave', 'Name', 'a' ],
1109
            [ 'break-a', 'enter', 'SelectionSet', null ],
1110
            [ 'break-b', 'enter', 'SelectionSet', null ],
1111
            [ 'break-a', 'enter', 'Field', null ],
1112
            [ 'break-b', 'enter', 'Field', null ],
1113
            [ 'break-a', 'enter', 'Name', 'y' ],
1114
            [ 'break-b', 'enter', 'Name', 'y' ],
1115
            [ 'break-a', 'leave', 'Name', 'y' ],
1116
            [ 'break-b', 'leave', 'Name', 'y' ],
1117
            [ 'break-a', 'leave', 'Field', null ],
1118
            [ 'break-b', 'leave', 'Field', null ],
1119
            [ 'break-a', 'leave', 'SelectionSet', null ],
1120
            [ 'break-b', 'leave', 'SelectionSet', null ],
1121
            [ 'break-a', 'leave', 'Field', null ],
1122
            [ 'break-b', 'leave', 'Field', null ],
1123
            [ 'break-b', 'enter', 'Field', null ],
1124
            [ 'break-b', 'enter', 'Name', 'b' ],
1125
            [ 'break-b', 'leave', 'Name', 'b' ],
1126
            [ 'break-b', 'enter', 'SelectionSet', null ],
1127
            [ 'break-b', 'enter', 'Field', null ],
1128
            [ 'break-b', 'enter', 'Name', 'x' ],
1129
            [ 'break-b', 'leave', 'Name', 'x' ],
1130
            [ 'break-b', 'leave', 'Field', null ],
1131
            [ 'break-b', 'leave', 'SelectionSet', null ],
1132
            [ 'break-b', 'leave', 'Field', null ],
1133
        ], $visited);
1134
    }
1135
1136
    public function testAllowsForEditingOnEnter2()
1137
    {
1138
        $visited = [];
1139
1140
        $ast       = Parser::parse('{ a, b, c { a, b, c } }', ['noLocation' => true]);
1141
        $editedAst = Visitor::visit($ast, Visitor::visitInParallel([
1142
            [
1143
                'enter' => function ($node) use (&$visited, $ast) {
1144
                    $this->checkVisitorFnArgs($ast, func_get_args());
1145
                    if ($node->kind === 'Field' && isset($node->name->value) && $node->name->value === 'b') {
1146
                        return Visitor::removeNode();
1147
                    }
1148
                },
1149
            ],
1150
            [
1151
                'enter' => function ($node) use (&$visited, $ast) {
1152
                    $this->checkVisitorFnArgs($ast, func_get_args());
1153
                    $visited[] = ['enter', $node->kind, $node->value ?? null];
1154
                },
1155
                'leave' => function ($node) use (&$visited, $ast) {
1156
                    $this->checkVisitorFnArgs($ast, func_get_args(), true);
1157
                    $visited[] = ['leave', $node->kind, $node->value ?? null];
1158
                },
1159
            ],
1160
        ]));
1161
1162
        $this->assertEquals(
1163
            Parser::parse('{ a, b, c { a, b, c } }', ['noLocation' => true]),
1164
            $ast
1165
        );
1166
1167
        $this->assertEquals(
1168
            Parser::parse('{ a,    c { a,    c } }', ['noLocation' => true]),
1169
            $editedAst
1170
        );
1171
1172
        $this->assertEquals([
1173
            ['enter', 'Document', null],
1174
            ['enter', 'OperationDefinition', null],
1175
            ['enter', 'SelectionSet', null],
1176
            ['enter', 'Field', null],
1177
            ['enter', 'Name', 'a'],
1178
            ['leave', 'Name', 'a'],
1179
            ['leave', 'Field', null],
1180
            ['enter', 'Field', null],
1181
            ['enter', 'Name', 'c'],
1182
            ['leave', 'Name', 'c'],
1183
            ['enter', 'SelectionSet', null],
1184
            ['enter', 'Field', null],
1185
            ['enter', 'Name', 'a'],
1186
            ['leave', 'Name', 'a'],
1187
            ['leave', 'Field', null],
1188
            ['enter', 'Field', null],
1189
            ['enter', 'Name', 'c'],
1190
            ['leave', 'Name', 'c'],
1191
            ['leave', 'Field', null],
1192
            ['leave', 'SelectionSet', null],
1193
            ['leave', 'Field', null],
1194
            ['leave', 'SelectionSet', null],
1195
            ['leave', 'OperationDefinition', null],
1196
            ['leave', 'Document', null],
1197
        ], $visited);
1198
    }
1199
1200
    public function testAllowsForEditingOnLeave2()
1201
    {
1202
        $visited = [];
1203
1204
        $ast       = Parser::parse('{ a, b, c { a, b, c } }', ['noLocation' => true]);
1205
        $editedAst = Visitor::visit($ast, Visitor::visitInParallel([
1206
            [
1207
                'leave' => function ($node) use (&$visited, $ast) {
1208
                    $this->checkVisitorFnArgs($ast, func_get_args(), true);
1209
                    if ($node->kind === 'Field' && isset($node->name->value) && $node->name->value === 'b') {
1210
                        return Visitor::removeNode();
1211
                    }
1212
                },
1213
            ],
1214
            [
1215
                'enter' => function ($node) use (&$visited, $ast) {
1216
                    $this->checkVisitorFnArgs($ast, func_get_args());
1217
                    $visited[] = ['enter', $node->kind, $node->value ?? null];
1218
                },
1219
                'leave' => function ($node) use (&$visited, $ast) {
1220
                    $this->checkVisitorFnArgs($ast, func_get_args(), true);
1221
                    $visited[] = ['leave', $node->kind, $node->value ?? null];
1222
                },
1223
            ],
1224
        ]));
1225
1226
        $this->assertEquals(
1227
            Parser::parse('{ a, b, c { a, b, c } }', ['noLocation' => true]),
1228
            $ast
1229
        );
1230
1231
        $this->assertEquals(
1232
            Parser::parse('{ a,    c { a,    c } }', ['noLocation' => true]),
1233
            $editedAst
1234
        );
1235
1236
        $this->assertEquals([
1237
            ['enter', 'Document', null],
1238
            ['enter', 'OperationDefinition', null],
1239
            ['enter', 'SelectionSet', null],
1240
            ['enter', 'Field', null],
1241
            ['enter', 'Name', 'a'],
1242
            ['leave', 'Name', 'a'],
1243
            ['leave', 'Field', null],
1244
            ['enter', 'Field', null],
1245
            ['enter', 'Name', 'b'],
1246
            ['leave', 'Name', 'b'],
1247
            ['enter', 'Field', null],
1248
            ['enter', 'Name', 'c'],
1249
            ['leave', 'Name', 'c'],
1250
            ['enter', 'SelectionSet', null],
1251
            ['enter', 'Field', null],
1252
            ['enter', 'Name', 'a'],
1253
            ['leave', 'Name', 'a'],
1254
            ['leave', 'Field', null],
1255
            ['enter', 'Field', null],
1256
            ['enter', 'Name', 'b'],
1257
            ['leave', 'Name', 'b'],
1258
            ['enter', 'Field', null],
1259
            ['enter', 'Name', 'c'],
1260
            ['leave', 'Name', 'c'],
1261
            ['leave', 'Field', null],
1262
            ['leave', 'SelectionSet', null],
1263
            ['leave', 'Field', null],
1264
            ['leave', 'SelectionSet', null],
1265
            ['leave', 'OperationDefinition', null],
1266
            ['leave', 'Document', null],
1267
        ], $visited);
1268
    }
1269
1270
1271
    /**
1272
     * Describe: visitWithTypeInfo
1273
     */
1274
    public function testMaintainsTypeInfoDuringVisit()
1275
    {
1276
        $visited = [];
1277
1278
        $typeInfo = new TypeInfo(ValidatorTestCase::getTestSchema());
1279
1280
        $ast = Parser::parse('{ human(id: 4) { name, pets { ... { name } }, unknown } }');
1281
        Visitor::visit($ast, Visitor::visitWithTypeInfo($typeInfo, [
1282
            'enter' => function ($node) use ($typeInfo, &$visited, $ast) {
1283
                $this->checkVisitorFnArgs($ast, func_get_args());
1284
                $parentType = $typeInfo->getParentType();
1285
                $type       = $typeInfo->getType();
1286
                $inputType  = $typeInfo->getInputType();
1287
                $visited[]  = [
1288
                    'enter',
1289
                    $node->kind,
1290
                    $node->kind === 'Name' ? $node->value : null,
1291
                    $parentType ? (string) $parentType : null,
0 ignored issues
show
introduced by
$parentType is of type GraphQL\Type\Definition\Type, thus it always evaluated to true.
Loading history...
1292
                    $type ? (string) $type : null,
0 ignored issues
show
introduced by
$type is of type GraphQL\Type\Definition\Type, thus it always evaluated to true.
Loading history...
1293
                    $inputType ? (string) $inputType : null,
0 ignored issues
show
introduced by
$inputType is of type GraphQL\Type\Definition\InputType, thus it always evaluated to true.
Loading history...
1294
                ];
1295
            },
1296
            'leave' => function ($node) use ($typeInfo, &$visited, $ast) {
1297
                $this->checkVisitorFnArgs($ast, func_get_args());
1298
                $parentType = $typeInfo->getParentType();
1299
                $type       = $typeInfo->getType();
1300
                $inputType  = $typeInfo->getInputType();
1301
                $visited[]  = [
1302
                    'leave',
1303
                    $node->kind,
1304
                    $node->kind === 'Name' ? $node->value : null,
1305
                    $parentType ? (string) $parentType : null,
0 ignored issues
show
introduced by
$parentType is of type GraphQL\Type\Definition\Type, thus it always evaluated to true.
Loading history...
1306
                    $type ? (string) $type : null,
0 ignored issues
show
introduced by
$type is of type GraphQL\Type\Definition\Type, thus it always evaluated to true.
Loading history...
1307
                    $inputType ? (string) $inputType : null,
0 ignored issues
show
introduced by
$inputType is of type GraphQL\Type\Definition\InputType, thus it always evaluated to true.
Loading history...
1308
                ];
1309
            },
1310
        ]));
1311
1312
        $this->assertEquals([
1313
            ['enter', 'Document', null, null, null, null],
1314
            ['enter', 'OperationDefinition', null, null, 'QueryRoot', null],
1315
            ['enter', 'SelectionSet', null, 'QueryRoot', 'QueryRoot', null],
1316
            ['enter', 'Field', null, 'QueryRoot', 'Human', null],
1317
            ['enter', 'Name', 'human', 'QueryRoot', 'Human', null],
1318
            ['leave', 'Name', 'human', 'QueryRoot', 'Human', null],
1319
            ['enter', 'Argument', null, 'QueryRoot', 'Human', 'ID'],
1320
            ['enter', 'Name', 'id', 'QueryRoot', 'Human', 'ID'],
1321
            ['leave', 'Name', 'id', 'QueryRoot', 'Human', 'ID'],
1322
            ['enter', 'IntValue', null, 'QueryRoot', 'Human', 'ID'],
1323
            ['leave', 'IntValue', null, 'QueryRoot', 'Human', 'ID'],
1324
            ['leave', 'Argument', null, 'QueryRoot', 'Human', 'ID'],
1325
            ['enter', 'SelectionSet', null, 'Human', 'Human', null],
1326
            ['enter', 'Field', null, 'Human', 'String', null],
1327
            ['enter', 'Name', 'name', 'Human', 'String', null],
1328
            ['leave', 'Name', 'name', 'Human', 'String', null],
1329
            ['leave', 'Field', null, 'Human', 'String', null],
1330
            ['enter', 'Field', null, 'Human', '[Pet]', null],
1331
            ['enter', 'Name', 'pets', 'Human', '[Pet]', null],
1332
            ['leave', 'Name', 'pets', 'Human', '[Pet]', null],
1333
            ['enter', 'SelectionSet', null, 'Pet', '[Pet]', null],
1334
            ['enter', 'InlineFragment', null, 'Pet', 'Pet', null],
1335
            ['enter', 'SelectionSet', null, 'Pet', 'Pet', null],
1336
            ['enter', 'Field', null, 'Pet', 'String', null],
1337
            ['enter', 'Name', 'name', 'Pet', 'String', null],
1338
            ['leave', 'Name', 'name', 'Pet', 'String', null],
1339
            ['leave', 'Field', null, 'Pet', 'String', null],
1340
            ['leave', 'SelectionSet', null, 'Pet', 'Pet', null],
1341
            ['leave', 'InlineFragment', null, 'Pet', 'Pet', null],
1342
            ['leave', 'SelectionSet', null, 'Pet', '[Pet]', null],
1343
            ['leave', 'Field', null, 'Human', '[Pet]', null],
1344
            ['enter', 'Field', null, 'Human', null, null],
1345
            ['enter', 'Name', 'unknown', 'Human', null, null],
1346
            ['leave', 'Name', 'unknown', 'Human', null, null],
1347
            ['leave', 'Field', null, 'Human', null, null],
1348
            ['leave', 'SelectionSet', null, 'Human', 'Human', null],
1349
            ['leave', 'Field', null, 'QueryRoot', 'Human', null],
1350
            ['leave', 'SelectionSet', null, 'QueryRoot', 'QueryRoot', null],
1351
            ['leave', 'OperationDefinition', null, null, 'QueryRoot', null],
1352
            ['leave', 'Document', null, null, null, null],
1353
        ], $visited);
1354
    }
1355
1356
    public function testMaintainsTypeInfoDuringEdit()
1357
    {
1358
        $visited  = [];
1359
        $typeInfo = new TypeInfo(ValidatorTestCase::getTestSchema());
1360
1361
        $ast       = Parser::parse(
1362
            '{ human(id: 4) { name, pets }, alien }'
1363
        );
1364
        $editedAst = Visitor::visit($ast, Visitor::visitWithTypeInfo($typeInfo, [
1365
            'enter' => function ($node) use ($typeInfo, &$visited, $ast) {
1366
                $this->checkVisitorFnArgs($ast, func_get_args(), true);
1367
                $parentType = $typeInfo->getParentType();
1368
                $type       = $typeInfo->getType();
1369
                $inputType  = $typeInfo->getInputType();
1370
                $visited[]  = [
1371
                    'enter',
1372
                    $node->kind,
1373
                    $node->kind === 'Name' ? $node->value : null,
1374
                    $parentType ? (string) $parentType : null,
0 ignored issues
show
introduced by
$parentType is of type GraphQL\Type\Definition\Type, thus it always evaluated to true.
Loading history...
1375
                    $type ? (string) $type : null,
0 ignored issues
show
introduced by
$type is of type GraphQL\Type\Definition\Type, thus it always evaluated to true.
Loading history...
1376
                    $inputType ? (string) $inputType : null,
0 ignored issues
show
introduced by
$inputType is of type GraphQL\Type\Definition\InputType, thus it always evaluated to true.
Loading history...
1377
                ];
1378
1379
                // Make a query valid by adding missing selection sets.
1380
                if ($node->kind === 'Field' &&
1381
                    ! $node->selectionSet &&
1382
                    Type::isCompositeType(Type::getNamedType($type))
1383
                ) {
1384
                    return new FieldNode([
1385
                        'alias' => $node->alias,
1386
                        'name' => $node->name,
1387
                        'arguments' => $node->arguments,
1388
                        'directives' => $node->directives,
1389
                        'selectionSet' => new SelectionSetNode([
1390
                            'kind' => 'SelectionSet',
1391
                            'selections' => [new FieldNode([
1392
                                    'name' => new NameNode(['value' => '__typename']),
1393
                                ]),
1394
                            ],
1395
                        ]),
1396
                    ]);
1397
                }
1398
            },
1399
            'leave' => function ($node) use ($typeInfo, &$visited, $ast) {
1400
                $this->checkVisitorFnArgs($ast, func_get_args(), true);
1401
                $parentType = $typeInfo->getParentType();
1402
                $type       = $typeInfo->getType();
1403
                $inputType  = $typeInfo->getInputType();
1404
                $visited[]  = [
1405
                    'leave',
1406
                    $node->kind,
1407
                    $node->kind === 'Name' ? $node->value : null,
1408
                    $parentType ? (string) $parentType : null,
0 ignored issues
show
introduced by
$parentType is of type GraphQL\Type\Definition\Type, thus it always evaluated to true.
Loading history...
1409
                    $type ? (string) $type : null,
0 ignored issues
show
introduced by
$type is of type GraphQL\Type\Definition\Type, thus it always evaluated to true.
Loading history...
1410
                    $inputType ? (string) $inputType : null,
0 ignored issues
show
introduced by
$inputType is of type GraphQL\Type\Definition\InputType, thus it always evaluated to true.
Loading history...
1411
                ];
1412
            },
1413
        ]));
1414
1415
        $this->assertEquals(Printer::doPrint(Parser::parse(
1416
            '{ human(id: 4) { name, pets }, alien }'
1417
        )), Printer::doPrint($ast));
1418
1419
        $this->assertEquals(Printer::doPrint(Parser::parse(
1420
            '{ human(id: 4) { name, pets { __typename } }, alien { __typename } }'
1421
        )), Printer::doPrint($editedAst));
1422
1423
        $this->assertEquals([
1424
            ['enter', 'Document', null, null, null, null],
1425
            ['enter', 'OperationDefinition', null, null, 'QueryRoot', null],
1426
            ['enter', 'SelectionSet', null, 'QueryRoot', 'QueryRoot', null],
1427
            ['enter', 'Field', null, 'QueryRoot', 'Human', null],
1428
            ['enter', 'Name', 'human', 'QueryRoot', 'Human', null],
1429
            ['leave', 'Name', 'human', 'QueryRoot', 'Human', null],
1430
            ['enter', 'Argument', null, 'QueryRoot', 'Human', 'ID'],
1431
            ['enter', 'Name', 'id', 'QueryRoot', 'Human', 'ID'],
1432
            ['leave', 'Name', 'id', 'QueryRoot', 'Human', 'ID'],
1433
            ['enter', 'IntValue', null, 'QueryRoot', 'Human', 'ID'],
1434
            ['leave', 'IntValue', null, 'QueryRoot', 'Human', 'ID'],
1435
            ['leave', 'Argument', null, 'QueryRoot', 'Human', 'ID'],
1436
            ['enter', 'SelectionSet', null, 'Human', 'Human', null],
1437
            ['enter', 'Field', null, 'Human', 'String', null],
1438
            ['enter', 'Name', 'name', 'Human', 'String', null],
1439
            ['leave', 'Name', 'name', 'Human', 'String', null],
1440
            ['leave', 'Field', null, 'Human', 'String', null],
1441
            ['enter', 'Field', null, 'Human', '[Pet]', null],
1442
            ['enter', 'Name', 'pets', 'Human', '[Pet]', null],
1443
            ['leave', 'Name', 'pets', 'Human', '[Pet]', null],
1444
            ['enter', 'SelectionSet', null, 'Pet', '[Pet]', null],
1445
            ['enter', 'Field', null, 'Pet', 'String!', null],
1446
            ['enter', 'Name', '__typename', 'Pet', 'String!', null],
1447
            ['leave', 'Name', '__typename', 'Pet', 'String!', null],
1448
            ['leave', 'Field', null, 'Pet', 'String!', null],
1449
            ['leave', 'SelectionSet', null, 'Pet', '[Pet]', null],
1450
            ['leave', 'Field', null, 'Human', '[Pet]', null],
1451
            ['leave', 'SelectionSet', null, 'Human', 'Human', null],
1452
            ['leave', 'Field', null, 'QueryRoot', 'Human', null],
1453
            ['enter', 'Field', null, 'QueryRoot', 'Alien', null],
1454
            ['enter', 'Name', 'alien', 'QueryRoot', 'Alien', null],
1455
            ['leave', 'Name', 'alien', 'QueryRoot', 'Alien', null],
1456
            ['enter', 'SelectionSet', null, 'Alien', 'Alien', null],
1457
            ['enter', 'Field', null, 'Alien', 'String!', null],
1458
            ['enter', 'Name', '__typename', 'Alien', 'String!', null],
1459
            ['leave', 'Name', '__typename', 'Alien', 'String!', null],
1460
            ['leave', 'Field', null, 'Alien', 'String!', null],
1461
            ['leave', 'SelectionSet', null, 'Alien', 'Alien', null],
1462
            ['leave', 'Field', null, 'QueryRoot', 'Alien', null],
1463
            ['leave', 'SelectionSet', null, 'QueryRoot', 'QueryRoot', null],
1464
            ['leave', 'OperationDefinition', null, null, 'QueryRoot', null],
1465
            ['leave', 'Document', null, null, null, null],
1466
        ], $visited);
1467
    }
1468
}
1469