Skip to content

Commit 4573a65

Browse files
committed
feat(refactor): Add RenameGarbageVariableNameRector class
- Introduces a new rector to rename garbage variable names in function-like nodes. - Enhances code readability and maintainability by enforcing better naming conventions. - Provides code samples for usage and demonstrates the refactoring process.
1 parent b7efd3e commit 4573a65

File tree

9 files changed

+369
-2
lines changed

9 files changed

+369
-2
lines changed

composer-dependency-analyser.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313

1414
use ShipMonk\ComposerDependencyAnalyser\Config\Configuration;
1515
use ShipMonk\ComposerDependencyAnalyser\Config\ErrorType;
16+
use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample;
1617

1718
return (new Configuration)
1819
->addPathsToScan(
@@ -26,7 +27,7 @@
2627
__DIR__.'/tests/',
2728
])
2829
->ignoreUnknownClasses([
29-
// \SensitiveParameter::class,
30+
CodeSample::class,
3031
])
3132
/** @see \ShipMonk\ComposerDependencyAnalyser\Analyser::CORE_EXTENSIONS */
3233
->ignoreErrorsOnExtensions(

composer.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -378,7 +378,7 @@
378378
"rector:process-clear-cache": "@rector:process --clear-cache",
379379
"rector:process-clear-cache-dry-run": "@rector:process-clear-cache --dry-run",
380380
"rector:process-dry-run": "@rector:process --dry-run",
381-
"rector:process-only": "@rector:process-clear-cache tests.php --only=Guanguans\\RectorRules\\Rector\\Array_\\SortListItemOfSameScalarTypeRector",
381+
"rector:process-only": "@rector:process-clear-cache tests.php --only=Guanguans\\RectorRules\\Rector\\FunctionLike\\RenameGarbageVariableNameRector",
382382
"rector:process-only-dry-run": "@rector:process-only --dry-run",
383383
"rule-doc-generator": [
384384
"@putenv:php",

config/set/common.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,15 @@
1717
use Guanguans\RectorRules\Rector\Array_\SortListItemOfSameScalarTypeRector;
1818
use Guanguans\RectorRules\Rector\File\SortFileFirstStmtDocblockRector;
1919
use Guanguans\RectorRules\Rector\File\SortFileFunctionStmtRector;
20+
use Guanguans\RectorRules\Rector\FunctionLike\RenameGarbageVariableNameRector;
2021
use Guanguans\RectorRules\Rector\Namespace_\RemoveNamespaceRector;
2122
use Rector\Config\RectorConfig;
2223

2324
return static function (RectorConfig $rectorConfig): void {
2425
$rectorConfig->import(__DIR__.'/../config.php');
2526
$rectorConfig->rules([
2627
RemoveNamespaceRector::class,
28+
RenameGarbageVariableNameRector::class,
2729
SimplifyListIndexRector::class,
2830
SortFileFirstStmtDocblockRector::class,
2931
SortFileFunctionStmtRector::class,

rector.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
use Rector\DowngradePhp80\Rector\FuncCall\DowngradeStrEndsWithRector;
3939
use Rector\DowngradePhp80\Rector\FuncCall\DowngradeStrStartsWithRector;
4040
use Rector\DowngradePhp81\Rector\FuncCall\DowngradeArrayIsListRector;
41+
use Rector\DowngradePhp85\Rector\FuncCall\DowngradeArrayFirstLastRector;
4142
use Rector\EarlyReturn\Rector\If_\ChangeOrIfContinueToMultiContinueRector;
4243
use Rector\EarlyReturn\Rector\Return_\ReturnBinaryOrToEarlyReturnRector;
4344
use Rector\Naming\Rector\ClassMethod\RenameParamToMatchTypeRector;
@@ -175,6 +176,7 @@ classes(static fn (string $class, string $file): bool => str_starts_with($class,
175176
'test' => 'it',
176177
])
177178
->withSkip([
179+
DowngradeArrayFirstLastRector::class,
178180
DowngradeArrayIsListRector::class,
179181
DowngradeStrContainsRector::class,
180182
DowngradeStrEndsWithRector::class,
Lines changed: 240 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,240 @@
1+
<?php
2+
3+
/** @noinspection PhpMultipleClassDeclarationsInspection */
4+
/** @noinspection PhpUnusedAliasInspection */
5+
declare(strict_types=1);
6+
7+
/**
8+
* Copyright (c) 2025-2026 guanguans<ityaozm@gmail.com>
9+
*
10+
* For the full copyright and license information, please view
11+
* the LICENSE file that was distributed with this source code.
12+
*
13+
* @see https://github.com/guanguans/rector-rules
14+
*/
15+
16+
namespace Guanguans\RectorRules\Rector\FunctionLike;
17+
18+
use Guanguans\RectorRules\Rector\AbstractRector;
19+
use PhpParser\Node;
20+
use PhpParser\Node\Expr\ArrowFunction;
21+
use PhpParser\Node\Expr\Closure;
22+
use PhpParser\Node\Expr\Variable;
23+
use PhpParser\Node\FunctionLike;
24+
use PhpParser\Node\Param;
25+
use PhpParser\Node\Stmt\Foreach_;
26+
use PhpParser\NodeFinder;
27+
use PhpParser\NodeTraverser;
28+
use PhpParser\NodeVisitor\FirstFindingVisitor;
29+
use PHPStan\PhpDocParser\Ast\PhpDoc\ParamTagValueNode;
30+
use Rector\BetterPhpDocParser\PhpDocInfo\PhpDocInfo;
31+
use Rector\BetterPhpDocParser\PhpDocInfo\PhpDocInfoFactory;
32+
use Rector\Comments\NodeDocBlock\DocBlockUpdater;
33+
use Rector\DeadCode\NodeAnalyzer\ExprUsedInNodeAnalyzer;
34+
use Rector\NodeManipulator\StmtsManipulator;
35+
use Rector\PhpParser\Enum\NodeGroup;
36+
use Rector\PhpParser\Node\BetterNodeFinder;
37+
use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample;
38+
39+
/**
40+
* @see \Guanguans\RectorRulesTests\Rector\FunctionLike\RenameGarbageVariableNameRector\RenameGarbageVariableNameRectorTest
41+
*/
42+
final class RenameGarbageVariableNameRector extends AbstractRector
43+
{
44+
private const GARBAGE_VARIABLE_NAME = '_';
45+
private BetterNodeFinder $betterNodeFinder;
46+
private DocBlockUpdater $docBlockUpdater;
47+
private ExprUsedInNodeAnalyzer $exprUsedInNodeAnalyzer;
48+
private NodeFinder $nodeFinder;
49+
private PhpDocInfoFactory $phpDocInfoFactory;
50+
private StmtsManipulator $stmtsManipulator;
51+
52+
public function __construct(
53+
BetterNodeFinder $betterNodeFinder,
54+
DocBlockUpdater $docBlockUpdater,
55+
ExprUsedInNodeAnalyzer $exprUsedInNodeAnalyzer,
56+
NodeFinder $nodeFinder,
57+
PhpDocInfoFactory $phpDocInfoFactory,
58+
StmtsManipulator $stmtsManipulator
59+
) {
60+
$this->betterNodeFinder = $betterNodeFinder;
61+
$this->docBlockUpdater = $docBlockUpdater;
62+
$this->exprUsedInNodeAnalyzer = $exprUsedInNodeAnalyzer;
63+
$this->nodeFinder = $nodeFinder;
64+
$this->phpDocInfoFactory = $phpDocInfoFactory;
65+
$this->stmtsManipulator = $stmtsManipulator;
66+
}
67+
68+
public function getNodeTypes(): array
69+
{
70+
return [
71+
// ArrowFunction::class,
72+
// Closure::class,
73+
// NodeGroup::STMTS_AWARE,
74+
FunctionLike::class,
75+
Foreach_::class,
76+
];
77+
}
78+
79+
/**
80+
* @see \Rector\DeadCode\Rector\Assign\RemoveUnusedVariableAssignRector
81+
* @see \Rector\DeadCode\Rector\ClassMethod\RemoveUnusedConstructorParamRector
82+
* @see \Rector\DeadCode\Rector\Closure\RemoveUnusedClosureVariableUseRector
83+
* @see \Rector\DeadCode\Rector\Foreach_\RemoveUnusedForeachKeyRector
84+
* @see \Rector\DeadCode\Rector\If_\RemoveUnusedNonEmptyArrayBeforeForeachRector
85+
* @see \Rector\Php80\Rector\Catch_\RemoveUnusedVariableInCatchRector
86+
*
87+
* @param \PhpParser\Node\FunctionLike $node
88+
*/
89+
public function refactor(Node $node): ?Node
90+
{
91+
return $node instanceof Foreach_
92+
? $this->refactorForeach($node)
93+
: $this->refactorFunctionLike($node);
94+
}
95+
96+
/**
97+
* @return list<\Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample>
98+
*/
99+
protected function codeSamples(): array
100+
{
101+
return [
102+
new CodeSample(
103+
<<<'PHP'
104+
/** @noinspection ALL */
105+
collect($array)->filter(static function (string $value, int $key): bool {
106+
return 2 === $key;
107+
});
108+
109+
function array_is_list(array $array): bool
110+
{
111+
$nextKey = -1;
112+
113+
foreach ($array as $key => $value) {
114+
if ($key !== ++$nextKey) {
115+
return false;
116+
}
117+
}
118+
119+
return true;
120+
}
121+
PHP,
122+
<<<'PHP'
123+
/** @noinspection ALL */
124+
collect($array)->filter(static function (string $_, int $key): bool {
125+
return 2 === $key;
126+
});
127+
128+
function array_is_list(array $array): bool
129+
{
130+
$nextKey = -1;
131+
132+
foreach ($array as $key => $_) {
133+
if ($key !== ++$nextKey) {
134+
return false;
135+
}
136+
}
137+
138+
return true;
139+
}
140+
PHP,
141+
),
142+
];
143+
}
144+
145+
private function refactorForeach(Foreach_ $foreachNode): ?Foreach_
146+
{
147+
if (
148+
!$foreachNode->keyVar instanceof Variable
149+
|| !$foreachNode->valueVar instanceof Variable
150+
|| !$this->isUsedVariable($foreachNode, $foreachNode->keyVar)
151+
|| $this->isUsedVariable($foreachNode, $foreachNode->valueVar)
152+
) {
153+
return null;
154+
}
155+
156+
$hasChanged = false;
157+
158+
if (self::GARBAGE_VARIABLE_NAME !== $foreachNode->valueVar->name) {
159+
$foreachNode->valueVar->name = self::GARBAGE_VARIABLE_NAME;
160+
$hasChanged = true;
161+
}
162+
163+
return $hasChanged ? $foreachNode : null;
164+
}
165+
166+
private function refactorFunctionLike(FunctionLike $node): ?FunctionLike
167+
{
168+
$lastParamNode = array_last($node->getParams());
169+
170+
if (
171+
!$lastParamNode->var instanceof Variable
172+
|| !$this->isUsedVariable($node, $lastParamNode->var)
173+
) {
174+
return null;
175+
}
176+
177+
$paramsNode = collect($node->getParams())
178+
->slice(0, -1)
179+
->filter(
180+
fn (Param $paramNode): bool => $paramNode->var instanceof Variable
181+
&& !$this->isUsedVariable($node, $paramNode->var)
182+
);
183+
184+
$hasChanged = false;
185+
$newName = self::GARBAGE_VARIABLE_NAME;
186+
$phpDocInfo = $this->phpDocInfoFactory->createFromNode($node);
187+
188+
foreach ($paramsNode as $paramNode) {
189+
if ($newName !== $paramNode->var->name) {
190+
$oldName = $paramNode->var->name;
191+
$paramNode->var->name = $newName;
192+
$hasChanged = true;
193+
194+
if (!$phpDocInfo instanceof PhpDocInfo) {
195+
continue;
196+
}
197+
198+
$paramTagValues = $phpDocInfo->getPhpDocNode()->getParamTagValues();
199+
200+
/**
201+
* @see \Rector\Naming\Rector\ClassMethod\RenameParamToMatchTypeRector
202+
*/
203+
foreach ($paramTagValues as $paramTagValue) {
204+
if ('$'.$oldName === $paramTagValue->parameterName) {
205+
$paramTagValue->parameterName = '$'.$newName;
206+
$phpDocInfo->removeByType(ParamTagValueNode::class, $oldName);
207+
$this->docBlockUpdater->updateRefactoredNodeWithPhpDocInfo($node);
208+
}
209+
}
210+
}
211+
212+
$newName .= self::GARBAGE_VARIABLE_NAME;
213+
}
214+
215+
return $hasChanged ? $node : null;
216+
}
217+
218+
private function isUsedVariable(Node $node, Variable $variableNode): bool
219+
{
220+
// $variableName = $this->getName($variableNode);
221+
// $nodeTraverser = new NodeTraverser($firstFindingVisitor = new FirstFindingVisitor(
222+
// function (Node $node) use ($variableName, $variableNode): bool {
223+
// if (
224+
// !$node instanceof Variable
225+
// || !$this->isName($node, $variableName)
226+
// ) {
227+
// return false;
228+
// }
229+
//
230+
// return $node !== $variableNode;
231+
// }
232+
// ));
233+
// $nodeTraverser->traverse([$node]);
234+
// return $firstFindingVisitor->getFoundNode();
235+
return (bool) $this->betterNodeFinder->findFirst(
236+
$node->stmts ?? [],
237+
fn (Node $subNode): bool => $this->exprUsedInNodeAnalyzer->isUsed($subNode, $variableNode)
238+
);
239+
}
240+
}
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
<?php
2+
3+
/** @noinspection ALL */
4+
collect($array)->filter(
5+
/**
6+
* @param mixed $value
7+
*/
8+
static function ($value, int $key): bool {
9+
return 2 === $key;
10+
}
11+
);
12+
13+
function array_is_list(array $array): bool
14+
{
15+
$nextKey = -1;
16+
17+
foreach ($array as $key => $value) {
18+
if ($key !== ++$nextKey) {
19+
return false;
20+
}
21+
}
22+
23+
return true;
24+
}
25+
26+
?>
27+
-----
28+
<?php
29+
30+
/** @noinspection ALL */
31+
collect($array)->filter(
32+
/**
33+
* @param mixed $value
34+
*/
35+
static function ($_, int $key): bool {
36+
return 2 === $key;
37+
}
38+
);
39+
40+
function array_is_list(array $array): bool
41+
{
42+
$nextKey = -1;
43+
44+
foreach ($array as $key => $_) {
45+
if ($key !== ++$nextKey) {
46+
return false;
47+
}
48+
}
49+
50+
return true;
51+
}
52+
53+
?>
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<?php
2+
3+
/** @noinspection ALL */
4+
static fn(string $class, string $file): bool => true;
5+
6+
abstract class Foo
7+
{
8+
abstract protected function classMethodNode(Class_ $classNode): ?ClassMethod;
9+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
<?php
2+
3+
/** @noinspection AnonymousFunctionStaticInspection */
4+
/** @noinspection NullPointerExceptionInspection */
5+
/** @noinspection PhpPossiblePolymorphicInvocationInspection */
6+
/** @noinspection PhpUndefinedClassInspection */
7+
/** @noinspection PhpUnhandledExceptionInspection */
8+
/** @noinspection PhpVoidFunctionResultUsedInspection */
9+
/** @noinspection StaticClosureCanBeUsedInspection */
10+
declare(strict_types=1);
11+
12+
/**
13+
* Copyright (c) 2025-2026 guanguans<ityaozm@gmail.com>
14+
*
15+
* For the full copyright and license information, please view
16+
* the LICENSE file that was distributed with this source code.
17+
*
18+
* @see https://github.com/guanguans/rector-rules
19+
*/
20+
21+
namespace Guanguans\RectorRulesTests\Rector\FunctionLike\RenameGarbageVariableNameRector;
22+
23+
use Guanguans\RectorRulesTests\Rector\AbstractRectorTestCase;
24+
25+
/**
26+
* @covers \Guanguans\RectorRules\Rector\FunctionLike\RenameGarbageVariableNameRector
27+
*/
28+
final class RenameGarbageVariableNameRectorTest extends AbstractRectorTestCase
29+
{
30+
protected static function directory(): string
31+
{
32+
return __DIR__;
33+
}
34+
}

0 commit comments

Comments
 (0)