Skip to content

Commit 199388d

Browse files
committed
add variable support to CreateStubOverCreateMockArgRector
1 parent 4608de8 commit 199388d

File tree

5 files changed

+233
-19
lines changed

5 files changed

+233
-19
lines changed
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
<?php
2+
3+
namespace Rector\PHPUnit\Tests\PHPUnit120\Rector\CallLike\CreateStubOverCreateMockArgRector\Fixture;
4+
5+
use PHPUnit\Framework\TestCase;
6+
7+
final class HandleAssignedVariable extends TestCase
8+
{
9+
public function testThat()
10+
{
11+
$itemMock = $this->createMock(\stdClass::class);
12+
13+
$items = [
14+
$itemMock
15+
];
16+
}
17+
}
18+
19+
?>
20+
-----
21+
<?php
22+
23+
namespace Rector\PHPUnit\Tests\PHPUnit120\Rector\CallLike\CreateStubOverCreateMockArgRector\Fixture;
24+
25+
use PHPUnit\Framework\TestCase;
26+
27+
final class HandleAssignedVariable extends TestCase
28+
{
29+
public function testThat()
30+
{
31+
$itemMock = $this->createStub(\stdClass::class);
32+
33+
$items = [
34+
$itemMock
35+
];
36+
}
37+
}
38+
39+
?>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
<?php
2+
3+
namespace Rector\PHPUnit\Tests\PHPUnit120\Rector\CallLike\CreateStubOverCreateMockArgRector\Fixture;
4+
5+
use PHPUnit\Framework\TestCase;
6+
7+
final class HandleAssignedVariableMultipleTimes extends TestCase
8+
{
9+
public function testThat()
10+
{
11+
$itemMock = $this->createMock(\stdClass::class);
12+
13+
$items = [
14+
$itemMock
15+
];
16+
17+
$anotherValue = new \stdClass($itemMock);
18+
}
19+
}
20+
21+
?>
22+
-----
23+
<?php
24+
25+
namespace Rector\PHPUnit\Tests\PHPUnit120\Rector\CallLike\CreateStubOverCreateMockArgRector\Fixture;
26+
27+
use PHPUnit\Framework\TestCase;
28+
29+
final class HandleAssignedVariableMultipleTimes extends TestCase
30+
{
31+
public function testThat()
32+
{
33+
$itemMock = $this->createStub(\stdClass::class);
34+
35+
$items = [
36+
$itemMock
37+
];
38+
39+
$anotherValue = new \stdClass($itemMock);
40+
}
41+
}
42+
43+
?>
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Rector\PHPUnit\CodeQuality\NodeAnalyser;
6+
7+
use PhpParser\Node\Expr;
8+
use PhpParser\Node\Expr\MethodCall;
9+
use PhpParser\Node\Expr\Variable;
10+
use PhpParser\Node\Stmt\ClassMethod;
11+
use Rector\NodeNameResolver\NodeNameResolver;
12+
use Rector\PhpParser\Node\BetterNodeFinder;
13+
use Rector\PHPUnit\CodeQuality\NodeFinder\VariableFinder;
14+
15+
final readonly class MockObjectExprDetector
16+
{
17+
public function __construct(
18+
private BetterNodeFinder $betterNodeFinder,
19+
private NodeNameResolver $nodeNameResolver,
20+
private VariableFinder $variableFinder,
21+
) {
22+
}
23+
24+
public function isUsedForMocking(Expr $expr, ClassMethod $classMethod): bool
25+
{
26+
if (! $expr instanceof Variable) {
27+
return false;
28+
}
29+
30+
$variableName = $this->nodeNameResolver->getName($expr);
31+
32+
// to be safe
33+
if ($variableName === null) {
34+
return true;
35+
}
36+
37+
$relatedVariables = $this->variableFinder->find($classMethod, $variableName);
38+
39+
// only self variable found, nothing to mock
40+
if (count($relatedVariables) === 1) {
41+
return false;
42+
}
43+
44+
// find out, how many are used in call likes as args
45+
/** @var array<Expr\MethodCall> $methodCalls */
46+
$methodCalls = $this->betterNodeFinder->findInstancesOfScoped((array) $classMethod->stmts, [MethodCall::class]);
47+
48+
foreach ($methodCalls as $methodCall) {
49+
if (! $methodCall->var instanceof Variable) {
50+
continue;
51+
}
52+
53+
if ($this->nodeNameResolver->isName($methodCall->var, $variableName)) {
54+
// variable is being called on, most like mocking, lets skip
55+
return true;
56+
}
57+
}
58+
59+
return false;
60+
}
61+
}

rules/PHPUnit120/Rector/CallLike/CreateStubOverCreateMockArgRector.php

Lines changed: 88 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,18 @@
66

77
use PhpParser\Node;
88
use PhpParser\Node\ArrayItem;
9+
use PhpParser\Node\Expr;
10+
use PhpParser\Node\Expr\Assign;
911
use PhpParser\Node\Expr\MethodCall;
1012
use PhpParser\Node\Expr\New_;
1113
use PhpParser\Node\Expr\StaticCall;
1214
use PhpParser\Node\Identifier;
15+
use PhpParser\Node\Stmt\ClassMethod;
16+
use PhpParser\Node\Stmt\Expression;
1317
use Rector\PHPStan\ScopeFetcher;
18+
use Rector\PHPUnit\CodeQuality\NodeAnalyser\MockObjectExprDetector;
1419
use Rector\PHPUnit\Enum\PHPUnitClassName;
20+
use Rector\PHPUnit\NodeAnalyzer\TestsNodeAnalyzer;
1521
use Rector\Rector\AbstractRector;
1622
use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample;
1723
use Symplify\RuleDocGenerator\ValueObject\RuleDefinition;
@@ -23,6 +29,12 @@
2329
*/
2430
final class CreateStubOverCreateMockArgRector extends AbstractRector
2531
{
32+
public function __construct(
33+
private readonly TestsNodeAnalyzer $testsNodeAnalyzer,
34+
private readonly MockObjectExprDetector $mockObjectExprDetector,
35+
) {
36+
}
37+
2638
public function getRuleDefinition(): RuleDefinition
2739
{
2840
return new RuleDefinition(
@@ -70,13 +82,13 @@ private function someMethod($someClass)
7082
*/
7183
public function getNodeTypes(): array
7284
{
73-
return [StaticCall::class, MethodCall::class, New_::class, ArrayItem::class];
85+
return [StaticCall::class, MethodCall::class, New_::class, ArrayItem::class, ClassMethod::class];
7486
}
7587

7688
/**
77-
* @param MethodCall|StaticCall|New_|ArrayItem $node
89+
* @param MethodCall|StaticCall|New_|ArrayItem|ClassMethod $node
7890
*/
79-
public function refactor(Node $node): MethodCall|StaticCall|New_|ArrayItem|null
91+
public function refactor(Node $node): MethodCall|StaticCall|New_|ArrayItem|ClassMethod|null
8092
{
8193
$scope = ScopeFetcher::fetch($node);
8294
if (! $scope->isInClass()) {
@@ -88,19 +100,12 @@ public function refactor(Node $node): MethodCall|StaticCall|New_|ArrayItem|null
88100
return null;
89101
}
90102

91-
if ($node instanceof ArrayItem) {
92-
if (! $node->value instanceof MethodCall) {
93-
return null;
94-
}
95-
96-
$methodCall = $node->value;
97-
if (! $this->isName($methodCall->name, 'createMock')) {
98-
return null;
99-
}
100-
101-
$methodCall->name = new Identifier('createStub');
103+
if ($node instanceof ClassMethod) {
104+
return $this->refactorClassMethod($node);
105+
}
102106

103-
return $node;
107+
if ($node instanceof ArrayItem) {
108+
return $this->refactorArrayItem($node);
104109
}
105110

106111
$hasChanges = false;
@@ -129,4 +134,72 @@ public function refactor(Node $node): MethodCall|StaticCall|New_|ArrayItem|null
129134

130135
return null;
131136
}
137+
138+
private function matchCreateMockMethodCall(Expr $expr): null|MethodCall
139+
{
140+
if (! $expr instanceof MethodCall) {
141+
return null;
142+
}
143+
144+
if (! $this->isName($expr->name, 'createMock')) {
145+
return null;
146+
}
147+
148+
return $expr;
149+
}
150+
151+
private function refactorClassMethod(ClassMethod $classMethod): ?ClassMethod
152+
{
153+
if (! $this->testsNodeAnalyzer->isTestClassMethod($classMethod)) {
154+
return null;
155+
}
156+
157+
$hasChanged = false;
158+
foreach ((array) $classMethod->stmts as $stmt) {
159+
if (! $stmt instanceof Expression) {
160+
continue;
161+
}
162+
163+
if (! $stmt->expr instanceof Assign) {
164+
continue;
165+
}
166+
167+
$assign = $stmt->expr;
168+
$createMockMethodCall = $this->matchCreateMockMethodCall($assign->expr);
169+
170+
if (! $createMockMethodCall instanceof MethodCall) {
171+
continue;
172+
}
173+
174+
// no change, as we use the variable for mocking later
175+
if ($this->mockObjectExprDetector->isUsedForMocking($assign->var, $classMethod)) {
176+
continue;
177+
}
178+
179+
$createMockMethodCall->name = new Identifier('createStub');
180+
$hasChanged = true;
181+
}
182+
183+
if ($hasChanged) {
184+
return $classMethod;
185+
}
186+
187+
return null;
188+
}
189+
190+
private function refactorArrayItem(ArrayItem $arrayItem): ?ArrayItem
191+
{
192+
if (! $arrayItem->value instanceof MethodCall) {
193+
return null;
194+
}
195+
196+
$methodCall = $arrayItem->value;
197+
if (! $this->isName($methodCall->name, 'createMock')) {
198+
return null;
199+
}
200+
201+
$methodCall->name = new Identifier('createStub');
202+
203+
return $arrayItem;
204+
}
132205
}

rules/PHPUnit120/Rector/ClassMethod/ExpressionCreateMockToCreateStubRector.php

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,6 @@
99
use PhpParser\Node\Expr\Assign;
1010
use PhpParser\Node\Expr\CallLike;
1111
use PhpParser\Node\Expr\MethodCall;
12-
use PhpParser\Node\Expr\New_;
13-
use PhpParser\Node\Expr\StaticCall;
1412
use PhpParser\Node\Expr\Variable;
1513
use PhpParser\Node\Identifier;
1614
use PhpParser\Node\Stmt\ClassMethod;
@@ -128,8 +126,8 @@ public function refactor(Node $node): ?ClassMethod
128126
$usedVariables = $this->variableFinder->find($node, $variableName);
129127

130128
// used variable in calls
131-
/** @var array<StaticCall|MethodCall|New_> $callLikes */
132-
$callLikes = $this->betterNodeFinder->findInstancesOfScoped($node->stmts, [CallLike::class]);
129+
/** @var array<CallLike> $callLikes */
130+
$callLikes = $this->betterNodeFinder->findInstancesOfScoped($node->stmts, CallLike::class);
133131

134132
$callLikeUsedVariables = $this->collectVariableInCallLikeArg($callLikes, $variableName);
135133

0 commit comments

Comments
 (0)