Failed Conditions
Push — master ( bf4e7d...c70528 )
by Vladimir
09:31
created

VisitorTest::testAllowsEarlyExitWhileVisiting2()   A

Complexity

Conditions 3
Paths 1

Size

Total Lines 41
Code Lines 29

Duplication

Lines 0
Ratio 0 %

Importance

Changes 3
Bugs 0 Features 0
Metric Value
eloc 29
c 3
b 0
f 0
dl 0
loc 41
rs 9.456
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\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);
0 ignored issues
show
Deprecated Code introduced by
The function PHPUnit\Framework\Assert::assertInternalType() has been deprecated: https://github.com/sebastianbergmann/phpunit/issues/3369 ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

141
        /** @scrutinizer ignore-deprecated */ self::assertInternalType('array', $path);

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
142
        self::assertEquals($key, $path[count($path) - 1]);
143
144
        self::assertInternalType('array', $ancestors);
0 ignored issues
show
Deprecated Code introduced by
The function PHPUnit\Framework\Assert::assertInternalType() has been deprecated: https://github.com/sebastianbergmann/phpunit/issues/3369 ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

144
        /** @scrutinizer ignore-deprecated */ self::assertInternalType('array', $ancestors);

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
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;
0 ignored issues
show
Bug introduced by
The property didEnter does not seem to exist on GraphQL\Language\AST\OperationDefinitionNode.
Loading history...
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;
0 ignored issues
show
Bug introduced by
The property didLeave does not seem to exist on GraphQL\Language\AST\OperationDefinitionNode.
Loading history...
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;
0 ignored issues
show
Bug introduced by
The property didEnter does not seem to exist on GraphQL\Language\AST\DocumentNode.
Loading history...
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;
0 ignored issues
show
Bug introduced by
The property didLeave does not seem to exist on GraphQL\Language\AST\DocumentNode.
Loading history...
237
                    },
238
                ],
239
            ]
240
        );
241
242
        self::assertNotEquals($ast, $editedAst);
243
244
        $tmp           = $ast->cloneDeep();
245
        $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...
246
        $tmp->didLeave = true;
0 ignored issues
show
Bug introduced by
The property didLeave does not seem to exist on GraphQL\Language\AST\DocumentNode.
Loading history...
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,
0 ignored issues
show
introduced by
$parentType is of type GraphQL\Type\Definition\Type, thus it always evaluated to true.
Loading history...
1437
                            $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...
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,
0 ignored issues
show
introduced by
$parentType is of type GraphQL\Type\Definition\Type, thus it always evaluated to true.
Loading history...
1452
                            $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...
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,
0 ignored issues
show
introduced by
$parentType is of type GraphQL\Type\Definition\Type, thus it always evaluated to true.
Loading history...
1531
                            $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...
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,
0 ignored issues
show
introduced by
$parentType is of type GraphQL\Type\Definition\Type, thus it always evaluated to true.
Loading history...
1566
                            $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...
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