From aad2056ddd701cdf90229150c88bdb751d0e7ef8 Mon Sep 17 00:00:00 2001 From: Tomas Votruba Date: Mon, 3 Nov 2025 11:34:26 +0100 Subject: [PATCH 1/3] add implicit method call support --- .../Fixture/assert_method_call_true.php.inc | 49 +++++++++++++++++++ .../Source/SomeClassWithMethodCall.php | 11 +++++ .../NodeAnalyser/ClosureUsesResolver.php | 21 +++++--- .../FromBinaryAndAssertExpressionsFactory.php | 12 +++++ ...backIdenticalToStandaloneAssertsRector.php | 39 +++++++++------ 5 files changed, 111 insertions(+), 21 deletions(-) create mode 100644 rules-tests/CodeQuality/Rector/MethodCall/WithCallbackIdenticalToStandaloneAssertsRector/Fixture/assert_method_call_true.php.inc create mode 100644 rules-tests/CodeQuality/Rector/MethodCall/WithCallbackIdenticalToStandaloneAssertsRector/Source/SomeClassWithMethodCall.php diff --git a/rules-tests/CodeQuality/Rector/MethodCall/WithCallbackIdenticalToStandaloneAssertsRector/Fixture/assert_method_call_true.php.inc b/rules-tests/CodeQuality/Rector/MethodCall/WithCallbackIdenticalToStandaloneAssertsRector/Fixture/assert_method_call_true.php.inc new file mode 100644 index 00000000..5f81cbad --- /dev/null +++ b/rules-tests/CodeQuality/Rector/MethodCall/WithCallbackIdenticalToStandaloneAssertsRector/Fixture/assert_method_call_true.php.inc @@ -0,0 +1,49 @@ +getMockBuilder('AnyType')->getMock(); + + $someMock->expects($this->any()) + ->method('trans') + ->with($this->callback(fn ($arg) => $arg instanceof SomeClassWithMethodCall && $arg->isReady())); + } +} + +?> +----- +getMockBuilder('AnyType')->getMock(); + + $someMock->expects($this->any()) + ->method('trans') + ->with($this->callback(function ($arg): bool { + $this->assertInstanceOf(\Rector\PHPUnit\Tests\CodeQuality\Rector\MethodCall\WithCallbackIdenticalToStandaloneAssertsRector\Source\SomeClassWithMethodCall::class, $arg); + $this->assertTrue($arg->isReady()); + return true; + })); + } +} + +?> diff --git a/rules-tests/CodeQuality/Rector/MethodCall/WithCallbackIdenticalToStandaloneAssertsRector/Source/SomeClassWithMethodCall.php b/rules-tests/CodeQuality/Rector/MethodCall/WithCallbackIdenticalToStandaloneAssertsRector/Source/SomeClassWithMethodCall.php new file mode 100644 index 00000000..4197dbce --- /dev/null +++ b/rules-tests/CodeQuality/Rector/MethodCall/WithCallbackIdenticalToStandaloneAssertsRector/Source/SomeClassWithMethodCall.php @@ -0,0 +1,11 @@ +createClosureUses($externalVariableNames); } /** @@ -71,4 +66,18 @@ private function resolveParamNames(ArrowFunction $arrowFunction): array return $paramNames; } + + /** + * @param string[] $externalVariableNames + * @return ClosureUse[] + */ + private function createClosureUses(array $externalVariableNames): array + { + $closureUses = []; + foreach ($externalVariableNames as $externalVariableName) { + $closureUses[] = new ClosureUse(new Variable($externalVariableName)); + } + + return $closureUses; + } } diff --git a/rules/CodeQuality/NodeFactory/FromBinaryAndAssertExpressionsFactory.php b/rules/CodeQuality/NodeFactory/FromBinaryAndAssertExpressionsFactory.php index e8b880f8..7d8a9cbf 100644 --- a/rules/CodeQuality/NodeFactory/FromBinaryAndAssertExpressionsFactory.php +++ b/rules/CodeQuality/NodeFactory/FromBinaryAndAssertExpressionsFactory.php @@ -12,6 +12,7 @@ use PhpParser\Node\Expr\FuncCall; use PhpParser\Node\Expr\Instanceof_; use PhpParser\Node\Expr\Isset_; +use PhpParser\Node\Expr\MethodCall; use PhpParser\Node\Name; use PhpParser\Node\Name\FullyQualified; use PhpParser\Node\Scalar\Int_; @@ -37,6 +38,17 @@ public function create(array $exprs): array $assertMethodCalls = []; foreach ($exprs as $expr) { + // implicit bool compare + if ($expr instanceof MethodCall) { + $assertMethodCalls[] = $this->nodeFactory->createMethodCall( + 'this', + 'assertTrue', + [$expr] + ); + + continue; + } + if ($expr instanceof FuncCall && $this->nodeNameResolver->isName($expr, 'array_key_exists')) { $variableExpr = $expr->getArgs()[1] ->value; diff --git a/rules/CodeQuality/Rector/MethodCall/WithCallbackIdenticalToStandaloneAssertsRector.php b/rules/CodeQuality/Rector/MethodCall/WithCallbackIdenticalToStandaloneAssertsRector.php index dbab7cea..8673a915 100644 --- a/rules/CodeQuality/Rector/MethodCall/WithCallbackIdenticalToStandaloneAssertsRector.php +++ b/rules/CodeQuality/Rector/MethodCall/WithCallbackIdenticalToStandaloneAssertsRector.php @@ -125,34 +125,24 @@ public function refactor(Node $node): MethodCall|null continue; } - $assertExpressions = $this->fromBinaryAndAssertExpressionsFactory->create($joinedExprs); - if ($assertExpressions === []) { + $assertExprStmts = $this->fromBinaryAndAssertExpressionsFactory->create($joinedExprs); + if ($assertExprStmts === []) { continue; } $nonReturnCallbackStmts = $this->resolveNonReturnCallbackStmts($argAndFunctionLike); // last si return true; - $assertExpressions[] = new Return_($this->nodeFactory->createTrue()); - + $assertExprStmts[] = new Return_($this->nodeFactory->createTrue()); $innerFunctionLike = $argAndFunctionLike->getFunctionLike(); if ($innerFunctionLike instanceof Closure) { - $innerFunctionLike->stmts = array_merge($nonReturnCallbackStmts, $assertExpressions); + $innerFunctionLike->stmts = array_merge($nonReturnCallbackStmts, $assertExprStmts); } else { // arrow function -> flip to closure $functionLikeInArg = $argAndFunctionLike->getArg(); - $externalVariables = $this->closureUsesResolver->resolveFromArrowFunction($innerFunctionLike); - - $closure = new Closure([ - 'params' => $argAndFunctionLike->getFunctionLike() - ->params, - 'stmts' => $assertExpressions, - 'returnType' => new Identifier('bool'), - 'uses' => $externalVariables, - ]); - + $closure = $this->createClosure($innerFunctionLike, $argAndFunctionLike, $assertExprStmts); $functionLikeInArg->value = $closure; } @@ -273,4 +263,23 @@ private function resolveNonReturnCallbackStmts(ArgAndFunctionLike $argAndFunctio return []; } + + /** + * @param Stmt[] $assertExprStmts + */ + private function createClosure( + Closure|ArrowFunction $innerFunctionLike, + ArgAndFunctionLike $argAndFunctionLike, + array $assertExprStmts + ): Closure { + $externalVariables = $this->closureUsesResolver->resolveFromArrowFunction($innerFunctionLike); + + return new Closure([ + 'params' => $argAndFunctionLike->getFunctionLike() + ->params, + 'stmts' => $assertExprStmts, + 'returnType' => new Identifier('bool'), + 'uses' => $externalVariables, + ]); + } } From 29c5d381ed46b4b835eb1759e3d5588d859dad86 Mon Sep 17 00:00:00 2001 From: Tomas Votruba Date: Mon, 3 Nov 2025 11:34:29 +0100 Subject: [PATCH 2/3] cs --- .../NodeFactory/FromBinaryAndAssertExpressionsFactory.php | 6 +----- .../WithCallbackIdenticalToStandaloneAssertsRector.php | 2 +- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/rules/CodeQuality/NodeFactory/FromBinaryAndAssertExpressionsFactory.php b/rules/CodeQuality/NodeFactory/FromBinaryAndAssertExpressionsFactory.php index 7d8a9cbf..b4c967d5 100644 --- a/rules/CodeQuality/NodeFactory/FromBinaryAndAssertExpressionsFactory.php +++ b/rules/CodeQuality/NodeFactory/FromBinaryAndAssertExpressionsFactory.php @@ -40,11 +40,7 @@ public function create(array $exprs): array foreach ($exprs as $expr) { // implicit bool compare if ($expr instanceof MethodCall) { - $assertMethodCalls[] = $this->nodeFactory->createMethodCall( - 'this', - 'assertTrue', - [$expr] - ); + $assertMethodCalls[] = $this->nodeFactory->createMethodCall('this', 'assertTrue', [$expr]); continue; } diff --git a/rules/CodeQuality/Rector/MethodCall/WithCallbackIdenticalToStandaloneAssertsRector.php b/rules/CodeQuality/Rector/MethodCall/WithCallbackIdenticalToStandaloneAssertsRector.php index 8673a915..a2291131 100644 --- a/rules/CodeQuality/Rector/MethodCall/WithCallbackIdenticalToStandaloneAssertsRector.php +++ b/rules/CodeQuality/Rector/MethodCall/WithCallbackIdenticalToStandaloneAssertsRector.php @@ -268,7 +268,7 @@ private function resolveNonReturnCallbackStmts(ArgAndFunctionLike $argAndFunctio * @param Stmt[] $assertExprStmts */ private function createClosure( - Closure|ArrowFunction $innerFunctionLike, + ArrowFunction $innerFunctionLike, ArgAndFunctionLike $argAndFunctionLike, array $assertExprStmts ): Closure { From 037af353000d031d117ab346dec5c244aee68105 Mon Sep 17 00:00:00 2001 From: Tomas Votruba Date: Mon, 3 Nov 2025 11:35:49 +0100 Subject: [PATCH 3/3] cs --- .../WithCallbackIdenticalToStandaloneAssertsRector.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rules/CodeQuality/Rector/MethodCall/WithCallbackIdenticalToStandaloneAssertsRector.php b/rules/CodeQuality/Rector/MethodCall/WithCallbackIdenticalToStandaloneAssertsRector.php index a2291131..a7766975 100644 --- a/rules/CodeQuality/Rector/MethodCall/WithCallbackIdenticalToStandaloneAssertsRector.php +++ b/rules/CodeQuality/Rector/MethodCall/WithCallbackIdenticalToStandaloneAssertsRector.php @@ -268,11 +268,11 @@ private function resolveNonReturnCallbackStmts(ArgAndFunctionLike $argAndFunctio * @param Stmt[] $assertExprStmts */ private function createClosure( - ArrowFunction $innerFunctionLike, + ArrowFunction $arrowFunction, ArgAndFunctionLike $argAndFunctionLike, array $assertExprStmts ): Closure { - $externalVariables = $this->closureUsesResolver->resolveFromArrowFunction($innerFunctionLike); + $externalVariables = $this->closureUsesResolver->resolveFromArrowFunction($arrowFunction); return new Closure([ 'params' => $argAndFunctionLike->getFunctionLike()