From f7347848505fcf3a92fbea97833430b1d312d068 Mon Sep 17 00:00:00 2001 From: dominikkaluza Date: Wed, 17 Dec 2025 11:19:46 +0100 Subject: [PATCH 1/3] Add TestsExtendMockeryTestCaseRule which enforces a Mockery base test case class --- phpstan.neon | 5 ++ .../TestsExtendMockeryTestCaseRule.php | 89 ++++++++++++++++++ .../TestsExtendMockeryTestCaseRuleTest.php | 90 +++++++++++++++++++ ...xtendsClassWhichExtendsMockeryTestCase.php | 11 +++ ...xtendsClassWhichExtendsPhpUnitTestCase.php | 9 ++ .../TestExtendsMockeryTestCase.php | 11 +++ .../TestExtendsPhpUnitTestCase.php | 9 ++ .../TestExtendsPhpstanRuleTestCase.php | 10 +++ .../TestExtendsRectorRuleTestCase.php | 14 +++ ...tExtendsSlevomatCodingStandardTestCase.php | 10 +++ 10 files changed, 258 insertions(+) create mode 100644 src/BrandEmbassyCodingStandard/PhpStan/Rules/Mockery/TestsExtendMockeryTestCaseRule/TestsExtendMockeryTestCaseRule.php create mode 100644 src/BrandEmbassyCodingStandard/PhpStan/Rules/Mockery/TestsExtendMockeryTestCaseRule/TestsExtendMockeryTestCaseRuleTest.php create mode 100644 src/BrandEmbassyCodingStandard/PhpStan/Rules/Mockery/TestsExtendMockeryTestCaseRule/__fixtures__/TestExtendsClassWhichExtendsMockeryTestCase.php create mode 100644 src/BrandEmbassyCodingStandard/PhpStan/Rules/Mockery/TestsExtendMockeryTestCaseRule/__fixtures__/TestExtendsClassWhichExtendsPhpUnitTestCase.php create mode 100644 src/BrandEmbassyCodingStandard/PhpStan/Rules/Mockery/TestsExtendMockeryTestCaseRule/__fixtures__/TestExtendsMockeryTestCase.php create mode 100644 src/BrandEmbassyCodingStandard/PhpStan/Rules/Mockery/TestsExtendMockeryTestCaseRule/__fixtures__/TestExtendsPhpUnitTestCase.php create mode 100644 src/BrandEmbassyCodingStandard/PhpStan/Rules/Mockery/TestsExtendMockeryTestCaseRule/__fixtures__/TestExtendsPhpstanRuleTestCase.php create mode 100644 src/BrandEmbassyCodingStandard/PhpStan/Rules/Mockery/TestsExtendMockeryTestCaseRule/__fixtures__/TestExtendsRectorRuleTestCase.php create mode 100644 src/BrandEmbassyCodingStandard/PhpStan/Rules/Mockery/TestsExtendMockeryTestCaseRule/__fixtures__/TestExtendsSlevomatCodingStandardTestCase.php diff --git a/phpstan.neon b/phpstan.neon index cb841bf..824d929 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -16,6 +16,11 @@ services: - Rector\Skipper\Fnmatcher - Rector\Skipper\RealpathMatcher + - + class: BrandEmbassyCodingStandard\PhpStan\Rules\Mockery\TestsExtendMockeryTestCaseRule\TestsExtendMockeryTestCaseRule + tags: + - phpstan.rules.rule + parameters: level: max paths: diff --git a/src/BrandEmbassyCodingStandard/PhpStan/Rules/Mockery/TestsExtendMockeryTestCaseRule/TestsExtendMockeryTestCaseRule.php b/src/BrandEmbassyCodingStandard/PhpStan/Rules/Mockery/TestsExtendMockeryTestCaseRule/TestsExtendMockeryTestCaseRule.php new file mode 100644 index 0000000..76276c1 --- /dev/null +++ b/src/BrandEmbassyCodingStandard/PhpStan/Rules/Mockery/TestsExtendMockeryTestCaseRule/TestsExtendMockeryTestCaseRule.php @@ -0,0 +1,89 @@ + + */ +class TestsExtendMockeryTestCaseRule implements Rule +{ + public function __construct( + private readonly ReflectionProvider $reflectionProvider, + ) { + } + + + public function getNodeType(): string + { + return Class_::class; + } + + + /** + * @param Class_ $node + * + * @return list + */ + public function processNode(Node $node, Scope $scope): array + { + if ($node->name === null || $node->isAbstract()) { + return []; + } + + $namespacedName = $node->namespacedName; + + if ($namespacedName === null) { + return []; + } + + $className = $scope->resolveName($namespacedName); + + if (!$this->reflectionProvider->hasClass($className)) { + return []; + } + + $class = $this->reflectionProvider->getClass($className); + + if (!$class->isSubclassOf(TestCase::class)) { + return []; + } + + // Allow these code quality tools test cases as they are not using Mockery + if ($class->isSubclassOf(SlevomatCodingStandardTestCase::class) + || $class->isSubclassOf(AbstractRectorTestCase::class) + || $class->isSubclassOf(RuleTestCase::class)) { + return []; + } + + if ($class->isSubclassOf(MockeryTestCase::class)) { + return []; + } + + $ruleErrorMessage = sprintf( + 'PHPUnit test %s must extend %s (directly or indirectly).', + $class->getName(), + MockeryTestCase::class, + ); + + return [ + RuleErrorBuilder::message($ruleErrorMessage) + ->identifier('mockery.testCase') + ->nonIgnorable() + ->build(), + ]; + } +} diff --git a/src/BrandEmbassyCodingStandard/PhpStan/Rules/Mockery/TestsExtendMockeryTestCaseRule/TestsExtendMockeryTestCaseRuleTest.php b/src/BrandEmbassyCodingStandard/PhpStan/Rules/Mockery/TestsExtendMockeryTestCaseRule/TestsExtendMockeryTestCaseRuleTest.php new file mode 100644 index 0000000..18bc67c --- /dev/null +++ b/src/BrandEmbassyCodingStandard/PhpStan/Rules/Mockery/TestsExtendMockeryTestCaseRule/TestsExtendMockeryTestCaseRuleTest.php @@ -0,0 +1,90 @@ + + */ +class TestsExtendMockeryTestCaseRuleTest extends RuleTestCase +{ + protected function getRule(): Rule + { + return new TestsExtendMockeryTestCaseRule($this->createReflectionProvider()); + } + + + public function testExtendsMockeryTestCase(): void + { + $this->analyse( + [__DIR__ . '/__fixtures__/TestExtendsMockeryTestCase.php'], + [], + ); + } + + + public function testExtendsClassWhichExtendsMockeryTestCase(): void + { + $this->analyse( + [__DIR__ . '/__fixtures__/TestExtendsClassWhichExtendsMockeryTestCase.php'], + [], + ); + } + + + public function testExtendsPhpUnitTestCase(): void + { + $this->analyse( + [__DIR__ . '/__fixtures__/TestExtendsPhpUnitTestCase.php'], + [ + [ + 'PHPUnit test BrandEmbassyCodingStandard\PhpStan\Rules\Mockery\TestsExtendMockeryTestCaseRule\__fixtures__\TestExtendsPhpUnitTestCase must extend Mockery\Adapter\Phpunit\MockeryTestCase (directly or indirectly).', + 7, + ], + ], + ); + } + + + public function testExtendsClassWhichExtendsPhpUnitTestCase(): void + { + $this->analyse( + [__DIR__ . '/__fixtures__/TestExtendsClassWhichExtendsPhpUnitTestCase.php'], + [ + [ + 'PHPUnit test BrandEmbassyCodingStandard\PhpStan\Rules\Mockery\TestsExtendMockeryTestCaseRule\__fixtures__\TestExtendsClassWhichExtendsPhpUnitTestCase must extend Mockery\Adapter\Phpunit\MockeryTestCase (directly or indirectly).', + 7, + ], + ], + ); + } + + + public function testExtendsPhpstanRuleTestCase(): void + { + $this->analyse( + [__DIR__ . '/__fixtures__/TestExtendsPhpstanRuleTestCase.php'], + [], + ); + } + + + public function testExtendsRectorRuleTestCase(): void + { + $this->analyse( + [__DIR__ . '/__fixtures__/TestExtendsRectorRuleTestCase.php'], + [], + ); + } + + + public function testExtendsSlevomatCodingStandardTestCase(): void + { + $this->analyse( + [__DIR__ . '/__fixtures__/TestExtendsSlevomatCodingStandardTestCase.php'], + [], + ); + } +} diff --git a/src/BrandEmbassyCodingStandard/PhpStan/Rules/Mockery/TestsExtendMockeryTestCaseRule/__fixtures__/TestExtendsClassWhichExtendsMockeryTestCase.php b/src/BrandEmbassyCodingStandard/PhpStan/Rules/Mockery/TestsExtendMockeryTestCaseRule/__fixtures__/TestExtendsClassWhichExtendsMockeryTestCase.php new file mode 100644 index 0000000..cde1980 --- /dev/null +++ b/src/BrandEmbassyCodingStandard/PhpStan/Rules/Mockery/TestsExtendMockeryTestCaseRule/__fixtures__/TestExtendsClassWhichExtendsMockeryTestCase.php @@ -0,0 +1,11 @@ + Date: Wed, 17 Dec 2025 14:10:25 +0100 Subject: [PATCH 2/3] TestExtendsClassWhichExtendsMockeryTestCase fixture: Extend correct test case --- .../TestExtendsClassWhichExtendsMockeryTestCase.php | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/BrandEmbassyCodingStandard/PhpStan/Rules/Mockery/TestsExtendMockeryTestCaseRule/__fixtures__/TestExtendsClassWhichExtendsMockeryTestCase.php b/src/BrandEmbassyCodingStandard/PhpStan/Rules/Mockery/TestsExtendMockeryTestCaseRule/__fixtures__/TestExtendsClassWhichExtendsMockeryTestCase.php index cde1980..def7c55 100644 --- a/src/BrandEmbassyCodingStandard/PhpStan/Rules/Mockery/TestsExtendMockeryTestCaseRule/__fixtures__/TestExtendsClassWhichExtendsMockeryTestCase.php +++ b/src/BrandEmbassyCodingStandard/PhpStan/Rules/Mockery/TestsExtendMockeryTestCaseRule/__fixtures__/TestExtendsClassWhichExtendsMockeryTestCase.php @@ -2,10 +2,6 @@ namespace BrandEmbassyCodingStandard\PhpStan\Rules\Mockery\TestsExtendMockeryTestCaseRule\__fixtures__; -use Mockery; -use Mockery\Adapter\Phpunit\MockeryTestCase; -use PHPUnit\Framework\TestCase; - -class TestExtendsClassWhichExtendsMockeryTestCase extends MockeryTestCase +class TestExtendsClassWhichExtendsMockeryTestCase extends TestExtendsMockeryTestCase { } From e5a69c74949c65f9090e5eadd78ecbfb98b49830 Mon Sep 17 00:00:00 2001 From: dominikkaluza Date: Wed, 17 Dec 2025 14:10:35 +0100 Subject: [PATCH 3/3] Fixtures: Remove unused imports --- .../TestsExtendMockeryTestCaseRuleTest.php | 2 +- .../TestExtendsClassWhichExtendsPhpUnitTestCase.php | 2 -- .../__fixtures__/TestExtendsMockeryTestCase.php | 2 -- .../__fixtures__/TestExtendsPhpstanRuleTestCase.php | 1 - .../__fixtures__/TestExtendsRectorRuleTestCase.php | 1 - .../__fixtures__/TestExtendsSlevomatCodingStandardTestCase.php | 1 - 6 files changed, 1 insertion(+), 8 deletions(-) diff --git a/src/BrandEmbassyCodingStandard/PhpStan/Rules/Mockery/TestsExtendMockeryTestCaseRule/TestsExtendMockeryTestCaseRuleTest.php b/src/BrandEmbassyCodingStandard/PhpStan/Rules/Mockery/TestsExtendMockeryTestCaseRule/TestsExtendMockeryTestCaseRuleTest.php index 18bc67c..aba47d7 100644 --- a/src/BrandEmbassyCodingStandard/PhpStan/Rules/Mockery/TestsExtendMockeryTestCaseRule/TestsExtendMockeryTestCaseRuleTest.php +++ b/src/BrandEmbassyCodingStandard/PhpStan/Rules/Mockery/TestsExtendMockeryTestCaseRule/TestsExtendMockeryTestCaseRuleTest.php @@ -55,7 +55,7 @@ public function testExtendsClassWhichExtendsPhpUnitTestCase(): void [ [ 'PHPUnit test BrandEmbassyCodingStandard\PhpStan\Rules\Mockery\TestsExtendMockeryTestCaseRule\__fixtures__\TestExtendsClassWhichExtendsPhpUnitTestCase must extend Mockery\Adapter\Phpunit\MockeryTestCase (directly or indirectly).', - 7, + 5, ], ], ); diff --git a/src/BrandEmbassyCodingStandard/PhpStan/Rules/Mockery/TestsExtendMockeryTestCaseRule/__fixtures__/TestExtendsClassWhichExtendsPhpUnitTestCase.php b/src/BrandEmbassyCodingStandard/PhpStan/Rules/Mockery/TestsExtendMockeryTestCaseRule/__fixtures__/TestExtendsClassWhichExtendsPhpUnitTestCase.php index c8bf128..4c55cbb 100644 --- a/src/BrandEmbassyCodingStandard/PhpStan/Rules/Mockery/TestsExtendMockeryTestCaseRule/__fixtures__/TestExtendsClassWhichExtendsPhpUnitTestCase.php +++ b/src/BrandEmbassyCodingStandard/PhpStan/Rules/Mockery/TestsExtendMockeryTestCaseRule/__fixtures__/TestExtendsClassWhichExtendsPhpUnitTestCase.php @@ -2,8 +2,6 @@ namespace BrandEmbassyCodingStandard\PhpStan\Rules\Mockery\TestsExtendMockeryTestCaseRule\__fixtures__; -use PHPUnit\Framework\TestCase; - class TestExtendsClassWhichExtendsPhpUnitTestCase extends TestExtendsPhpUnitTestCase { } diff --git a/src/BrandEmbassyCodingStandard/PhpStan/Rules/Mockery/TestsExtendMockeryTestCaseRule/__fixtures__/TestExtendsMockeryTestCase.php b/src/BrandEmbassyCodingStandard/PhpStan/Rules/Mockery/TestsExtendMockeryTestCaseRule/__fixtures__/TestExtendsMockeryTestCase.php index 42e7726..c38c81c 100644 --- a/src/BrandEmbassyCodingStandard/PhpStan/Rules/Mockery/TestsExtendMockeryTestCaseRule/__fixtures__/TestExtendsMockeryTestCase.php +++ b/src/BrandEmbassyCodingStandard/PhpStan/Rules/Mockery/TestsExtendMockeryTestCaseRule/__fixtures__/TestExtendsMockeryTestCase.php @@ -2,9 +2,7 @@ namespace BrandEmbassyCodingStandard\PhpStan\Rules\Mockery\TestsExtendMockeryTestCaseRule\__fixtures__; -use Mockery; use Mockery\Adapter\Phpunit\MockeryTestCase; -use PHPUnit\Framework\TestCase; class TestExtendsMockeryTestCase extends MockeryTestCase { diff --git a/src/BrandEmbassyCodingStandard/PhpStan/Rules/Mockery/TestsExtendMockeryTestCaseRule/__fixtures__/TestExtendsPhpstanRuleTestCase.php b/src/BrandEmbassyCodingStandard/PhpStan/Rules/Mockery/TestsExtendMockeryTestCaseRule/__fixtures__/TestExtendsPhpstanRuleTestCase.php index 02c59ea..b04537c 100644 --- a/src/BrandEmbassyCodingStandard/PhpStan/Rules/Mockery/TestsExtendMockeryTestCaseRule/__fixtures__/TestExtendsPhpstanRuleTestCase.php +++ b/src/BrandEmbassyCodingStandard/PhpStan/Rules/Mockery/TestsExtendMockeryTestCaseRule/__fixtures__/TestExtendsPhpstanRuleTestCase.php @@ -2,7 +2,6 @@ namespace BrandEmbassyCodingStandard\PhpStan\Rules\Mockery\TestsExtendMockeryTestCaseRule\__fixtures__; - use PHPStan\Testing\RuleTestCase; class TestExtendsPhpstanRuleTestCase extends RuleTestCase diff --git a/src/BrandEmbassyCodingStandard/PhpStan/Rules/Mockery/TestsExtendMockeryTestCaseRule/__fixtures__/TestExtendsRectorRuleTestCase.php b/src/BrandEmbassyCodingStandard/PhpStan/Rules/Mockery/TestsExtendMockeryTestCaseRule/__fixtures__/TestExtendsRectorRuleTestCase.php index 4723a7c..f9d6c65 100644 --- a/src/BrandEmbassyCodingStandard/PhpStan/Rules/Mockery/TestsExtendMockeryTestCaseRule/__fixtures__/TestExtendsRectorRuleTestCase.php +++ b/src/BrandEmbassyCodingStandard/PhpStan/Rules/Mockery/TestsExtendMockeryTestCaseRule/__fixtures__/TestExtendsRectorRuleTestCase.php @@ -2,7 +2,6 @@ namespace BrandEmbassyCodingStandard\PhpStan\Rules\Mockery\TestsExtendMockeryTestCaseRule\__fixtures__; - use Rector\Testing\PHPUnit\AbstractRectorTestCase; class TestExtendsRectorRuleTestCase extends AbstractRectorTestCase diff --git a/src/BrandEmbassyCodingStandard/PhpStan/Rules/Mockery/TestsExtendMockeryTestCaseRule/__fixtures__/TestExtendsSlevomatCodingStandardTestCase.php b/src/BrandEmbassyCodingStandard/PhpStan/Rules/Mockery/TestsExtendMockeryTestCaseRule/__fixtures__/TestExtendsSlevomatCodingStandardTestCase.php index 316b2de..c2cdfd3 100644 --- a/src/BrandEmbassyCodingStandard/PhpStan/Rules/Mockery/TestsExtendMockeryTestCaseRule/__fixtures__/TestExtendsSlevomatCodingStandardTestCase.php +++ b/src/BrandEmbassyCodingStandard/PhpStan/Rules/Mockery/TestsExtendMockeryTestCaseRule/__fixtures__/TestExtendsSlevomatCodingStandardTestCase.php @@ -2,7 +2,6 @@ namespace BrandEmbassyCodingStandard\PhpStan\Rules\Mockery\TestsExtendMockeryTestCaseRule\__fixtures__; - use SlevomatCodingStandard\Sniffs\TestCase; class TestExtendsSlevomatCodingStandardTestCase extends TestCase