From 9d574c6734479c36d8ebebea6029b85214d31e64 Mon Sep 17 00:00:00 2001 From: Abdul Malik Ikhsan Date: Wed, 16 Sep 2026 20:44:27 +0700 Subject: [PATCH] [CodeQuality] Skip SingularSwitchToIfRector on nested break or continue in case body --- .../Fixture/closure_with_break.php.inc | 41 +++++++++++++++++++ .../Fixture/skip_nested_break.php.inc | 21 ++++++++++ .../Switch_/SingularSwitchToIfRector.php | 39 ++++++++++++++++++ 3 files changed, 101 insertions(+) create mode 100644 rules-tests/CodeQuality/Rector/Switch_/SingularSwitchToIfRector/Fixture/closure_with_break.php.inc create mode 100644 rules-tests/CodeQuality/Rector/Switch_/SingularSwitchToIfRector/Fixture/skip_nested_break.php.inc diff --git a/rules-tests/CodeQuality/Rector/Switch_/SingularSwitchToIfRector/Fixture/closure_with_break.php.inc b/rules-tests/CodeQuality/Rector/Switch_/SingularSwitchToIfRector/Fixture/closure_with_break.php.inc new file mode 100644 index 00000000000..55b6c963139 --- /dev/null +++ b/rules-tests/CodeQuality/Rector/Switch_/SingularSwitchToIfRector/Fixture/closure_with_break.php.inc @@ -0,0 +1,41 @@ + +----- + diff --git a/rules-tests/CodeQuality/Rector/Switch_/SingularSwitchToIfRector/Fixture/skip_nested_break.php.inc b/rules-tests/CodeQuality/Rector/Switch_/SingularSwitchToIfRector/Fixture/skip_nested_break.php.inc new file mode 100644 index 00000000000..ce235b55038 --- /dev/null +++ b/rules-tests/CodeQuality/Rector/Switch_/SingularSwitchToIfRector/Fixture/skip_nested_break.php.inc @@ -0,0 +1,21 @@ + diff --git a/rules/CodeQuality/Rector/Switch_/SingularSwitchToIfRector.php b/rules/CodeQuality/Rector/Switch_/SingularSwitchToIfRector.php index 939503c8447..a8494976bf9 100644 --- a/rules/CodeQuality/Rector/Switch_/SingularSwitchToIfRector.php +++ b/rules/CodeQuality/Rector/Switch_/SingularSwitchToIfRector.php @@ -7,10 +7,14 @@ use PhpParser\Node; use PhpParser\Node\Expr; use PhpParser\Node\Expr\BinaryOp\Identical; +use PhpParser\Node\FunctionLike; use PhpParser\Node\Stmt; use PhpParser\Node\Stmt\Break_; +use PhpParser\Node\Stmt\Class_; +use PhpParser\Node\Stmt\Continue_; use PhpParser\Node\Stmt\If_; use PhpParser\Node\Stmt\Switch_; +use PhpParser\NodeVisitor; use Rector\Rector\AbstractRector; use Rector\Renaming\NodeManipulator\SwitchManipulator; use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample; @@ -85,6 +89,11 @@ public function refactor(Node $node): array|If_|null $onlyCase = $node->cases[0]; + // nested break/continue would lose the switch to target and cause a fatal error + if ($this->hasNestedBreakOrContinue($onlyCase->stmts)) { + return null; + } + // only default → basically unwrap if (! $onlyCase->cond instanceof Expr) { // remove default clause because it cause syntax error @@ -96,4 +105,34 @@ public function refactor(Node $node): array|If_|null return $if; } + + /** + * @param Stmt[] $stmts + */ + private function hasNestedBreakOrContinue(array $stmts): bool + { + $hasNested = false; + + foreach ($stmts as $stmt) { + // top level break is removed by SwitchManipulator + if ($stmt instanceof Break_) { + continue; + } + + $this->traverseNodesWithCallable($stmt, static function (Node $subNode) use (&$hasNested): ?int { + if ($subNode instanceof Class_ || $subNode instanceof FunctionLike) { + return NodeVisitor::DONT_TRAVERSE_CURRENT_AND_CHILDREN; + } + + if ($subNode instanceof Break_ || $subNode instanceof Continue_) { + $hasNested = true; + return NodeVisitor::STOP_TRAVERSAL; + } + + return null; + }); + } + + return $hasNested; + } }