Passed
Pull Request — master (#586)
by Šimon
12:52
created

VisitorTest::testAllowsEarlyExitFromLeavingDifferentPoints()   B

Complexity

Conditions 7
Paths 1

Size

Total Lines 77
Code Lines 59

Duplication

Lines 0
Ratio 0 %

Importance

Changes 3
Bugs 0 Features 0
Metric Value
eloc 59
c 3
b 0
f 0
dl 0
loc 77
rs 7.9612
cc 7
nc 1
nop 0

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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