From ff86aa97878ea0bc4f5fd67f1a5fe75d812bbaac Mon Sep 17 00:00:00 2001 From: phpstan-bot <79867460+phpstan-bot@users.noreply.github.com> Date: Tue, 10 Mar 2026 14:32:59 +0000 Subject: [PATCH 1/4] Report error when using enum case outside of enum - Added new EnumCaseOutsideEnumRule that reports an error when `case` is used in classes or interfaces instead of enums - New regression test in tests/PHPStan/Rules/EnumCases/data/bug-14252.php - The rule is registered at level 0 and uses the identifier `enum.caseOutsideOfEnum` Closes https://github.com/phpstan/phpstan/issues/14252 --- .../EnumCases/EnumCaseOutsideEnumRule.php | 42 +++++++++++++++++++ .../EnumCases/EnumCaseOutsideEnumRuleTest.php | 35 ++++++++++++++++ .../Rules/EnumCases/data/bug-14252.php | 20 +++++++++ 3 files changed, 97 insertions(+) create mode 100644 src/Rules/EnumCases/EnumCaseOutsideEnumRule.php create mode 100644 tests/PHPStan/Rules/EnumCases/EnumCaseOutsideEnumRuleTest.php create mode 100644 tests/PHPStan/Rules/EnumCases/data/bug-14252.php diff --git a/src/Rules/EnumCases/EnumCaseOutsideEnumRule.php b/src/Rules/EnumCases/EnumCaseOutsideEnumRule.php new file mode 100644 index 00000000000..ba0290e5ea6 --- /dev/null +++ b/src/Rules/EnumCases/EnumCaseOutsideEnumRule.php @@ -0,0 +1,42 @@ + + */ +#[RegisteredRule(level: 0)] +final class EnumCaseOutsideEnumRule implements Rule +{ + + public function getNodeType(): string + { + return Node\Stmt\EnumCase::class; + } + + public function processNode(Node $node, Scope $scope): array + { + if (!$scope->isInClass()) { + return []; + } + + $classReflection = $scope->getClassReflection(); + if ($classReflection->isEnum()) { + return []; + } + + return [ + RuleErrorBuilder::message('Enum case can only be used in enums.') + ->nonIgnorable() + ->identifier('enum.caseOutsideOfEnum') + ->build(), + ]; + } + +} diff --git a/tests/PHPStan/Rules/EnumCases/EnumCaseOutsideEnumRuleTest.php b/tests/PHPStan/Rules/EnumCases/EnumCaseOutsideEnumRuleTest.php new file mode 100644 index 00000000000..7c864121677 --- /dev/null +++ b/tests/PHPStan/Rules/EnumCases/EnumCaseOutsideEnumRuleTest.php @@ -0,0 +1,35 @@ + + */ +class EnumCaseOutsideEnumRuleTest extends RuleTestCase +{ + + protected function getRule(): Rule + { + return new EnumCaseOutsideEnumRule(); + } + + #[RequiresPhp('>= 8.1')] + public function testRule(): void + { + $this->analyse([__DIR__ . '/data/bug-14252.php'], [ + [ + 'Enum case can only be used in enums.', + 9, + ], + [ + 'Enum case can only be used in enums.', + 14, + ], + ]); + } + +} diff --git a/tests/PHPStan/Rules/EnumCases/data/bug-14252.php b/tests/PHPStan/Rules/EnumCases/data/bug-14252.php new file mode 100644 index 00000000000..d8f86ec3364 --- /dev/null +++ b/tests/PHPStan/Rules/EnumCases/data/bug-14252.php @@ -0,0 +1,20 @@ += 8.1 + +declare(strict_types = 1); + +namespace Bug14252; + +class Foo +{ + case Active; +} + +interface Bar +{ + case Active; +} + +enum Qux +{ + case Active; +} From d40ae3edf838c1c16536a7cf52dbb26b030cf7cc Mon Sep 17 00:00:00 2001 From: phpstan-bot Date: Thu, 12 Mar 2026 21:09:01 +0000 Subject: [PATCH 2/4] Also report enum case outside of enum in traits Add trait test case as requested in review. Handle the trait scope separately since traits are analyzed in the context of their consumer class. Co-Authored-By: Claude Opus 4.6 --- src/Rules/EnumCases/EnumCaseOutsideEnumRule.php | 9 +++++++++ .../Rules/EnumCases/EnumCaseOutsideEnumRuleTest.php | 4 ++++ tests/PHPStan/Rules/EnumCases/data/bug-14252.php | 10 ++++++++++ 3 files changed, 23 insertions(+) diff --git a/src/Rules/EnumCases/EnumCaseOutsideEnumRule.php b/src/Rules/EnumCases/EnumCaseOutsideEnumRule.php index ba0290e5ea6..a771a702f34 100644 --- a/src/Rules/EnumCases/EnumCaseOutsideEnumRule.php +++ b/src/Rules/EnumCases/EnumCaseOutsideEnumRule.php @@ -22,6 +22,15 @@ public function getNodeType(): string public function processNode(Node $node, Scope $scope): array { + if ($scope->isInTrait()) { + return [ + RuleErrorBuilder::message('Enum case can only be used in enums.') + ->nonIgnorable() + ->identifier('enum.caseOutsideOfEnum') + ->build(), + ]; + } + if (!$scope->isInClass()) { return []; } diff --git a/tests/PHPStan/Rules/EnumCases/EnumCaseOutsideEnumRuleTest.php b/tests/PHPStan/Rules/EnumCases/EnumCaseOutsideEnumRuleTest.php index 7c864121677..f29475c5279 100644 --- a/tests/PHPStan/Rules/EnumCases/EnumCaseOutsideEnumRuleTest.php +++ b/tests/PHPStan/Rules/EnumCases/EnumCaseOutsideEnumRuleTest.php @@ -29,6 +29,10 @@ public function testRule(): void 'Enum case can only be used in enums.', 14, ], + [ + 'Enum case can only be used in enums.', + 19, + ], ]); } diff --git a/tests/PHPStan/Rules/EnumCases/data/bug-14252.php b/tests/PHPStan/Rules/EnumCases/data/bug-14252.php index d8f86ec3364..71b75a453e4 100644 --- a/tests/PHPStan/Rules/EnumCases/data/bug-14252.php +++ b/tests/PHPStan/Rules/EnumCases/data/bug-14252.php @@ -14,6 +14,16 @@ interface Bar case Active; } +trait Baz +{ + case Active; +} + +class BazConsumer +{ + use Baz; +} + enum Qux { case Active; From f97c99a2989195261ed2b06429dd83507faacede Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Sun, 15 Mar 2026 21:29:11 +0100 Subject: [PATCH 3/4] Update Makefile --- Makefile | 1 + 1 file changed, 1 insertion(+) diff --git a/Makefile b/Makefile index 42ca3f45873..06b47b463a3 100644 --- a/Makefile +++ b/Makefile @@ -129,6 +129,7 @@ lint: --exclude tests/PHPStan/Rules/Properties/data/override-attr-on-property.php \ --exclude tests/PHPStan/Rules/Properties/data/property-override-attr.php \ --exclude tests/PHPStan/Rules/Operators/data/bug-3585.php \ + --exclude tests/PHPStan/Rules/EnumCases/data/bug-14252.php \ src tests install-paratest: From b12db14b0999e3ce6175544d12aa8361b283c29c Mon Sep 17 00:00:00 2001 From: phpstan-bot Date: Mon, 23 Mar 2026 08:15:43 +0000 Subject: [PATCH 4/4] Deduplicate RuleErrorBuilder call in EnumCaseOutsideEnumRule Restructure the condition logic to return early for the valid case (enum context) and have a single RuleErrorBuilder call for all invalid contexts. Co-Authored-By: Claude Opus 4.6 --- src/Rules/EnumCases/EnumCaseOutsideEnumRule.php | 16 +--------------- 1 file changed, 1 insertion(+), 15 deletions(-) diff --git a/src/Rules/EnumCases/EnumCaseOutsideEnumRule.php b/src/Rules/EnumCases/EnumCaseOutsideEnumRule.php index a771a702f34..f01ae569235 100644 --- a/src/Rules/EnumCases/EnumCaseOutsideEnumRule.php +++ b/src/Rules/EnumCases/EnumCaseOutsideEnumRule.php @@ -22,21 +22,7 @@ public function getNodeType(): string public function processNode(Node $node, Scope $scope): array { - if ($scope->isInTrait()) { - return [ - RuleErrorBuilder::message('Enum case can only be used in enums.') - ->nonIgnorable() - ->identifier('enum.caseOutsideOfEnum') - ->build(), - ]; - } - - if (!$scope->isInClass()) { - return []; - } - - $classReflection = $scope->getClassReflection(); - if ($classReflection->isEnum()) { + if (!$scope->isInTrait() && $scope->isInClass() && $scope->getClassReflection()->isEnum()) { return []; }