From 17b3e8c9d06ff232983104f5d28fe6265ab0a031 Mon Sep 17 00:00:00 2001 From: Tomas Votruba Date: Mon, 13 Jul 2026 11:53:14 +0200 Subject: [PATCH] [CodeQuality] Skip mock property removal when used in a trait in RemoveNeverUsedMockPropertyRector --- .../Fixture/skip_if_used_in_trait.php.inc | 22 ++++++++++++++ .../Source/UsesMockPropertyTrait.php | 11 +++++++ .../RemoveNeverUsedMockPropertyRector.php | 29 +++++++++++++++++++ 3 files changed, 62 insertions(+) create mode 100644 rules-tests/CodeQuality/Rector/Class_/RemoveNeverUsedMockPropertyRector/Fixture/skip_if_used_in_trait.php.inc create mode 100644 rules-tests/CodeQuality/Rector/Class_/RemoveNeverUsedMockPropertyRector/Source/UsesMockPropertyTrait.php diff --git a/rules-tests/CodeQuality/Rector/Class_/RemoveNeverUsedMockPropertyRector/Fixture/skip_if_used_in_trait.php.inc b/rules-tests/CodeQuality/Rector/Class_/RemoveNeverUsedMockPropertyRector/Fixture/skip_if_used_in_trait.php.inc new file mode 100644 index 00000000..ad6622d0 --- /dev/null +++ b/rules-tests/CodeQuality/Rector/Class_/RemoveNeverUsedMockPropertyRector/Fixture/skip_if_used_in_trait.php.inc @@ -0,0 +1,22 @@ +mockProperty = $this->createMock(\stdClass::class); + $this->mockProperty->expects($this->once()) + ->method('someMethod') + ->willReturn('someValue'); + } +} diff --git a/rules-tests/CodeQuality/Rector/Class_/RemoveNeverUsedMockPropertyRector/Source/UsesMockPropertyTrait.php b/rules-tests/CodeQuality/Rector/Class_/RemoveNeverUsedMockPropertyRector/Source/UsesMockPropertyTrait.php new file mode 100644 index 00000000..b4a6d6eb --- /dev/null +++ b/rules-tests/CodeQuality/Rector/Class_/RemoveNeverUsedMockPropertyRector/Source/UsesMockPropertyTrait.php @@ -0,0 +1,11 @@ +mockProperty->someMethod(); + } +} diff --git a/rules/CodeQuality/Rector/Class_/RemoveNeverUsedMockPropertyRector.php b/rules/CodeQuality/Rector/Class_/RemoveNeverUsedMockPropertyRector.php index 3f520807..84ba8433 100644 --- a/rules/CodeQuality/Rector/Class_/RemoveNeverUsedMockPropertyRector.php +++ b/rules/CodeQuality/Rector/Class_/RemoveNeverUsedMockPropertyRector.php @@ -13,6 +13,8 @@ use PhpParser\Node\Stmt\ClassMethod; use PhpParser\Node\Stmt\Expression; use PhpParser\Node\Stmt\Property; +use PhpParser\Node\Stmt\Trait_; +use Rector\PhpParser\AstResolver; use Rector\PhpParser\Node\BetterNodeFinder; use Rector\PhpParser\NodeFinder\PropertyFetchFinder; use Rector\PHPUnit\CodeQuality\NodeAnalyser\MockObjectPropertyDetector; @@ -32,6 +34,7 @@ public function __construct( private readonly MockObjectPropertyDetector $mockObjectPropertyDetector, private readonly PropertyFetchFinder $propertyFetchFinder, private readonly BetterNodeFinder $betterNodeFinder, + private readonly AstResolver $astResolver, ) { } @@ -235,9 +238,35 @@ private function resolvePropertyNamesToRemove(array $propertyNamesToCreateMockMe continue; } + // the property might be used in a used trait, skip removal to stay safe + if ($this->isPropertyUsedInTrait($class, $propertyName)) { + continue; + } + $propertyNamesToRemove[] = $propertyName; } return $propertyNamesToRemove; } + + private function isPropertyUsedInTrait(Class_ $class, string $propertyName): bool + { + foreach ($class->getTraitUses() as $traitUse) { + foreach ($traitUse->traits as $traitName) { + $trait = $this->astResolver->resolveClassFromName($traitName->toString()); + if (! $trait instanceof Trait_) { + continue; + } + + $propertyFetches = $this->betterNodeFinder->findInstancesOf($trait, [PropertyFetch::class]); + foreach ($propertyFetches as $propertyFetch) { + if ($this->isName($propertyFetch->name, $propertyName)) { + return true; + } + } + } + } + + return false; + } }