@@ -42,6 +42,16 @@ final class AssertTrueFalseToSpecificMethodRector extends AbstractRector
4242 'is_nan ' => ['is_nan ' , 'assertNan ' , '' ],
4343 'is_a ' => ['is_a ' , 'assertInstanceOf ' , 'assertNotInstanceOf ' ],
4444 'str_contains ' => ['str_contains ' , 'assertStringContainsString ' , 'assertStringNotContainsString ' ],
45+ 'is_array ' => ['is_array ' , 'assertIsArray ' , 'assertIsNotArray ' ],
46+ 'is_bool ' => ['is_bool ' , 'assertIsBool ' , 'assertIsNotBool ' ],
47+ 'is_callable ' => ['is_callable ' , 'assertIsCallable ' , 'assertIsNotCallable ' ],
48+ 'is_float ' => ['is_float ' , 'assertIsFloat ' , 'assertIsNotFloat ' ],
49+ 'is_int ' => ['is_int ' , 'assertIsInt ' , 'assertIsNotInt ' ],
50+ 'is_iterable ' => ['is_iterable ' , 'assertIsIterable ' , 'assertIsNotIterable ' ],
51+ 'is_numeric ' => ['is_numeric ' , 'assertIsNumeric ' , 'assertIsNotNumeric ' ],
52+ 'is_object ' => ['is_object ' , 'assertIsObject ' , 'assertIsNotObject ' ],
53+ 'is_scalar ' => ['is_scalar ' , 'assertIsScalar ' , 'assertIsNotScalar ' ],
54+ 'is_string ' => ['is_string ' , 'assertIsString ' , 'assertIsNotString ' ],
4555 ];
4656
4757 /**
@@ -115,6 +125,10 @@ public function refactor(Node $node): ?Node
115125 return null ;
116126 }
117127
128+ if ($ firstArgumentValue instanceof FuncCall && $ this ->hasUnmovableArgs ($ firstArgumentValue )) {
129+ return null ;
130+ }
131+
118132 if ($ firstArgumentName === 'is_a ' ) {
119133 /** @var FuncCall $firstArgumentValue */
120134 $ args = $ firstArgumentValue ->getArgs ();
@@ -130,6 +144,14 @@ public function refactor(Node $node): ?Node
130144 }
131145 }
132146
147+ // the is_callable() $syntax_only and $callable_name arguments have no counterpart in assertIsCallable()
148+ if ($ firstArgumentName === 'is_callable ' ) {
149+ /** @var FuncCall $firstArgumentValue */
150+ if (count ($ firstArgumentValue ->getArgs ()) > 1 ) {
151+ return null ;
152+ }
153+ }
154+
133155 [$ functionName , $ assetMethodName , $ notAssertMethodName ] = self ::FUNCTION_NAME_WITH_ASSERT_METHOD_NAMES [$ firstArgumentName ];
134156
135157 $ functionNameWithAssertMethods = new FunctionNameWithAssertMethods ($ assetMethodName , $ notAssertMethodName );
@@ -148,6 +170,32 @@ private function resolveFirstArgument(FuncCall|Empty_ $firstArgumentValue): ?str
148170 : $ this ->getName ($ firstArgumentValue );
149171 }
150172
173+ /**
174+ * Args are moved up by position, so a named arg only survives when the assert method happens to
175+ * use the same parameter name: is_readable(filename:) fits assertIsReadable(), while
176+ * is_string(value:) does not fit assertIsString(), whose parameter is $actual. Bail out instead
177+ * of tracking a parameter name per mapped function. Spread args cannot be moved up either,
178+ * as their count is unknown.
179+ */
180+ private function hasUnmovableArgs (FuncCall $ funcCall ): bool
181+ {
182+ if ($ funcCall ->isFirstClassCallable ()) {
183+ return true ;
184+ }
185+
186+ foreach ($ funcCall ->getArgs () as $ arg ) {
187+ if ($ arg ->name instanceof Identifier) {
188+ return true ;
189+ }
190+
191+ if ($ arg ->unpack ) {
192+ return true ;
193+ }
194+ }
195+
196+ return false ;
197+ }
198+
151199 private function renameMethod (
152200 MethodCall |StaticCall $ node ,
153201 FunctionNameWithAssertMethods $ functionNameWithAssertMethods
0 commit comments