Skip to content

Commit 93cbee5

Browse files
authored
[type-declaration-docblocks] Add DocblockVarArrayFromGetterReturnRector (#7313)
1 parent 7353627 commit 93cbee5

File tree

8 files changed

+309
-0
lines changed

8 files changed

+309
-0
lines changed

config/set/type-declaration-docblocks.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
use Rector\TypeDeclaration\Rector\ClassMethod\AddReturnDocblockForScalarArrayFromAssignsRector;
88
use Rector\TypeDeclarationDocblocks\Rector\Class_\AddReturnDocblockDataProviderRector;
99
use Rector\TypeDeclarationDocblocks\Rector\Class_\ClassMethodArrayDocblockParamFromLocalCallsRector;
10+
use Rector\TypeDeclarationDocblocks\Rector\Class_\DocblockVarArrayFromGetterReturnRector;
1011
use Rector\TypeDeclarationDocblocks\Rector\Class_\DocblockVarArrayFromPropertyDefaultsRector;
1112
use Rector\TypeDeclarationDocblocks\Rector\Class_\DocblockVarFromParamDocblockInConstructorRector;
1213
use Rector\TypeDeclarationDocblocks\Rector\ClassMethod\AddParamArrayDocblockFromDataProviderRector;
@@ -24,6 +25,7 @@
2425
// property var
2526
DocblockVarFromParamDocblockInConstructorRector::class,
2627
DocblockVarArrayFromPropertyDefaultsRector::class,
28+
DocblockVarArrayFromGetterReturnRector::class,
2729

2830
// param
2931
AddParamArrayDocblockFromDimFetchAccessRector::class,
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\Class_\DocblockVarArrayFromGetterReturnRector;
6+
7+
use Iterator;
8+
use PHPUnit\Framework\Attributes\DataProvider;
9+
use Rector\Testing\PHPUnit\AbstractRectorTestCase;
10+
11+
final class DocblockVarArrayFromGetterReturnRectorTest 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,16 @@
1+
<?php
2+
3+
namespace Rector\Tests\TypeDeclarationDocblocks\Rector\Class_\DocblockVarArrayFromGetterReturnRector\Fixture;
4+
5+
final class SkipMissingArray
6+
{
7+
private $names;
8+
9+
/**
10+
* @return string[]
11+
*/
12+
public function getNames(): array
13+
{
14+
return $this->names;
15+
}
16+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<?php
2+
3+
namespace Rector\Tests\TypeDeclarationDocblocks\Rector\Class_\DocblockVarArrayFromGetterReturnRector\Fixture;
4+
5+
final class SkipMissingReturnOnGetter
6+
{
7+
private array $names;
8+
9+
public function getNames(): array
10+
{
11+
return $this->names;
12+
}
13+
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
<?php
2+
3+
namespace Rector\Tests\TypeDeclarationDocblocks\Rector\Class_\DocblockVarArrayFromGetterReturnRector\Fixture;
4+
5+
final class SomeClass
6+
{
7+
private array $names;
8+
9+
/**
10+
* @return string[]
11+
*/
12+
public function getNames(): array
13+
{
14+
return $this->names;
15+
}
16+
}
17+
18+
?>
19+
-----
20+
<?php
21+
22+
namespace Rector\Tests\TypeDeclarationDocblocks\Rector\Class_\DocblockVarArrayFromGetterReturnRector\Fixture;
23+
24+
final class SomeClass
25+
{
26+
/**
27+
* @var string[]
28+
*/
29+
private array $names;
30+
31+
/**
32+
* @return string[]
33+
*/
34+
public function getNames(): array
35+
{
36+
return $this->names;
37+
}
38+
}
39+
40+
?>
Lines changed: 10 additions & 0 deletions
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\TypeDeclarationDocblocks\Rector\Class_\DocblockVarArrayFromGetterReturnRector;
7+
8+
return static function (RectorConfig $rectorConfig): void {
9+
$rectorConfig->rule(DocblockVarArrayFromGetterReturnRector::class);
10+
};
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Rector\TypeDeclarationDocblocks\NodeFinder;
6+
7+
use PhpParser\Node\Expr\PropertyFetch;
8+
use PhpParser\Node\Expr\Variable;
9+
use PhpParser\Node\Stmt\Class_;
10+
use PhpParser\Node\Stmt\ClassMethod;
11+
use PhpParser\Node\Stmt\Property;
12+
use PhpParser\Node\Stmt\Return_;
13+
use Rector\NodeNameResolver\NodeNameResolver;
14+
15+
final readonly class PropertyGetterFinder
16+
{
17+
public function __construct(
18+
private NodeNameResolver $nodeNameResolver
19+
) {
20+
}
21+
22+
public function find(Property $property, Class_ $class): ?ClassMethod
23+
{
24+
$propertyName = $this->nodeNameResolver->getName($property);
25+
foreach ($class->getMethods() as $classMethod) {
26+
if ($classMethod->isMagic()) {
27+
continue;
28+
}
29+
30+
if ($classMethod->isAbstract()) {
31+
continue;
32+
}
33+
34+
if ($classMethod->stmts === null) {
35+
continue;
36+
}
37+
38+
if (count($classMethod->stmts) !== 1) {
39+
continue;
40+
}
41+
42+
$onlyStmt = $classMethod->stmts[0];
43+
if (! $onlyStmt instanceof Return_) {
44+
continue;
45+
}
46+
47+
if (! $onlyStmt->expr instanceof PropertyFetch) {
48+
continue;
49+
}
50+
51+
$propertyFetch = $onlyStmt->expr;
52+
if (! $propertyFetch->var instanceof Variable) {
53+
continue;
54+
}
55+
56+
if (! $this->nodeNameResolver->isName($propertyFetch->var, 'this')) {
57+
continue;
58+
}
59+
60+
if (! $this->nodeNameResolver->isName($propertyFetch->name, $propertyName)) {
61+
continue;
62+
}
63+
64+
return $classMethod;
65+
}
66+
67+
return null;
68+
}
69+
}
Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Rector\TypeDeclarationDocblocks\Rector\Class_;
6+
7+
use PhpParser\Node;
8+
use PhpParser\Node\Identifier;
9+
use PhpParser\Node\Stmt\Class_;
10+
use PhpParser\Node\Stmt\ClassMethod;
11+
use PHPStan\PhpDocParser\Ast\PhpDoc\ReturnTagValueNode;
12+
use PHPStan\PhpDocParser\Ast\PhpDoc\VarTagValueNode;
13+
use Rector\BetterPhpDocParser\PhpDocInfo\PhpDocInfoFactory;
14+
use Rector\Comments\NodeDocBlock\DocBlockUpdater;
15+
use Rector\Rector\AbstractRector;
16+
use Rector\TypeDeclarationDocblocks\NodeFinder\PropertyGetterFinder;
17+
use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample;
18+
use Symplify\RuleDocGenerator\ValueObject\RuleDefinition;
19+
20+
/**
21+
* @see \Rector\Tests\TypeDeclarationDocblocks\Rector\Class_\DocblockVarArrayFromGetterReturnRector\DocblockVarArrayFromGetterReturnRectorTest
22+
*/
23+
final class DocblockVarArrayFromGetterReturnRector extends AbstractRector
24+
{
25+
public function __construct(
26+
private readonly PhpDocInfoFactory $phpDocInfoFactory,
27+
private readonly PropertyGetterFinder $propertyGetterFinder,
28+
private readonly DocBlockUpdater $docBlockUpdater
29+
) {
30+
}
31+
32+
public function getNodeTypes(): array
33+
{
34+
return [Class_::class];
35+
}
36+
37+
public function getRuleDefinition(): RuleDefinition
38+
{
39+
return new RuleDefinition('Add @var array property docblock from its getter @return', [
40+
new CodeSample(
41+
<<<'CODE_SAMPLE'
42+
class SomeClass
43+
{
44+
private array $items;
45+
46+
/**
47+
* @return int[]
48+
*/
49+
public function getItems(): array
50+
{
51+
return $this->items;
52+
}
53+
}
54+
CODE_SAMPLE
55+
,
56+
<<<'CODE_SAMPLE'
57+
class SomeClass
58+
{
59+
/**
60+
* @var int[]
61+
*/
62+
private array $items;
63+
64+
/**
65+
* @return int[]
66+
*/
67+
public function getItems(): array
68+
{
69+
return $this->items;
70+
}
71+
}
72+
CODE_SAMPLE
73+
),
74+
]);
75+
}
76+
77+
/**
78+
* @param Class_ $node
79+
*/
80+
public function refactor(Node $node): ?Node
81+
{
82+
$hasChanged = false;
83+
84+
foreach ($node->getProperties() as $property) {
85+
// property type must be known
86+
if (! $property->type instanceof Identifier) {
87+
continue;
88+
}
89+
90+
if (! $this->isName($property->type, 'array')) {
91+
continue;
92+
}
93+
94+
if (count($property->props) > 1) {
95+
continue;
96+
}
97+
98+
$propertyPhpDocInfo = $this->phpDocInfoFactory->createFromNodeOrEmpty($property);
99+
100+
// type is already known, skip it
101+
if ($propertyPhpDocInfo->getVarTagValueNode() instanceof VarTagValueNode) {
102+
continue;
103+
}
104+
105+
$propertyGetterMethod = $this->propertyGetterFinder->find($property, $node);
106+
if (! $propertyGetterMethod instanceof ClassMethod) {
107+
continue;
108+
}
109+
110+
$classMethodDocInfo = $this->phpDocInfoFactory->createFromNodeOrEmpty($propertyGetterMethod);
111+
$returnTagValueNode = $classMethodDocInfo->getReturnTagValue();
112+
if (! $returnTagValueNode instanceof ReturnTagValueNode) {
113+
continue;
114+
}
115+
116+
$varTagValeNode = new VarTagValueNode($returnTagValueNode->type, '', '');
117+
118+
// find matching getter and its @return docblock
119+
$propertyPhpDocInfo->addTagValueNode($varTagValeNode);
120+
$this->docBlockUpdater->updateRefactoredNodeWithPhpDocInfo($property);
121+
122+
$hasChanged = true;
123+
}
124+
125+
if (! $hasChanged) {
126+
return null;
127+
}
128+
129+
return $node;
130+
}
131+
}

0 commit comments

Comments
 (0)