diff --git a/Makefile b/Makefile index 1122b4ab0f..eec3e3ba33 100644 --- a/Makefile +++ b/Makefile @@ -98,6 +98,7 @@ lint: --exclude tests/PHPStan/Rules/Classes/data/invalid-hooked-properties.php \ --exclude tests/PHPStan/Parser/data/cleaning-property-hooks-before.php \ --exclude tests/PHPStan/Parser/data/cleaning-property-hooks-after.php \ + --exclude tests/PHPStan/Rules/Properties/data/abstract-private-property-hook.php \ --exclude tests/PHPStan/Rules/Properties/data/existing-classes-property-hooks.php \ --exclude tests/PHPStan/Rules/Properties/data/set-property-hook-parameter.php \ --exclude tests/PHPStan/Rules/Properties/data/overriding-final-property.php \ diff --git a/src/Rules/Properties/PropertyInClassRule.php b/src/Rules/Properties/PropertyInClassRule.php index 607fa30792..18190223b2 100644 --- a/src/Rules/Properties/PropertyInClassRule.php +++ b/src/Rules/Properties/PropertyInClassRule.php @@ -94,6 +94,15 @@ public function processNode(Node $node, Scope $scope): array } if ($node->isPrivate()) { + if ($node->isAbstract()) { + return [ + RuleErrorBuilder::message('Property cannot be both abstract and private.') + ->nonIgnorable() + ->identifier('property.abstractPrivate') + ->build(), + ]; + } + if ($node->isFinal()) { return [ RuleErrorBuilder::message('Property cannot be both final and private.') diff --git a/tests/PHPStan/Rules/Properties/PropertyInClassRuleTest.php b/tests/PHPStan/Rules/Properties/PropertyInClassRuleTest.php index 7be86e2089..e0b65f8bd1 100644 --- a/tests/PHPStan/Rules/Properties/PropertyInClassRuleTest.php +++ b/tests/PHPStan/Rules/Properties/PropertyInClassRuleTest.php @@ -245,6 +245,20 @@ public function testPhp84AndAbstractFinalHookedProperties(): void ]); } + public function testPhp84AndAbstractPrivateHookedProperties(): void + { + if (PHP_VERSION_ID < 80400) { + $this->markTestSkipped('Test requires PHP 8.4 or later.'); + } + + $this->analyse([__DIR__ . '/data/abstract-private-property-hook.php'], [ + [ + 'Property cannot be both abstract and private.', + 7, + ], + ]); + } + public function testPhp84AndAbstractFinalHookedPropertiesParseError(): void { if (PHP_VERSION_ID < 80400) { diff --git a/tests/PHPStan/Rules/Properties/data/abstract-private-property-hook.php b/tests/PHPStan/Rules/Properties/data/abstract-private-property-hook.php new file mode 100644 index 0000000000..fc85379bc5 --- /dev/null +++ b/tests/PHPStan/Rules/Properties/data/abstract-private-property-hook.php @@ -0,0 +1,10 @@ += 8.4 + +namespace AbstractPrivateHook; + +abstract class Foo +{ + abstract private int $i { get; } + abstract protected int $ii { get; } + abstract public int $iii { get; } +}