From 11a538ff5733e4e036722f3d0c2ff83fbdeb34c7 Mon Sep 17 00:00:00 2001 From: Abdul Malik Ikhsan Date: Wed, 20 Aug 2025 23:08:55 +0700 Subject: [PATCH 1/8] [Php85] Add ColonAfterSwitchCaseRector --- .../ColonAfterSwitchCaseRectorTest.php | 28 +++++ .../Fixture/fixture.php.inc | 37 ++++++ .../Fixture/skip_with_colon.php.inc | 16 +++ .../Fixture/without_stmt.php.inc | 53 ++++++++ .../config/configured_rule.php | 10 ++ .../Switch_/ColonAfterSwitchCaseRector.php | 118 ++++++++++++++++++ src/ValueObject/PhpVersionFeature.php | 6 + 7 files changed, 268 insertions(+) create mode 100644 rules-tests/Php85/Rector/Switch_/ColonAfterSwitchCaseRector/ColonAfterSwitchCaseRectorTest.php create mode 100644 rules-tests/Php85/Rector/Switch_/ColonAfterSwitchCaseRector/Fixture/fixture.php.inc create mode 100644 rules-tests/Php85/Rector/Switch_/ColonAfterSwitchCaseRector/Fixture/skip_with_colon.php.inc create mode 100644 rules-tests/Php85/Rector/Switch_/ColonAfterSwitchCaseRector/Fixture/without_stmt.php.inc create mode 100644 rules-tests/Php85/Rector/Switch_/ColonAfterSwitchCaseRector/config/configured_rule.php create mode 100644 rules/Php85/Rector/Switch_/ColonAfterSwitchCaseRector.php diff --git a/rules-tests/Php85/Rector/Switch_/ColonAfterSwitchCaseRector/ColonAfterSwitchCaseRectorTest.php b/rules-tests/Php85/Rector/Switch_/ColonAfterSwitchCaseRector/ColonAfterSwitchCaseRectorTest.php new file mode 100644 index 00000000000..be28a0bdf15 --- /dev/null +++ b/rules-tests/Php85/Rector/Switch_/ColonAfterSwitchCaseRector/ColonAfterSwitchCaseRectorTest.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/Php85/Rector/Switch_/ColonAfterSwitchCaseRector/Fixture/fixture.php.inc b/rules-tests/Php85/Rector/Switch_/ColonAfterSwitchCaseRector/Fixture/fixture.php.inc new file mode 100644 index 00000000000..8e8e0bc149a --- /dev/null +++ b/rules-tests/Php85/Rector/Switch_/ColonAfterSwitchCaseRector/Fixture/fixture.php.inc @@ -0,0 +1,37 @@ + +----- + diff --git a/rules-tests/Php85/Rector/Switch_/ColonAfterSwitchCaseRector/Fixture/skip_with_colon.php.inc b/rules-tests/Php85/Rector/Switch_/ColonAfterSwitchCaseRector/Fixture/skip_with_colon.php.inc new file mode 100644 index 00000000000..63ea8da70e2 --- /dev/null +++ b/rules-tests/Php85/Rector/Switch_/ColonAfterSwitchCaseRector/Fixture/skip_with_colon.php.inc @@ -0,0 +1,16 @@ + +----- + diff --git a/rules-tests/Php85/Rector/Switch_/ColonAfterSwitchCaseRector/config/configured_rule.php b/rules-tests/Php85/Rector/Switch_/ColonAfterSwitchCaseRector/config/configured_rule.php new file mode 100644 index 00000000000..6f357c18365 --- /dev/null +++ b/rules-tests/Php85/Rector/Switch_/ColonAfterSwitchCaseRector/config/configured_rule.php @@ -0,0 +1,10 @@ +rule(ColonAfterSwitchCaseRector::class); +}; diff --git a/rules/Php85/Rector/Switch_/ColonAfterSwitchCaseRector.php b/rules/Php85/Rector/Switch_/ColonAfterSwitchCaseRector.php new file mode 100644 index 00000000000..2ea15a31053 --- /dev/null +++ b/rules/Php85/Rector/Switch_/ColonAfterSwitchCaseRector.php @@ -0,0 +1,118 @@ +file->getOldTokens(); + + foreach ($node->cases as $key => $case) { + $cond = $case->cond; + + if (! $cond instanceof Expr) { + continue; + } + + $endTokenPos = $case->cond->getEndTokenPos(); + + if ($endTokenPos < 0) { + continue; + } + + $startCaseStmtsPos = count($case->stmts) === 0 + ? ( + isset($node->cases[$key + 1]) + ? $node->cases[$key + 1]->getStartTokenPos() + : 0 + ) + : $case->stmts[0]->getStartTokenPos(); + + if ($startCaseStmtsPos < 0) { + continue; + } + + $nextTokenPos = $endTokenPos; + while (++$nextTokenPos > 0) { + if ($nextTokenPos === $startCaseStmtsPos) { + break; + } + + if (! isset($oldTokens[$nextTokenPos])) { + continue 2; + } + + $nextToken = $oldTokens[$nextTokenPos]; + if (trim($nextToken->text) === '') { + continue; + } + + if ($nextToken->text === ':') { + continue 2; + } + + if ($nextToken->text === ';') { + $hasChanged = true; + $nextToken->text = ':'; + break; + } + } + } + + if (! $hasChanged) { + return null; + } + + return $node; + } + + public function provideMinPhpVersion(): int + { + return PhpVersionFeature::COLON_AFTER_SWITCH_CASE; + } +} diff --git a/src/ValueObject/PhpVersionFeature.php b/src/ValueObject/PhpVersionFeature.php index 4cde90e8564..a37b164c45b 100644 --- a/src/ValueObject/PhpVersionFeature.php +++ b/src/ValueObject/PhpVersionFeature.php @@ -786,4 +786,10 @@ final class PhpVersionFeature * @var int */ public const DEPRECATED_ATTRIBUTE_ON_CONSTANT = PhpVersion::PHP_85; + + /** + * @see https://wiki.php.net/rfc/deprecations_php_8_5#deprecate_semicolon_after_case_in_switch_statement + * @var int + */ + public const COLON_AFTER_SWITCH_CASE = PhpVersion::PHP_85; } From 31a5c9e23999ef25c8061f1aa8972a5bd8fb7426 Mon Sep 17 00:00:00 2001 From: Abdul Malik Ikhsan Date: Wed, 20 Aug 2025 23:14:31 +0700 Subject: [PATCH 2/8] register --- config/set/php85.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/config/set/php85.php b/config/set/php85.php index a81f317de0a..afb09306273 100644 --- a/config/set/php85.php +++ b/config/set/php85.php @@ -11,6 +11,7 @@ use Rector\Php85\Rector\ClassMethod\NullDebugInfoReturnRector; use Rector\Php85\Rector\Const_\DeprecatedAnnotationToDeprecatedAttributeRector; use Rector\Php85\Rector\FuncCall\RemoveFinfoBufferContextArgRector; +use Rector\Php85\Rector\Switch_\ColonAfterSwitchCaseRector; use Rector\Removing\Rector\FuncCall\RemoveFuncCallArgRector; use Rector\Removing\ValueObject\RemoveFuncCallArg; use Rector\Renaming\Rector\Cast\RenameCastRector; @@ -30,6 +31,7 @@ RemoveFinfoBufferContextArgRector::class, NullDebugInfoReturnRector::class, DeprecatedAnnotationToDeprecatedAttributeRector::class, + ColonAfterSwitchCaseRector::class, ] ); From 2be04b5bc65d20d471360d4623af10ef0ad7d18c Mon Sep 17 00:00:00 2001 From: GitHub Action Date: Wed, 20 Aug 2025 16:15:28 +0000 Subject: [PATCH 3/8] [ci-review] Rector Rectify --- ...cCallWithPhpVersionIdCheckerRectorTest.php | 2 ++ ...dAnnotationToDeprecatedAttributeRector.php | 4 +-- ...dAnnotationToDeprecatedAttributeRector.php | 4 +-- ...pFuncCallWithPhpVersionIdCheckerRector.php | 27 ++++++++++--------- ...notationToDeprecatedAttributeConverter.php | 10 +++---- 5 files changed, 25 insertions(+), 22 deletions(-) diff --git a/rules-tests/Transform/Rector/FuncCall/WrapFuncCallWithPhpVersionIdCheckerRector/WrapFuncCallWithPhpVersionIdCheckerRectorTest.php b/rules-tests/Transform/Rector/FuncCall/WrapFuncCallWithPhpVersionIdCheckerRector/WrapFuncCallWithPhpVersionIdCheckerRectorTest.php index 6a3fbf7ad80..03384cc4e38 100644 --- a/rules-tests/Transform/Rector/FuncCall/WrapFuncCallWithPhpVersionIdCheckerRector/WrapFuncCallWithPhpVersionIdCheckerRectorTest.php +++ b/rules-tests/Transform/Rector/FuncCall/WrapFuncCallWithPhpVersionIdCheckerRector/WrapFuncCallWithPhpVersionIdCheckerRectorTest.php @@ -1,5 +1,7 @@ converter->convert($node); + return $this->deprecatedAnnotationToDeprecatedAttributeConverter->convert($node); } public function provideMinPhpVersion(): int diff --git a/rules/Php85/Rector/Const_/DeprecatedAnnotationToDeprecatedAttributeRector.php b/rules/Php85/Rector/Const_/DeprecatedAnnotationToDeprecatedAttributeRector.php index d06031cb0c1..153aced3225 100644 --- a/rules/Php85/Rector/Const_/DeprecatedAnnotationToDeprecatedAttributeRector.php +++ b/rules/Php85/Rector/Const_/DeprecatedAnnotationToDeprecatedAttributeRector.php @@ -19,7 +19,7 @@ final class DeprecatedAnnotationToDeprecatedAttributeRector extends AbstractRector implements MinPhpVersionInterface { public function __construct( - private readonly DeprecatedAnnotationToDeprecatedAttributeConverter $converter, + private readonly DeprecatedAnnotationToDeprecatedAttributeConverter $deprecatedAnnotationToDeprecatedAttributeConverter, ) { } @@ -52,7 +52,7 @@ public function getNodeTypes(): array */ public function refactor(Node $node): ?Node { - return $this->converter->convert($node); + return $this->deprecatedAnnotationToDeprecatedAttributeConverter->convert($node); } public function provideMinPhpVersion(): int diff --git a/rules/Transform/Rector/FuncCall/WrapFuncCallWithPhpVersionIdCheckerRector.php b/rules/Transform/Rector/FuncCall/WrapFuncCallWithPhpVersionIdCheckerRector.php index 850646f0321..069c7565e35 100644 --- a/rules/Transform/Rector/FuncCall/WrapFuncCallWithPhpVersionIdCheckerRector.php +++ b/rules/Transform/Rector/FuncCall/WrapFuncCallWithPhpVersionIdCheckerRector.php @@ -81,10 +81,12 @@ public function refactor(Node $node): null|Node|int $hasChanged = false; foreach ($node->stmts as $key => $stmt) { - if (! $stmt instanceof Expression || ! $stmt->expr instanceof FuncCall) { + if (! $stmt instanceof Expression) { + continue; + } + if (! $stmt->expr instanceof FuncCall) { continue; } - $funcCall = $stmt->expr; foreach ($this->wrapFuncCallWithPhpVersionIdCheckers as $wrapFuncCallWithPhpVersionIdChecker) { @@ -121,35 +123,34 @@ public function configure(array $configuration): void $this->wrapFuncCallWithPhpVersionIdCheckers = $configuration; } - private function isWrappedFuncCall(StmtsAwareInterface $node): bool + private function isWrappedFuncCall(StmtsAwareInterface $stmtsAware): bool { - if (! $node instanceof If_) { + if (! $stmtsAware instanceof If_) { return false; } - $phpVersionId = $this->getPhpVersionId($node->cond); - if ($phpVersionId === null) { + $phpVersionId = $this->getPhpVersionId($stmtsAware->cond); + if (!$phpVersionId instanceof Int_) { return false; } - if (count($node->stmts) !== 1) { + if (count($stmtsAware->stmts) !== 1) { return false; } - $childStmt = $node->stmts[0]; + $childStmt = $stmtsAware->stmts[0]; if (! $childStmt instanceof Expression || ! $childStmt->expr instanceof FuncCall) { return false; } foreach ($this->wrapFuncCallWithPhpVersionIdCheckers as $wrapFuncCallWithPhpVersionIdChecker) { - if ( - $this->getName($childStmt->expr) !== $wrapFuncCallWithPhpVersionIdChecker->getFunctionName() - || $phpVersionId->value !== $wrapFuncCallWithPhpVersionIdChecker->getPhpVersionId() - ) { + if ($this->getName($childStmt->expr) !== $wrapFuncCallWithPhpVersionIdChecker->getFunctionName()) { + continue; + } + if ($phpVersionId->value !== $wrapFuncCallWithPhpVersionIdChecker->getPhpVersionId()) { continue; } - return true; } diff --git a/src/PhpAttribute/DeprecatedAnnotationToDeprecatedAttributeConverter.php b/src/PhpAttribute/DeprecatedAnnotationToDeprecatedAttributeConverter.php index 19401e28b09..3cdfb86e86e 100644 --- a/src/PhpAttribute/DeprecatedAnnotationToDeprecatedAttributeConverter.php +++ b/src/PhpAttribute/DeprecatedAnnotationToDeprecatedAttributeConverter.php @@ -25,7 +25,7 @@ use Rector\NodeTypeResolver\Node\AttributeKey; use Rector\PhpAttribute\NodeFactory\PhpAttributeGroupFactory; -final class DeprecatedAnnotationToDeprecatedAttributeConverter +final readonly class DeprecatedAnnotationToDeprecatedAttributeConverter { /** * @see https://regex101.com/r/qNytVk/1 @@ -40,10 +40,10 @@ final class DeprecatedAnnotationToDeprecatedAttributeConverter private const START_STAR_SPACED_REGEX = '#^ *\*#ms'; public function __construct( - private readonly PhpDocTagRemover $phpDocTagRemover, - private readonly PhpAttributeGroupFactory $phpAttributeGroupFactory, - private readonly DocBlockUpdater $docBlockUpdater, - private readonly PhpDocInfoFactory $phpDocInfoFactory, + private PhpDocTagRemover $phpDocTagRemover, + private PhpAttributeGroupFactory $phpAttributeGroupFactory, + private DocBlockUpdater $docBlockUpdater, + private PhpDocInfoFactory $phpDocInfoFactory, ) { } From 5e4c8859c0e9cf98f1ad1bb23067b8ec12ea2022 Mon Sep 17 00:00:00 2001 From: Abdul Malik Ikhsan Date: Wed, 20 Aug 2025 23:17:41 +0700 Subject: [PATCH 4/8] next start --- rules/Php85/Rector/Switch_/ColonAfterSwitchCaseRector.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rules/Php85/Rector/Switch_/ColonAfterSwitchCaseRector.php b/rules/Php85/Rector/Switch_/ColonAfterSwitchCaseRector.php index 2ea15a31053..65ee035fde0 100644 --- a/rules/Php85/Rector/Switch_/ColonAfterSwitchCaseRector.php +++ b/rules/Php85/Rector/Switch_/ColonAfterSwitchCaseRector.php @@ -69,7 +69,7 @@ public function refactor(Node $node): ?Node ? ( isset($node->cases[$key + 1]) ? $node->cases[$key + 1]->getStartTokenPos() - : 0 + : $node->getEndTokenPos() ) : $case->stmts[0]->getStartTokenPos(); From c14de0d515cb04285c70f8b120df78a5aab40172 Mon Sep 17 00:00:00 2001 From: GitHub Action Date: Wed, 20 Aug 2025 16:17:51 +0000 Subject: [PATCH 5/8] [ci-review] Rector Rectify --- .../FuncCall/WrapFuncCallWithPhpVersionIdCheckerRector.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/rules/Transform/Rector/FuncCall/WrapFuncCallWithPhpVersionIdCheckerRector.php b/rules/Transform/Rector/FuncCall/WrapFuncCallWithPhpVersionIdCheckerRector.php index 069c7565e35..87c289f37b9 100644 --- a/rules/Transform/Rector/FuncCall/WrapFuncCallWithPhpVersionIdCheckerRector.php +++ b/rules/Transform/Rector/FuncCall/WrapFuncCallWithPhpVersionIdCheckerRector.php @@ -84,9 +84,11 @@ public function refactor(Node $node): null|Node|int if (! $stmt instanceof Expression) { continue; } + if (! $stmt->expr instanceof FuncCall) { continue; } + $funcCall = $stmt->expr; foreach ($this->wrapFuncCallWithPhpVersionIdCheckers as $wrapFuncCallWithPhpVersionIdChecker) { @@ -148,9 +150,11 @@ private function isWrappedFuncCall(StmtsAwareInterface $stmtsAware): bool if ($this->getName($childStmt->expr) !== $wrapFuncCallWithPhpVersionIdChecker->getFunctionName()) { continue; } + if ($phpVersionId->value !== $wrapFuncCallWithPhpVersionIdChecker->getPhpVersionId()) { continue; } + return true; } From 0125f817dcdbf66ddbe7dc3f712f4560a2f998ad Mon Sep 17 00:00:00 2001 From: Abdul Malik Ikhsan Date: Wed, 20 Aug 2025 23:18:34 +0700 Subject: [PATCH 6/8] next start --- rules/Php85/Rector/Switch_/ColonAfterSwitchCaseRector.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rules/Php85/Rector/Switch_/ColonAfterSwitchCaseRector.php b/rules/Php85/Rector/Switch_/ColonAfterSwitchCaseRector.php index 65ee035fde0..dce6c68bab7 100644 --- a/rules/Php85/Rector/Switch_/ColonAfterSwitchCaseRector.php +++ b/rules/Php85/Rector/Switch_/ColonAfterSwitchCaseRector.php @@ -59,7 +59,7 @@ public function refactor(Node $node): ?Node continue; } - $endTokenPos = $case->cond->getEndTokenPos(); + $endTokenPos = $cond->getEndTokenPos(); if ($endTokenPos < 0) { continue; From 3e372d59c762df424587f0ee8735f1b2e0833f45 Mon Sep 17 00:00:00 2001 From: Abdul Malik Ikhsan Date: Wed, 20 Aug 2025 23:23:30 +0700 Subject: [PATCH 7/8] Fix --- rules/Php85/Rector/Switch_/ColonAfterSwitchCaseRector.php | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/rules/Php85/Rector/Switch_/ColonAfterSwitchCaseRector.php b/rules/Php85/Rector/Switch_/ColonAfterSwitchCaseRector.php index dce6c68bab7..f7e9758f160 100644 --- a/rules/Php85/Rector/Switch_/ColonAfterSwitchCaseRector.php +++ b/rules/Php85/Rector/Switch_/ColonAfterSwitchCaseRector.php @@ -78,11 +78,7 @@ public function refactor(Node $node): ?Node } $nextTokenPos = $endTokenPos; - while (++$nextTokenPos > 0) { - if ($nextTokenPos === $startCaseStmtsPos) { - break; - } - + while (++$nextTokenPos < $startCaseStmtsPos) { if (! isset($oldTokens[$nextTokenPos])) { continue 2; } From 8123548823d76352fe1301a3fde2e8b63b33e3a2 Mon Sep 17 00:00:00 2001 From: Abdul Malik Ikhsan Date: Wed, 20 Aug 2025 23:25:01 +0700 Subject: [PATCH 8/8] Fix --- .../Fixture/fixture.php.inc | 16 ++++++++++++++++ .../Switch_/ColonAfterSwitchCaseRector.php | 2 +- 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/rules-tests/Php85/Rector/Switch_/ColonAfterSwitchCaseRector/Fixture/fixture.php.inc b/rules-tests/Php85/Rector/Switch_/ColonAfterSwitchCaseRector/Fixture/fixture.php.inc index 8e8e0bc149a..07dfd311e52 100644 --- a/rules-tests/Php85/Rector/Switch_/ColonAfterSwitchCaseRector/Fixture/fixture.php.inc +++ b/rules-tests/Php85/Rector/Switch_/ColonAfterSwitchCaseRector/Fixture/fixture.php.inc @@ -13,6 +13,14 @@ final class Fixture echo 'baz'; } } + + public function spaceBeforeSemiColon() + { + switch ($value) { + case 'baz' ; + echo 'baz'; + } + } } ?> @@ -32,6 +40,14 @@ final class Fixture echo 'baz'; } } + + public function spaceBeforeSemiColon() + { + switch ($value) { + case 'baz' : + echo 'baz'; + } + } } ?> diff --git a/rules/Php85/Rector/Switch_/ColonAfterSwitchCaseRector.php b/rules/Php85/Rector/Switch_/ColonAfterSwitchCaseRector.php index f7e9758f160..6cdacd3613e 100644 --- a/rules/Php85/Rector/Switch_/ColonAfterSwitchCaseRector.php +++ b/rules/Php85/Rector/Switch_/ColonAfterSwitchCaseRector.php @@ -95,7 +95,7 @@ public function refactor(Node $node): ?Node if ($nextToken->text === ';') { $hasChanged = true; $nextToken->text = ':'; - break; + continue 2; } } }