Passed
Push — master ( 447363...d5288f )
by Jan Philipp
02:42
created

NodeSearch::recurse()   B

Complexity

Conditions 6
Paths 6

Size

Total Lines 22
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 11
CRAP Score 6.1308

Importance

Changes 0
Metric Value
dl 0
loc 22
ccs 11
cts 13
cp 0.8462
rs 8.6737
c 0
b 0
f 0
cc 6
eloc 12
nc 6
nop 2
crap 6.1308
1
<?php
2
3
4
namespace JanPiet\PhpTranspiler;
5
6
use PhpParser\Node;
7
8
class NodeSearch
9
{
10
    /**
11
     * @var \PhpParser\Node[]
12
     */
13
    private $tree;
14
15
    /**
16
     * @var Node[]
17
     */
18
    private $allNodes;
19
20
    /**
21
     * @var Node[]
22
     */
23
    private $parentChain;
24
25
    /**
26
     * NodeSearch constructor.
27
     * @param Node[] $nodes
28
     */
29 20
    public function __construct(array $nodes)
30
    {
31 20
        $this->tree = $nodes;
32 20
    }
33
34
    /**
35
     * @param \string[] ...$classNames
36
     * @return \Traversable
37
     */
38 18
    public function eachType(string ...$classNames): \Traversable
39
    {
40 18
        $this->update();
41
42 18
        foreach ($this->allNodes as $node) {
43 18
            $found = false;
44 18
            foreach ($classNames as $className) {
45 18
                if ($node instanceof  $className) {
46 18
                    $found = true;
47 18
                    break;
48
                }
49
            }
50
51 18
            if (!$found) {
52 17
                continue;
53
            }
54
55 18
            yield $node;
56
        }
57 18
    }
58
59 5
    public function findParent(string $class, Node $fromNode)
60
    {
61 5
        $this->update();
62
63
        do {
64 5
            $nodeId = spl_object_hash($fromNode);
65 5
            $fromNode = $this->parentChain[$nodeId];
66
            
67 5
            if (null === $fromNode) {
68 3
                throw new ParentNotFoundException('Could not find the parent "' . $class . '" for node');
69
            }
70 4
        } while (!$fromNode instanceof $class);
71
72 3
        return $fromNode;
73
    }
74
75
    /**
76
     * @param Node $newClass
77
     */
78 2
    public function appendToRoot(Node $newClass)
79
    {
80 2
        $this->tree[] = $newClass;
81 2
    }
82
83
    /**
84
     * @return \PhpParser\Node[]
85
     */
86 14
    public function getTree()
87
    {
88 14
        return $this->tree;
89
    }
90
91 20
    private function update()
92
    {
93 20
        $allNodes = [];
94 20
        $parentChain = [];
95
96 20
        foreach ($this->recurse($this->tree) as $parent => $node) {
97 20
            $nodeId = spl_object_hash($node);
98 20
            $allNodes[$nodeId] = $node;
99 20
            $parentChain[$nodeId] = $parent;
100
        }
101
102 20
        $this->allNodes = $allNodes;
103 20
        $this->parentChain = $parentChain;
104 20
    }
105
106
    /**
107
     * @param Node[] $nodes
108
     * @return \Traversable
109
     */
110 20
    private function recurse(array $nodes, Node $parent = null): \Traversable
111
    {
112 20
        foreach ($nodes as $node) {
113 20
            if (is_array($node)) {
114
                yield from $this->recurse($node, $node);
0 ignored issues
show
Documentation introduced by
$node is of type array, but the function expects a null|object<PhpParser\Node>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
115
            } elseif ($node instanceof Node) {
116 20
                yield $parent => $node;
117
118 20
                $subNodeNames = $node->getSubNodeNames();
119
120 20
                foreach ($subNodeNames as $subNodeName) {
121 20
                    $subNode = $node->{$subNodeName};
122
123 20
                    if (!is_array($subNode)) {
124 20
                        $subNode = [$subNode];
125
                    }
126
127 20
                    yield from $this->recurse($subNode, $node);
128
                }
129
            }
130
        }
131 20
    }
132
133
    /**
134
     * @param $sourceNode
135
     * @param $newNode
136
     */
137 5
    public function replaceNode(Node $sourceNode, Node $newNode)
138
    {
139 5
        $this->update();
140
141 5
        $hash = spl_object_hash($sourceNode);
142
143 5
        $parents = $this->determineReplaceParents($hash);
144
145 5
        foreach ($parents as $key => $parent) {
146 5
            if ($parent === $sourceNode) {
147
                $parent[$key] = $newNode;
148
                return;
149
            }
150
151 5
            if($this->checkReplaceSubNodes($parent, $sourceNode, $newNode)) {
152 5
                return;
153
            }
154
        }
155
        
156 1
        throw new NodeNotFoundException('Node not found, replace not possible');
157
    }
158
159
    /**
160
     * @param Node $parent
161
     * @param Node $sourceNode
162
     * @param Node $newNode
163
     * @return bool
164
     */
165 5
    private function checkReplaceSubNodes(Node $parent, Node $sourceNode, Node $newNode): bool
166
    {
167 5
        foreach ($parent->getSubNodeNames() as $subNodeName) {
168 5
            $nodes = &$parent->{$subNodeName};
169
170 5
            if (!is_array($nodes)) {
171 4
                if ($nodes === $sourceNode) {
172 2
                    $parent->{$subNodeName} = $newNode;
173 2
                    return true;
174
                }
175
176 4
                continue;
177
            }
178
179 4
            $foundKey = false;
180 4
            foreach ($nodes as $key => $node) {
181 4
                if ($node === $sourceNode) {
182 4
                    $foundKey = $key;
183
                }
184
            }
185
186 4
            if (false !== $foundKey) {
187 3
                $nodes[$foundKey] = $newNode;
188 4
                return true;
189
            }
190
        }
191
192 1
        return false;
193
    }
194
195
    /**
196
     * @param string $hash
197
     * @return \PhpParser\Node[]
198
     */
199 5
    private function determineReplaceParents(string $hash): array
200
    {
201 5
        if (array_key_exists($hash, $this->parentChain)) {
202 4
            $parents = [$this->parentChain[$hash]];
203 4
            return $parents;
204
        }
205
206 1
        $parents = $this->tree;
207 1
        return $parents;
208
    }
209
}
210