Skip to content

Commit

Permalink
Add support for NodeVisitor::REPLACE_WITH_NULL
Browse files Browse the repository at this point in the history
Fixes #716.
  • Loading branch information
nikic committed May 21, 2023
1 parent 289756d commit afe1628
Show file tree
Hide file tree
Showing 3 changed files with 78 additions and 6 deletions.
12 changes: 12 additions & 0 deletions lib/PhpParser/NodeTraverser.php
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,9 @@ protected function traverseNode(Node $node): Node {
} elseif (NodeVisitor::STOP_TRAVERSAL === $return) {
$this->stopTraversal = true;
break 2;
} elseif (NodeVisitor::REPLACE_WITH_NULL === $return) {
$subNode = null;
continue 2;
} else {
throw new \LogicException(
'enterNode() returned invalid value of type ' . gettype($return)
Expand All @@ -142,6 +145,9 @@ protected function traverseNode(Node $node): Node {
} elseif (NodeVisitor::STOP_TRAVERSAL === $return) {
$this->stopTraversal = true;
break 2;
} elseif (NodeVisitor::REPLACE_WITH_NULL === $return) {
$subNode = null;
break;
} elseif (\is_array($return)) {
throw new \LogicException(
'leaveNode() may only return an array ' .
Expand Down Expand Up @@ -195,6 +201,9 @@ protected function traverseArray(array $nodes): array {
} elseif (NodeVisitor::STOP_TRAVERSAL === $return) {
$this->stopTraversal = true;
break 2;
} elseif (NodeVisitor::REPLACE_WITH_NULL === $return) {
throw new \LogicException(
'REPLACE_WITH_NULL can not be used if the parent structure is an array');
} else {
throw new \LogicException(
'enterNode() returned invalid value of type ' . gettype($return)
Expand Down Expand Up @@ -227,6 +236,9 @@ protected function traverseArray(array $nodes): array {
} elseif (NodeVisitor::STOP_TRAVERSAL === $return) {
$this->stopTraversal = true;
break 2;
} elseif (NodeVisitor::REPLACE_WITH_NULL === $return) {
throw new \LogicException(
'REPLACE_WITH_NULL can not be used if the parent structure is an array');
} else {
throw new \LogicException(
'leaveNode() returned invalid value of type ' . gettype($return)
Expand Down
23 changes: 17 additions & 6 deletions lib/PhpParser/NodeVisitor.php
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,13 @@ interface NodeVisitor {
*/
public const DONT_TRAVERSE_CURRENT_AND_CHILDREN = 4;

/**
* If NodeVisitor::enterNode() or NodeVisitor::leaveNode() returns REPLACE_WITH_NULL,
* the node will be replaced with null. This is not a legal return value if the node is part
* of an array, rather than another node.
*/
public const REPLACE_WITH_NULL = 5;

/**
* Called once before traversal.
*
Expand All @@ -59,14 +66,16 @@ public function beforeTraverse(array $nodes);
* => $node stays as-is
* * array (of Nodes)
* => The return value is merged into the parent array (at the position of the $node)
* * NodeTraverser::REMOVE_NODE
* * NodeVisitor::REMOVE_NODE
* => $node is removed from the parent array
* * NodeTraverser::DONT_TRAVERSE_CHILDREN
* * NodeVisitor::REPLACE_WITH_NULL
* => $node is replaced with null
* * NodeVisitor::DONT_TRAVERSE_CHILDREN
* => Children of $node are not traversed. $node stays as-is
* * NodeTraverser::DONT_TRAVERSE_CURRENT_AND_CHILDREN
* * NodeVisitor::DONT_TRAVERSE_CURRENT_AND_CHILDREN
* => Further visitors for the current node are skipped, and its children are not
* traversed. $node stays as-is.
* * NodeTraverser::STOP_TRAVERSAL
* * NodeVisitor::STOP_TRAVERSAL
* => Traversal is aborted. $node stays as-is
* * otherwise
* => $node is set to the return value
Expand All @@ -83,9 +92,11 @@ public function enterNode(Node $node);
* Return value semantics:
* * null
* => $node stays as-is
* * NodeTraverser::REMOVE_NODE
* * NodeVisitor::REMOVE_NODE
* => $node is removed from the parent array
* * NodeTraverser::STOP_TRAVERSAL
* * NodeVisitor::REPLACE_WITH_NULL
* => $node is replaced with null
* * NodeVisitor::STOP_TRAVERSAL
* => Traversal is aborted. $node stays as-is
* * array (of Nodes)
* => The return value is merged into the parent array (at the position of the $node)
Expand Down
49 changes: 49 additions & 0 deletions test/PhpParser/NodeTraverserTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,10 @@
namespace PhpParser;

use PhpParser\Node\Expr;
use PhpParser\Node\Scalar\Int_;
use PhpParser\Node\Scalar\String_;
use PhpParser\Node\Stmt\Else_;
use PhpParser\Node\Stmt\If_;

class NodeTraverserTest extends \PHPUnit\Framework\TestCase {
public function testNonModifying() {
Expand Down Expand Up @@ -343,6 +346,44 @@ public function testStopTraversal() {
], $visitor->trace);
}

public function testReplaceWithNull() {
$one = new Int_(1);
$else1 = new Else_();
$else2 = new Else_();
$if1 = new If_($one, ['else' => $else1]);
$if2 = new If_($one, ['else' => $else2]);
$stmts = [$if1, $if2];
$visitor1 = new NodeVisitorForTesting([
['enterNode', $else1, NodeVisitor::REPLACE_WITH_NULL],
['leaveNode', $else2, NodeVisitor::REPLACE_WITH_NULL],
]);
$visitor2 = new NodeVisitorForTesting();
$traverser = new NodeTraverser();
$traverser->addVisitor($visitor1);
$traverser->addVisitor($visitor2);
$newStmts = $traverser->traverse($stmts);
$this->assertEquals([
new If_($one),
new If_($one),
], $newStmts);
$this->assertEquals([
['beforeTraverse', $stmts],
['enterNode', $if1],
['enterNode', $one],
// We never see the if1 Else node.
['leaveNode', $one],
['leaveNode', $if1],
['enterNode', $if2],
['enterNode', $one],
['leaveNode', $one],
// We do see the if2 Else node, as it will only be replaced afterwards.
['enterNode', $else2],
['leaveNode', $else2],
['leaveNode', $if2],
['afterTraverse', $stmts],
], $visitor2->trace);
}

public function testRemovingVisitor() {
$visitor1 = new class () extends NodeVisitorAbstract {};
$visitor2 = new class () extends NodeVisitorAbstract {};
Expand Down Expand Up @@ -415,6 +456,12 @@ public function provideTestInvalidReturn() {
$visitor8 = new NodeVisitorForTesting([
['enterNode', $num, new Node\Stmt\Return_()],
]);
$visitor9 = new NodeVisitorForTesting([
['enterNode', $expr, NodeVisitor::REPLACE_WITH_NULL],
]);
$visitor10 = new NodeVisitorForTesting([
['leaveNode', $expr, NodeVisitor::REPLACE_WITH_NULL],
]);

return [
[$stmts, $visitor1, 'enterNode() returned invalid value of type string'],
Expand All @@ -425,6 +472,8 @@ public function provideTestInvalidReturn() {
[$stmts, $visitor6, 'leaveNode() returned invalid value of type bool'],
[$stmts, $visitor7, 'Trying to replace statement (Stmt_Expression) with expression (Scalar_Int). Are you missing a Stmt_Expression wrapper?'],
[$stmts, $visitor8, 'Trying to replace expression (Scalar_Int) with statement (Stmt_Return)'],
[$stmts, $visitor9, 'REPLACE_WITH_NULL can not be used if the parent structure is an array'],
[$stmts, $visitor10, 'REPLACE_WITH_NULL can not be used if the parent structure is an array'],
];
}
}

0 comments on commit afe1628

Please sign in to comment.