Skip to content

Commit 1f485cd

Browse files
committed
[code-quality] Add TypeWillReturnCallableArrowFunctionRector
1 parent 33c2f56 commit 1f485cd

12 files changed

+673
-1
lines changed

config/sets/phpunit-code-quality.php

+4-1
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
use Rector\PHPUnit\CodeQuality\Rector\Class_\RemoveDataProviderParamKeysRector;
1010
use Rector\PHPUnit\CodeQuality\Rector\Class_\SingleMockPropertyTypeRector;
1111
use Rector\PHPUnit\CodeQuality\Rector\Class_\TestWithToDataProviderRector;
12+
use Rector\PHPUnit\CodeQuality\Rector\Class_\TypeWillReturnCallableArrowFunctionRector;
1213
use Rector\PHPUnit\CodeQuality\Rector\Class_\YieldDataProviderRector;
1314
use Rector\PHPUnit\CodeQuality\Rector\ClassMethod\AddInstanceofAssertForNullableInstanceRector;
1415
use Rector\PHPUnit\CodeQuality\Rector\ClassMethod\DataProviderArrayItemsNewLinedRector;
@@ -60,10 +61,12 @@
6061
NarrowSingleWillReturnCallbackRector::class,
6162
SingleWithConsecutiveToWithRector::class,
6263

64+
// type declarations
65+
TypeWillReturnCallableArrowFunctionRector::class,
66+
6367
NarrowUnusedSetUpDefinedPropertyRector::class,
6468

6569
// specific asserts
66-
6770
AssertCompareOnCountableWithMethodToAssertCountRector::class,
6871
AssertComparisonToSpecificMethodRector::class,
6972
AssertNotOperatorRector::class,
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
<?php
2+
3+
namespace Rector\PHPUnit\Tests\CodeQuality\Rector\Class_\TypeWillReturnCallableArrowFunctionRector\Fixture;
4+
5+
use Rector\PHPUnit\Tests\CodeQuality\Rector\Class_\TypeWillReturnCallableArrowFunctionRector\Source\SomeFinalMockedClass;
6+
use PHPUnit\Framework\MockObject\MockObject;
7+
use PHPUnit\Framework\TestCase;
8+
9+
final class AssignedFinalInSetup extends TestCase
10+
{
11+
private MockObject $someFinalMockedClass;
12+
13+
protected function setUp(): void
14+
{
15+
$this->someFinalMockedClass = $this->createMock(SomeFinalMockedClass::class);
16+
}
17+
18+
public function test($value): void
19+
{
20+
$this->someFinalMockedClass
21+
->method('anotherMethod')
22+
->willReturnCallback(fn ($age) => $value);
23+
}
24+
}
25+
26+
?>
27+
-----
28+
<?php
29+
30+
namespace Rector\PHPUnit\Tests\CodeQuality\Rector\Class_\TypeWillReturnCallableArrowFunctionRector\Fixture;
31+
32+
use Rector\PHPUnit\Tests\CodeQuality\Rector\Class_\TypeWillReturnCallableArrowFunctionRector\Source\SomeFinalMockedClass;
33+
use PHPUnit\Framework\MockObject\MockObject;
34+
use PHPUnit\Framework\TestCase;
35+
36+
final class AssignedFinalInSetup extends TestCase
37+
{
38+
private MockObject $someFinalMockedClass;
39+
40+
protected function setUp(): void
41+
{
42+
$this->someFinalMockedClass = $this->createMock(SomeFinalMockedClass::class);
43+
}
44+
45+
public function test($value): void
46+
{
47+
$this->someFinalMockedClass
48+
->method('anotherMethod')
49+
->willReturnCallback(fn (int $age): float => $value);
50+
}
51+
}
52+
53+
?>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
<?php
2+
3+
namespace Rector\PHPUnit\Tests\CodeQuality\Rector\Class_\TypeWillReturnCallableArrowFunctionRector\Fixture;
4+
5+
use PHPUnit\Framework\TestCase;
6+
use Rector\PHPUnit\Tests\CodeQuality\Rector\Class_\TypeWillReturnCallableArrowFunctionRector\Source\SomeMockedClass;
7+
8+
final class FillKnownParamType extends TestCase
9+
{
10+
public function test($value): void
11+
{
12+
$this->createMock(SomeMockedClass::class)
13+
->method('someMethod')
14+
->willReturnCallback(fn ($name) => $value);
15+
}
16+
}
17+
18+
?>
19+
-----
20+
<?php
21+
22+
namespace Rector\PHPUnit\Tests\CodeQuality\Rector\Class_\TypeWillReturnCallableArrowFunctionRector\Fixture;
23+
24+
use PHPUnit\Framework\TestCase;
25+
use Rector\PHPUnit\Tests\CodeQuality\Rector\Class_\TypeWillReturnCallableArrowFunctionRector\Source\SomeMockedClass;
26+
27+
final class FillKnownParamType extends TestCase
28+
{
29+
public function test($value): void
30+
{
31+
$this->createMock(SomeMockedClass::class)
32+
->method('someMethod')
33+
->willReturnCallback(fn (string $name): int => $value);
34+
}
35+
}
36+
37+
?>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
<?php
2+
3+
namespace Rector\PHPUnit\Tests\CodeQuality\Rector\Class_\TypeWillReturnCallableArrowFunctionRector\Fixture;
4+
5+
use PHPUnit\Framework\TestCase;
6+
use Rector\PHPUnit\Tests\CodeQuality\Rector\Class_\TypeWillReturnCallableArrowFunctionRector\Source\SomeMockedClass;
7+
8+
final class IncludeMatcher extends TestCase
9+
{
10+
public function test($value): void
11+
{
12+
$this->createMock(SomeMockedClass::class)
13+
->expects($this->any())
14+
->method('someMethod')
15+
->willReturnCallback(fn ($name) => $value);
16+
}
17+
}
18+
19+
?>
20+
-----
21+
<?php
22+
23+
namespace Rector\PHPUnit\Tests\CodeQuality\Rector\Class_\TypeWillReturnCallableArrowFunctionRector\Fixture;
24+
25+
use PHPUnit\Framework\TestCase;
26+
use Rector\PHPUnit\Tests\CodeQuality\Rector\Class_\TypeWillReturnCallableArrowFunctionRector\Source\SomeMockedClass;
27+
28+
final class IncludeMatcher extends TestCase
29+
{
30+
public function test($value): void
31+
{
32+
$this->createMock(SomeMockedClass::class)
33+
->expects($this->any())
34+
->method('someMethod')
35+
->willReturnCallback(fn (string $name): int => $value);
36+
}
37+
}
38+
39+
?>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Rector\PHPUnit\Tests\CodeQuality\Rector\Class_\TypeWillReturnCallableArrowFunctionRector\Source;
6+
7+
final class SomeFinalMockedClass
8+
{
9+
public function anotherMethod(int $age): float
10+
{
11+
return 25.55;
12+
}
13+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<?php
2+
3+
namespace Rector\PHPUnit\Tests\CodeQuality\Rector\Class_\TypeWillReturnCallableArrowFunctionRector\Source;
4+
5+
// non final on purpose so PHPStan can analyze it
6+
class SomeMockedClass
7+
{
8+
public function someMethod(string $name): int
9+
{
10+
return 100;
11+
}
12+
}
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\Class_\TypeWillReturnCallableArrowFunctionRector;
6+
7+
use Iterator;
8+
use PHPUnit\Framework\Attributes\DataProvider;
9+
use Rector\Testing\PHPUnit\AbstractRectorTestCase;
10+
11+
final class TypeWillReturnCallableArrowFunctionRectorTest 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+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
use Rector\Config\RectorConfig;
6+
use Rector\PHPUnit\CodeQuality\Rector\Class_\TypeWillReturnCallableArrowFunctionRector;
7+
8+
return static function (RectorConfig $rectorConfig): void {
9+
$rectorConfig->rule(TypeWillReturnCallableArrowFunctionRector::class);
10+
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Rector\PHPUnit\CodeQuality\NodeAnalyser;
6+
7+
use PhpParser\Node\Expr\Assign;
8+
use PhpParser\Node\Expr\ClassConstFetch;
9+
use PhpParser\Node\Expr\MethodCall;
10+
use PhpParser\Node\Expr\PropertyFetch;
11+
use PhpParser\Node\Expr\Variable;
12+
use PhpParser\Node\Stmt\Class_;
13+
use PhpParser\Node\Stmt\ClassMethod;
14+
use PhpParser\Node\Stmt\Expression;
15+
use Rector\NodeNameResolver\NodeNameResolver;
16+
use Rector\ValueObject\MethodName;
17+
use Webmozart\Assert\Assert;
18+
19+
final readonly class SetUpAssignedMockTypesResolver
20+
{
21+
public function __construct(
22+
private NodeNameResolver $nodeNameResolver
23+
) {
24+
}
25+
26+
/**
27+
* @return array<string, string>
28+
*/
29+
public function resolveFromClass(Class_ $class): array
30+
{
31+
$setUpClassMethod = $class->getMethod(MethodName::SET_UP);
32+
if (! $setUpClassMethod instanceof ClassMethod) {
33+
return [];
34+
}
35+
36+
$propertyNameToMockedTypes = [];
37+
foreach ((array) $setUpClassMethod->stmts as $stmt) {
38+
if (! $stmt instanceof Expression) {
39+
continue;
40+
}
41+
42+
if (! $stmt->expr instanceof Assign) {
43+
continue;
44+
}
45+
46+
$assign = $stmt->expr;
47+
if (! $assign->expr instanceof MethodCall) {
48+
continue;
49+
}
50+
51+
if (! $this->nodeNameResolver->isNames($assign->expr->name, ['createMock', 'getMockBuilder'])) {
52+
continue;
53+
}
54+
55+
if (! $assign->var instanceof PropertyFetch && ! $assign->var instanceof Variable) {
56+
continue;
57+
}
58+
59+
$mockedClassNameExpr = $assign->expr->getArgs()[0]
60+
->value;
61+
if (! $mockedClassNameExpr instanceof ClassConstFetch) {
62+
continue;
63+
}
64+
65+
$propertyOrVariableName = $this->resolvePropertyOrVariableName($assign->var);
66+
$mockedClass = $this->nodeNameResolver->getName($mockedClassNameExpr->class);
67+
68+
Assert::string($mockedClass);
69+
70+
$propertyNameToMockedTypes[$propertyOrVariableName] = $mockedClass;
71+
}
72+
73+
return $propertyNameToMockedTypes;
74+
}
75+
76+
private function resolvePropertyOrVariableName(PropertyFetch|Variable $propertyFetchOrVariable): ?string
77+
{
78+
if ($propertyFetchOrVariable instanceof Variable) {
79+
return $this->nodeNameResolver->getName($propertyFetchOrVariable);
80+
}
81+
82+
return $this->nodeNameResolver->getName($propertyFetchOrVariable->name);
83+
}
84+
}

0 commit comments

Comments
 (0)