diff --git a/rules-tests/Restoration/Rector/Property/MakeTypedPropertyNullableIfCheckedRector/Fixture/skip_on_constructor_assignment.php.inc b/rules-tests/Restoration/Rector/Property/MakeTypedPropertyNullableIfCheckedRector/Fixture/skip_on_constructor_assignment.php.inc new file mode 100644 index 00000000000..6ac7629fffa --- /dev/null +++ b/rules-tests/Restoration/Rector/Property/MakeTypedPropertyNullableIfCheckedRector/Fixture/skip_on_constructor_assignment.php.inc @@ -0,0 +1,22 @@ +message = 'foo'; + } + + private function getMessage(): ?string + { + if (!$this->message) { + return null; + } + + return $this->message; + } +} diff --git a/rules/Restoration/Rector/Property/MakeTypedPropertyNullableIfCheckedRector.php b/rules/Restoration/Rector/Property/MakeTypedPropertyNullableIfCheckedRector.php index 8cc1b93c36e..8469ccce9a4 100644 --- a/rules/Restoration/Rector/Property/MakeTypedPropertyNullableIfCheckedRector.php +++ b/rules/Restoration/Rector/Property/MakeTypedPropertyNullableIfCheckedRector.php @@ -17,6 +17,7 @@ use PhpParser\Node\Stmt\PropertyProperty; use Rector\Core\Rector\AbstractRector; use Rector\Privatization\NodeManipulator\VisibilityManipulator; +use Rector\TypeDeclaration\AlreadyAssignDetector\ConstructorAssignDetector; use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample; use Symplify\RuleDocGenerator\ValueObject\RuleDefinition; @@ -26,7 +27,8 @@ final class MakeTypedPropertyNullableIfCheckedRector extends AbstractRector { public function __construct( - private readonly VisibilityManipulator $visibilityManipulator + private readonly VisibilityManipulator $visibilityManipulator, + private readonly ConstructorAssignDetector $constructorAssignDetector ) { } @@ -90,7 +92,17 @@ public function refactor(Node $node): ?Node return null; } - $isPropertyNullChecked = $this->isPropertyNullChecked($onlyProperty); + $classLike = $this->betterNodeFinder->findParentType($onlyProperty, Class_::class); + if (! $classLike instanceof Class_) { + return null; + } + + $isPropertyConstructorAssigned = $this->isPropertyConstructorAssigned($classLike, $onlyProperty); + if ($isPropertyConstructorAssigned) { + return null; + } + + $isPropertyNullChecked = $this->isPropertyNullChecked($classLike, $onlyProperty); if (! $isPropertyNullChecked) { return null; } @@ -127,18 +139,19 @@ private function shouldSkipProperty(Property $property): bool return $property->type instanceof NullableType; } - private function isPropertyNullChecked(PropertyProperty $onlyPropertyProperty): bool + private function isPropertyConstructorAssigned(Class_ $class, PropertyProperty $onlyPropertyProperty): bool { - $classLike = $this->betterNodeFinder->findParentType($onlyPropertyProperty, Class_::class); - if (! $classLike instanceof Class_) { - return false; - } + $propertyName = $this->nodeNameResolver->getName($onlyPropertyProperty); + return $this->constructorAssignDetector->isPropertyAssigned($class, $propertyName); + } - if ($this->isIdenticalOrNotIdenticalToNull($classLike, $onlyPropertyProperty)) { + private function isPropertyNullChecked(Class_ $class, PropertyProperty $onlyPropertyProperty): bool + { + if ($this->isIdenticalOrNotIdenticalToNull($class, $onlyPropertyProperty)) { return true; } - return $this->isBooleanNot($classLike, $onlyPropertyProperty); + return $this->isBooleanNot($class, $onlyPropertyProperty); } private function isIdenticalOrNotIdenticalToNull(Class_ $class, PropertyProperty $onlyPropertyProperty): bool