From 8b341b9d5ca11f7ac0f83979280cc41c6463f9d0 Mon Sep 17 00:00:00 2001 From: Tomas Votruba Date: Mon, 29 Jun 2026 15:17:50 +0200 Subject: [PATCH] [CodeQuality] Add SwitchTrueToMatchRector as replacement for deprecated SwitchTrueToIfRector --- .../Fixture/skip_break.php.inc | 21 +++ .../Fixture/skip_default_not_last.php.inc | 16 ++ .../Fixture/skip_false.php.inc | 16 ++ .../Fixture/skip_no_default.php.inc | 16 ++ .../Fixture/with_default.php.inc | 41 ++++++ .../SwitchTrueToMatchRectorTest.php | 28 ++++ .../config/configured_rule.php | 12 ++ .../Switch_/SwitchTrueToMatchRector.php | 138 ++++++++++++++++++ src/Config/Level/CodeQualityLevel.php | 2 + 9 files changed, 290 insertions(+) create mode 100644 rules-tests/CodeQuality/Rector/Switch_/SwitchTrueToMatchRector/Fixture/skip_break.php.inc create mode 100644 rules-tests/CodeQuality/Rector/Switch_/SwitchTrueToMatchRector/Fixture/skip_default_not_last.php.inc create mode 100644 rules-tests/CodeQuality/Rector/Switch_/SwitchTrueToMatchRector/Fixture/skip_false.php.inc create mode 100644 rules-tests/CodeQuality/Rector/Switch_/SwitchTrueToMatchRector/Fixture/skip_no_default.php.inc create mode 100644 rules-tests/CodeQuality/Rector/Switch_/SwitchTrueToMatchRector/Fixture/with_default.php.inc create mode 100644 rules-tests/CodeQuality/Rector/Switch_/SwitchTrueToMatchRector/SwitchTrueToMatchRectorTest.php create mode 100644 rules-tests/CodeQuality/Rector/Switch_/SwitchTrueToMatchRector/config/configured_rule.php create mode 100644 rules/CodeQuality/Rector/Switch_/SwitchTrueToMatchRector.php diff --git a/rules-tests/CodeQuality/Rector/Switch_/SwitchTrueToMatchRector/Fixture/skip_break.php.inc b/rules-tests/CodeQuality/Rector/Switch_/SwitchTrueToMatchRector/Fixture/skip_break.php.inc new file mode 100644 index 00000000000..41442776eda --- /dev/null +++ b/rules-tests/CodeQuality/Rector/Switch_/SwitchTrueToMatchRector/Fixture/skip_break.php.inc @@ -0,0 +1,21 @@ + +----- + 'no', + $value === 1 => 'yes', + $value === 2 => 'maybe', + default => 'nevermind', + }; + } +} + +?> diff --git a/rules-tests/CodeQuality/Rector/Switch_/SwitchTrueToMatchRector/SwitchTrueToMatchRectorTest.php b/rules-tests/CodeQuality/Rector/Switch_/SwitchTrueToMatchRector/SwitchTrueToMatchRectorTest.php new file mode 100644 index 00000000000..41dbff0ec04 --- /dev/null +++ b/rules-tests/CodeQuality/Rector/Switch_/SwitchTrueToMatchRector/SwitchTrueToMatchRectorTest.php @@ -0,0 +1,28 @@ +doTestFile($filePath); + } + + public static function provideData(): Iterator + { + return self::yieldFilesFromDirectory(__DIR__ . '/Fixture'); + } + + public function provideConfigFilePath(): string + { + return __DIR__ . '/config/configured_rule.php'; + } +} diff --git a/rules-tests/CodeQuality/Rector/Switch_/SwitchTrueToMatchRector/config/configured_rule.php b/rules-tests/CodeQuality/Rector/Switch_/SwitchTrueToMatchRector/config/configured_rule.php new file mode 100644 index 00000000000..b864c1187a0 --- /dev/null +++ b/rules-tests/CodeQuality/Rector/Switch_/SwitchTrueToMatchRector/config/configured_rule.php @@ -0,0 +1,12 @@ +phpVersion(PhpVersion::PHP_80); + $rectorConfig->rule(SwitchTrueToMatchRector::class); +}; diff --git a/rules/CodeQuality/Rector/Switch_/SwitchTrueToMatchRector.php b/rules/CodeQuality/Rector/Switch_/SwitchTrueToMatchRector.php new file mode 100644 index 00000000000..858a6a797a3 --- /dev/null +++ b/rules/CodeQuality/Rector/Switch_/SwitchTrueToMatchRector.php @@ -0,0 +1,138 @@ + 'no', + $value === 1 => 'yes', + default => 'maybe', + }; + } +} +CODE_SAMPLE + ), + ]); + } + + /** + * @return array> + */ + public function getNodeTypes(): array + { + return [Switch_::class]; + } + + /** + * @param Switch_ $node + */ + public function refactor(Node $node): ?Node + { + if (! $this->valueResolver->isTrue($node->cond)) { + return null; + } + + if ($node->cases === []) { + return null; + } + + $matchArms = []; + $hasDefault = false; + + $lastCaseKey = array_key_last($node->cases); + + foreach ($node->cases as $key => $case) { + // a match arm can only carry a single return expression + if (count($case->stmts) !== 1) { + return null; + } + + $onlyStmt = $case->stmts[0]; + if (! $onlyStmt instanceof Return_) { + return null; + } + + if (! $onlyStmt->expr instanceof Expr) { + return null; + } + + if ($case->cond instanceof Expr) { + $matchArms[] = new MatchArm([$case->cond], $onlyStmt->expr); + continue; + } + + // default case must be the last one, as a match default arm catches everything + if ($key !== $lastCaseKey) { + return null; + } + + $hasDefault = true; + $matchArms[] = new MatchArm(null, $onlyStmt->expr); + } + + // require a default arm, otherwise match (true) throws UnhandledMatchError where switch (true) falls through + if (! $hasDefault) { + return null; + } + + $match = new Match_($this->nodeFactory->createTrue(), $matchArms); + + return new Return_($match); + } + + public function provideMinPhpVersion(): int + { + return PhpVersionFeature::MATCH_EXPRESSION; + } +} diff --git a/src/Config/Level/CodeQualityLevel.php b/src/Config/Level/CodeQualityLevel.php index 3f1c9bb6e4f..ddeb933c002 100644 --- a/src/Config/Level/CodeQualityLevel.php +++ b/src/Config/Level/CodeQualityLevel.php @@ -75,6 +75,7 @@ use Rector\CodeQuality\Rector\Property\FixClassCaseSensitivityVarDocblockRector; use Rector\CodeQuality\Rector\StmtsAwareInterface\MoveInnerFunctionToTopLevelRector; use Rector\CodeQuality\Rector\Switch_\SingularSwitchToIfRector; +use Rector\CodeQuality\Rector\Switch_\SwitchTrueToMatchRector; use Rector\CodeQuality\Rector\Ternary\ArrayKeyExistsTernaryThenValueToCoalescingRector; use Rector\CodeQuality\Rector\Ternary\NumberCompareToMaxFuncCallRector; use Rector\CodeQuality\Rector\Ternary\SimplifyTautologyTernaryRector; @@ -170,6 +171,7 @@ final class CodeQualityLevel VariableConstFetchToClassConstFetchRector::class, SwitchNegatedTernaryRector::class, SingularSwitchToIfRector::class, + SwitchTrueToMatchRector::class, SimplifyIfNullableReturnRector::class, CallUserFuncWithArrowFunctionToInlineRector::class, FlipTypeControlToUseExclusiveTypeRector::class,