Skip to content

Commit 3368ed4

Browse files
authored
add external variable use (#570)
1 parent 2e3cd8f commit 3368ed4

File tree

3 files changed

+146
-0
lines changed

3 files changed

+146
-0
lines changed
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
<?php
2+
3+
namespace Rector\PHPUnit\Tests\CodeQuality\Rector\MethodCall\WithCallbackIdenticalToStandaloneAssertsRector\Fixture;
4+
5+
use PHPUnit\Framework\TestCase;
6+
7+
final class UseVariable extends TestCase
8+
{
9+
public function test()
10+
{
11+
$expectedValue = 100;
12+
$someMock = $this->getMockBuilder('AnyType')->getMock();
13+
14+
$someMock->expects($this->any())
15+
->method('trans')
16+
->with(
17+
$this->callback(fn ($args): bool => count($args) === 5 && $args[0] === $expectedValue)
18+
);
19+
}
20+
}
21+
22+
?>
23+
-----
24+
<?php
25+
26+
namespace Rector\PHPUnit\Tests\CodeQuality\Rector\MethodCall\WithCallbackIdenticalToStandaloneAssertsRector\Fixture;
27+
28+
use PHPUnit\Framework\TestCase;
29+
30+
final class UseVariable extends TestCase
31+
{
32+
public function test()
33+
{
34+
$expectedValue = 100;
35+
$someMock = $this->getMockBuilder('AnyType')->getMock();
36+
37+
$someMock->expects($this->any())
38+
->method('trans')
39+
->with(
40+
$this->callback(function ($args) use ($expectedValue): void {
41+
$this->assertCount(5, $args);
42+
$this->assertSame($expectedValue, $args[0]);
43+
})
44+
);
45+
}
46+
}
47+
48+
?>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
<?php
2+
3+
namespace Rector\PHPUnit\Tests\CodeQuality\Rector\MethodCall\WithCallbackIdenticalToStandaloneAssertsRector\Fixture;
4+
5+
use PHPUnit\Framework\TestCase;
6+
7+
final class UseVariableRequireOnce extends TestCase
8+
{
9+
public function test()
10+
{
11+
$expectedValue = 100;
12+
$someMock = $this->getMockBuilder('AnyType')->getMock();
13+
14+
$someMock->expects($this->any())
15+
->method('trans')
16+
->with(
17+
$this->callback(fn ($args): bool => count($args) === 5 && $args[0] === $expectedValue && $args[2] === $expectedValue)
18+
);
19+
}
20+
}
21+
22+
?>
23+
-----
24+
<?php
25+
26+
namespace Rector\PHPUnit\Tests\CodeQuality\Rector\MethodCall\WithCallbackIdenticalToStandaloneAssertsRector\Fixture;
27+
28+
use PHPUnit\Framework\TestCase;
29+
30+
final class UseVariableRequireOnce extends TestCase
31+
{
32+
public function test()
33+
{
34+
$expectedValue = 100;
35+
$someMock = $this->getMockBuilder('AnyType')->getMock();
36+
37+
$someMock->expects($this->any())
38+
->method('trans')
39+
->with(
40+
$this->callback(function ($args) use ($expectedValue): void {
41+
$this->assertCount(5, $args);
42+
$this->assertSame($expectedValue, $args[0]);
43+
$this->assertSame($expectedValue, $args[2]);
44+
})
45+
);
46+
}
47+
}
48+
49+
?>

rules/CodeQuality/Rector/MethodCall/WithCallbackIdenticalToStandaloneAssertsRector.php

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,17 @@
55
namespace Rector\PHPUnit\CodeQuality\Rector\MethodCall;
66

77
use PhpParser\Node;
8+
use PhpParser\Node\ClosureUse;
89
use PhpParser\Node\Expr;
910
use PhpParser\Node\Expr\ArrowFunction;
1011
use PhpParser\Node\Expr\BinaryOp\BooleanAnd;
1112
use PhpParser\Node\Expr\BinaryOp\BooleanOr;
1213
use PhpParser\Node\Expr\Closure;
1314
use PhpParser\Node\Expr\MethodCall;
15+
use PhpParser\Node\Expr\Variable;
1416
use PhpParser\Node\Identifier;
1517
use PhpParser\Node\Stmt\Return_;
18+
use Rector\PhpParser\Node\BetterNodeFinder;
1619
use Rector\PHPUnit\CodeQuality\NodeFactory\FromBinaryAndAssertExpressionsFactory;
1720
use Rector\PHPUnit\CodeQuality\ValueObject\ArgAndFunctionLike;
1821
use Rector\PHPUnit\NodeAnalyzer\TestsNodeAnalyzer;
@@ -28,6 +31,7 @@ final class WithCallbackIdenticalToStandaloneAssertsRector extends AbstractRecto
2831
public function __construct(
2932
private readonly TestsNodeAnalyzer $testsNodeAnalyzer,
3033
private readonly FromBinaryAndAssertExpressionsFactory $fromBinaryAndAssertExpressionsFactory,
34+
private readonly BetterNodeFinder $betterNodeFinder,
3135
) {
3236
}
3337

@@ -130,12 +134,16 @@ public function refactor(Node $node): MethodCall|null
130134
// arrow function -> flip to closure
131135
$functionLikeInArg = $argAndFunctionLike->getArg();
132136

137+
$externalVariables = $this->resolveExternalClosureUses($innerFunctionLike);
138+
133139
$closure = new Closure([
134140
'params' => $argAndFunctionLike->getFunctionLike()
135141
->params,
136142
'stmts' => $assertExpressions,
137143
'returnType' => new Identifier('void'),
144+
'uses' => $externalVariables,
138145
]);
146+
139147
$functionLikeInArg->value = $closure;
140148
}
141149

@@ -210,4 +218,45 @@ private function matchInnerSoleExpr(Closure|ArrowFunction $functionLike): ?Expr
210218

211219
return $functionLike->expr;
212220
}
221+
222+
/**
223+
* @return ClosureUse[]
224+
*/
225+
private function resolveExternalClosureUses(ArrowFunction $arrowFunction): array
226+
{
227+
// fill needed uses from arrow function to closure
228+
$arrowFunctionVariables = $this->betterNodeFinder->findInstancesOfScoped(
229+
$arrowFunction->getStmts(),
230+
Variable::class
231+
);
232+
$paramNames = [];
233+
foreach ($arrowFunction->getParams() as $param) {
234+
$paramNames[] = $this->getName($param);
235+
}
236+
237+
$externalVariableNames = [];
238+
239+
foreach ($arrowFunctionVariables as $arrowFunctionVariable) {
240+
// skip those defined in params
241+
if ($this->isNames($arrowFunctionVariable, $paramNames)) {
242+
continue;
243+
}
244+
245+
$variableName = $this->getName($arrowFunctionVariable);
246+
if (! is_string($variableName)) {
247+
continue;
248+
}
249+
250+
$externalVariableNames[] = $variableName;
251+
}
252+
253+
$externalVariableNames = array_unique($externalVariableNames);
254+
255+
$closureUses = [];
256+
foreach ($externalVariableNames as $externalVariableName) {
257+
$closureUses[] = new ClosureUse(new Variable($externalVariableName));
258+
}
259+
260+
return $closureUses;
261+
}
213262
}

0 commit comments

Comments
 (0)