Skip to content

Commit 99d8606

Browse files
committed
[type-declaration-docblocks] Add PrivateClassMethodArrayDocblockParamFromLocalCallsRector
1 parent 1514b5e commit 99d8606

8 files changed

Lines changed: 289 additions & 18 deletions

File tree

config/set/type-declaration-docblocks.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
use Rector\TypeDeclaration\Rector\ClassMethod\AddReturnArrayDocblockBasedOnArrayMapRector;
77
use Rector\TypeDeclaration\Rector\ClassMethod\AddReturnDocblockForScalarArrayFromAssignsRector;
88
use Rector\TypeDeclarationDocblocks\Rector\Class_\DocblockVarFromParamDocblockInConstructorRector;
9+
use Rector\TypeDeclarationDocblocks\Rector\Class_\PrivateClassMethodArrayDocblockParamFromLocalCallsRector;
910
use Rector\TypeDeclarationDocblocks\Rector\ClassMethod\AddParamArrayDocblockFromDataProviderRector;
1011
use Rector\TypeDeclarationDocblocks\Rector\ClassMethod\AddParamArrayDocblockFromDimFetchAccessRector;
1112
use Rector\TypeDeclarationDocblocks\Rector\ClassMethod\AddReturnDocblockForCommonObjectDenominatorRector;
@@ -24,5 +25,6 @@
2425
AddReturnDocblockForCommonObjectDenominatorRector::class,
2526
AddParamArrayDocblockFromDimFetchAccessRector::class,
2627
AddParamArrayDocblockFromDataProviderRector::class,
28+
PrivateClassMethodArrayDocblockParamFromLocalCallsRector::class,
2729
]);
2830
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
<?php
2+
3+
namespace Rector\Tests\TypeDeclarationDocblocks\Rector\Class_\PrivateClassMethodArrayDocblockParamFromLocalCallsRector\Fixture;
4+
5+
final class MultipleCalls
6+
{
7+
public function go()
8+
{
9+
$this->run(['item1', 'item2']);
10+
11+
$this->run(['item1', 'item2']);
12+
}
13+
14+
private function run(array $items)
15+
{
16+
}
17+
}
18+
19+
?>
20+
-----
21+
<?php
22+
23+
namespace Rector\Tests\TypeDeclarationDocblocks\Rector\Class_\PrivateClassMethodArrayDocblockParamFromLocalCallsRector\Fixture;
24+
25+
final class MultipleCalls
26+
{
27+
public function go()
28+
{
29+
$this->run(['item1', 'item2']);
30+
31+
$this->run(['item1', 'item2']);
32+
}
33+
34+
/**
35+
* @param string[] $items
36+
*/
37+
private function run(array $items)
38+
{
39+
}
40+
}
41+
42+
?>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
<?php
2+
3+
namespace Rector\Tests\TypeDeclarationDocblocks\Rector\Class_\PrivateClassMethodArrayDocblockParamFromLocalCallsRector\Fixture;
4+
5+
class SomeClass
6+
{
7+
public function go()
8+
{
9+
$this->run(['item1', 'item2']);
10+
}
11+
12+
private function run(array $items)
13+
{
14+
}
15+
}
16+
17+
?>
18+
-----
19+
<?php
20+
21+
namespace Rector\Tests\TypeDeclarationDocblocks\Rector\Class_\PrivateClassMethodArrayDocblockParamFromLocalCallsRector\Fixture;
22+
23+
class SomeClass
24+
{
25+
public function go()
26+
{
27+
$this->run(['item1', 'item2']);
28+
}
29+
30+
/**
31+
* @param string[] $items
32+
*/
33+
private function run(array $items)
34+
{
35+
}
36+
}
37+
38+
?>
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_\PrivateClassMethodArrayDocblockParamFromLocalCallsRector;
6+
7+
use Iterator;
8+
use PHPUnit\Framework\Attributes\DataProvider;
9+
use Rector\Testing\PHPUnit\AbstractRectorTestCase;
10+
11+
final class PrivateClassMethodArrayDocblockParamFromLocalCallsRectorTest 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\TypeDeclarationDocblocks\Rector\Class_\PrivateClassMethodArrayDocblockParamFromLocalCallsRector;
7+
8+
return static function (RectorConfig $rectorConfig): void {
9+
$rectorConfig->rule(PrivateClassMethodArrayDocblockParamFromLocalCallsRector::class);
10+
};

rules/TypeDeclaration/Rector/ClassMethod/AddMethodCallBasedStrictParamTypeRector.php

Lines changed: 3 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,13 @@
55
namespace Rector\TypeDeclaration\Rector\ClassMethod;
66

77
use PhpParser\Node;
8-
use PhpParser\Node\Name;
98
use PhpParser\Node\Stmt\Class_;
109
use PhpParser\Node\Stmt\ClassMethod;
11-
use Rector\Configuration\Parameter\FeatureFlags;
1210
use Rector\PhpParser\NodeFinder\LocalMethodCallFinder;
1311
use Rector\Rector\AbstractRector;
1412
use Rector\TypeDeclaration\NodeAnalyzer\CallTypesResolver;
1513
use Rector\TypeDeclaration\NodeAnalyzer\ClassMethodParamTypeCompleter;
14+
use Rector\TypeDeclarationDocblocks\PrivateMethodFlagger;
1615
use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample;
1716
use Symplify\RuleDocGenerator\ValueObject\RuleDefinition;
1817

@@ -30,6 +29,7 @@ public function __construct(
3029
private readonly CallTypesResolver $callTypesResolver,
3130
private readonly ClassMethodParamTypeCompleter $classMethodParamTypeCompleter,
3231
private readonly LocalMethodCallFinder $localMethodCallFinder,
32+
private readonly PrivateMethodFlagger $privateMethodFlagger
3333
) {
3434
}
3535

@@ -92,7 +92,7 @@ public function refactor(Node $node): ?Node
9292
continue;
9393
}
9494

95-
if (! $this->isClassMethodPrivate($node, $classMethod)) {
95+
if (! $this->privateMethodFlagger->isClassMethodPrivate($node, $classMethod)) {
9696
continue;
9797
}
9898

@@ -120,19 +120,4 @@ public function refactor(Node $node): ?Node
120120

121121
return null;
122122
}
123-
124-
private function isClassMethodPrivate(Class_ $class, ClassMethod $classMethod): bool
125-
{
126-
if ($classMethod->isPrivate()) {
127-
return true;
128-
}
129-
130-
if ($classMethod->isFinal() && ! $class->extends instanceof Name && $class->implements === []) {
131-
return true;
132-
}
133-
134-
$isClassFinal = $class->isFinal() || FeatureFlags::treatClassesAsFinal($class);
135-
136-
return $isClassFinal && ! $class->extends instanceof Name && $class->implements === [] && $classMethod->isProtected();
137-
}
138123
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Rector\TypeDeclarationDocblocks;
6+
7+
use PhpParser\Node\Name;
8+
use PhpParser\Node\Stmt\Class_;
9+
use PhpParser\Node\Stmt\ClassMethod;
10+
use Rector\Configuration\Parameter\FeatureFlags;
11+
12+
final class PrivateMethodFlagger
13+
{
14+
public function isClassMethodPrivate(Class_ $class, ClassMethod $classMethod): bool
15+
{
16+
if ($classMethod->isPrivate()) {
17+
return true;
18+
}
19+
20+
if ($classMethod->isFinal() && ! $class->extends instanceof Name && $class->implements === []) {
21+
return true;
22+
}
23+
24+
$isClassFinal = $class->isFinal() || FeatureFlags::treatClassesAsFinal($class);
25+
26+
return $isClassFinal && ! $class->extends instanceof Name && $class->implements === [] && $classMethod->isProtected();
27+
}
28+
}
Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Rector\TypeDeclarationDocblocks\Rector\Class_;
6+
7+
use PhpParser\Node;
8+
use PhpParser\Node\Stmt\Class_;
9+
use PHPStan\PhpDocParser\Ast\PhpDoc\ParamTagValueNode;
10+
use PHPStan\Type\ArrayType;
11+
use Rector\BetterPhpDocParser\PhpDocInfo\PhpDocInfoFactory;
12+
use Rector\Comments\NodeDocBlock\DocBlockUpdater;
13+
use Rector\PhpParser\NodeFinder\LocalMethodCallFinder;
14+
use Rector\Privatization\TypeManipulator\TypeNormalizer;
15+
use Rector\Rector\AbstractRector;
16+
use Rector\StaticTypeMapper\StaticTypeMapper;
17+
use Rector\TypeDeclaration\NodeAnalyzer\CallTypesResolver;
18+
use Rector\TypeDeclarationDocblocks\PrivateMethodFlagger;
19+
use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample;
20+
use Symplify\RuleDocGenerator\ValueObject\RuleDefinition;
21+
22+
/**
23+
* @see \Rector\Tests\TypeDeclarationDocblocks\Rector\Class_\PrivateClassMethodArrayDocblockParamFromLocalCallsRector\PrivateClassMethodArrayDocblockParamFromLocalCallsRectorTest
24+
*/
25+
final class PrivateClassMethodArrayDocblockParamFromLocalCallsRector extends AbstractRector
26+
{
27+
public function __construct(
28+
private readonly PhpDocInfoFactory $phpDocInfoFactory,
29+
private readonly DocBlockUpdater $docBlockUpdater,
30+
private readonly StaticTypeMapper $staticTypeMapper,
31+
private readonly CallTypesResolver $callTypesResolver,
32+
private readonly LocalMethodCallFinder $localMethodCallFinder,
33+
private readonly PrivateMethodFlagger $privateMethodFlagger,
34+
private readonly TypeNormalizer $typeNormalizer
35+
) {
36+
}
37+
38+
public function getNodeTypes(): array
39+
{
40+
return [Class_::class];
41+
}
42+
43+
public function getRuleDefinition(): RuleDefinition
44+
{
45+
return new RuleDefinition('Add @param array docblock to a class method based on local call types', [
46+
new CodeSample(
47+
<<<'CODE_SAMPLE'
48+
class SomeClass
49+
{
50+
public function go()
51+
{
52+
$this->run(['item1', 'item2']);
53+
}
54+
55+
private function run(array $items)
56+
{
57+
}
58+
}
59+
CODE_SAMPLE
60+
,
61+
<<<'CODE_SAMPLE'
62+
class SomeClass
63+
{
64+
public function go()
65+
{
66+
$this->run(['item1', 'item2']);
67+
}
68+
69+
/**
70+
* @param string[] $items
71+
*/
72+
private function run(array $items)
73+
{
74+
}
75+
}
76+
CODE_SAMPLE
77+
),
78+
79+
]);
80+
}
81+
82+
/**
83+
* @param Class_ $node
84+
*/
85+
public function refactor(Node $node): ?Node
86+
{
87+
$hasChanged = false;
88+
89+
foreach ($node->getMethods() as $classMethod) {
90+
if (! $this->privateMethodFlagger->isClassMethodPrivate($node, $classMethod)) {
91+
continue;
92+
}
93+
94+
if ($classMethod->getParams() === []) {
95+
continue;
96+
}
97+
98+
$classMethodPhpDocInfo = $this->phpDocInfoFactory->createFromNodeOrEmpty($classMethod);
99+
100+
$methodCalls = $this->localMethodCallFinder->match($node, $classMethod);
101+
$classMethodParameterTypes = $this->callTypesResolver->resolveStrictTypesFromCalls($methodCalls);
102+
103+
foreach ($classMethod->getParams() as $parameterPosition => $param) {
104+
$parameterName = $this->getName($param);
105+
$parameterTagValueNode = $classMethodPhpDocInfo->getParamTagValueByName($parameterName);
106+
107+
// already known, skip
108+
if ($parameterTagValueNode instanceof ParamTagValueNode) {
109+
continue;
110+
}
111+
112+
$resolvedParameterType = $classMethodParameterTypes[$parameterPosition] ?? null;
113+
if (! $resolvedParameterType instanceof ArrayType) {
114+
continue;
115+
}
116+
117+
$normalizedResolvedParameterType = $this->typeNormalizer->generalizeConstantBoolTypes(
118+
$resolvedParameterType
119+
);
120+
$arrayDocTypeNode = $this->staticTypeMapper->mapPHPStanTypeToPHPStanPhpDocTypeNode(
121+
$normalizedResolvedParameterType
122+
);
123+
124+
$paramTagValueNode = new ParamTagValueNode($arrayDocTypeNode, false, '$' . $parameterName, '', false);
125+
$classMethodPhpDocInfo->addTagValueNode($paramTagValueNode);
126+
127+
$hasChanged = true;
128+
$this->docBlockUpdater->updateRefactoredNodeWithPhpDocInfo($classMethod);
129+
}
130+
}
131+
132+
if (! $hasChanged) {
133+
return null;
134+
}
135+
136+
return $node;
137+
}
138+
}

0 commit comments

Comments
 (0)