Skip to content

Commit 9c0a109

Browse files
committed
[code-quality] Add DecorateWillReturnMapWithExpectsMockRector
1 parent 5617724 commit 9c0a109

File tree

4 files changed

+203
-0
lines changed

4 files changed

+203
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Rector\PHPUnit\Tests\CodeQuality\Rector\Expression\DecorateWillReturnMapWithExpectsMockRector;
6+
7+
use Iterator;
8+
use PHPUnit\Framework\Attributes\DataProvider;
9+
use Rector\Testing\PHPUnit\AbstractRectorTestCase;
10+
11+
final class DecorateWillReturnMapWithExpectsMockRectorTest extends AbstractRectorTestCase
12+
{
13+
#[DataProvider('provideData')]
14+
public function test(string $filePath): void
15+
{
16+
$this->doTestFile($filePath);
17+
}
18+
19+
public static function provideData(): Iterator
20+
{
21+
return self::yieldFilesFromDirectory(__DIR__ . '/Fixture');
22+
}
23+
24+
public function provideConfigFilePath(): string
25+
{
26+
return __DIR__ . '/config/configured_rule.php';
27+
}
28+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Rector\PHPUnit\Tests\CodeQuality\Rector\Expression\DecorateWillReturnMapWithExpectsMockRector\Fixture;
6+
7+
final class SomeTest extends \PHPUnit\Framework\TestCase
8+
{
9+
public function test()
10+
{
11+
$someMock = $this->createMock(\stdClass::class);
12+
$someMock->method('some')->willReturnMap([1, 2]);
13+
}
14+
}
15+
16+
?>
17+
-----
18+
<?php
19+
20+
declare(strict_types=1);
21+
22+
namespace Rector\PHPUnit\Tests\CodeQuality\Rector\Expression\DecorateWillReturnMapWithExpectsMockRector\Fixture;
23+
24+
final class SomeTest extends \PHPUnit\Framework\TestCase
25+
{
26+
public function test()
27+
{
28+
$someMock = $this->createMock(\stdClass::class);
29+
$someMock->expects($this->exactly(2))->method('some')->willReturnMap([1, 2]);
30+
}
31+
}
32+
33+
?>
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
use Rector\Config\RectorConfig;
6+
use Rector\PHPUnit\CodeQuality\Rector\Expression\DecorateWillReturnMapWithExpectsMockRector;
7+
8+
return RectorConfig::configure()
9+
->withRules([DecorateWillReturnMapWithExpectsMockRector::class]);
Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Rector\PHPUnit\CodeQuality\Rector\Expression;
6+
7+
use PhpParser\Node;
8+
use PhpParser\Node\Arg;
9+
use PhpParser\Node\Expr\Array_;
10+
use PhpParser\Node\Expr\MethodCall;
11+
use PhpParser\Node\Expr\Variable;
12+
use PhpParser\Node\Identifier;
13+
use PhpParser\Node\Scalar\Int_;
14+
use PhpParser\Node\Stmt\Expression;
15+
use Rector\Rector\AbstractRector;
16+
use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample;
17+
use Symplify\RuleDocGenerator\ValueObject\RuleDefinition;
18+
19+
/**
20+
* @see \Rector\PHPUnit\Tests\CodeQuality\Rector\Expression\DecorateWillReturnMapWithExpectsMockRector\DecorateWillReturnMapWithExpectsMockRectorTest
21+
*/
22+
final class DecorateWillReturnMapWithExpectsMockRector extends AbstractRector
23+
{
24+
public function getRuleDefinition(): RuleDefinition
25+
{
26+
return new RuleDefinition('Decorate willReturnMap() calls with expects on the mock object if missing', [
27+
new CodeSample(
28+
<<<'CODE_SAMPLE'
29+
use PHPUnit\Framework\TestCase;
30+
use PHPUnit\Framework\MockObject\MockObject;
31+
32+
final class SomeTest extends TestCase
33+
{
34+
private MockObject $someMock;
35+
36+
protected function setUp(): void
37+
{
38+
$this->someMock = $this->createMock(SomeClass::class);
39+
40+
$this->someMock->method("someMethod")
41+
->willReturnMap([
42+
["arg1", "arg2", "result1"],
43+
["arg3", "arg4", "result2"],
44+
]);
45+
}
46+
}
47+
CODE_SAMPLE
48+
,
49+
<<<'CODE_SAMPLE'
50+
use PHPUnit\Framework\TestCase;
51+
use PHPUnit\Framework\MockObject\MockObject;
52+
53+
final class SomeTest extends TestCase
54+
{
55+
private MockObject $someMock;
56+
57+
protected function setUp(): void
58+
{
59+
$this->someMock = $this->createMock(SomeClass::class);
60+
61+
$this->someMock->expects($this->exactly(2))
62+
->method("someMethod")
63+
->willReturnMap([
64+
["arg1", "arg2", "result1"],
65+
["arg3", "arg4", "result2"],
66+
]);
67+
}
68+
}
69+
CODE_SAMPLE
70+
),
71+
72+
]);
73+
}
74+
75+
public function getNodeTypes(): array
76+
{
77+
return [Expression::class];
78+
}
79+
80+
/**
81+
* @param Expression $node
82+
* @return Expression|null
83+
*/
84+
public function refactor(Node $node)
85+
{
86+
if (! $node->expr instanceof MethodCall) {
87+
return null;
88+
}
89+
90+
$methodCall = $node->expr;
91+
if (! $this->isName($methodCall->name, 'willReturnMap')) {
92+
return null;
93+
}
94+
95+
$topmostCall = $this->resolveTopmostCall($methodCall);
96+
97+
// already covered
98+
if ($this->isName($topmostCall->name, 'expects')) {
99+
return null;
100+
}
101+
102+
// count values in will map arg
103+
$willReturnMapArg = $methodCall->getArgs()[0];
104+
if (! $willReturnMapArg->value instanceof Array_) {
105+
return null;
106+
}
107+
108+
$array = $willReturnMapArg->value;
109+
$mapCount = count($array->items);
110+
111+
$topmostCall->var = new MethodCall(
112+
$topmostCall->var,
113+
new Identifier('expects'),
114+
[new Arg(new MethodCall(
115+
new Variable('this'),
116+
new Identifier('exactly'),
117+
[new Arg(new Int_($mapCount))]
118+
))]
119+
);
120+
121+
return $node;
122+
}
123+
124+
private function resolveTopmostCall(MethodCall $methodCall): MethodCall
125+
{
126+
$currentCall = $methodCall;
127+
while ($currentCall->var instanceof MethodCall) {
128+
$currentCall = $currentCall->var;
129+
}
130+
131+
return $currentCall;
132+
}
133+
}

0 commit comments

Comments
 (0)