comparison vendor/nikic/php-parser/lib/PhpParser/NodeTraverser.php @ 0:c75dbcec494b

Initial commit from drush-created site
author Chris Cannam
date Thu, 05 Jul 2018 14:24:15 +0000
parents
children a9cd425dd02b
comparison
equal deleted inserted replaced
-1:000000000000 0:c75dbcec494b
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 }