fe3045aebcf45998783fbe96d15c7ea770d6e85e
[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     /** @var NodeVisitor[] Visitors */
34     protected $visitors;
35
36     /** @var bool Whether traversal should be stopped */
37     protected $stopTraversal;
38
39     /**
40      * Constructs a node traverser.
41      */
42     public function __construct() {
43         $this->visitors = [];
44     }
45
46     /**
47      * Adds a visitor.
48      *
49      * @param NodeVisitor $visitor Visitor to add
50      */
51     public function addVisitor(NodeVisitor $visitor) {
52         $this->visitors[] = $visitor;
53     }
54
55     /**
56      * Removes an added visitor.
57      *
58      * @param NodeVisitor $visitor
59      */
60     public function removeVisitor(NodeVisitor $visitor) {
61         foreach ($this->visitors as $index => $storedVisitor) {
62             if ($storedVisitor === $visitor) {
63                 unset($this->visitors[$index]);
64                 break;
65             }
66         }
67     }
68
69     /**
70      * Traverses an array of nodes using the registered visitors.
71      *
72      * @param Node[] $nodes Array of nodes
73      *
74      * @return Node[] Traversed array of nodes
75      */
76     public function traverse(array $nodes) : array {
77         $this->stopTraversal = false;
78
79         foreach ($this->visitors as $visitor) {
80             if (null !== $return = $visitor->beforeTraverse($nodes)) {
81                 $nodes = $return;
82             }
83         }
84
85         $nodes = $this->traverseArray($nodes);
86
87         foreach ($this->visitors as $visitor) {
88             if (null !== $return = $visitor->afterTraverse($nodes)) {
89                 $nodes = $return;
90             }
91         }
92
93         return $nodes;
94     }
95
96     /**
97      * Recursively traverse a node.
98      *
99      * @param Node $node Node to traverse.
100      *
101      * @return Node Result of traversal (may be original node or new one)
102      */
103     protected function traverseNode(Node $node) : Node {
104         foreach ($node->getSubNodeNames() as $name) {
105             $subNode =& $node->$name;
106
107             if (\is_array($subNode)) {
108                 $subNode = $this->traverseArray($subNode);
109                 if ($this->stopTraversal) {
110                     break;
111                 }
112             } elseif ($subNode instanceof Node) {
113                 $traverseChildren = true;
114                 foreach ($this->visitors as $visitor) {
115                     $return = $visitor->enterNode($subNode);
116                     if (null !== $return) {
117                         if ($return instanceof Node) {
118                             $this->ensureReplacementReasonable($subNode, $return);
119                             $subNode = $return;
120                         } elseif (self::DONT_TRAVERSE_CHILDREN === $return) {
121                             $traverseChildren = false;
122                         } elseif (self::STOP_TRAVERSAL === $return) {
123                             $this->stopTraversal = true;
124                             break 2;
125                         } else {
126                             throw new \LogicException(
127                                 'enterNode() returned invalid value of type ' . gettype($return)
128                             );
129                         }
130                     }
131                 }
132
133                 if ($traverseChildren) {
134                     $subNode = $this->traverseNode($subNode);
135                     if ($this->stopTraversal) {
136                         break;
137                     }
138                 }
139
140                 foreach ($this->visitors as $visitor) {
141                     $return = $visitor->leaveNode($subNode);
142                     if (null !== $return) {
143                         if ($return instanceof Node) {
144                             $this->ensureReplacementReasonable($subNode, $return);
145                             $subNode = $return;
146                         } elseif (self::STOP_TRAVERSAL === $return) {
147                             $this->stopTraversal = true;
148                             break 2;
149                         } elseif (\is_array($return)) {
150                             throw new \LogicException(
151                                 'leaveNode() may only return an array ' .
152                                 'if the parent structure is an array'
153                             );
154                         } else {
155                             throw new \LogicException(
156                                 'leaveNode() returned invalid value of type ' . gettype($return)
157                             );
158                         }
159                     }
160                 }
161             }
162         }
163
164         return $node;
165     }
166
167     /**
168      * Recursively traverse array (usually of nodes).
169      *
170      * @param array $nodes Array to traverse
171      *
172      * @return array Result of traversal (may be original array or changed one)
173      */
174     protected function traverseArray(array $nodes) : array {
175         $doNodes = [];
176
177         foreach ($nodes as $i => &$node) {
178             if ($node instanceof Node) {
179                 $traverseChildren = true;
180                 foreach ($this->visitors as $visitor) {
181                     $return = $visitor->enterNode($node);
182                     if (null !== $return) {
183                         if ($return instanceof Node) {
184                             $this->ensureReplacementReasonable($node, $return);
185                             $node = $return;
186                         } elseif (self::DONT_TRAVERSE_CHILDREN === $return) {
187                             $traverseChildren = false;
188                         } elseif (self::STOP_TRAVERSAL === $return) {
189                             $this->stopTraversal = true;
190                             break 2;
191                         } else {
192                             throw new \LogicException(
193                                 'enterNode() returned invalid value of type ' . gettype($return)
194                             );
195                         }
196                     }
197                 }
198
199                 if ($traverseChildren) {
200                     $node = $this->traverseNode($node);
201                     if ($this->stopTraversal) {
202                         break;
203                     }
204                 }
205
206                 foreach ($this->visitors as $visitor) {
207                     $return = $visitor->leaveNode($node);
208                     if (null !== $return) {
209                         if ($return instanceof Node) {
210                             $this->ensureReplacementReasonable($node, $return);
211                             $node = $return;
212                         } elseif (\is_array($return)) {
213                             $doNodes[] = [$i, $return];
214                             break;
215                         } elseif (self::REMOVE_NODE === $return) {
216                             $doNodes[] = [$i, []];
217                             break;
218                         } elseif (self::STOP_TRAVERSAL === $return) {
219                             $this->stopTraversal = true;
220                             break 2;
221                         } elseif (false === $return) {
222                             throw new \LogicException(
223                                 'bool(false) return from leaveNode() no longer supported. ' .
224                                 'Return NodeTraverser::REMOVE_NODE instead'
225                             );
226                         } else {
227                             throw new \LogicException(
228                                 'leaveNode() returned invalid value of type ' . gettype($return)
229                             );
230                         }
231                     }
232                 }
233             } elseif (\is_array($node)) {
234                 throw new \LogicException('Invalid node structure: Contains nested arrays');
235             }
236         }
237
238         if (!empty($doNodes)) {
239             while (list($i, $replace) = array_pop($doNodes)) {
240                 array_splice($nodes, $i, 1, $replace);
241             }
242         }
243
244         return $nodes;
245     }
246
247     private function ensureReplacementReasonable($old, $new) {
248         if ($old instanceof Node\Stmt && $new instanceof Node\Expr) {
249             throw new \LogicException(
250                 "Trying to replace statement ({$old->getType()}) " .
251                 "with expression ({$new->getType()}). Are you missing a " .
252                 "Stmt_Expression wrapper?"
253             );
254         }
255
256         if ($old instanceof Node\Expr && $new instanceof Node\Stmt) {
257             throw new \LogicException(
258                 "Trying to replace expression ({$old->getType()}) " .
259                 "with statement ({$new->getType()})"
260             );
261         }
262     }
263 }