diff --git a/src/Collectors/PublicStaticPropertyFetchCollector.php b/src/Collectors/PublicStaticPropertyFetchCollector.php index 4b8b0a1..c6ea961 100644 --- a/src/Collectors/PublicStaticPropertyFetchCollector.php +++ b/src/Collectors/PublicStaticPropertyFetchCollector.php @@ -10,6 +10,8 @@ use PhpParser\Node\Name; use PHPStan\Analyser\Scope; use PHPStan\Collectors\Collector; +use PHPStan\Reflection\ClassReflection; +use TomasVotruba\UnusedPublic\ClassTypeDetector; use TomasVotruba\UnusedPublic\Configuration; /** @@ -18,7 +20,8 @@ final readonly class PublicStaticPropertyFetchCollector implements Collector { public function __construct( - private Configuration $configuration + private Configuration $configuration, + private ClassTypeDetector $classTypeDetector, ) { } @@ -33,26 +36,38 @@ public function getNodeType(): string */ public function processNode(Node $node, Scope $scope): ?array { - if (! $this->configuration->isUnusedPropertyEnabled()) { + if (!$this->configuration->isUnusedPropertyEnabled()) { return null; } - if (! $node->class instanceof Name) { + if (!$node->name instanceof Identifier) { return null; } - if (! $node->name instanceof Identifier) { + if ( + $node->class instanceof Name + && ($node->class->toString() === 'self' || $node->class->toString() === 'static') + ) { + // self fetch is allowed return null; } - if ($node->class->toString() === 'self') { - // self fetch is allowed + $classReflection = $scope->getClassReflection(); + if ($classReflection instanceof ClassReflection && $this->classTypeDetector->isTestClass($classReflection)) { return null; } - $className = $node->class->toString(); - $propertyName = $node->name->toString(); + if ($node->class instanceof Name) { + $classType = $scope->resolveTypeByName($node->class); + } else { + $classType = $scope->getType($node->class); + } + $result = []; + foreach($classType->getObjectClassNames() as $className) { + $propertyName = $node->name->toString(); + $result[] = $className . '::' . $propertyName; + } - return [$className . '::' . $propertyName]; + return $result; } } diff --git a/tests/Rules/UnusedPublicPropertyRule/Fixture/StaticUsedInTestCaseOnly.php b/tests/Rules/UnusedPublicPropertyRule/Fixture/StaticUsedInTestCaseOnly.php new file mode 100644 index 0000000..40c95c3 --- /dev/null +++ b/tests/Rules/UnusedPublicPropertyRule/Fixture/StaticUsedInTestCaseOnly.php @@ -0,0 +1,10 @@ +property = 'a value'; + + $o = new StaticUsedInTestCaseOnly(); + $o::$property = 'a value'; } } diff --git a/tests/Rules/UnusedPublicPropertyRule/UnusedPublicPropertyRuleTest.php b/tests/Rules/UnusedPublicPropertyRule/UnusedPublicPropertyRuleTest.php index 8ae8a23..2109fe7 100644 --- a/tests/Rules/UnusedPublicPropertyRule/UnusedPublicPropertyRuleTest.php +++ b/tests/Rules/UnusedPublicPropertyRule/UnusedPublicPropertyRuleTest.php @@ -9,6 +9,7 @@ use PHPStan\Rules\Rule; use PHPStan\Testing\RuleTestCase; use PHPUnit\Framework\Attributes\DataProvider; +use Rules\UnusedPublicPropertyRule\Fixture\StaticUsedInTestCaseOnly; use TomasVotruba\UnusedPublic\Collectors\PublicPropertyCollector; use TomasVotruba\UnusedPublic\Collectors\PublicPropertyFetchCollector; use TomasVotruba\UnusedPublic\Collectors\PublicStaticPropertyFetchCollector; @@ -99,6 +100,12 @@ public static function provideData(): Iterator [[$errorMessage1, 7, RuleTips::SOLUTION_MESSAGE]], ]; + $errorMessage1 = sprintf(UnusedPublicPropertyRule::ERROR_MESSAGE, StaticUsedInTestCaseOnly::class, 'property'); + yield [ + [__DIR__ . '/Fixture/StaticUsedInTestCaseOnly.php', __DIR__ . '/Source/TestCaseUser.php'], + [[$errorMessage1, 7, RuleTips::SOLUTION_MESSAGE]], + ]; + yield [[__DIR__ . '/Fixture/plain.php', __DIR__ . '/Source/PublicPropertyClass.php'], []]; yield [