Skip to content

Commit 1a58300

Browse files
authored
[TypeDeclaration] Handle nullable MockObject on TypedPropertyFromCreateMockAssignRector (#6179)
* [TypeDeclaration] Handle nullable MockObject on TypedPropertyFromCreateMockAssignRector * Fix phpstan * Fix phpstan
1 parent 07fffd5 commit 1a58300

File tree

3 files changed

+80
-53
lines changed

3 files changed

+80
-53
lines changed
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
<?php
2+
3+
namespace Rector\Tests\TypeDeclaration\Rector\Class_\TypedPropertyFromCreateMockAssignRector\Fixture;
4+
5+
use PHPUnit\Framework\TestCase;
6+
use Rector\Tests\TypeDeclaration\Rector\Class_\TypedPropertyFromCreateMockAssignRector\Source\SomeMockedClass;
7+
8+
class NullableMockObject extends TestCase
9+
{
10+
public $someMock;
11+
12+
protected function setUp(): void
13+
{
14+
$this->someMock = $this->createMock(SomeMockedClass::class);
15+
}
16+
17+
protected function tearDown(): void
18+
{
19+
$this->someMock = null;
20+
}
21+
}
22+
23+
?>
24+
-----
25+
<?php
26+
27+
namespace Rector\Tests\TypeDeclaration\Rector\Class_\TypedPropertyFromCreateMockAssignRector\Fixture;
28+
29+
use PHPUnit\Framework\TestCase;
30+
use Rector\Tests\TypeDeclaration\Rector\Class_\TypedPropertyFromCreateMockAssignRector\Source\SomeMockedClass;
31+
32+
class NullableMockObject extends TestCase
33+
{
34+
public ?\PHPUnit\Framework\MockObject\MockObject $someMock;
35+
36+
protected function setUp(): void
37+
{
38+
$this->someMock = $this->createMock(SomeMockedClass::class);
39+
}
40+
41+
protected function tearDown(): void
42+
{
43+
$this->someMock = null;
44+
}
45+
}
46+
47+
?>

rules-tests/TypeDeclaration/Rector/Class_/TypedPropertyFromCreateMockAssignRector/Fixture/skip_value_resetted.php.inc

Lines changed: 0 additions & 22 deletions
This file was deleted.

rules/TypeDeclaration/Rector/Class_/TypedPropertyFromCreateMockAssignRector.php

Lines changed: 33 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,16 @@
55
namespace Rector\TypeDeclaration\Rector\Class_;
66

77
use PhpParser\Node;
8-
use PhpParser\Node\Name\FullyQualified;
98
use PhpParser\Node\Stmt\Class_;
109
use PhpParser\Node\Stmt\ClassMethod;
11-
use PHPStan\Type\IntersectionType;
10+
use PHPStan\Reflection\ClassReflection;
1211
use PHPStan\Type\ObjectType;
1312
use PHPStan\Type\Type;
13+
use Rector\PHPStanStaticTypeMapper\Enum\TypeKind;
1414
use Rector\Rector\AbstractRector;
15-
use Rector\TypeDeclaration\TypeInferer\PropertyTypeInferer\TrustedClassMethodPropertyTypeInferer;
15+
use Rector\Reflection\ReflectionResolver;
16+
use Rector\StaticTypeMapper\StaticTypeMapper;
17+
use Rector\TypeDeclaration\TypeInferer\PropertyTypeInferer\AllAssignNodePropertyTypeInferer;
1618
use Rector\ValueObject\MethodName;
1719
use Rector\ValueObject\PhpVersionFeature;
1820
use Rector\VersionBonding\Contract\MinPhpVersionInterface;
@@ -35,7 +37,9 @@ final class TypedPropertyFromCreateMockAssignRector extends AbstractRector imple
3537
private const MOCK_OBJECT_CLASS = 'PHPUnit\Framework\MockObject\MockObject';
3638

3739
public function __construct(
38-
private readonly TrustedClassMethodPropertyTypeInferer $trustedClassMethodPropertyTypeInferer
40+
private readonly ReflectionResolver $reflectionResolver,
41+
private readonly AllAssignNodePropertyTypeInferer $allAssignNodePropertyTypeInferer,
42+
private readonly StaticTypeMapper $staticTypeMapper
3943
) {
4044
}
4145

@@ -88,7 +92,9 @@ public function refactor(Node $node): ?Node
8892
return null;
8993
}
9094

95+
$classReflection = null;
9196
$hasChanged = false;
97+
9298
foreach ($node->getProperties() as $property) {
9399
// already typed
94100
if ($property->type instanceof Node) {
@@ -100,17 +106,35 @@ public function refactor(Node $node): ?Node
100106
continue;
101107
}
102108

103-
$type = $this->trustedClassMethodPropertyTypeInferer->inferProperty(
104-
$node,
109+
if (! $classReflection instanceof ClassReflection) {
110+
$classReflection = $this->reflectionResolver->resolveClassReflection($node);
111+
}
112+
113+
// ClassReflection not detected, early skip
114+
if (! $classReflection instanceof ClassReflection) {
115+
return null;
116+
}
117+
118+
$type = $this->allAssignNodePropertyTypeInferer->inferProperty(
105119
$property,
106-
$setUpClassMethod
120+
$classReflection,
121+
$this->file
107122
);
108123

109-
if (! $this->isMockObjectType($type)) {
124+
if (! $type instanceof Type) {
125+
continue;
126+
}
127+
128+
$propertyType = $this->staticTypeMapper->mapPHPStanTypeToPhpParserNode($type, TypeKind::PROPERTY);
129+
if (! $propertyType instanceof Node) {
130+
continue;
131+
}
132+
133+
if (! $this->isObjectType($propertyType, new ObjectType(self::MOCK_OBJECT_CLASS))) {
110134
continue;
111135
}
112136

113-
$property->type = new FullyQualified(self::MOCK_OBJECT_CLASS);
137+
$property->type = $propertyType;
114138
$hasChanged = true;
115139
}
116140

@@ -125,26 +149,4 @@ public function provideMinPhpVersion(): int
125149
{
126150
return PhpVersionFeature::TYPED_PROPERTIES;
127151
}
128-
129-
private function isMockObjectType(Type $type): bool
130-
{
131-
if ($type instanceof ObjectType && $type->isInstanceOf(self::MOCK_OBJECT_CLASS)->yes()) {
132-
return true;
133-
}
134-
135-
return $this->isIntersectionWithMockObjectType($type);
136-
}
137-
138-
private function isIntersectionWithMockObjectType(Type $type): bool
139-
{
140-
if (! $type instanceof IntersectionType) {
141-
return false;
142-
}
143-
144-
if (count($type->getTypes()) !== 2) {
145-
return false;
146-
}
147-
148-
return in_array(self::MOCK_OBJECT_CLASS, $type->getObjectClassNames());
149-
}
150152
}

0 commit comments

Comments
 (0)