Skip to content

Commit 1a40dae

Browse files
committed
feat(class): Add UpdateClassMethodNodeParamDocblockFromNodeTypesRector
- Introduce a new rector to update method parameter docblocks based on node types. - Enhance type safety by ensuring the correct parameter types are reflected in the docblocks. - Improve maintainability and readability of class method definitions.
1 parent e3894b3 commit 1a40dae

File tree

6 files changed

+169
-94
lines changed

6 files changed

+169
-94
lines changed

baselines/loader.neon

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1-
# total 4 errors
1+
# total 5 errors
22

33
includes:
44
- complexity.functionLike.neon
55
- method.nonObject.neon
6+
- rector.noClassReflectionStaticReflection.neon
67
- symplify.preferredClass.neon
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
# total 1 error
2+
3+
parameters:
4+
ignoreErrors:
5+
-
6+
message: '#^Instead of "new ClassReflection\(\)" use ReflectionProvider service or "\(new PHPStan\\Reflection\\ClassReflection\(\<desired_type\>\)\)" for static reflection to work$#'
7+
count: 1
8+
path: ../src/Rector/Class_/AbstractUpdateClassMethodNodeParamDocblockFromNodeTypesRector.php

composer.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -388,7 +388,7 @@
388388
"rule-doc-generator:validate": "@rule-doc-generator validate src/Rector/ src/Rector/Array_/UpdateRectorCodeSamplesFromFixturesRector.php",
389389
"sk": "@php vendor/bin/swiss-knife --ansi -vv",
390390
"sk:alice-yaml-fixtures-to-php": "@sk alice-yaml-fixtures-to-php --help",
391-
"sk:check-commented-code": "@sk check-commented-code src/ --line-limit=5 --skip-file=src/Support/helpers.php --skip-file=src/Rector/Name/RenameToPsrNameRector.php --skip-file=src/Rector/Array_/UpdateRectorCodeSamplesFromFixturesRector.php --skip-file=src/Rector/Class_/UpdateRectorRefactorParamDocblockFromNodeTypesRector.php",
391+
"sk:check-commented-code": "@sk check-commented-code src/ --line-limit=5 --skip-file=src/Support/helpers.php --skip-file=src/Rector/Name/RenameToPsrNameRector.php --skip-file=src/Rector/Array_/UpdateRectorCodeSamplesFromFixturesRector.php --skip-file=src/Rector/Class_/AbstractUpdateClassMethodNodeParamDocblockFromNodeTypesRector.php",
392392
"sk:check-conflicts": "@sk check-conflicts src/",
393393
"sk:dump-editorconfig": "@sk dump-editorconfig",
394394
"sk:finalize-classes": "@sk finalize-classes src/",
Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
<?php
2+
3+
/** @noinspection PhpMultipleClassDeclarationsInspection */
4+
/** @noinspection PhpUnusedAliasInspection */
5+
6+
declare(strict_types=1);
7+
8+
/**
9+
* Copyright (c) 2025-2026 guanguans<ityaozm@gmail.com>
10+
*
11+
* For the full copyright and license information, please view
12+
* the LICENSE file that was distributed with this source code.
13+
*
14+
* @see https://github.com/guanguans/rector-rules
15+
*/
16+
17+
namespace Guanguans\RectorRules\Rector\Class_;
18+
19+
use Guanguans\RectorRules\Rector\AbstractRector;
20+
use Illuminate\Support\Collection;
21+
use PhpParser\Node;
22+
use PhpParser\Node\Stmt\Class_;
23+
use PhpParser\Node\Stmt\ClassMethod;
24+
use PHPStan\PhpDocParser\Ast\Type\IdentifierTypeNode;
25+
use PHPStan\Type\ObjectType;
26+
use PHPStan\Type\Type;
27+
use PHPStan\Type\UnionType;
28+
use Rector\BetterPhpDocParser\PhpDocInfo\PhpDocInfoFactory;
29+
use Rector\BetterPhpDocParser\PhpDocManipulator\PhpDocTypeChanger;
30+
use Webmozart\Assert\Assert;
31+
32+
abstract class AbstractUpdateClassMethodNodeParamDocblockFromNodeTypesRector extends AbstractRector
33+
{
34+
private PhpDocInfoFactory $phpDocInfoFactory;
35+
private PhpDocTypeChanger $phpDocTypeChanger;
36+
37+
public function __construct(
38+
PhpDocInfoFactory $phpDocInfoFactory,
39+
PhpDocTypeChanger $phpDocTypeChanger
40+
) {
41+
$this->phpDocInfoFactory = $phpDocInfoFactory;
42+
$this->phpDocTypeChanger = $phpDocTypeChanger;
43+
}
44+
45+
final public function getNodeTypes(): array
46+
{
47+
return [
48+
Class_::class,
49+
];
50+
}
51+
52+
/**
53+
* @param \PhpParser\Node\Stmt\Class_ $node
54+
*
55+
* @throws \PHPStan\ShouldNotHappenException
56+
* @throws \ReflectionException
57+
*/
58+
final public function refactor(Node $node): ?Node
59+
{
60+
$class = $this->getName($node);
61+
62+
if (!is_subclass_of($class, $this->classType())) {
63+
return null;
64+
}
65+
66+
$reflectionClass = new \ReflectionClass($class);
67+
68+
if (!$reflectionClass->isInstantiable()) {
69+
return null;
70+
}
71+
72+
$classMethodNode = $this->classMethodNode($node);
73+
74+
if (!$classMethodNode instanceof ClassMethod) {
75+
return null;
76+
}
77+
78+
$hasChanged = $this->changeNodeParamTypeOfRefactorMethod($classMethodNode, $this->nodeTypes($reflectionClass));
79+
80+
return $hasChanged ? $node : null;
81+
}
82+
83+
/**
84+
* @return class-string
85+
*/
86+
abstract protected function classType(): string;
87+
88+
abstract protected function classMethodNode(Class_ $classNode): ?ClassMethod;
89+
90+
/**
91+
* @param \ReflectionClass<object> $reflectionClass
92+
*
93+
* @return list<class-string<\PhpParser\Node>>
94+
*/
95+
abstract protected function nodeTypes(\ReflectionClass $reflectionClass): array;
96+
97+
/**
98+
* @see \Rector\BetterPhpDocParser\PhpDocManipulator\PhpDocTypeChanger
99+
* @see \Rector\Config\Level\TypeDeclarationDocblocksLevel
100+
* @see \Rector\TypeDeclarationDocblocks\NodeDocblockTypeDecorator
101+
*
102+
* @param list<class-string<\PhpParser\Node>> $nodeTypes
103+
*
104+
* @throws \PHPStan\ShouldNotHappenException
105+
*/
106+
private function changeNodeParamTypeOfRefactorMethod(ClassMethod $classMethodNode, array $nodeTypes): bool
107+
{
108+
// Assert::allIsInstanceOf($nodeTypes, Node::class);
109+
// Assert::allIsAOf($nodeTypes, Node::class);
110+
Assert::allSubclassOf($nodeTypes, Node::class);
111+
112+
// $this->phpDocTypeChanger->changeParamTypeNode(
113+
// $classMethodNode,
114+
// $this->phpDocInfoFactory->createFromNodeOrEmpty($classMethodNode),
115+
// $classMethodNode->getParams()[0],
116+
// 'node',
117+
// new IdentifierTypeNode(
118+
// collect($nodeTypes)->sort()->map(static fn (string $nodeType): string => "\\$nodeType")->implode('|')
119+
// )
120+
// );
121+
122+
return $this->phpDocTypeChanger->changeParamType(
123+
$classMethodNode,
124+
$this->phpDocInfoFactory->createFromNodeOrEmpty($classMethodNode),
125+
collect($nodeTypes)
126+
->sort()
127+
->map(static fn (string $nodeType): ObjectType => new ObjectType($nodeType))
128+
->pipe(
129+
static fn (Collection $types): Type => $types->count() > 1
130+
? new UnionType($types->all())
131+
: $types->first()
132+
),
133+
$classMethodNode->getParams()[0],
134+
'node'
135+
);
136+
}
137+
}

src/Rector/Class_/UpdateRectorRefactorParamDocblockFromNodeTypesRector.php

Lines changed: 20 additions & 92 deletions
Original file line numberDiff line numberDiff line change
@@ -16,72 +16,16 @@
1616

1717
namespace Guanguans\RectorRules\Rector\Class_;
1818

19-
use Guanguans\RectorRules\Rector\AbstractRector;
20-
use Illuminate\Support\Collection;
21-
use PhpParser\Node;
2219
use PhpParser\Node\Stmt\Class_;
2320
use PhpParser\Node\Stmt\ClassMethod;
24-
use PHPStan\PhpDocParser\Ast\Type\IdentifierTypeNode;
25-
use PHPStan\Type\ObjectType;
26-
use PHPStan\Type\Type;
27-
use PHPStan\Type\UnionType;
28-
use Rector\BetterPhpDocParser\PhpDocInfo\PhpDocInfoFactory;
29-
use Rector\BetterPhpDocParser\PhpDocManipulator\PhpDocTypeChanger;
21+
use Rector\Rector\AbstractRector;
3022
use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample;
31-
use Webmozart\Assert\Assert;
3223

3324
/**
3425
* @see \Guanguans\RectorRulesTests\Rector\Class_\UpdateRectorRefactorParamDocblockFromNodeTypesRector\UpdateRectorRefactorParamDocblockFromNodeTypesRectorTest
3526
*/
36-
final class UpdateRectorRefactorParamDocblockFromNodeTypesRector extends AbstractRector
27+
final class UpdateRectorRefactorParamDocblockFromNodeTypesRector extends AbstractUpdateClassMethodNodeParamDocblockFromNodeTypesRector
3728
{
38-
private PhpDocInfoFactory $phpDocInfoFactory;
39-
private PhpDocTypeChanger $phpDocTypeChanger;
40-
41-
public function __construct(
42-
PhpDocInfoFactory $phpDocInfoFactory,
43-
PhpDocTypeChanger $phpDocTypeChanger
44-
) {
45-
$this->phpDocInfoFactory = $phpDocInfoFactory;
46-
$this->phpDocTypeChanger = $phpDocTypeChanger;
47-
}
48-
49-
public function getNodeTypes(): array
50-
{
51-
return [
52-
Class_::class,
53-
];
54-
}
55-
56-
/**
57-
* @param \PhpParser\Node\Stmt\Class_ $node
58-
*
59-
* @throws \PHPStan\ShouldNotHappenException
60-
* @throws \ReflectionException
61-
*/
62-
public function refactor(Node $node): ?Node
63-
{
64-
$class = $this->getName($node);
65-
66-
if (!is_subclass_of($class, \Rector\Rector\AbstractRector::class)) {
67-
return null;
68-
}
69-
70-
/** @var \ReflectionClass<\Rector\Rector\AbstractRector> $reflectionClass */
71-
$reflectionClass = new \ReflectionClass($class);
72-
73-
if (!$reflectionClass->isInstantiable()) {
74-
return null;
75-
}
76-
77-
$hasChanged = $this->changeNodeParamTypeOfRefactorMethod(
78-
$node->getMethod('refactor'),
79-
$reflectionClass->newInstanceWithoutConstructor()->getNodeTypes()
80-
);
81-
82-
return $hasChanged ? $node : null;
83-
}
84-
8529
/**
8630
* @return list<\Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample>
8731
*/
@@ -143,43 +87,27 @@ public function refactor(Node $node): ?Node
14387
}
14488

14589
/**
146-
* @see \Rector\BetterPhpDocParser\PhpDocManipulator\PhpDocTypeChanger
147-
* @see \Rector\Config\Level\TypeDeclarationDocblocksLevel
148-
* @see \Rector\TypeDeclarationDocblocks\NodeDocblockTypeDecorator
149-
*
150-
* @param list<class-string<\PhpParser\Node>> $nodeTypes
151-
*
152-
* @throws \PHPStan\ShouldNotHappenException
90+
* @return class-string<\Rector\Rector\AbstractRector>
15391
*/
154-
private function changeNodeParamTypeOfRefactorMethod(ClassMethod $classMethodNode, array $nodeTypes): bool
92+
protected function classType(): string
15593
{
156-
// Assert::allIsInstanceOf($nodeTypes, Node::class);
157-
// Assert::allIsAOf($nodeTypes, Node::class);
158-
Assert::allSubclassOf($nodeTypes, Node::class);
94+
return AbstractRector::class;
95+
}
15996

160-
// $this->phpDocTypeChanger->changeParamTypeNode(
161-
// $classMethodNode,
162-
// $this->phpDocInfoFactory->createFromNodeOrEmpty($classMethodNode),
163-
// $classMethodNode->getParams()[0],
164-
// 'node',
165-
// new IdentifierTypeNode(
166-
// collect($nodeTypes)->sort()->map(static fn (string $nodeType): string => "\\$nodeType")->implode('|')
167-
// )
168-
// );
97+
protected function classMethodNode(Class_ $classNode): ?ClassMethod
98+
{
99+
return $classNode->getMethod('refactor');
100+
}
169101

170-
return $this->phpDocTypeChanger->changeParamType(
171-
$classMethodNode,
172-
$this->phpDocInfoFactory->createFromNodeOrEmpty($classMethodNode),
173-
collect($nodeTypes)
174-
->sort()
175-
->map(static fn (string $nodeType): ObjectType => new ObjectType($nodeType))
176-
->pipe(
177-
static fn (Collection $types): Type => $types->count() > 1
178-
? new UnionType($types->all())
179-
: $types->first()
180-
),
181-
$classMethodNode->getParams()[0],
182-
'node'
183-
);
102+
/**
103+
* @param \ReflectionClass<\Rector\Rector\AbstractRector> $reflectionClass
104+
*
105+
* @throws \ReflectionException
106+
*
107+
* @return list<class-string<\PhpParser\Node>>
108+
*/
109+
protected function nodeTypes(\ReflectionClass $reflectionClass): array
110+
{
111+
return $reflectionClass->newInstanceWithoutConstructor()->getNodeTypes();
184112
}
185113
}

tests/Rector/Class_/UpdateRectorRefactorParamDocblockFromNodeTypesRector/UpdateRectorRefactorParamDocblockFromNodeTypesRectorTest.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
use Guanguans\RectorRulesTests\Rector\AbstractRectorTestCase;
2424

2525
/**
26+
* @covers \Guanguans\RectorRules\Rector\Class_\AbstractUpdateClassMethodNodeParamDocblockFromNodeTypesRector
2627
* @covers \Guanguans\RectorRules\Rector\Class_\UpdateRectorRefactorParamDocblockFromNodeTypesRector
2728
*/
2829
final class UpdateRectorRefactorParamDocblockFromNodeTypesRectorTest extends AbstractRectorTestCase

0 commit comments

Comments
 (0)