97d45bdaaa7c95ba7ecdae9580a83e3239732bd1
[yaffs-website] / vendor / nikic / php-parser / lib / PhpParser / NodeTraverser.php
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 }