Add support for NodeVisitor::REPLACE_WITH_NULL

Fixes #716.
This commit is contained in:
Nikita Popov 2023-05-21 15:41:41 +02:00
parent 289756d056
commit afe1628a72
3 changed files with 78 additions and 6 deletions

View File

@ -116,6 +116,9 @@ class NodeTraverser implements NodeTraverserInterface {
} 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)
@ -142,6 +145,9 @@ class NodeTraverser implements NodeTraverserInterface {
} 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 ' .
@ -195,6 +201,9 @@ class NodeTraverser implements NodeTraverserInterface {
} 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)
@ -227,6 +236,9 @@ class NodeTraverser implements NodeTraverserInterface {
} 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)

View File

@ -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.
*
@ -59,14 +66,16 @@ interface NodeVisitor {
* => $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
@ -83,9 +92,11 @@ interface NodeVisitor {
* 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)

View File

@ -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() {
@ -343,6 +346,44 @@ class NodeTraverserTest extends \PHPUnit\Framework\TestCase {
], $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 {};
@ -415,6 +456,12 @@ class NodeTraverserTest extends \PHPUnit\Framework\TestCase {
$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'],
@ -425,6 +472,8 @@ class NodeTraverserTest extends \PHPUnit\Framework\TestCase {
[$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'],
];
}
}