Repository navigation
Expand file tree
/
Copy pathUninitializedPropertyAnalyzer.php
More file actions
98 lines (77 loc) · 3.14 KB
/
Copy pathUninitializedPropertyAnalyzer.php
File metadata and controls
98 lines (77 loc) · 3.14 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
<?php
declare(strict_types=1);
namespace Rector\Strict\NodeAnalyzer;
use PhpParser\Node\Expr;
use PhpParser\Node\Expr\PropertyFetch;
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;
use Rector\PhpParser\AstResolver;
use Rector\StaticTypeMapper\Resolver\ClassNameFromObjectTypeResolver;
use Rector\TypeDeclaration\AlreadyAssignDetector\ConstructorAssignDetector;
final readonly class UninitializedPropertyAnalyzer
{
public function __construct(
private AstResolver $astResolver,
private NodeTypeResolver $nodeTypeResolver,
private ConstructorAssignDetector $constructorAssignDetector,
private NodeNameResolver $nodeNameResolver,
private ReflectionProvider $reflectionProvider
) {
}
public function isUninitialized(Expr $expr): bool
{
if (! $expr instanceof PropertyFetch && ! $expr instanceof StaticPropertyFetch) {
return false;
}
$varType = $expr instanceof PropertyFetch
? $this->nodeTypeResolver->getType($expr->var)
: $this->nodeTypeResolver->getType($expr->class);
if ($varType instanceof ThisType) {
$varType = $varType->getStaticObjectType();
}
$className = ClassNameFromObjectTypeResolver::resolve($varType);
if ($className === null) {
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;
}
$property = $classLike->getProperty($propertyName);
if (! $property instanceof Property) {
return false;
}
if (count($property->props) !== 1) {
return false;
}
if ($property->props[0]->default instanceof Expr) {
return false;
}
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();
}
}