diff --git a/extension.neon b/extension.neon index 80cf7ed..f1d36f9 100644 --- a/extension.neon +++ b/extension.neon @@ -7,3 +7,4 @@ parameters: - stubs/Money/MoneyParser.stub rules: - Ibexa\PHPStan\Rules\NoConfigResolverParametersInConstructorRule + - Ibexa\PHPStan\Rules\ClassTypeNamingRule diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon new file mode 100644 index 0000000..e69de29 diff --git a/phpstan.neon b/phpstan.neon index ab3d432..c4d75b1 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -1,3 +1,6 @@ +includes: + - phpstan-baseline.neon + parameters: level: 8 paths: diff --git a/rules/ClassTypeNamingRule.php b/rules/ClassTypeNamingRule.php new file mode 100644 index 0000000..c259fab --- /dev/null +++ b/rules/ClassTypeNamingRule.php @@ -0,0 +1,75 @@ + + */ +final class ClassTypeNamingRule implements Rule +{ + public function getNodeType(): string + { + return Node\Stmt\ClassLike::class; + } + + public function processNode(Node $node, Scope $scope): array + { + if (!$this->isRelevantNode($node)) { + return []; + } + + if ($node->name === null) { + return []; + } + + $className = $node->name->toString(); + $errors = []; + + if ($node instanceof Node\Stmt\Interface_ && substr($className, -9) !== 'Interface') { + $errors[] = RuleErrorBuilder::message( + sprintf( + 'Interface "%s" should have "Interface" suffix', + $className + ) + )->build(); + } + + if ($node instanceof Node\Stmt\Trait_ && substr($className, -5) !== 'Trait') { + $errors[] = RuleErrorBuilder::message( + sprintf( + 'Trait "%s" should have "Trait" suffix', + $className + ) + )->build(); + } + + if ($node instanceof Node\Stmt\Class_ && $node->isAbstract() && strpos($className, 'Abstract') !== 0) { + $errors[] = RuleErrorBuilder::message( + sprintf( + 'Abstract class "%s" should have "Abstract" prefix', + $className + ) + )->build(); + } + + return $errors; + } + + private function isRelevantNode(Node $node): bool + { + return $node instanceof Node\Stmt\Interface_ + || $node instanceof Node\Stmt\Trait_ + || ($node instanceof Node\Stmt\Class_ && $node->isAbstract()); + } +} diff --git a/tests/rules/ClassTypeNamingRuleTest.php b/tests/rules/ClassTypeNamingRuleTest.php new file mode 100644 index 0000000..507f757 --- /dev/null +++ b/tests/rules/ClassTypeNamingRuleTest.php @@ -0,0 +1,52 @@ + + */ +final class ClassTypeNamingRuleTest extends RuleTestCase +{ + protected function getRule(): Rule + { + return new ClassTypeNamingRule(); + } + + public function testRule(): void + { + $this->analyse( + [ + __DIR__ . '/Fixtures/ClassTypeNaming/WrongName.php', + __DIR__ . '/Fixtures/ClassTypeNaming/SimpleThing.php', + __DIR__ . '/Fixtures/ClassTypeNaming/SimpleClass.php', + __DIR__ . '/Fixtures/ClassTypeNaming/CorrectNameInterface.php', + __DIR__ . '/Fixtures/ClassTypeNaming/CorrectNameTrait.php', + __DIR__ . '/Fixtures/ClassTypeNaming/AbstractCorrectClass.php', + ], + [ + [ + 'Interface "WrongName" should have "Interface" suffix', + 11, + ], + [ + 'Trait "SimpleThing" should have "Trait" suffix', + 11, + ], + [ + 'Abstract class "SimpleClass" should have "Abstract" prefix', + 11, + ], + ] + ); + } +} diff --git a/tests/rules/Fixtures/ClassTypeNaming/AbstractCorrectClass.php b/tests/rules/Fixtures/ClassTypeNaming/AbstractCorrectClass.php new file mode 100644 index 0000000..1a2f293 --- /dev/null +++ b/tests/rules/Fixtures/ClassTypeNaming/AbstractCorrectClass.php @@ -0,0 +1,13 @@ +