annotate vendor/nikic/php-parser/lib/PhpParser/NodeTraverser.php @ 19:fa3358dc1485 tip

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