From cf8fac1a1ba7db5faf7f22bf8c646669acc2ae58 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=20G=C3=B3mez?= Date: Mon, 2 Oct 2023 17:22:29 +0200 Subject: [PATCH] feat: add ShouldHaveOnlyOnePublicMethod rule --- docs/documentation/assertions.md | 3 ++ extension.neon | 6 +++ .../HasOnlyOnePublicMethodRule.php | 10 ++++ .../ShouldHaveOnlyOnePublicMethod.php | 41 ++++++++++++++++ .../Declaration/OnePublicMethodExtractor.php | 29 +++++++++++ src/Test/Builder/AssertionStep.php | 8 +++ .../Special/ClassWithOnePublicMethod.php | 13 +++++ .../ClassWithOnlyOnePublicMethodTest.php | 49 +++++++++++++++++++ ...tationClassWithOnlyOnePublicMethodTest.php | 48 ++++++++++++++++++ 9 files changed, 207 insertions(+) create mode 100644 src/Rule/Assertion/Declaration/ShouldHaveOnlyOnePublicMethod/HasOnlyOnePublicMethodRule.php create mode 100644 src/Rule/Assertion/Declaration/ShouldHaveOnlyOnePublicMethod/ShouldHaveOnlyOnePublicMethod.php create mode 100644 src/Rule/Extractor/Declaration/OnePublicMethodExtractor.php create mode 100644 tests/fixtures/Special/ClassWithOnePublicMethod.php create mode 100644 tests/unit/rules/ShouldHaveOnlyOnePublicMethod/ClassWithOnlyOnePublicMethodTest.php create mode 100644 tests/unit/rules/ShouldHaveOnlyOnePublicMethod/GoodImplementationClassWithOnlyOnePublicMethodTest.php diff --git a/docs/documentation/assertions.md b/docs/documentation/assertions.md index bca2925b..92707aac 100644 --- a/docs/documentation/assertions.md +++ b/docs/documentation/assertions.md @@ -35,3 +35,6 @@ It asserts that the selected classes **do not use the constructor** of the targe It asserts that the selected classes **does not depend** on anything else than the target classes. This would be equivalent to `shouldNotDependOn()` with the negation of the target classes. + +## shouldHaveOnlyOnePublicMethod() +It asserts that the selected classes **only have one public method**. diff --git a/extension.neon b/extension.neon index 9616147d..6cfc8e3b 100644 --- a/extension.neon +++ b/extension.neon @@ -48,6 +48,12 @@ services: tags: - phpstan.rules.rule + # ShouldHaveOnlyOnePublicMethod rules + - + class: PHPat\Rule\Assertion\Declaration\ShouldHaveOnlyOnePublicMethod\HasOnlyOnePublicMethodRule + tags: + - phpstan.rules.rule + # # # # # RELATION RULES # # # # # # ShouldImplement rules diff --git a/src/Rule/Assertion/Declaration/ShouldHaveOnlyOnePublicMethod/HasOnlyOnePublicMethodRule.php b/src/Rule/Assertion/Declaration/ShouldHaveOnlyOnePublicMethod/HasOnlyOnePublicMethodRule.php new file mode 100644 index 00000000..f173636b --- /dev/null +++ b/src/Rule/Assertion/Declaration/ShouldHaveOnlyOnePublicMethod/HasOnlyOnePublicMethodRule.php @@ -0,0 +1,10 @@ +applyShould($ruleName, $subject, $meetsDeclaration, $tips); + } + + protected function getMessage(string $ruleName, string $subject): string + { + return $this->prepareMessage($ruleName, sprintf('%s should have only one public method', $subject)); + } +} diff --git a/src/Rule/Extractor/Declaration/OnePublicMethodExtractor.php b/src/Rule/Extractor/Declaration/OnePublicMethodExtractor.php new file mode 100644 index 00000000..34aa0c83 --- /dev/null +++ b/src/Rule/Extractor/Declaration/OnePublicMethodExtractor.php @@ -0,0 +1,29 @@ +getClassReflection()->getName()); + $methods = $reflectionClass->getMethods(ReflectionMethod::IS_PUBLIC); + + return count($methods) === 1; + } +} diff --git a/src/Test/Builder/AssertionStep.php b/src/Test/Builder/AssertionStep.php index 330c0779..a84cdbfe 100644 --- a/src/Test/Builder/AssertionStep.php +++ b/src/Test/Builder/AssertionStep.php @@ -5,6 +5,7 @@ use PHPat\Rule\Assertion\Declaration\ShouldBeAbstract\ShouldBeAbstract; use PHPat\Rule\Assertion\Declaration\ShouldBeFinal\ShouldBeFinal; use PHPat\Rule\Assertion\Declaration\ShouldBeReadonly\ShouldBeReadonly; +use PHPat\Rule\Assertion\Declaration\ShouldHaveOnlyOnePublicMethod\ShouldHaveOnlyOnePublicMethod; use PHPat\Rule\Assertion\Declaration\ShouldNotBeAbstract\ShouldNotBeAbstract; use PHPat\Rule\Assertion\Declaration\ShouldNotBeFinal\ShouldNotBeFinal; use PHPat\Rule\Assertion\Relation\CanOnlyDepend\CanOnlyDepend; @@ -100,4 +101,11 @@ public function shouldExtend(): TargetStep return new TargetStep($this->rule); } + + public function shouldHaveOnlyOnePublicMethod(): Rule + { + $this->rule->assertion = ShouldHaveOnlyOnePublicMethod::class; + + return new BuildStep($this->rule); + } } diff --git a/tests/fixtures/Special/ClassWithOnePublicMethod.php b/tests/fixtures/Special/ClassWithOnePublicMethod.php new file mode 100644 index 00000000..009cea99 --- /dev/null +++ b/tests/fixtures/Special/ClassWithOnePublicMethod.php @@ -0,0 +1,13 @@ + + * @internal + * @coversNothing + */ +class ClassWithOnlyOnePublicMethodTest extends RuleTestCase +{ + public const RULE_NAME = 'test_FixtureClassShouldHaveOnlyOnePublicMethod'; + + public function testRule(): void + { + $this->analyse(['tests/fixtures/FixtureClass.php'], [ + [sprintf('%s should have only one public method', FixtureClass::class), 29], + ]); + } + + protected function getRule(): Rule + { + $testParser = FakeTestParser::create( + self::RULE_NAME, + ShouldHaveOnlyOnePublicMethod::class, + [new Classname(FixtureClass::class, false)], + [] + ); + + return new HasOnlyOnePublicMethodRule( + new StatementBuilderFactory($testParser), + new Configuration(false, true, false), + $this->createReflectionProvider(), + self::getContainer()->getByType(FileTypeMapper::class) + ); + } + +} diff --git a/tests/unit/rules/ShouldHaveOnlyOnePublicMethod/GoodImplementationClassWithOnlyOnePublicMethodTest.php b/tests/unit/rules/ShouldHaveOnlyOnePublicMethod/GoodImplementationClassWithOnlyOnePublicMethodTest.php new file mode 100644 index 00000000..98d44655 --- /dev/null +++ b/tests/unit/rules/ShouldHaveOnlyOnePublicMethod/GoodImplementationClassWithOnlyOnePublicMethodTest.php @@ -0,0 +1,48 @@ + + * @internal + * @coversNothing + */ +class GoodImplementationClassWithOnlyOnePublicMethodTest extends RuleTestCase +{ + public const RULE_NAME = 'test_FixtureClassShouldHaveOnlyOnePublicMethod'; + + public function testRule(): void + { + $this->analyse(['tests/fixtures/Special/ClassWithOnePublicMethod.php'], []); + } + + protected function getRule(): Rule + { + $testParser = FakeTestParser::create( + self::RULE_NAME, + ShouldHaveOnlyOnePublicMethod::class, + [new Classname(ClassWithOnePublicMethod::class, false)], + [] + ); + + return new HasOnlyOnePublicMethodRule( + new StatementBuilderFactory($testParser), + new Configuration(false, true, false), + $this->createReflectionProvider(), + self::getContainer()->getByType(FileTypeMapper::class) + ); + } + +}