Subversion-Projekte lars-tiefland.laravel_shop

Revision

Details | Letzte Änderung | Log anzeigen | RSS feed

Revision Autor Zeilennr. Zeile
148 lars 1
<?php declare(strict_types=1);
2
 
3
namespace PhpParser;
4
 
5
class NodeTraverser implements NodeTraverserInterface
6
{
7
    /**
8
     * If NodeVisitor::enterNode() returns DONT_TRAVERSE_CHILDREN, child nodes
9
     * of the current node will not be traversed for any visitors.
10
     *
11
     * For subsequent visitors enterNode() will still be called on the current
12
     * node and leaveNode() will also be invoked for the current node.
13
     */
14
    const DONT_TRAVERSE_CHILDREN = 1;
15
 
16
    /**
17
     * If NodeVisitor::enterNode() or NodeVisitor::leaveNode() returns
18
     * STOP_TRAVERSAL, traversal is aborted.
19
     *
20
     * The afterTraverse() method will still be invoked.
21
     */
22
    const STOP_TRAVERSAL = 2;
23
 
24
    /**
25
     * If NodeVisitor::leaveNode() returns REMOVE_NODE for a node that occurs
26
     * in an array, it will be removed from the array.
27
     *
28
     * For subsequent visitors leaveNode() will still be invoked for the
29
     * removed node.
30
     */
31
    const REMOVE_NODE = 3;
32
 
33
    /**
34
     * If NodeVisitor::enterNode() returns DONT_TRAVERSE_CURRENT_AND_CHILDREN, child nodes
35
     * of the current node will not be traversed for any visitors.
36
     *
37
     * For subsequent visitors enterNode() will not be called as well.
38
     * leaveNode() will be invoked for visitors that has enterNode() method invoked.
39
     */
40
    const DONT_TRAVERSE_CURRENT_AND_CHILDREN = 4;
41
 
42
    /** @var NodeVisitor[] Visitors */
43
    protected $visitors = [];
44
 
45
    /** @var bool Whether traversal should be stopped */
46
    protected $stopTraversal;
47
 
48
    public function __construct() {
49
        // for BC
50
    }
51
 
52
    /**
53
     * Adds a visitor.
54
     *
55
     * @param NodeVisitor $visitor Visitor to add
56
     */
57
    public function addVisitor(NodeVisitor $visitor) {
58
        $this->visitors[] = $visitor;
59
    }
60
 
61
    /**
62
     * Removes an added visitor.
63
     *
64
     * @param NodeVisitor $visitor
65
     */
66
    public function removeVisitor(NodeVisitor $visitor) {
67
        foreach ($this->visitors as $index => $storedVisitor) {
68
            if ($storedVisitor === $visitor) {
69
                unset($this->visitors[$index]);
70
                break;
71
            }
72
        }
73
    }
74
 
75
    /**
76
     * Traverses an array of nodes using the registered visitors.
77
     *
78
     * @param Node[] $nodes Array of nodes
79
     *
80
     * @return Node[] Traversed array of nodes
81
     */
82
    public function traverse(array $nodes) : array {
83
        $this->stopTraversal = false;
84
 
85
        foreach ($this->visitors as $visitor) {
86
            if (null !== $return = $visitor->beforeTraverse($nodes)) {
87
                $nodes = $return;
88
            }
89
        }
90
 
91
        $nodes = $this->traverseArray($nodes);
92
 
93
        foreach ($this->visitors as $visitor) {
94
            if (null !== $return = $visitor->afterTraverse($nodes)) {
95
                $nodes = $return;
96
            }
97
        }
98
 
99
        return $nodes;
100
    }
101
 
102
    /**
103
     * Recursively traverse a node.
104
     *
105
     * @param Node $node Node to traverse.
106
     *
107
     * @return Node Result of traversal (may be original node or new one)
108
     */
109
    protected function traverseNode(Node $node) : Node {
110
        foreach ($node->getSubNodeNames() as $name) {
111
            $subNode =& $node->$name;
112
 
113
            if (\is_array($subNode)) {
114
                $subNode = $this->traverseArray($subNode);
115
                if ($this->stopTraversal) {
116
                    break;
117
                }
118
            } elseif ($subNode instanceof Node) {
119
                $traverseChildren = true;
120
                $breakVisitorIndex = null;
121
 
122
                foreach ($this->visitors as $visitorIndex => $visitor) {
123
                    $return = $visitor->enterNode($subNode);
124
                    if (null !== $return) {
125
                        if ($return instanceof Node) {
126
                            $this->ensureReplacementReasonable($subNode, $return);
127
                            $subNode = $return;
128
                        } elseif (self::DONT_TRAVERSE_CHILDREN === $return) {
129
                            $traverseChildren = false;
130
                        } elseif (self::DONT_TRAVERSE_CURRENT_AND_CHILDREN === $return) {
131
                            $traverseChildren = false;
132
                            $breakVisitorIndex = $visitorIndex;
133
                            break;
134
                        } elseif (self::STOP_TRAVERSAL === $return) {
135
                            $this->stopTraversal = true;
136
                            break 2;
137
                        } else {
138
                            throw new \LogicException(
139
                                'enterNode() returned invalid value of type ' . gettype($return)
140
                            );
141
                        }
142
                    }
143
                }
144
 
145
                if ($traverseChildren) {
146
                    $subNode = $this->traverseNode($subNode);
147
                    if ($this->stopTraversal) {
148
                        break;
149
                    }
150
                }
151
 
152
                foreach ($this->visitors as $visitorIndex => $visitor) {
153
                    $return = $visitor->leaveNode($subNode);
154
 
155
                    if (null !== $return) {
156
                        if ($return instanceof Node) {
157
                            $this->ensureReplacementReasonable($subNode, $return);
158
                            $subNode = $return;
159
                        } elseif (self::STOP_TRAVERSAL === $return) {
160
                            $this->stopTraversal = true;
161
                            break 2;
162
                        } elseif (\is_array($return)) {
163
                            throw new \LogicException(
164
                                'leaveNode() may only return an array ' .
165
                                'if the parent structure is an array'
166
                            );
167
                        } else {
168
                            throw new \LogicException(
169
                                'leaveNode() returned invalid value of type ' . gettype($return)
170
                            );
171
                        }
172
                    }
173
 
174
                    if ($breakVisitorIndex === $visitorIndex) {
175
                        break;
176
                    }
177
                }
178
            }
179
        }
180
 
181
        return $node;
182
    }
183
 
184
    /**
185
     * Recursively traverse array (usually of nodes).
186
     *
187
     * @param array $nodes Array to traverse
188
     *
189
     * @return array Result of traversal (may be original array or changed one)
190
     */
191
    protected function traverseArray(array $nodes) : array {
192
        $doNodes = [];
193
 
194
        foreach ($nodes as $i => &$node) {
195
            if ($node instanceof Node) {
196
                $traverseChildren = true;
197
                $breakVisitorIndex = null;
198
 
199
                foreach ($this->visitors as $visitorIndex => $visitor) {
200
                    $return = $visitor->enterNode($node);
201
                    if (null !== $return) {
202
                        if ($return instanceof Node) {
203
                            $this->ensureReplacementReasonable($node, $return);
204
                            $node = $return;
205
                        } elseif (self::DONT_TRAVERSE_CHILDREN === $return) {
206
                            $traverseChildren = false;
207
                        } elseif (self::DONT_TRAVERSE_CURRENT_AND_CHILDREN === $return) {
208
                            $traverseChildren = false;
209
                            $breakVisitorIndex = $visitorIndex;
210
                            break;
211
                        } elseif (self::STOP_TRAVERSAL === $return) {
212
                            $this->stopTraversal = true;
213
                            break 2;
214
                        } else {
215
                            throw new \LogicException(
216
                                'enterNode() returned invalid value of type ' . gettype($return)
217
                            );
218
                        }
219
                    }
220
                }
221
 
222
                if ($traverseChildren) {
223
                    $node = $this->traverseNode($node);
224
                    if ($this->stopTraversal) {
225
                        break;
226
                    }
227
                }
228
 
229
                foreach ($this->visitors as $visitorIndex => $visitor) {
230
                    $return = $visitor->leaveNode($node);
231
 
232
                    if (null !== $return) {
233
                        if ($return instanceof Node) {
234
                            $this->ensureReplacementReasonable($node, $return);
235
                            $node = $return;
236
                        } elseif (\is_array($return)) {
237
                            $doNodes[] = [$i, $return];
238
                            break;
239
                        } elseif (self::REMOVE_NODE === $return) {
240
                            $doNodes[] = [$i, []];
241
                            break;
242
                        } elseif (self::STOP_TRAVERSAL === $return) {
243
                            $this->stopTraversal = true;
244
                            break 2;
245
                        } elseif (false === $return) {
246
                            throw new \LogicException(
247
                                'bool(false) return from leaveNode() no longer supported. ' .
248
                                'Return NodeTraverser::REMOVE_NODE instead'
249
                            );
250
                        } else {
251
                            throw new \LogicException(
252
                                'leaveNode() returned invalid value of type ' . gettype($return)
253
                            );
254
                        }
255
                    }
256
 
257
                    if ($breakVisitorIndex === $visitorIndex) {
258
                        break;
259
                    }
260
                }
261
            } elseif (\is_array($node)) {
262
                throw new \LogicException('Invalid node structure: Contains nested arrays');
263
            }
264
        }
265
 
266
        if (!empty($doNodes)) {
267
            while (list($i, $replace) = array_pop($doNodes)) {
268
                array_splice($nodes, $i, 1, $replace);
269
            }
270
        }
271
 
272
        return $nodes;
273
    }
274
 
275
    private function ensureReplacementReasonable($old, $new) {
276
        if ($old instanceof Node\Stmt && $new instanceof Node\Expr) {
277
            throw new \LogicException(
278
                "Trying to replace statement ({$old->getType()}) " .
279
                "with expression ({$new->getType()}). Are you missing a " .
280
                "Stmt_Expression wrapper?"
281
            );
282
        }
283
 
284
        if ($old instanceof Node\Expr && $new instanceof Node\Stmt) {
285
            throw new \LogicException(
286
                "Trying to replace expression ({$old->getType()}) " .
287
                "with statement ({$new->getType()})"
288
            );
289
        }
290
    }
291
}