Skip to content

Commit 163dfd9

Browse files
committed
[tdd] Add AddReturnDocblockFromMethodCallDocblockRector
1 parent 8bd31f0 commit 163dfd9

7 files changed

Lines changed: 262 additions & 0 deletions

File tree

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Rector\Tests\TypeDeclarationDocblocks\Rector\ClassMethod\AddReturnDocblockFromMethodCallDocblockRector;
6+
7+
use Iterator;
8+
use PHPUnit\Framework\Attributes\DataProvider;
9+
use Rector\Testing\PHPUnit\AbstractRectorTestCase;
10+
11+
final class AddReturnDocblockFromMethodCallDocblockRectorTest 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,52 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Rector\Tests\TypeDeclarationDocblocks\Rector\ClassMethod\AddReturnDocblockFromMethodCallDocblockRector\Fixture;
6+
7+
use Rector\Tests\TypeDeclarationDocblocks\Rector\ClassMethod\AddReturnDocblockFromMethodCallDocblockRector\Source\SomeRepository;
8+
9+
final class SomeClass
10+
{
11+
private SomeRepository $someRepository;
12+
13+
public function __construct(SomeRepository $someRepository)
14+
{
15+
$this->someRepository = $someRepository;
16+
}
17+
18+
public function getAll(): array
19+
{
20+
return $this->someRepository->findAll();
21+
}
22+
}
23+
24+
?>
25+
-----
26+
<?php
27+
28+
declare(strict_types=1);
29+
30+
namespace Rector\Tests\TypeDeclarationDocblocks\Rector\ClassMethod\AddReturnDocblockFromMethodCallDocblockRector\Fixture;
31+
32+
use Rector\Tests\TypeDeclarationDocblocks\Rector\ClassMethod\AddReturnDocblockFromMethodCallDocblockRector\Source\SomeRepository;
33+
34+
final class SomeClass
35+
{
36+
private SomeRepository $someRepository;
37+
38+
public function __construct(SomeRepository $someRepository)
39+
{
40+
$this->someRepository = $someRepository;
41+
}
42+
43+
/**
44+
* @return SomeEntity[]
45+
*/
46+
public function getAll(): array
47+
{
48+
return $this->someRepository->findAll();
49+
}
50+
}
51+
52+
?>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
<?php
2+
3+
namespace Rector\Tests\TypeDeclarationDocblocks\Rector\ClassMethod\AddReturnDocblockFromMethodCallDocblockRector\Source;
4+
5+
final class SomeEntity
6+
{
7+
8+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Rector\Tests\TypeDeclarationDocblocks\Rector\ClassMethod\AddReturnDocblockFromMethodCallDocblockRector\Source;
6+
7+
final class SomeRepository
8+
{
9+
/**
10+
* @return SomeEntity[]
11+
*/
12+
public function findAll(): array
13+
{
14+
return [];
15+
}
16+
}
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\TypeDeclarationDocblocks\Rector\ClassMethod\AddReturnDocblockFromMethodCallDocblockRector;
7+
8+
return RectorConfig::configure()
9+
->withRules([AddReturnDocblockFromMethodCallDocblockRector::class]);
Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Rector\TypeDeclarationDocblocks\Rector\ClassMethod;
6+
7+
use PhpParser\Node;
8+
use PhpParser\Node\Expr\MethodCall;
9+
use PhpParser\Node\Identifier;
10+
use PhpParser\Node\Stmt\ClassMethod;
11+
use PhpParser\Node\Stmt\Return_;
12+
use PHPStan\PhpDocParser\Ast\PhpDoc\ReturnTagValueNode;
13+
use PHPStan\Reflection\ReflectionProvider;
14+
use Rector\BetterPhpDocParser\PhpDocInfo\PhpDocInfoFactory;
15+
use Rector\BetterPhpDocParser\PhpDocManipulator\PhpDocTypeChanger;
16+
use Rector\PhpParser\AstResolver;
17+
use Rector\Rector\AbstractRector;
18+
use Rector\TypeDeclarationDocblocks\NodeDocblockTypeDecorator;
19+
use Rector\TypeDeclarationDocblocks\NodeFinder\ReturnNodeFinder;
20+
use Rector\TypeDeclarationDocblocks\TagNodeAnalyzer\UsefulArrayTagNodeAnalyzer;
21+
use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample;
22+
use Symplify\RuleDocGenerator\ValueObject\RuleDefinition;
23+
24+
/**
25+
* @see \Rector\Tests\TypeDeclarationDocblocks\Rector\ClassMethod\AddReturnDocblockFromMethodCallDocblockRector\AddReturnDocblockFromMethodCallDocblockRectorTest
26+
*/
27+
final class AddReturnDocblockFromMethodCallDocblockRector extends AbstractRector
28+
{
29+
public function __construct(
30+
private readonly PhpDocInfoFactory $phpDocInfoFactory,
31+
private readonly ReturnNodeFinder $returnNodeFinder,
32+
private readonly ReflectionProvider $reflectionProvider,
33+
private readonly UsefulArrayTagNodeAnalyzer $usefulArrayTagNodeAnalyzer,
34+
private readonly NodeDocblockTypeDecorator $nodeDocblockTypeDecorator,
35+
private readonly AstResolver $astResolver,
36+
private readonly PhpDocTypeChanger $phpDocTypeChanger,
37+
) {
38+
}
39+
40+
public function getRuleDefinition(): RuleDefinition
41+
{
42+
return new RuleDefinition(
43+
'Add @return docblock based on detailed type of method call docblock',
44+
[
45+
new CodeSample(
46+
<<<'CODE_SAMPLE'
47+
final class SomeController
48+
{
49+
public function getAll(): array
50+
{
51+
return $this->repository->findAll();
52+
}
53+
}
54+
55+
final class Repository
56+
{
57+
/**
58+
* @return SomeEntity[]
59+
*/
60+
public function findAll(): array
61+
{
62+
// ...
63+
}
64+
}
65+
}
66+
CODE_SAMPLE
67+
,
68+
<<<'CODE_SAMPLE'
69+
final class SomeController
70+
{
71+
/**
72+
* @return SomeEntity[]
73+
*/
74+
public function getAll(): array
75+
{
76+
return $this->repository->findAll();
77+
}
78+
}
79+
80+
final class Repository
81+
{
82+
/**
83+
* @return SomeEntity[]
84+
*/
85+
public function findAll(): array
86+
{
87+
// ...
88+
}
89+
}
90+
CODE_SAMPLE
91+
),
92+
93+
]
94+
);
95+
}
96+
97+
/**
98+
* @return array<class-string<Node>>
99+
*/
100+
public function getNodeTypes(): array
101+
{
102+
return [ClassMethod::class];
103+
}
104+
105+
/**
106+
* @param ClassMethod $node
107+
*/
108+
public function refactor(Node $node): ?Node
109+
{
110+
$phpDocInfo = $this->phpDocInfoFactory->createFromNodeOrEmpty($node);
111+
if ($this->usefulArrayTagNodeAnalyzer->isUsefulArrayTag($phpDocInfo->getReturnTagValue())) {
112+
return null;
113+
}
114+
115+
// definitely not an array return
116+
if ($node->returnType instanceof Node && ! $this->isName($node->returnType, 'array')) {
117+
return null;
118+
}
119+
120+
$onlyReturnWithExpr = $this->returnNodeFinder->findOnlyReturnWithExpr($node);
121+
if (! $onlyReturnWithExpr instanceof Return_ || ! $onlyReturnWithExpr->expr instanceof MethodCall) {
122+
return null;
123+
}
124+
125+
$returnedMethodCall = $onlyReturnWithExpr->expr;
126+
127+
$calledClassMethod = $this->astResolver->resolveClassMethodFromCall($returnedMethodCall);
128+
if (! $calledClassMethod->returnType instanceof Identifier) {
129+
return null;
130+
}
131+
132+
if (! $this->isName($calledClassMethod->returnType, 'array')) {
133+
return null;
134+
}
135+
$calledClassMethodPhpDocInfo = $this->phpDocInfoFactory->createFromNode($calledClassMethod);
136+
137+
$calledReturnTagValue = $calledClassMethodPhpDocInfo->getReturnTagValue();
138+
if (! $calledReturnTagValue instanceof ReturnTagValueNode) {
139+
return null;
140+
}
141+
142+
$this->phpDocTypeChanger->changeReturnTypeNode($node, $phpDocInfo, $calledReturnTagValue->type);
143+
144+
return $node;
145+
}
146+
}

src/Config/Level/TypeDeclarationDocblocksLevel.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
use Rector\TypeDeclarationDocblocks\Rector\ClassMethod\AddReturnDocblockForCommonObjectDenominatorRector;
2222
use Rector\TypeDeclarationDocblocks\Rector\ClassMethod\AddReturnDocblockForDimFetchArrayFromAssignsRector;
2323
use Rector\TypeDeclarationDocblocks\Rector\ClassMethod\AddReturnDocblockForJsonArrayRector;
24+
use Rector\TypeDeclarationDocblocks\Rector\ClassMethod\AddReturnDocblockFromMethodCallDocblockRector;
2425
use Rector\TypeDeclarationDocblocks\Rector\ClassMethod\DocblockGetterReturnArrayFromPropertyDocblockVarRector;
2526
use Rector\TypeDeclarationDocblocks\Rector\ClassMethod\DocblockReturnArrayFromDirectArrayInstanceRector;
2627

@@ -63,5 +64,7 @@ final class TypeDeclarationDocblocksLevel
6364

6465
// run latter after other rules, as more generic
6566
AddReturnDocblockForDimFetchArrayFromAssignsRector::class,
67+
68+
AddReturnDocblockFromMethodCallDocblockRector::class,
6669
];
6770
}

0 commit comments

Comments
 (0)