From 592a2ef417f39a790ab7c02e41577d6d7cc64d9d Mon Sep 17 00:00:00 2001 From: Tomas Votruba Date: Wed, 4 Feb 2026 10:15:43 +0100 Subject: [PATCH] [phpunit 12] Add AllowMockObjectsWhereParentClassRector --- config/sets/phpunit120.php | 5 +- ...wMockObjectsWhereParentClassRectorTest.php | 28 ++++ .../Fixture/some_class.php.inc | 26 ++++ .../config/configured_rule.php | 9 ++ ...AllowMockObjectsWhereParentClassRector.php | 130 ++++++++++++++++++ ...ectsWithoutExpectationsAttributeRector.php | 2 +- src/Enum/PHPUnitClassName.php | 2 + .../Test/ConstraintValidatorTestCase.php | 9 ++ 8 files changed, 209 insertions(+), 2 deletions(-) create mode 100644 rules-tests/PHPUnit120/Rector/Class_/AllowMockObjectsWhereParentClassRector/AllowMockObjectsWhereParentClassRectorTest.php create mode 100644 rules-tests/PHPUnit120/Rector/Class_/AllowMockObjectsWhereParentClassRector/Fixture/some_class.php.inc create mode 100644 rules-tests/PHPUnit120/Rector/Class_/AllowMockObjectsWhereParentClassRector/config/configured_rule.php create mode 100644 rules/PHPUnit120/Rector/Class_/AllowMockObjectsWhereParentClassRector.php create mode 100644 stubs/Symfony/Component/Validator/Test/ConstraintValidatorTestCase.php diff --git a/config/sets/phpunit120.php b/config/sets/phpunit120.php index bf480d6a..67168887 100644 --- a/config/sets/phpunit120.php +++ b/config/sets/phpunit120.php @@ -4,6 +4,8 @@ use Rector\Config\RectorConfig; use Rector\PHPUnit\PHPUnit120\Rector\CallLike\CreateStubOverCreateMockArgRector; +use Rector\PHPUnit\PHPUnit120\Rector\Class_\AllowMockObjectsWhereParentClassRector; +use Rector\PHPUnit\PHPUnit120\Rector\Class_\AllowMockObjectsWithoutExpectationsAttributeRector; use Rector\PHPUnit\PHPUnit120\Rector\Class_\AssertIsTypeMethodCallRector; use Rector\PHPUnit\PHPUnit120\Rector\Class_\PropertyCreateMockToCreateStubRector; use Rector\PHPUnit\PHPUnit120\Rector\Class_\RemoveOverrideFinalConstructTestCaseRector; @@ -18,9 +20,10 @@ CreateStubOverCreateMockArgRector::class, ExpressionCreateMockToCreateStubRector::class, PropertyCreateMockToCreateStubRector::class, + AllowMockObjectsWhereParentClassRector::class, // experimental, from PHPUnit 12.5.2 // @see https://github.com/sebastianbergmann/phpunit/commit/24c208d6a340c3071f28a9b5cce02b9377adfd43 - // AllowMockObjectsWithoutExpectationsAttributeRector::class, + AllowMockObjectsWithoutExpectationsAttributeRector::class, ]); }; diff --git a/rules-tests/PHPUnit120/Rector/Class_/AllowMockObjectsWhereParentClassRector/AllowMockObjectsWhereParentClassRectorTest.php b/rules-tests/PHPUnit120/Rector/Class_/AllowMockObjectsWhereParentClassRector/AllowMockObjectsWhereParentClassRectorTest.php new file mode 100644 index 00000000..68e41906 --- /dev/null +++ b/rules-tests/PHPUnit120/Rector/Class_/AllowMockObjectsWhereParentClassRector/AllowMockObjectsWhereParentClassRectorTest.php @@ -0,0 +1,28 @@ +doTestFile($filePath); + } + + public static function provideData(): Iterator + { + return self::yieldFilesFromDirectory(__DIR__ . '/Fixture'); + } + + public function provideConfigFilePath(): string + { + return __DIR__ . '/config/configured_rule.php'; + } +} diff --git a/rules-tests/PHPUnit120/Rector/Class_/AllowMockObjectsWhereParentClassRector/Fixture/some_class.php.inc b/rules-tests/PHPUnit120/Rector/Class_/AllowMockObjectsWhereParentClassRector/Fixture/some_class.php.inc new file mode 100644 index 00000000..f796ef81 --- /dev/null +++ b/rules-tests/PHPUnit120/Rector/Class_/AllowMockObjectsWhereParentClassRector/Fixture/some_class.php.inc @@ -0,0 +1,26 @@ + +----- + diff --git a/rules-tests/PHPUnit120/Rector/Class_/AllowMockObjectsWhereParentClassRector/config/configured_rule.php b/rules-tests/PHPUnit120/Rector/Class_/AllowMockObjectsWhereParentClassRector/config/configured_rule.php new file mode 100644 index 00000000..1e84cf8a --- /dev/null +++ b/rules-tests/PHPUnit120/Rector/Class_/AllowMockObjectsWhereParentClassRector/config/configured_rule.php @@ -0,0 +1,9 @@ +withRules([AllowMockObjectsWhereParentClassRector::class]); diff --git a/rules/PHPUnit120/Rector/Class_/AllowMockObjectsWhereParentClassRector.php b/rules/PHPUnit120/Rector/Class_/AllowMockObjectsWhereParentClassRector.php new file mode 100644 index 00000000..50c5cbe1 --- /dev/null +++ b/rules/PHPUnit120/Rector/Class_/AllowMockObjectsWhereParentClassRector.php @@ -0,0 +1,130 @@ +shouldSkipClass($node)) { + return null; + } + + if (! $this->hasRelateParentClass($node)) { + return null; + } + + // add attribute + $node->attrGroups[] = new AttributeGroup([ + new Attribute(new FullyQualified(PHPUnitAttribute::ALLOW_MOCK_OBJECTS_WITHOUT_EXPECTATIONS)), + ]); + + return $node; + } + + public function getRuleDefinition(): RuleDefinition + { + return new RuleDefinition( + 'Add #[AllowMockObjectsWithoutExpectations] attribute to PHPUnit test classes with a 3rd party test case, that provides any mocks', + [ + new CodeSample( + <<<'CODE_SAMPLE' +use Symfony\Component\Validator\Test\ConstraintValidatorTestCase; + +final class SomeTest extends ConstraintValidatorTestCase +{ +} +CODE_SAMPLE + , + <<<'CODE_SAMPLE' +use Symfony\Component\Validator\Test\ConstraintValidatorTestCase; + +#[\PHPUnit\Framework\Attributes\AllowMockObjectsWithoutExpectations] +final class SomeTest extends ConstraintValidatorTestCase +{ +} +CODE_SAMPLE + ), + ] + ); + } + + private function shouldSkipClass(Class_ $class): bool + { + if (! $this->testsNodeAnalyzer->isInTestClass($class)) { + return true; + } + + // attribute must exist for the rule to work + if (! $this->reflectionProvider->hasClass(PHPUnitAttribute::ALLOW_MOCK_OBJECTS_WITHOUT_EXPECTATIONS)) { + return true; + } + + // already filled + return $this->attributeFinder->hasAttributeByClasses( + $class, + [PHPUnitAttribute::ALLOW_MOCK_OBJECTS_WITHOUT_EXPECTATIONS] + ); + } + + private function hasRelateParentClass(Class_ $class): bool + { + $scope = ScopeFetcher::fetch($class); + $classReflection = $scope->getClassReflection(); + + if (! $classReflection instanceof ClassReflection) { + return false; + } + + foreach (self::PARENT_CLASSES as $parentClass) { + if ($classReflection->is($parentClass)) { + return true; + } + } + + return false; + } +} diff --git a/rules/PHPUnit120/Rector/Class_/AllowMockObjectsWithoutExpectationsAttributeRector.php b/rules/PHPUnit120/Rector/Class_/AllowMockObjectsWithoutExpectationsAttributeRector.php index 058d52ef..bf93ebde 100644 --- a/rules/PHPUnit120/Rector/Class_/AllowMockObjectsWithoutExpectationsAttributeRector.php +++ b/rules/PHPUnit120/Rector/Class_/AllowMockObjectsWithoutExpectationsAttributeRector.php @@ -4,12 +4,12 @@ namespace Rector\PHPUnit\PHPUnit120\Rector\Class_; -use PhpParser\Node\Expr\Variable; use PhpParser\Node; use PhpParser\Node\Attribute; use PhpParser\Node\AttributeGroup; use PhpParser\Node\Expr\MethodCall; use PhpParser\Node\Expr\PropertyFetch; +use PhpParser\Node\Expr\Variable; use PhpParser\Node\Name; use PhpParser\Node\Name\FullyQualified; use PhpParser\Node\Stmt\Class_; diff --git a/src/Enum/PHPUnitClassName.php b/src/Enum/PHPUnitClassName.php index 0af96492..27065194 100644 --- a/src/Enum/PHPUnitClassName.php +++ b/src/Enum/PHPUnitClassName.php @@ -27,6 +27,8 @@ final class PHPUnitClassName public const string STUB = 'PHPUnit\Framework\MockObject\Stub'; + public const string SYMFONY_CONSTRAINT_VALIDATOR_TEST_CASE = 'Symfony\Component\Validator\Test\ConstraintValidatorTestCase'; + /** * @var string[] */ diff --git a/stubs/Symfony/Component/Validator/Test/ConstraintValidatorTestCase.php b/stubs/Symfony/Component/Validator/Test/ConstraintValidatorTestCase.php new file mode 100644 index 00000000..cee4e315 --- /dev/null +++ b/stubs/Symfony/Component/Validator/Test/ConstraintValidatorTestCase.php @@ -0,0 +1,9 @@ +