diff --git a/src/Rules/DeadCode/UnusedPrivateConstantRule.php b/src/Rules/DeadCode/UnusedPrivateConstantRule.php index 6e017873d2..c238a6b95b 100644 --- a/src/Rules/DeadCode/UnusedPrivateConstantRule.php +++ b/src/Rules/DeadCode/UnusedPrivateConstantRule.php @@ -35,11 +35,12 @@ public function processNode(Node $node, Scope $scope): array } $classReflection = $node->getClassReflection(); + $isClassFinal = $classReflection->isFinalByKeyword(); $classType = new ObjectType($classReflection->getName(), classReflection: $classReflection); $constants = []; foreach ($node->getConstants() as $constant) { - if (!$constant->isPrivate()) { + if ($constant->isPublic() || ($constant->isProtected() && !$isClassFinal)) { continue; } diff --git a/src/Rules/DeadCode/UnusedPrivateMethodRule.php b/src/Rules/DeadCode/UnusedPrivateMethodRule.php index 2c1ab6ce64..c3cc6e3d7d 100644 --- a/src/Rules/DeadCode/UnusedPrivateMethodRule.php +++ b/src/Rules/DeadCode/UnusedPrivateMethodRule.php @@ -40,6 +40,7 @@ public function processNode(Node $node, Scope $scope): array return []; } $classReflection = $node->getClassReflection(); + $isClassFinal = $classReflection->isFinalByKeyword(); $classType = new ObjectType($classReflection->getName(), classReflection: $classReflection); $constructor = null; if ($classReflection->hasConstructor()) { @@ -48,13 +49,15 @@ public function processNode(Node $node, Scope $scope): array $methods = []; foreach ($node->getMethods() as $method) { - if (!$method->getNode()->isPrivate()) { + $methodNode = $method->getNode(); + if ($methodNode->isPublic() || ($methodNode->isProtected() && !$isClassFinal)) { continue; } if ($method->isDeclaredInTrait()) { continue; } - $methodName = $method->getNode()->name->toString(); + + $methodName = $methodNode->name->toString(); if ($constructor !== null && $constructor->getName() === $methodName) { continue; } @@ -63,6 +66,10 @@ public function processNode(Node $node, Scope $scope): array } $methodReflection = $classReflection->getNativeMethod($methodName); + if ($methodReflection->getDeclaringClass()->getName() !== $classReflection->getName()) { + continue; + } + foreach ($this->extensionProvider->getExtensions() as $extension) { if ($extension->isAlwaysUsed($methodReflection)) { continue 2; diff --git a/src/Rules/DeadCode/UnusedPrivatePropertyRule.php b/src/Rules/DeadCode/UnusedPrivatePropertyRule.php index 7d2ccc7328..ba510d1e52 100644 --- a/src/Rules/DeadCode/UnusedPrivatePropertyRule.php +++ b/src/Rules/DeadCode/UnusedPrivatePropertyRule.php @@ -56,10 +56,11 @@ public function processNode(Node $node, Scope $scope): array return []; } $classReflection = $node->getClassReflection(); + $isClassFinal = $classReflection->isFinalByKeyword(); $classType = new ObjectType($classReflection->getName(), classReflection: $classReflection); $properties = []; foreach ($node->getProperties() as $property) { - if (!$property->isPrivate()) { + if ($property->isPublic() || ($property->isProtected() && !$isClassFinal)) { continue; } if ($property->isDeclaredInTrait()) { @@ -96,6 +97,9 @@ public function processNode(Node $node, Scope $scope): array } $propertyReflection = $classReflection->getNativeProperty($propertyName); + if ($propertyReflection->getDeclaringClass()->getName() !== $classReflection->getName()) { + continue; + } foreach ($this->extensionProvider->getExtensions() as $extension) { if ($alwaysRead && $alwaysWritten) { diff --git a/tests/PHPStan/Rules/DeadCode/UnusedPrivateConstantRuleTest.php b/tests/PHPStan/Rules/DeadCode/UnusedPrivateConstantRuleTest.php index 79e15a843c..99e76f6519 100644 --- a/tests/PHPStan/Rules/DeadCode/UnusedPrivateConstantRuleTest.php +++ b/tests/PHPStan/Rules/DeadCode/UnusedPrivateConstantRuleTest.php @@ -49,6 +49,22 @@ public function testRule(): void ]); } + public function testProtected(): void + { + $this->analyse([__DIR__ . '/data/unused-protected-constant.php'], [ + [ + 'Constant UnusedProtectedConstant\Bar::BAR_CONST is unused.', + 26, + 'See: https://phpstan.org/developing-extensions/always-used-class-constants', + ], + [ + 'Constant UnusedProtectedConstant\Bar::BAZ_CONST is unused.', + 28, + 'See: https://phpstan.org/developing-extensions/always-used-class-constants', + ], + ]); + } + public function testBug5651(): void { $this->analyse([__DIR__ . '/data/bug-5651.php'], []); diff --git a/tests/PHPStan/Rules/DeadCode/UnusedPrivateMethodRuleTest.php b/tests/PHPStan/Rules/DeadCode/UnusedPrivateMethodRuleTest.php index 093d372358..61da514fa2 100644 --- a/tests/PHPStan/Rules/DeadCode/UnusedPrivateMethodRuleTest.php +++ b/tests/PHPStan/Rules/DeadCode/UnusedPrivateMethodRuleTest.php @@ -62,6 +62,20 @@ public function testRule(): void ]); } + public function testProtected(): void + { + $this->analyse([__DIR__ . '/data/unused-protected-method.php'], [ + [ + 'Method UnusedProtectedMethod\Bar::unused1() is unused.', + 30, + ], + [ + 'Method UnusedProtectedMethod\Bar::unused2() is unused.', + 35, + ], + ]); + } + public function testBug3630(): void { $this->analyse([__DIR__ . '/data/bug-3630.php'], []); diff --git a/tests/PHPStan/Rules/DeadCode/UnusedPrivatePropertyRuleTest.php b/tests/PHPStan/Rules/DeadCode/UnusedPrivatePropertyRuleTest.php index 199e22d1a2..21cac2bac4 100644 --- a/tests/PHPStan/Rules/DeadCode/UnusedPrivatePropertyRuleTest.php +++ b/tests/PHPStan/Rules/DeadCode/UnusedPrivatePropertyRuleTest.php @@ -186,6 +186,24 @@ public function testTrait(): void $this->analyse([__DIR__ . '/data/private-property-trait.php'], []); } + public function testProtected(): void + { + $this->alwaysWrittenTags = []; + $this->alwaysReadTags = []; + $this->analyse([__DIR__ . '/data/unused-protected-property.php'], [ + [ + 'Property UnusedProtectedProperty\Bar::$bar is unused.', + 31, + 'See: https://phpstan.org/developing-extensions/always-read-written-properties', + ], + [ + 'Property UnusedProtectedProperty\Bar::$baz is unused.', + 33, + 'See: https://phpstan.org/developing-extensions/always-read-written-properties', + ], + ]); + } + public function testBug3636(): void { $this->alwaysWrittenTags = []; diff --git a/tests/PHPStan/Rules/DeadCode/data/unused-protected-constant.php b/tests/PHPStan/Rules/DeadCode/data/unused-protected-constant.php new file mode 100644 index 0000000000..c59f95c744 --- /dev/null +++ b/tests/PHPStan/Rules/DeadCode/data/unused-protected-constant.php @@ -0,0 +1,35 @@ += 8.1 + +namespace UnusedProtectedConstant; + +class Foo +{ + + protected const FOO_CONST = 1; + + protected const BAR_CONST = 2; + + final protected const BAZ_CONST = 2; + + public function doFoo() + { + echo self::FOO_CONST; + } + +} + +final class Bar +{ + + protected const FOO_CONST = 1; + + protected const BAR_CONST = 2; + + final protected const BAZ_CONST = 2; + + public function doFoo() + { + echo self::FOO_CONST; + } + +} diff --git a/tests/PHPStan/Rules/DeadCode/data/unused-protected-method.php b/tests/PHPStan/Rules/DeadCode/data/unused-protected-method.php new file mode 100644 index 0000000000..28ec116d14 --- /dev/null +++ b/tests/PHPStan/Rules/DeadCode/data/unused-protected-method.php @@ -0,0 +1,40 @@ +used1(); + } + + final protected function unused2() + { + $this->used1(); + } + +} + +final class Bar +{ + + protected function used1() + { + } + + protected function unused1() + { + $this->used1(); + } + + final protected function unused2() + { + $this->used1(); + } + +} diff --git a/tests/PHPStan/Rules/DeadCode/data/unused-protected-property.php b/tests/PHPStan/Rules/DeadCode/data/unused-protected-property.php new file mode 100644 index 0000000000..2baacfd585 --- /dev/null +++ b/tests/PHPStan/Rules/DeadCode/data/unused-protected-property.php @@ -0,0 +1,45 @@ += 8.4 + +namespace UnusedProtectedProperty; + +class Foo +{ + + protected $foo; + + protected string $bar; + + final protected string $baz; + + public function __construct() + { + $this->foo = 1; + } + + public function getFoo() + { + return $this->foo; + } + +} + +final class Bar +{ + + protected $foo; + + protected string $bar; + + final protected string $baz; + + public function __construct() + { + $this->foo = 1; + } + + public function getFoo() + { + return $this->foo; + } + +}