From 98618e8fc2a8011dd03527efa5ce9d9dc3b985e0 Mon Sep 17 00:00:00 2001 From: Abdul Malik Ikhsan Date: Fri, 24 Oct 2025 18:31:30 +0700 Subject: [PATCH 1/4] [Php81][CodingStyle] Skip param used as invokable function multi args on FunctionLikeToFirstClassCallableRector --- .../skip_param_used_as_invokable.php.inc | 14 +++++++++ ...FunctionLikeToFirstClassCallableRector.php | 29 +++++++++++++++++-- 2 files changed, 40 insertions(+), 3 deletions(-) create mode 100644 rules-tests/CodingStyle/Rector/FunctionLike/FunctionLikeToFirstClassCallableRector/Fixture/skip_param_used_as_invokable.php.inc diff --git a/rules-tests/CodingStyle/Rector/FunctionLike/FunctionLikeToFirstClassCallableRector/Fixture/skip_param_used_as_invokable.php.inc b/rules-tests/CodingStyle/Rector/FunctionLike/FunctionLikeToFirstClassCallableRector/Fixture/skip_param_used_as_invokable.php.inc new file mode 100644 index 00000000000..381d53d883a --- /dev/null +++ b/rules-tests/CodingStyle/Rector/FunctionLike/FunctionLikeToFirstClassCallableRector/Fixture/skip_param_used_as_invokable.php.inc @@ -0,0 +1,14 @@ +name, $this->object); + } +} + +$obj = new SkipParamUsedAsInvokable('name', new \stdClass()); +$obj->render(fn(string $label, object $object): string => ucfirst($label)); diff --git a/rules/CodingStyle/Rector/FunctionLike/FunctionLikeToFirstClassCallableRector.php b/rules/CodingStyle/Rector/FunctionLike/FunctionLikeToFirstClassCallableRector.php index 9041cc32385..6a218786982 100644 --- a/rules/CodingStyle/Rector/FunctionLike/FunctionLikeToFirstClassCallableRector.php +++ b/rules/CodingStyle/Rector/FunctionLike/FunctionLikeToFirstClassCallableRector.php @@ -27,6 +27,7 @@ use PHPStan\Type\CallableType; use Rector\NodeTypeResolver\PHPStan\ParametersAcceptorSelectorVariantsWrapper; use Rector\PhpParser\AstResolver; +use Rector\PhpParser\Node\BetterNodeFinder; use Rector\PHPStan\ScopeFetcher; use Rector\Rector\AbstractRector; use Rector\Reflection\ReflectionResolver; @@ -47,7 +48,8 @@ final class FunctionLikeToFirstClassCallableRector extends AbstractRector implem public function __construct( private readonly AstResolver $astResolver, - private readonly ReflectionResolver $reflectionResolver + private readonly ReflectionResolver $reflectionResolver, + private readonly BetterNodeFinder $betterNodeFinder ) { } @@ -112,14 +114,35 @@ public function refactor(Node $node): null|CallLike return null; } + $classMethodOrFunction = $this->astResolver->resolveClassMethodOrFunctionFromCall($node); foreach ($reflection->getParameters() as $index => $parameterReflection) { - if ($index === $key - && $parameterReflection->getType() instanceof CallableType + if ($index !== $key) { + continue; + } + + if ($parameterReflection->getType() instanceof CallableType && count($parameterReflection->getType()->getParameters()) > 1 ) { $args[$key]->value->setAttribute(self::HAS_CALLBACK_SIGNATURE_MULTI_PARAMS, true); return null; } + + if ($classMethodOrFunction instanceof FunctionLike) { + $parameterName = $parameterReflection->getName(); + + $isInvokable = (bool) $this->betterNodeFinder->findFirstInFunctionLikeScoped( + $classMethodOrFunction, + fn (Node $node): bool => $node instanceof FuncCall + && $node->name instanceof Variable + && $this->isName($node->name, $parameterName) + && count($node->args) > 1 + ); + + if ($isInvokable) { + $args[$key]->value->setAttribute(self::HAS_CALLBACK_SIGNATURE_MULTI_PARAMS, true); + return null; + } + } } } } From bc14950c7907870654eeb8bb41a2ac5e9cb40f74 Mon Sep 17 00:00:00 2001 From: Abdul Malik Ikhsan Date: Fri, 24 Oct 2025 18:43:02 +0700 Subject: [PATCH 2/4] add more use --- src/PhpParser/AstResolver.php | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/PhpParser/AstResolver.php b/src/PhpParser/AstResolver.php index 0e85c338d3e..cc15495afc5 100644 --- a/src/PhpParser/AstResolver.php +++ b/src/PhpParser/AstResolver.php @@ -8,6 +8,7 @@ use PhpParser\Node\Expr; use PhpParser\Node\Expr\FuncCall; use PhpParser\Node\Expr\MethodCall; +use PhpParser\Node\Expr\New_; use PhpParser\Node\Expr\NullsafeMethodCall; use PhpParser\Node\Expr\StaticCall; use PhpParser\Node\Name; @@ -111,7 +112,7 @@ function (Node $node) use ($classLikeName, $methodName, &$classMethod): bool { } public function resolveClassMethodOrFunctionFromCall( - FuncCall | StaticCall | MethodCall $call + FuncCall | StaticCall | MethodCall | New_ $call ): ClassMethod | Function_ | null { if ($call instanceof FuncCall) { return $this->resolveFunctionFromFuncCall($call); @@ -165,8 +166,17 @@ public function resolveClassMethod(string $className, string $methodName): ?Clas return $classMethod; } - public function resolveClassMethodFromCall(MethodCall | StaticCall | NullsafeMethodCall $call): ?ClassMethod + public function resolveClassMethodFromCall(MethodCall | StaticCall | NullsafeMethodCall | New_ $call): ?ClassMethod { + if ($call instanceof New_) { + if ($call->class instanceof Class_) { + return null; + } + + $className = $this->nodeNameResolver->getName($call->class); + return $this->resolveClassMethod($className, MethodName::CONSTRUCT); + } + $callerStaticType = ($call instanceof MethodCall || $call instanceof NullsafeMethodCall) ? $this->nodeTypeResolver->getType($call->var) : $this->nodeTypeResolver->getType($call->class); From bec8cfd8c69a149a0fd74eb1bbc967380ded0ebe Mon Sep 17 00:00:00 2001 From: Abdul Malik Ikhsan Date: Fri, 24 Oct 2025 18:43:06 +0700 Subject: [PATCH 3/4] add more use --- .../skip_param_used_as_invokable_from_new.php.inc | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 rules-tests/CodingStyle/Rector/FunctionLike/FunctionLikeToFirstClassCallableRector/Fixture/skip_param_used_as_invokable_from_new.php.inc diff --git a/rules-tests/CodingStyle/Rector/FunctionLike/FunctionLikeToFirstClassCallableRector/Fixture/skip_param_used_as_invokable_from_new.php.inc b/rules-tests/CodingStyle/Rector/FunctionLike/FunctionLikeToFirstClassCallableRector/Fixture/skip_param_used_as_invokable_from_new.php.inc new file mode 100644 index 00000000000..6ae020856a4 --- /dev/null +++ b/rules-tests/CodingStyle/Rector/FunctionLike/FunctionLikeToFirstClassCallableRector/Fixture/skip_param_used_as_invokable_from_new.php.inc @@ -0,0 +1,15 @@ +name, $this->object); + } +} + +$obj = new SkipParamUsedAsInvokableFromNew('name', new \stdClass(), fn(string $label, object $object): string => ucfirst($label)); From 5b42601a60e08e0b64e0006d4b035252a4ad09cd Mon Sep 17 00:00:00 2001 From: Abdul Malik Ikhsan Date: Fri, 24 Oct 2025 18:47:36 +0700 Subject: [PATCH 4/4] add more use --- src/PhpParser/AstResolver.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/PhpParser/AstResolver.php b/src/PhpParser/AstResolver.php index cc15495afc5..392b22b2f29 100644 --- a/src/PhpParser/AstResolver.php +++ b/src/PhpParser/AstResolver.php @@ -174,6 +174,10 @@ public function resolveClassMethodFromCall(MethodCall | StaticCall | NullsafeMet } $className = $this->nodeNameResolver->getName($call->class); + if ($className === null) { + return null; + } + return $this->resolveClassMethod($className, MethodName::CONSTRUCT); }