55namespace Rector \TypeDeclaration \Rector \Class_ ;
66
77use PhpParser \Node ;
8- use PhpParser \Node \Name \FullyQualified ;
98use PhpParser \Node \Stmt \Class_ ;
109use PhpParser \Node \Stmt \ClassMethod ;
11- use PHPStan \Type \ IntersectionType ;
10+ use PHPStan \Reflection \ ClassReflection ;
1211use PHPStan \Type \ObjectType ;
1312use PHPStan \Type \Type ;
13+ use Rector \PHPStanStaticTypeMapper \Enum \TypeKind ;
1414use 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 ;
1618use Rector \ValueObject \MethodName ;
1719use Rector \ValueObject \PhpVersionFeature ;
1820use 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