From bdd1c322593dc6bf658e0e776d490af45c3e701e Mon Sep 17 00:00:00 2001 From: Dawid Parafinski Date: Fri, 10 Oct 2025 15:40:12 +0200 Subject: [PATCH 1/3] Added rule for final class --- extension.neon | 1 + rules/FinalClassRule.php | 77 +++++++++++++++++++ tests/rules/FinalClassRuleTest.php | 53 +++++++++++++ tests/rules/Fixtures/FinalClassFixture.php | 19 +++++ .../Fixtures/FinalRule/AbstractClass.php | 11 +++ tests/rules/Fixtures/FinalRule/FinalClass.php | 11 +++ .../Fixtures/FinalRule/NonFinalClass.php | 11 +++ .../Fixtures/FinalRule/SomeInterface.php | 11 +++ tests/rules/Fixtures/FinalRule/SomeTrait.php | 11 +++ 9 files changed, 205 insertions(+) create mode 100644 rules/FinalClassRule.php create mode 100644 tests/rules/FinalClassRuleTest.php create mode 100644 tests/rules/Fixtures/FinalClassFixture.php create mode 100644 tests/rules/Fixtures/FinalRule/AbstractClass.php create mode 100644 tests/rules/Fixtures/FinalRule/FinalClass.php create mode 100644 tests/rules/Fixtures/FinalRule/NonFinalClass.php create mode 100644 tests/rules/Fixtures/FinalRule/SomeInterface.php create mode 100644 tests/rules/Fixtures/FinalRule/SomeTrait.php diff --git a/extension.neon b/extension.neon index 80cf7ed..9721f9a 100644 --- a/extension.neon +++ b/extension.neon @@ -7,3 +7,4 @@ parameters: - stubs/Money/MoneyParser.stub rules: - Ibexa\PHPStan\Rules\NoConfigResolverParametersInConstructorRule + - Ibexa\PHPStan\Rules\FinalClassRule diff --git a/rules/FinalClassRule.php b/rules/FinalClassRule.php new file mode 100644 index 0000000..300e826 --- /dev/null +++ b/rules/FinalClassRule.php @@ -0,0 +1,77 @@ + + */ +final class FinalClassRule implements Rule +{ + private ReflectionProvider $reflectionProvider; + + public function __construct( + ReflectionProvider $reflectionProvider + ) { + $this->reflectionProvider = $reflectionProvider; + } + + public function getNodeType(): string + { + return Node\Stmt\Class_::class; + } + + public function processNode(Node $node, Scope $scope): array + { + // Skip anonymous classes + if (!isset($node->namespacedName)) { + return []; + } + + $className = $node->namespacedName->toString(); + + if (!$this->reflectionProvider->hasClass($className)) { + return []; + } + + $reflection = $this->reflectionProvider->getClass($className); + + // Skip if already final + if ($reflection->isFinal()) { + return []; + } + + // Skip if abstract (abstract classes shouldn't be final) + if ($reflection->isAbstract()) { + return []; + } + + // Skip interfaces and traits + if ($reflection->isInterface() || $reflection->isTrait()) { + return []; + } + + return [ + RuleErrorBuilder::message( + sprintf( + 'Class %s is not final. All non-abstract classes should be final.', + $reflection->getName() + ) + ) + ->identifier('class.notFinal') + ->tip('Add "final" keyword to the class declaration.') + ->build() + ]; + } +} diff --git a/tests/rules/FinalClassRuleTest.php b/tests/rules/FinalClassRuleTest.php new file mode 100644 index 0000000..7d0ebda --- /dev/null +++ b/tests/rules/FinalClassRuleTest.php @@ -0,0 +1,53 @@ + + */ +final class FinalClassRuleTest extends RuleTestCase +{ + protected function getRule(): Rule + { + return new FinalClassRule($this->createReflectionProvider()); + } + + public function testRule(): void + { + $this->analyse( + [ + __DIR__ . '/Fixtures/FinalRule/NonFinalClass.php', + ], + [ + [ + 'Class Ibexa\Tests\PHPStan\Rules\Fixtures\FinalRule\NonFinalClass is not final. All non-abstract classes should be final.', + 11, + 'Add "final" keyword to the class declaration.', + ], + ] + ); + } + + public function testNoErrorsOnFinalAndAbstractClassesAndInterfaces(): void + { + $this->analyse( + [ + __DIR__ . '/Fixtures/FinalRule/FinalClass.php', + __DIR__ . '/Fixtures/FinalRule/AbstractClass.php', + __DIR__ . '/Fixtures/FinalRule/SomeInterface.php', + __DIR__ . '/Fixtures/FinalRule/SomeTrait.php', + ], + [] + ); + } +} diff --git a/tests/rules/Fixtures/FinalClassFixture.php b/tests/rules/Fixtures/FinalClassFixture.php new file mode 100644 index 0000000..fe61afc --- /dev/null +++ b/tests/rules/Fixtures/FinalClassFixture.php @@ -0,0 +1,19 @@ + Date: Fri, 10 Oct 2025 15:50:18 +0200 Subject: [PATCH 2/3] cleanup some names --- rules/FinalClassRule.php | 4 ++-- tests/rules/FinalClassRuleTest.php | 12 ++++++------ .../AbstractClass.php | 6 ++++-- .../{FinalRule => FinalClass}/FinalClass.php | 6 ++++-- .../NonFinalClass.php | 6 ++++-- .../SomeInterface.php | 6 ++++-- .../{FinalRule => FinalClass}/SomeTrait.php | 6 ++++-- tests/rules/Fixtures/FinalClassFixture.php | 19 ------------------- 8 files changed, 28 insertions(+), 37 deletions(-) rename tests/rules/Fixtures/{FinalRule => FinalClass}/AbstractClass.php (69%) rename tests/rules/Fixtures/{FinalRule => FinalClass}/FinalClass.php (71%) rename tests/rules/Fixtures/{FinalRule => FinalClass}/NonFinalClass.php (72%) rename tests/rules/Fixtures/{FinalRule => FinalClass}/SomeInterface.php (71%) rename tests/rules/Fixtures/{FinalRule => FinalClass}/SomeTrait.php (73%) delete mode 100644 tests/rules/Fixtures/FinalClassFixture.php diff --git a/rules/FinalClassRule.php b/rules/FinalClassRule.php index 300e826..962cb9b 100644 --- a/rules/FinalClassRule.php +++ b/rules/FinalClassRule.php @@ -10,9 +10,9 @@ use PhpParser\Node; use PHPStan\Analyser\Scope; +use PHPStan\Reflection\ReflectionProvider; use PHPStan\Rules\Rule; use PHPStan\Rules\RuleErrorBuilder; -use PHPStan\Reflection\ReflectionProvider; /** * @implements \PHPStan\Rules\Rule<\PhpParser\Node\Stmt\Class_> @@ -71,7 +71,7 @@ public function processNode(Node $node, Scope $scope): array ) ->identifier('class.notFinal') ->tip('Add "final" keyword to the class declaration.') - ->build() + ->build(), ]; } } diff --git a/tests/rules/FinalClassRuleTest.php b/tests/rules/FinalClassRuleTest.php index 7d0ebda..6d91f24 100644 --- a/tests/rules/FinalClassRuleTest.php +++ b/tests/rules/FinalClassRuleTest.php @@ -26,11 +26,11 @@ public function testRule(): void { $this->analyse( [ - __DIR__ . '/Fixtures/FinalRule/NonFinalClass.php', + __DIR__ . '/Fixtures/FinalClass/NonFinalClass.php', ], [ [ - 'Class Ibexa\Tests\PHPStan\Rules\Fixtures\FinalRule\NonFinalClass is not final. All non-abstract classes should be final.', + 'Class Ibexa\Tests\PHPStan\Rules\Fixtures\FinalClass\NonFinalClass is not final. All non-abstract classes should be final.', 11, 'Add "final" keyword to the class declaration.', ], @@ -42,10 +42,10 @@ public function testNoErrorsOnFinalAndAbstractClassesAndInterfaces(): void { $this->analyse( [ - __DIR__ . '/Fixtures/FinalRule/FinalClass.php', - __DIR__ . '/Fixtures/FinalRule/AbstractClass.php', - __DIR__ . '/Fixtures/FinalRule/SomeInterface.php', - __DIR__ . '/Fixtures/FinalRule/SomeTrait.php', + __DIR__ . '/Fixtures/FinalClass/FinalClass.php', + __DIR__ . '/Fixtures/FinalClass/AbstractClass.php', + __DIR__ . '/Fixtures/FinalClass/SomeInterface.php', + __DIR__ . '/Fixtures/FinalClass/SomeTrait.php', ], [] ); diff --git a/tests/rules/Fixtures/FinalRule/AbstractClass.php b/tests/rules/Fixtures/FinalClass/AbstractClass.php similarity index 69% rename from tests/rules/Fixtures/FinalRule/AbstractClass.php rename to tests/rules/Fixtures/FinalClass/AbstractClass.php index 0ad44df..39ee675 100644 --- a/tests/rules/Fixtures/FinalRule/AbstractClass.php +++ b/tests/rules/Fixtures/FinalClass/AbstractClass.php @@ -6,6 +6,8 @@ */ declare(strict_types=1); -namespace Ibexa\Tests\PHPStan\Rules\Fixtures\FinalRule; +namespace Ibexa\Tests\PHPStan\Rules\Fixtures\FinalClass; -abstract class AbstractClass {} +abstract class AbstractClass +{ +} diff --git a/tests/rules/Fixtures/FinalRule/FinalClass.php b/tests/rules/Fixtures/FinalClass/FinalClass.php similarity index 71% rename from tests/rules/Fixtures/FinalRule/FinalClass.php rename to tests/rules/Fixtures/FinalClass/FinalClass.php index c84dcc4..a521ad5 100644 --- a/tests/rules/Fixtures/FinalRule/FinalClass.php +++ b/tests/rules/Fixtures/FinalClass/FinalClass.php @@ -6,6 +6,8 @@ */ declare(strict_types=1); -namespace Ibexa\Tests\PHPStan\Rules\Fixtures\FinalRule; +namespace Ibexa\Tests\PHPStan\Rules\Fixtures\FinalClass; -final class FinalClass {} +final class FinalClass +{ +} diff --git a/tests/rules/Fixtures/FinalRule/NonFinalClass.php b/tests/rules/Fixtures/FinalClass/NonFinalClass.php similarity index 72% rename from tests/rules/Fixtures/FinalRule/NonFinalClass.php rename to tests/rules/Fixtures/FinalClass/NonFinalClass.php index f18dd99..fa997b8 100644 --- a/tests/rules/Fixtures/FinalRule/NonFinalClass.php +++ b/tests/rules/Fixtures/FinalClass/NonFinalClass.php @@ -6,6 +6,8 @@ */ declare(strict_types=1); -namespace Ibexa\Tests\PHPStan\Rules\Fixtures\FinalRule; +namespace Ibexa\Tests\PHPStan\Rules\Fixtures\FinalClass; -class NonFinalClass {} +class NonFinalClass +{ +} diff --git a/tests/rules/Fixtures/FinalRule/SomeInterface.php b/tests/rules/Fixtures/FinalClass/SomeInterface.php similarity index 71% rename from tests/rules/Fixtures/FinalRule/SomeInterface.php rename to tests/rules/Fixtures/FinalClass/SomeInterface.php index 4c0bd70..783c0f9 100644 --- a/tests/rules/Fixtures/FinalRule/SomeInterface.php +++ b/tests/rules/Fixtures/FinalClass/SomeInterface.php @@ -6,6 +6,8 @@ */ declare(strict_types=1); -namespace Ibexa\Tests\PHPStan\Rules\Fixtures\FinalRule; +namespace Ibexa\Tests\PHPStan\Rules\Fixtures\FinalClass; -interface SomeInterface {} +interface SomeInterface +{ +} diff --git a/tests/rules/Fixtures/FinalRule/SomeTrait.php b/tests/rules/Fixtures/FinalClass/SomeTrait.php similarity index 73% rename from tests/rules/Fixtures/FinalRule/SomeTrait.php rename to tests/rules/Fixtures/FinalClass/SomeTrait.php index cb7b889..2845579 100644 --- a/tests/rules/Fixtures/FinalRule/SomeTrait.php +++ b/tests/rules/Fixtures/FinalClass/SomeTrait.php @@ -6,6 +6,8 @@ */ declare(strict_types=1); -namespace Ibexa\Tests\PHPStan\Rules\Fixtures\FinalRule; +namespace Ibexa\Tests\PHPStan\Rules\Fixtures\FinalClass; -trait SomeTrait {} +trait SomeTrait +{ +} diff --git a/tests/rules/Fixtures/FinalClassFixture.php b/tests/rules/Fixtures/FinalClassFixture.php deleted file mode 100644 index fe61afc..0000000 --- a/tests/rules/Fixtures/FinalClassFixture.php +++ /dev/null @@ -1,19 +0,0 @@ - Date: Tue, 21 Oct 2025 14:52:55 +0200 Subject: [PATCH 3/3] for some reason, this works localy but not on CI --- tests/rules/Fixtures/FinalClass/SomeTrait.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/rules/Fixtures/FinalClass/SomeTrait.php b/tests/rules/Fixtures/FinalClass/SomeTrait.php index 2845579..b927783 100644 --- a/tests/rules/Fixtures/FinalClass/SomeTrait.php +++ b/tests/rules/Fixtures/FinalClass/SomeTrait.php @@ -8,6 +8,9 @@ namespace Ibexa\Tests\PHPStan\Rules\Fixtures\FinalClass; +/** + * @phpstan-ignore trait.unused + */ trait SomeTrait { }