Completed
Push — master ( 93aec9...ef6def )
by Freek
01:51
created

AnonymousClassReplacer::afterTraverse()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 14
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 14
rs 9.4285
cc 2
eloc 7
nc 2
nop 1
1
<?php
2
3
namespace Spatie\Php7to5\NodeVisitors;
4
5
use PhpParser\Node;
6
use PhpParser\Node\Stmt\Declare_;
7
use PhpParser\Node\Stmt\Namespace_;
8
use PhpParser\Node\Stmt\Use_;
9
use PhpParser\NodeVisitorAbstract;
10
use Spatie\Php7to5\Exceptions\InvalidPhpCode;
11
12
class AnonymousClassReplacer extends NodeVisitorAbstract
13
{
14
    /**
15
     * @var array
16
     */
17
    protected $anonymousClassNodes = [];
18
19
    /**
20
     * {@inheritdoc}
21
     */
22
    public function leaveNode(Node $node)
23
    {
24
        if (!$node instanceof Node\Expr\New_) {
25
            return;
26
        }
27
28
        $classNode = $node->class;
29
        if (!$classNode instanceof Node\Stmt\Class_) {
30
            return;
31
        }
32
33
        $newClassName = 'AnonymousClass'.count($this->anonymousClassNodes);
34
35
        $classNode->name = $newClassName;
36
37
        $this->anonymousClassNodes[] = $classNode;
38
39
        // Generate new code that instantiate our new class
40
        $newNode = new Node\Expr\New_(
41
            new Node\Expr\ConstFetch(
42
                new Node\Name($newClassName)
43
            )
44
        );
45
46
        return $newNode;
47
    }
48
49
    /**
50
     * {@inheritdoc}
51
     */
52
    public function afterTraverse(array $nodes)
53
    {
54
        if (count($this->anonymousClassNodes) === 0) {
55
            return $nodes;
56
        }
57
58
        $anonymousClassStatements = $this->anonymousClassNodes;
59
60
        $hookIndex = $this->getAnonymousClassHookIndex($nodes);
61
62
        $nodes = $this->moveAnonymousClassesToHook($nodes, $hookIndex, $anonymousClassStatements);
63
64
        return $nodes;
65
    }
66
67
    /**
68
     * Find the index of the first statement that is not a declare, use or namespace statement.
69
     *
70
     * @param array $statements
71
     *
72
     * @return int
73
     *
74
     * @throws \Spatie\Php7to5\Exceptions\InvalidPhpCode
75
     */
76
    protected function getAnonymousClassHookIndex(array $statements)
77
    {
78
        $hookIndex = false;
79
80
        foreach ($statements as $index => $statement) {
81
            if (!$statement instanceof Declare_ &&
82
                !$statement instanceof Use_ &&
83
                !$statement instanceof Namespace_) {
84
                $hookIndex = $index;
85
86
                break;
87
            }
88
        }
89
90
        if ($hookIndex === false) {
91
            throw InvalidPhpCode::noValidLocationFoundToInsertClasses();
92
        }
93
94
        return $hookIndex;
95
    }
96
97
    /**
98
     * @param array $nodes
99
     * @param $hookIndex
100
     * @param $anonymousClassStatements
101
     * 
102
     * @return array
103
     */
104
    protected function moveAnonymousClassesToHook(array $nodes, $hookIndex, $anonymousClassStatements)
105
    {
106
        $preStatements = array_slice($nodes, 0, $hookIndex);
107
        $postStatements = array_slice($nodes, $hookIndex);
108
109
        return array_merge($preStatements, $anonymousClassStatements, $postStatements);
110
    }
111
}
112