66
77use PhpParser \Node ;
88use PhpParser \Node \Arg ;
9+ use PhpParser \Node \Expr ;
910use PhpParser \Node \Expr \ArrowFunction ;
1011use PhpParser \Node \Expr \Closure ;
1112use PhpParser \Node \Expr \MethodCall ;
1213use PhpParser \Node \Expr \StaticCall ;
1314use PhpParser \Node \Identifier ;
15+ use PhpParser \Node \Name ;
1416use PhpParser \Node \Param ;
1517use PHPStan \Reflection \ReflectionProvider ;
1618use PHPStan \Type \Constant \ConstantStringType ;
1719use PHPStan \Type \Generic \GenericClassStringType ;
1820use PHPStan \Type \ObjectType ;
1921use PHPStan \Type \StringType ;
22+ use PHPStan \Type \Type ;
2023use Rector \Contract \Rector \ConfigurableRectorInterface ;
2124use Rector \NodeTypeResolver \TypeComparator \TypeComparator ;
2225use Rector \PHPStanStaticTypeMapper \Enum \TypeKind ;
@@ -37,8 +40,6 @@ final class AddClosureParamTypeFromArgRector extends AbstractRector implements C
3740 */
3841 private array $ addClosureParamTypeFromArgs = [];
3942
40- private bool $ hasChanged = false ;
41-
4243 public function __construct (
4344 private readonly TypeComparator $ typeComparator ,
4445 private readonly StaticTypeMapper $ staticTypeMapper ,
@@ -48,17 +49,19 @@ public function __construct(
4849
4950 public function getRuleDefinition (): RuleDefinition
5051 {
51- return new RuleDefinition ('Add param types where needed ' , [
52+ return new RuleDefinition ('Add closure param type based on known passed service/string types of method calls ' , [
5253 new ConfiguredCodeSample (
5354 <<<'CODE_SAMPLE'
55+ $app = new Container();
5456$app->extend(SomeClass::class, function ($parameter) {});
5557CODE_SAMPLE
5658 ,
5759 <<<'CODE_SAMPLE'
60+ $app = new Container();
5861$app->extend(SomeClass::class, function (SomeClass $parameter) {});
5962CODE_SAMPLE
6063 ,
61- [new AddClosureParamTypeFromArg ('SomeClass ' , 'extend ' , 1 , 0 , 0 )]
64+ [new AddClosureParamTypeFromArg ('Container ' , 'extend ' , 1 , 0 , 0 )]
6265 ),
6366 ]);
6467 }
@@ -76,8 +79,6 @@ public function getNodeTypes(): array
7679 */
7780 public function refactor (Node $ node ): ?Node
7881 {
79- $ this ->hasChanged = false ;
80-
8182 foreach ($ this ->addClosureParamTypeFromArgs as $ addClosureParamTypeFromArg ) {
8283 if ($ node instanceof MethodCall) {
8384 $ caller = $ node ->var ;
@@ -87,26 +88,14 @@ public function refactor(Node $node): ?Node
8788 continue ;
8889 }
8990
90- if (! $ this ->isObjectType ($ caller , $ addClosureParamTypeFromArg ->getObjectType ())) {
91- continue ;
92- }
93-
94- if (! $ node ->name instanceof Identifier) {
91+ if (! $ this ->isCallMatch ($ caller , $ addClosureParamTypeFromArg , $ node )) {
9592 continue ;
9693 }
9794
98- if (! $ this ->isName ($ node ->name , $ addClosureParamTypeFromArg ->getMethodName ())) {
99- continue ;
100- }
101-
102- $ this ->processCallLike ($ node , $ addClosureParamTypeFromArg );
103- }
104-
105- if (! $ this ->hasChanged ) {
106- return null ;
95+ return $ this ->processCallLike ($ node , $ addClosureParamTypeFromArg );
10796 }
10897
109- return $ node ;
98+ return null ;
11099 }
111100
112101 /**
@@ -122,82 +111,96 @@ public function configure(array $configuration): void
122111 private function processCallLike (
123112 MethodCall |StaticCall $ callLike ,
124113 AddClosureParamTypeFromArg $ addClosureParamTypeFromArg
125- ): void {
114+ ): MethodCall | StaticCall | null {
126115 if ($ callLike ->isFirstClassCallable ()) {
127- return ;
128- }
129-
130- if ($ callLike ->getArgs () === []) {
131- return ;
116+ return null ;
132117 }
133118
134- $ arg = $ callLike ->args [$ addClosureParamTypeFromArg ->getCallLikePosition ()] ?? null ;
135- if (! $ arg instanceof Arg) {
136- return ;
119+ $ callLikeArg = $ callLike ->args [$ addClosureParamTypeFromArg ->getCallLikePosition ()] ?? null ;
120+ if (! $ callLikeArg instanceof Arg) {
121+ return null ;
137122 }
138123
139124 // int positions shouldn't have names
140- if ($ arg ->name instanceof Identifier) {
141- return ;
125+ if ($ callLikeArg ->name instanceof Identifier) {
126+ return null ;
142127 }
143128
144- $ functionLike = $ arg ->value ;
129+ $ functionLike = $ callLikeArg ->value ;
145130 if (! $ functionLike instanceof Closure && ! $ functionLike instanceof ArrowFunction) {
146- return ;
131+ return null ;
147132 }
148133
149134 if (! isset ($ functionLike ->params [$ addClosureParamTypeFromArg ->getFunctionLikePosition ()])) {
150- return ;
135+ return null ;
151136 }
152137
153- if (! ($ arg = $ this ->getArg (
154- $ addClosureParamTypeFromArg ->getFromArgPosition (),
155- $ callLike ->getArgs ()
156- )) instanceof Arg) {
157- return ;
138+ $ callLikeArg = $ callLike ->getArgs ()[$ addClosureParamTypeFromArg ->getFromArgPosition ()] ?? null ;
139+ if (! $ callLikeArg instanceof Arg) {
140+ return null ;
158141 }
159142
160- $ this ->refactorParameter (
143+ $ hasChanged = $ this ->refactorParameter (
161144 $ functionLike ->params [$ addClosureParamTypeFromArg ->getFunctionLikePosition ()],
162- $ arg ,
145+ $ callLikeArg ,
163146 );
164- }
165147
166- /**
167- * @param Arg[] $args
168- */
169- private function getArg (int $ position , array $ args ): ?Arg
170- {
171- return $ args [$ position ] ?? null ;
148+ if ($ hasChanged ) {
149+ return $ callLike ;
150+ }
151+
152+ return null ;
172153 }
173154
174- private function refactorParameter (Param $ param , Arg $ arg ): void
155+ private function refactorParameter (Param $ param , Arg $ arg ): bool
175156 {
176- $ argType = $ this ->nodeTypeResolver ->getType ($ arg ->value );
177-
178- if ($ argType instanceof GenericClassStringType) {
179- $ closureType = $ argType ->getGenericType ();
180- } elseif ($ argType instanceof ConstantStringType) {
181- if ($ this ->reflectionProvider ->hasClass ($ argType ->getValue ())) {
182- $ closureType = new ObjectType ($ argType ->getValue ());
183- } else {
184- $ closureType = new StringType ();
185- }
186- } else {
187- return ;
157+ $ closureType = $ this ->resolveClosureType ($ arg ->value );
158+ if (! $ closureType instanceof Type) {
159+ return false ;
188160 }
189161
190162 // already set → no change
191- if ($ param ->type !== null ) {
163+ if ($ param ->type instanceof Node ) {
192164 $ currentParamType = $ this ->staticTypeMapper ->mapPhpParserNodePHPStanType ($ param ->type );
193165 if ($ this ->typeComparator ->areTypesEqual ($ currentParamType , $ closureType )) {
194- return ;
166+ return false ;
195167 }
196168 }
197169
198170 $ paramTypeNode = $ this ->staticTypeMapper ->mapPHPStanTypeToPhpParserNode ($ closureType , TypeKind::PARAM );
199- $ this ->hasChanged = true ;
200-
201171 $ param ->type = $ paramTypeNode ;
172+
173+ return true ;
174+ }
175+
176+ private function isCallMatch (
177+ Name |Expr $ caller ,
178+ AddClosureParamTypeFromArg $ addClosureParamTypeFromArg ,
179+ StaticCall |MethodCall $ call
180+ ): bool {
181+ if (! $ this ->isObjectType ($ caller , $ addClosureParamTypeFromArg ->getObjectType ())) {
182+ return false ;
183+ }
184+
185+ return $ this ->isName ($ call ->name , $ addClosureParamTypeFromArg ->getMethodName ());
186+ }
187+
188+ private function resolveClosureType (Expr $ expr ): ?Type
189+ {
190+ $ exprType = $ this ->nodeTypeResolver ->getType ($ expr );
191+
192+ if ($ exprType instanceof GenericClassStringType) {
193+ return $ exprType ->getGenericType ();
194+ }
195+
196+ if ($ exprType instanceof ConstantStringType) {
197+ if ($ this ->reflectionProvider ->hasClass ($ exprType ->getValue ())) {
198+ return new ObjectType ($ exprType ->getValue ());
199+ }
200+
201+ return new StringType ();
202+ }
203+
204+ return null ;
202205 }
203206}
0 commit comments