Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?php

namespace Rector\Tests\Strict\Rector\Empty_\DisallowedEmptyRuleFixerRector\Fixture;

final class SkipUntypedProperty
{
public $items;

public function isEmpty()
{
return empty($this->items);
}

public function isNotEmpty()
{
return ! empty($this->items);
}
}
31 changes: 29 additions & 2 deletions rules/Strict/NodeAnalyzer/UninitializedPropertyAnalyzer.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
use PhpParser\Node\Expr\StaticPropertyFetch;
use PhpParser\Node\Stmt\ClassLike;
use PhpParser\Node\Stmt\Property;
use PHPStan\Reflection\ReflectionProvider;
use PHPStan\Type\ThisType;
use Rector\NodeNameResolver\NodeNameResolver;
use Rector\NodeTypeResolver\NodeTypeResolver;
Expand All @@ -22,7 +23,8 @@ public function __construct(
private AstResolver $astResolver,
private NodeTypeResolver $nodeTypeResolver,
private ConstructorAssignDetector $constructorAssignDetector,
private NodeNameResolver $nodeNameResolver
private NodeNameResolver $nodeNameResolver,
private ReflectionProvider $reflectionProvider
) {
}

Expand All @@ -45,13 +47,20 @@ public function isUninitialized(Expr $expr): bool
return false;
}

$propertyName = (string) $this->nodeNameResolver->getName($expr);

// fast-path: a typed property with an explicit default is always initialized,
// so we can skip the heavy class parsing below
if ($this->hasTypedDefaultProperty($className, $propertyName)) {
return false;
}

$classLike = $this->astResolver->resolveClassFromName($className);

if (! $classLike instanceof ClassLike) {
return false;
}

$propertyName = (string) $this->nodeNameResolver->getName($expr);
$property = $classLike->getProperty($propertyName);

if (! $property instanceof Property) {
Expand All @@ -68,4 +77,22 @@ public function isUninitialized(Expr $expr): bool

return ! $this->constructorAssignDetector->isPropertyAssigned($classLike, $propertyName);
}

private function hasTypedDefaultProperty(string $className, string $propertyName): bool
{
if (! $this->reflectionProvider->hasClass($className)) {
return false;
}

$classReflection = $this->reflectionProvider->getClass($className);
if (! $classReflection->hasNativeProperty($propertyName)) {
return false;
}

$nativeReflectionProperty = $classReflection->getNativeProperty($propertyName)
->getNativeReflection();

// an untyped property has an implicit null default, which is not an explicit initialization
return $nativeReflectionProperty->hasType() && $nativeReflectionProperty->hasDefaultValue();
}
}
Loading