Skip to content

Commit fe1cc2d

Browse files
committed
add support for class as well
1 parent feb487b commit fe1cc2d

8 files changed

Lines changed: 342 additions & 1 deletion

File tree

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
<?php
2+
3+
namespace Rector\Tests\CodeQuality\Rector\Class_\InnerFunctionToPrivateMethodRector\Fixture;
4+
5+
class SomeClass
6+
{
7+
public function run(): void
8+
{
9+
function inner(): void
10+
{
11+
echo 'hello';
12+
}
13+
14+
inner();
15+
}
16+
}
17+
18+
?>
19+
-----
20+
<?php
21+
22+
namespace Rector\Tests\CodeQuality\Rector\Class_\InnerFunctionToPrivateMethodRector\Fixture;
23+
24+
class SomeClass
25+
{
26+
public function run(): void
27+
{
28+
$this->inner();
29+
}
30+
private function inner(): void
31+
{
32+
echo 'hello';
33+
}
34+
}
35+
36+
?>
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<?php
2+
3+
namespace Rector\Tests\CodeQuality\Rector\Class_\InnerFunctionToPrivateMethodRector\Fixture;
4+
5+
class CollisionClass
6+
{
7+
public function run(): void
8+
{
9+
function existing(): void
10+
{
11+
echo 'inner';
12+
}
13+
14+
existing();
15+
}
16+
17+
private function existing(): void
18+
{
19+
echo 'method';
20+
}
21+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<?php
2+
3+
namespace Rector\Tests\CodeQuality\Rector\Class_\InnerFunctionToPrivateMethodRector\Fixture;
4+
5+
class NoInnerFunction
6+
{
7+
public function run(): void
8+
{
9+
echo 'hello';
10+
}
11+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
<?php
2+
3+
namespace Rector\Tests\CodeQuality\Rector\Class_\InnerFunctionToPrivateMethodRector\Fixture;
4+
5+
class StaticClass
6+
{
7+
public static function run(): void
8+
{
9+
function staticInner(int $value): int
10+
{
11+
return $value * 2;
12+
}
13+
14+
staticInner(5);
15+
}
16+
}
17+
18+
?>
19+
-----
20+
<?php
21+
22+
namespace Rector\Tests\CodeQuality\Rector\Class_\InnerFunctionToPrivateMethodRector\Fixture;
23+
24+
class StaticClass
25+
{
26+
public static function run(): void
27+
{
28+
self::staticInner(5);
29+
}
30+
private static function staticInner(int $value): int
31+
{
32+
return $value * 2;
33+
}
34+
}
35+
36+
?>
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\Tests\CodeQuality\Rector\Class_\InnerFunctionToPrivateMethodRector;
6+
7+
use Iterator;
8+
use PHPUnit\Framework\Attributes\DataProvider;
9+
use Rector\Testing\PHPUnit\AbstractRectorTestCase;
10+
11+
final class InnerFunctionToPrivateMethodRectorTest 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+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
use Rector\CodeQuality\Rector\Class_\InnerFunctionToPrivateMethodRector;
6+
use Rector\Config\RectorConfig;
7+
8+
return RectorConfig::configure()
9+
->withRules([InnerFunctionToPrivateMethodRector::class]);
Lines changed: 198 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,198 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Rector\CodeQuality\Rector\Class_;
6+
7+
use PhpParser\Modifiers;
8+
use PhpParser\Node;
9+
use PhpParser\Node\Expr\FuncCall;
10+
use PhpParser\Node\Expr\MethodCall;
11+
use PhpParser\Node\Expr\StaticCall;
12+
use PhpParser\Node\Expr\Variable;
13+
use PhpParser\Node\Name;
14+
use PhpParser\Node\Stmt\Class_;
15+
use PhpParser\Node\Stmt\ClassMethod;
16+
use PhpParser\Node\Stmt\Function_;
17+
use Rector\Rector\AbstractRector;
18+
use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample;
19+
use Symplify\RuleDocGenerator\ValueObject\RuleDefinition;
20+
21+
/**
22+
* @see \Rector\Tests\CodeQuality\Rector\Class_\InnerFunctionToPrivateMethodRector\InnerFunctionToPrivateMethodRectorTest
23+
*/
24+
final class InnerFunctionToPrivateMethodRector extends AbstractRector
25+
{
26+
public function getRuleDefinition(): RuleDefinition
27+
{
28+
return new RuleDefinition(
29+
'Turn an inner named function declared inside a class method into a private method, as inner named functions are not supported by PHPStan',
30+
[
31+
new CodeSample(
32+
<<<'CODE_SAMPLE'
33+
class SomeClass
34+
{
35+
public function run(): void
36+
{
37+
function inner(): void
38+
{
39+
echo 'hello';
40+
}
41+
42+
inner();
43+
}
44+
}
45+
CODE_SAMPLE
46+
,
47+
<<<'CODE_SAMPLE'
48+
class SomeClass
49+
{
50+
public function run(): void
51+
{
52+
$this->inner();
53+
}
54+
55+
private function inner(): void
56+
{
57+
echo 'hello';
58+
}
59+
}
60+
CODE_SAMPLE
61+
),
62+
]
63+
);
64+
}
65+
66+
/**
67+
* @return array<class-string<Node>>
68+
*/
69+
public function getNodeTypes(): array
70+
{
71+
return [Class_::class];
72+
}
73+
74+
/**
75+
* @param Class_ $node
76+
*/
77+
public function refactor(Node $node): ?Node
78+
{
79+
$existingMethodNames = array_map(
80+
fn (ClassMethod $classMethod): string => $classMethod->name->toString(),
81+
$node->getMethods()
82+
);
83+
84+
$newClassMethods = [];
85+
86+
foreach ($node->getMethods() as $classMethod) {
87+
$innerFunctions = $this->extractInnerFunctions($classMethod, $existingMethodNames);
88+
if ($innerFunctions === []) {
89+
continue;
90+
}
91+
92+
$isStatic = $classMethod->isStatic();
93+
$innerFunctionNames = array_map(
94+
fn (Function_ $innerFunction): string => $innerFunction->name->toString(),
95+
$innerFunctions
96+
);
97+
98+
foreach ($innerFunctions as $innerFunction) {
99+
$newClassMethod = $this->createPrivateMethod($innerFunction, $isStatic);
100+
$this->rewriteCallsToMethod($newClassMethod, $innerFunctionNames, $isStatic);
101+
$newClassMethods[] = $newClassMethod;
102+
}
103+
104+
$this->rewriteCallsToMethod($classMethod, $innerFunctionNames, $isStatic);
105+
}
106+
107+
if ($newClassMethods === []) {
108+
return null;
109+
}
110+
111+
$node->stmts = array_merge($node->stmts, $newClassMethods);
112+
113+
return $node;
114+
}
115+
116+
/**
117+
* @param string[] $existingMethodNames
118+
* @return Function_[]
119+
*/
120+
private function extractInnerFunctions(ClassMethod $classMethod, array &$existingMethodNames): array
121+
{
122+
$innerFunctions = [];
123+
124+
foreach ($classMethod->stmts ?? [] as $key => $stmt) {
125+
if (! $stmt instanceof Function_) {
126+
continue;
127+
}
128+
129+
$functionName = $stmt->name->toString();
130+
131+
// avoid collision with an already existing method
132+
if (in_array($functionName, $existingMethodNames, true)) {
133+
continue;
134+
}
135+
136+
$existingMethodNames[] = $functionName;
137+
$innerFunctions[] = $stmt;
138+
unset($classMethod->stmts[$key]);
139+
}
140+
141+
if ($innerFunctions !== []) {
142+
$classMethod->stmts = array_values((array) $classMethod->stmts);
143+
}
144+
145+
return $innerFunctions;
146+
}
147+
148+
private function createPrivateMethod(Function_ $innerFunction, bool $isStatic): ClassMethod
149+
{
150+
$flags = Modifiers::PRIVATE;
151+
if ($isStatic) {
152+
$flags |= Modifiers::STATIC;
153+
}
154+
155+
$classMethod = new ClassMethod($innerFunction->name, [
156+
'flags' => $flags,
157+
'byRef' => $innerFunction->byRef,
158+
'params' => $innerFunction->params,
159+
'returnType' => $innerFunction->returnType,
160+
'stmts' => $innerFunction->stmts,
161+
'attrGroups' => $innerFunction->attrGroups,
162+
]);
163+
164+
$this->mirrorComments($classMethod, $innerFunction);
165+
166+
return $classMethod;
167+
}
168+
169+
/**
170+
* @param string[] $innerFunctionNames
171+
*/
172+
private function rewriteCallsToMethod(ClassMethod $classMethod, array $innerFunctionNames, bool $isStatic): void
173+
{
174+
$this->traverseNodesWithCallable($classMethod->stmts ?? [], function (Node $node) use (
175+
$innerFunctionNames,
176+
$isStatic
177+
): MethodCall|StaticCall|null {
178+
if (! $node instanceof FuncCall) {
179+
return null;
180+
}
181+
182+
if (! $node->name instanceof Name) {
183+
return null;
184+
}
185+
186+
$functionName = $node->name->toString();
187+
if (! in_array($functionName, $innerFunctionNames, true)) {
188+
return null;
189+
}
190+
191+
if ($isStatic) {
192+
return new StaticCall(new Name('self'), $functionName, $node->args);
193+
}
194+
195+
return new MethodCall(new Variable('this'), $functionName, $node->args);
196+
});
197+
}
198+
}

‎src/Config/Level/CodeQualityLevel.php‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
use Rector\CodeQuality\Rector\Class_\CompleteDynamicPropertiesRector;
1818
use Rector\CodeQuality\Rector\Class_\ConvertStaticToSelfRector;
1919
use Rector\CodeQuality\Rector\Class_\InlineConstructorDefaultToPropertyRector;
20+
use Rector\CodeQuality\Rector\Class_\InnerFunctionToPrivateMethodRector;
2021
use Rector\CodeQuality\Rector\Class_\RemoveReadonlyPropertyVisibilityOnReadonlyClassRector;
2122
use Rector\CodeQuality\Rector\ClassConstFetch\VariableConstFetchToClassConstFetchRector;
2223
use Rector\CodeQuality\Rector\ClassMethod\ExplicitReturnNullRector;
@@ -114,6 +115,8 @@ final class CodeQualityLevel
114115
ForeachToInArrayRector::class,
115116
RepeatedOrEqualToInArrayRector::class,
116117
RepeatedAndNotEqualToNotInArrayRector::class,
118+
MoveInnerFunctionToTopLevelRector::class,
119+
InnerFunctionToPrivateMethodRector::class,
117120
SimplifyForeachToCoalescingRector::class,
118121
SimplifyFuncGetArgsCountRector::class,
119122
SimplifyInArrayValuesRector::class,
@@ -186,7 +189,6 @@ final class CodeQualityLevel
186189
SortAttributeNamedArgsRector::class,
187190
RemoveReadonlyPropertyVisibilityOnReadonlyClassRector::class,
188191
SafeDeclareStrictTypesRector::class,
189-
MoveInnerFunctionToTopLevelRector::class,
190192
];
191193

192194
/**

0 commit comments

Comments
 (0)