From 735c8227e2086e4aba8f1828c7a5a40c64a79150 Mon Sep 17 00:00:00 2001 From: Martin Herndl Date: Sun, 29 May 2022 12:54:24 +0200 Subject: [PATCH] Use ReadWritePropertiesExtensions in `MissingReadOnlyPropertyAssignRule` --- build/ignore-by-php-version.neon.php | 1 + build/readonly-property.neon | 3 ++ ...singReadOnlyByPhpDocPropertyAssignRule.php | 3 +- .../MissingReadOnlyPropertyAssignRule.php | 3 +- ...ReadOnlyByPhpDocPropertyAssignRuleTest.php | 28 +++++++++++++++++++ .../MissingReadOnlyPropertyAssignRuleTest.php | 28 +++++++++++++++++++ ...issing-readonly-property-assign-phpdoc.php | 9 ++++++ .../data/missing-readonly-property-assign.php | 7 +++++ 8 files changed, 80 insertions(+), 2 deletions(-) create mode 100644 build/readonly-property.neon diff --git a/build/ignore-by-php-version.neon.php b/build/ignore-by-php-version.neon.php index da72769a9e..ca754105dd 100644 --- a/build/ignore-by-php-version.neon.php +++ b/build/ignore-by-php-version.neon.php @@ -13,6 +13,7 @@ $includes[] = __DIR__ . '/baseline-8.1.neon'; } else { $includes[] = __DIR__ . '/enums.neon'; + $includes[] = __DIR__ . '/readonly-property.neon'; } if (PHP_VERSION_ID >= 70400) { diff --git a/build/readonly-property.neon b/build/readonly-property.neon new file mode 100644 index 0000000000..96657fb795 --- /dev/null +++ b/build/readonly-property.neon @@ -0,0 +1,3 @@ +parameters: + excludePaths: + - ../tests/PHPStan/Rules/Properties/MissingReadOnlyPropertyAssignRuleTest.php diff --git a/src/Rules/Properties/MissingReadOnlyByPhpDocPropertyAssignRule.php b/src/Rules/Properties/MissingReadOnlyByPhpDocPropertyAssignRule.php index db533a378a..2880e3205c 100644 --- a/src/Rules/Properties/MissingReadOnlyByPhpDocPropertyAssignRule.php +++ b/src/Rules/Properties/MissingReadOnlyByPhpDocPropertyAssignRule.php @@ -19,6 +19,7 @@ class MissingReadOnlyByPhpDocPropertyAssignRule implements Rule public function __construct( private ConstructorsHelper $constructorsHelper, + private ReadWritePropertiesExtensionProvider $extensionProvider, ) { } @@ -34,7 +35,7 @@ public function processNode(Node $node, Scope $scope): array throw new ShouldNotHappenException(); } $classReflection = $scope->getClassReflection(); - [$properties, $prematureAccess, $additionalAssigns] = $node->getUninitializedProperties($scope, $this->constructorsHelper->getConstructors($classReflection), []); + [$properties, $prematureAccess, $additionalAssigns] = $node->getUninitializedProperties($scope, $this->constructorsHelper->getConstructors($classReflection), $this->extensionProvider->getExtensions()); $errors = []; foreach ($properties as $propertyName => $propertyNode) { diff --git a/src/Rules/Properties/MissingReadOnlyPropertyAssignRule.php b/src/Rules/Properties/MissingReadOnlyPropertyAssignRule.php index 4dd408a4db..62fbc5b153 100644 --- a/src/Rules/Properties/MissingReadOnlyPropertyAssignRule.php +++ b/src/Rules/Properties/MissingReadOnlyPropertyAssignRule.php @@ -19,6 +19,7 @@ class MissingReadOnlyPropertyAssignRule implements Rule public function __construct( private ConstructorsHelper $constructorsHelper, + private ReadWritePropertiesExtensionProvider $extensionProvider, ) { } @@ -34,7 +35,7 @@ public function processNode(Node $node, Scope $scope): array throw new ShouldNotHappenException(); } $classReflection = $scope->getClassReflection(); - [$properties, $prematureAccess, $additionalAssigns] = $node->getUninitializedProperties($scope, $this->constructorsHelper->getConstructors($classReflection), []); + [$properties, $prematureAccess, $additionalAssigns] = $node->getUninitializedProperties($scope, $this->constructorsHelper->getConstructors($classReflection), $this->extensionProvider->getExtensions()); $errors = []; foreach ($properties as $propertyName => $propertyNode) { diff --git a/tests/PHPStan/Rules/Properties/MissingReadOnlyByPhpDocPropertyAssignRuleTest.php b/tests/PHPStan/Rules/Properties/MissingReadOnlyByPhpDocPropertyAssignRuleTest.php index cdc681ca0e..6f5dd01d77 100644 --- a/tests/PHPStan/Rules/Properties/MissingReadOnlyByPhpDocPropertyAssignRuleTest.php +++ b/tests/PHPStan/Rules/Properties/MissingReadOnlyByPhpDocPropertyAssignRuleTest.php @@ -3,8 +3,10 @@ namespace PHPStan\Rules\Properties; use PHPStan\Reflection\ConstructorsHelper; +use PHPStan\Reflection\PropertyReflection; use PHPStan\Rules\Rule; use PHPStan\Testing\RuleTestCase; +use function in_array; use const PHP_VERSION_ID; /** @@ -19,6 +21,32 @@ protected function getRule(): Rule new ConstructorsHelper([ 'MissingReadOnlyPropertyAssignPhpDoc\\TestCase::setUp', ]), + new DirectReadWritePropertiesExtensionProvider([ + new class() implements ReadWritePropertiesExtension { + + public function isAlwaysRead(PropertyReflection $property, string $propertyName): bool + { + return $this->isEntityId($property, $propertyName); + } + + public function isAlwaysWritten(PropertyReflection $property, string $propertyName): bool + { + return $this->isEntityId($property, $propertyName); + } + + public function isInitialized(PropertyReflection $property, string $propertyName): bool + { + return $this->isEntityId($property, $propertyName); + } + + private function isEntityId(PropertyReflection $property, string $propertyName): bool + { + return $property->getDeclaringClass()->getName() === 'MissingReadOnlyPropertyAssignPhpDoc\\Entity' + && in_array($propertyName, ['id'], true); + } + + }, + ]), ); } diff --git a/tests/PHPStan/Rules/Properties/MissingReadOnlyPropertyAssignRuleTest.php b/tests/PHPStan/Rules/Properties/MissingReadOnlyPropertyAssignRuleTest.php index 1700b2331a..391afc01d1 100644 --- a/tests/PHPStan/Rules/Properties/MissingReadOnlyPropertyAssignRuleTest.php +++ b/tests/PHPStan/Rules/Properties/MissingReadOnlyPropertyAssignRuleTest.php @@ -3,8 +3,10 @@ namespace PHPStan\Rules\Properties; use PHPStan\Reflection\ConstructorsHelper; +use PHPStan\Reflection\PropertyReflection; use PHPStan\Rules\Rule; use PHPStan\Testing\RuleTestCase; +use function in_array; use const PHP_VERSION_ID; /** @@ -19,6 +21,32 @@ protected function getRule(): Rule new ConstructorsHelper([ 'MissingReadOnlyPropertyAssign\\TestCase::setUp', ]), + new DirectReadWritePropertiesExtensionProvider([ + new class() implements ReadWritePropertiesExtension { + + public function isAlwaysRead(PropertyReflection $property, string $propertyName): bool + { + return $this->isEntityId($property, $propertyName); + } + + public function isAlwaysWritten(PropertyReflection $property, string $propertyName): bool + { + return $this->isEntityId($property, $propertyName); + } + + public function isInitialized(PropertyReflection $property, string $propertyName): bool + { + return $this->isEntityId($property, $propertyName); + } + + private function isEntityId(PropertyReflection $property, string $propertyName): bool + { + return $property->getDeclaringClass()->getName() === 'MissingReadOnlyPropertyAssign\\Entity' + && in_array($propertyName, ['id'], true); + } + + }, + ]), ); } diff --git a/tests/PHPStan/Rules/Properties/data/missing-readonly-property-assign-phpdoc.php b/tests/PHPStan/Rules/Properties/data/missing-readonly-property-assign-phpdoc.php index 0ad5b65bc7..9d881ba963 100644 --- a/tests/PHPStan/Rules/Properties/data/missing-readonly-property-assign-phpdoc.php +++ b/tests/PHPStan/Rules/Properties/data/missing-readonly-property-assign-phpdoc.php @@ -196,3 +196,12 @@ public function __construct() } } + + +class Entity +{ + + /** @readonly */ + private int $id; // does not complain about being uninitialized because of a ReadWritePropertiesExtension + +} diff --git a/tests/PHPStan/Rules/Properties/data/missing-readonly-property-assign.php b/tests/PHPStan/Rules/Properties/data/missing-readonly-property-assign.php index 3a83a1a57b..8f642100fa 100644 --- a/tests/PHPStan/Rules/Properties/data/missing-readonly-property-assign.php +++ b/tests/PHPStan/Rules/Properties/data/missing-readonly-property-assign.php @@ -153,3 +153,10 @@ public function __construct( } } + +class Entity +{ + + private readonly int $id; // does not complain about being uninitialized because of a ReadWritePropertiesExtension + +}