Skip to content

Commit 231a4a0

Browse files
committed
[type-declaration-docblock] kick of docblock rules to help with iterables
1 parent 540d80b commit 231a4a0

6 files changed

Lines changed: 260 additions & 0 deletions

File tree

config/set/type-declaration-docblocks.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
*/
1212
return static function (RectorConfig $rectorConfig): void {
1313
$rectorConfig->rules([
14+
DocblockVarFromParamDocblockInConstructorRector::class,
1415
DocblockVarFromParamDocblockInConstructorRector::class,
1516
DocblockGetterReturnArrayFromPropertyDocblockVarRector::class,
1617
]);
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\DocblockReturnArrayFromDirectArrayInstanceRector;
6+
7+
use Iterator;
8+
use PHPUnit\Framework\Attributes\DataProvider;
9+
use Rector\Testing\PHPUnit\AbstractRectorTestCase;
10+
11+
final class DocblockReturnArrayFromDirectArrayInstanceRectorTest 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,15 @@
1+
<?php
2+
3+
namespace Rector\Tests\TypeDeclarationDocblocks\Rector\ClassMethod\DocblockReturnArrayFromDirectArrayInstanceRector\Fixture;
4+
5+
final class SomePropertyWithArrayDocblock
6+
{
7+
public function getNames(): array
8+
{
9+
return [
10+
'key' => 'value',
11+
];
12+
}
13+
}
14+
15+
?>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
<?php
2+
3+
namespace Rector\Tests\TypeDeclarationDocblocks\Rector\ClassMethod\DocblockReturnArrayFromDirectArrayInstanceRector\Fixture;
4+
5+
final class SomePropertyWithArrayDocblock
6+
{
7+
public function getNames(): array
8+
{
9+
return [
10+
'key' => 'value',
11+
];
12+
}
13+
}
14+
15+
?>
16+
-----
17+
<?php
18+
19+
namespace Rector\Tests\TypeDeclarationDocblocks\Rector\ClassMethod\DocblockReturnArrayFromDirectArrayInstanceRector\Fixture;
20+
21+
final class SomePropertyWithArrayDocblock
22+
{
23+
/**
24+
* @return array<string, string>
25+
*/
26+
public function getNames(): array
27+
{
28+
return [
29+
'key' => 'value',
30+
];
31+
}
32+
}
33+
34+
?>
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\DocblockReturnArrayFromDirectArrayInstanceRector;
7+
8+
return RectorConfig::configure()
9+
->withRules([DocblockReturnArrayFromDirectArrayInstanceRector::class]);
Lines changed: 173 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,173 @@
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\Array_;
9+
use PhpParser\Node\Stmt\ClassMethod;
10+
use PhpParser\Node\Stmt\Return_;
11+
use PHPStan\PhpDocParser\Ast\PhpDoc\ReturnTagValueNode;
12+
use PHPStan\PhpDocParser\Ast\Type\GenericTypeNode;
13+
use PHPStan\PhpDocParser\Ast\Type\IdentifierTypeNode;
14+
use PHPStan\Type\BooleanType;
15+
use PHPStan\Type\Constant\ConstantArrayType;
16+
use PHPStan\Type\FloatType;
17+
use PHPStan\Type\IntegerType;
18+
use PHPStan\Type\MixedType;
19+
use PHPStan\Type\StringType;
20+
use Rector\BetterPhpDocParser\PhpDocInfo\PhpDocInfoFactory;
21+
use Rector\Comments\NodeDocBlock\DocBlockUpdater;
22+
use Rector\Rector\AbstractRector;
23+
use Rector\StaticTypeMapper\StaticTypeMapper;
24+
use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample;
25+
use Symplify\RuleDocGenerator\ValueObject\RuleDefinition;
26+
27+
/**
28+
* @see \Rector\Tests\TypeDeclarationDocblocks\Rector\ClassMethod\DocblockReturnArrayFromDirectArrayInstanceRector\DocblockReturnArrayFromDirectArrayInstanceRectorTest
29+
*/
30+
final class DocblockReturnArrayFromDirectArrayInstanceRector extends AbstractRector
31+
{
32+
public function __construct(
33+
private readonly PhpDocInfoFactory $phpDocInfoFactory,
34+
private readonly DocBlockUpdater $docBlockUpdater,
35+
private readonly StaticTypeMapper $staticTypeMapper
36+
) {
37+
}
38+
39+
public function getNodeTypes(): array
40+
{
41+
if (count($node->stmts) !== 1) {
42+
return null;
43+
}
44+
45+
$soleReturn = $node->stmts[0];
46+
if (! $soleReturn instanceof Return_) {
47+
return null;
48+
}
49+
return [ClassMethod::class];
50+
}
51+
52+
public function getRuleDefinition(): RuleDefinition
53+
{
54+
return new RuleDefinition('Add @return array docblock based on direct single level direct return of []', [
55+
new CodeSample(
56+
<<<'CODE_SAMPLE'
57+
class SomeClass
58+
{
59+
public function getItems(): array
60+
{
61+
return [
62+
'hey' => 'now',
63+
];
64+
}
65+
}
66+
CODE_SAMPLE
67+
,
68+
<<<'CODE_SAMPLE'
69+
class SomeClass
70+
{
71+
/**
72+
* @return array<string, string>
73+
*/
74+
public function getItems(): array
75+
{
76+
return [
77+
'hey' => 'now',
78+
];
79+
}
80+
}
81+
CODE_SAMPLE
82+
),
83+
]);
84+
}
85+
86+
/**
87+
* @param ClassMethod $node
88+
*/
89+
public function refactor(Node $node): ?Node
90+
{
91+
if (! $node->returnType instanceof Node) {
92+
return null;
93+
}
94+
95+
if (! $this->isName($node->returnType, 'array')) {
96+
return null;
97+
}
98+
99+
$phpDocInfo = $this->phpDocInfoFactory->createFromNodeOrEmpty($node);
100+
101+
// return tag is already given
102+
if ($phpDocInfo->getReturnTagValue() instanceof ReturnTagValueNode) {
103+
return null;
104+
}
105+
106+
if (count($node->stmts) !== 1) {
107+
return null;
108+
}
109+
110+
$soleReturn = $node->stmts[0];
111+
if (! $soleReturn instanceof Return_) {
112+
return null;
113+
}
114+
115+
if (! $soleReturn->expr instanceof Array_) {
116+
return null;
117+
}
118+
119+
// resolve simple type
120+
$returnedType = $this->getType($soleReturn->expr);
121+
122+
if (! $returnedType instanceof ConstantArrayType) {
123+
return null;
124+
}
125+
126+
$genericKeyType = $this->constantToGenericType($returnedType->getKeyType());
127+
$genericItemType = $this->constantToGenericType($returnedType->getItemType());
128+
129+
$genericTypeNode = $this->createArrayGenericTypeNode($genericKeyType, $genericItemType);
130+
131+
$returnTagValueNode = new ReturnTagValueNode($genericTypeNode, '');
132+
$phpDocInfo->addTagValueNode($returnTagValueNode);
133+
134+
$this->docBlockUpdater->updateRefactoredNodeWithPhpDocInfo($node);
135+
136+
return $node;
137+
}
138+
139+
/**
140+
* covers constant types too and makes them more generic
141+
*/
142+
private function constantToGenericType(\PHPStan\Type\Type $type): \PHPStan\Type\Type
143+
{
144+
if ($type instanceof StringType) {
145+
return new StringType();
146+
}
147+
148+
if ($type instanceof IntegerType) {
149+
return new IntegerType();
150+
}
151+
152+
if ($type instanceof BooleanType) {
153+
return new BooleanType();
154+
}
155+
156+
if ($type instanceof FloatType) {
157+
return new FloatType();
158+
}
159+
160+
// unclear
161+
return new MixedType();
162+
}
163+
164+
private function createArrayGenericTypeNode(
165+
\PHPStan\Type\Type $keyType,
166+
\PHPStan\Type\Type $itemType
167+
): GenericTypeNode {
168+
$keyDocTypeNode = $this->staticTypeMapper->mapPHPStanTypeToPHPStanPhpDocTypeNode($keyType);
169+
$itemDocTypeNode = $this->staticTypeMapper->mapPHPStanTypeToPHPStanPhpDocTypeNode($itemType);
170+
171+
return new GenericTypeNode(new IdentifierTypeNode('array'), [$keyDocTypeNode, $itemDocTypeNode]);
172+
}
173+
}

0 commit comments

Comments
 (0)