Skip to content

Commit feb487b

Browse files
committed
[code-quality] Add MoveInnerFunctionToTopLevelRector
1 parent 2b27a74 commit feb487b

7 files changed

Lines changed: 240 additions & 0 deletions

File tree

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
<?php
2+
3+
namespace Rector\Tests\CodeQuality\Rector\StmtsAwareInterface\MoveInnerFunctionToTopLevelRector\Fixture;
4+
5+
function outer(): void
6+
{
7+
function inner(): void
8+
{
9+
echo 'hello';
10+
}
11+
12+
inner();
13+
}
14+
15+
?>
16+
-----
17+
<?php
18+
19+
namespace Rector\Tests\CodeQuality\Rector\StmtsAwareInterface\MoveInnerFunctionToTopLevelRector\Fixture;
20+
21+
function inner(): void
22+
{
23+
echo 'hello';
24+
}
25+
function outer(): void
26+
{
27+
inner();
28+
}
29+
30+
?>
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<?php
2+
3+
namespace Rector\Tests\CodeQuality\Rector\StmtsAwareInterface\MoveInnerFunctionToTopLevelRector\Fixture;
4+
5+
function existing(): void
6+
{
7+
}
8+
9+
function outer_with_collision(): void
10+
{
11+
function existing(): void
12+
{
13+
echo 'inner';
14+
}
15+
16+
existing();
17+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<?php
2+
3+
namespace Rector\Tests\CodeQuality\Rector\StmtsAwareInterface\MoveInnerFunctionToTopLevelRector\Fixture;
4+
5+
function already_top_level(): void
6+
{
7+
echo 'hello';
8+
}
9+
10+
function caller(): void
11+
{
12+
already_top_level();
13+
}
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\StmtsAwareInterface\MoveInnerFunctionToTopLevelRector;
6+
7+
use Iterator;
8+
use PHPUnit\Framework\Attributes\DataProvider;
9+
use Rector\Testing\PHPUnit\AbstractRectorTestCase;
10+
11+
final class MoveInnerFunctionToTopLevelRectorTest 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\StmtsAwareInterface\MoveInnerFunctionToTopLevelRector;
6+
use Rector\Config\RectorConfig;
7+
8+
return RectorConfig::configure()
9+
->withRules([MoveInnerFunctionToTopLevelRector::class]);
Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Rector\CodeQuality\Rector\StmtsAwareInterface;
6+
7+
use PhpParser\Node;
8+
use PhpParser\Node\Stmt\Function_;
9+
use Rector\PhpParser\Enum\NodeGroup;
10+
use Rector\Rector\AbstractRector;
11+
use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample;
12+
use Symplify\RuleDocGenerator\ValueObject\RuleDefinition;
13+
14+
/**
15+
* @see \Rector\Tests\CodeQuality\Rector\StmtsAwareInterface\MoveInnerFunctionToTopLevelRector\MoveInnerFunctionToTopLevelRectorTest
16+
*/
17+
final class MoveInnerFunctionToTopLevelRector extends AbstractRector
18+
{
19+
public function getRuleDefinition(): RuleDefinition
20+
{
21+
return new RuleDefinition(
22+
'Move an inner named function to the top level, as inner named functions are not supported by PHPStan',
23+
[
24+
new CodeSample(
25+
<<<'CODE_SAMPLE'
26+
function outer(): void
27+
{
28+
function inner(): void
29+
{
30+
echo 'hello';
31+
}
32+
33+
inner();
34+
}
35+
CODE_SAMPLE
36+
,
37+
<<<'CODE_SAMPLE'
38+
function inner(): void
39+
{
40+
echo 'hello';
41+
}
42+
43+
function outer(): void
44+
{
45+
inner();
46+
}
47+
CODE_SAMPLE
48+
),
49+
]
50+
);
51+
}
52+
53+
/**
54+
* @return array<class-string<Node>>
55+
*/
56+
public function getNodeTypes(): array
57+
{
58+
return NodeGroup::STMTS_AWARE;
59+
}
60+
61+
/**
62+
* @param StmtsAware $node
63+
*/
64+
public function refactor(Node $node): ?Node
65+
{
66+
$stmts = (array) $node->stmts;
67+
68+
$newStmts = [];
69+
$hasChanged = false;
70+
71+
foreach ($stmts as $stmt) {
72+
if (! $stmt instanceof Function_) {
73+
$newStmts[] = $stmt;
74+
continue;
75+
}
76+
77+
$innerFunctions = $this->extractInnerFunctions($stmt, $stmts);
78+
foreach ($innerFunctions as $innerFunction) {
79+
$newStmts[] = $innerFunction;
80+
$hasChanged = true;
81+
}
82+
83+
$newStmts[] = $stmt;
84+
}
85+
86+
if (! $hasChanged) {
87+
return null;
88+
}
89+
90+
$node->stmts = $newStmts;
91+
92+
return $node;
93+
}
94+
95+
/**
96+
* @param Node\Stmt[] $siblingStmts
97+
* @return Function_[]
98+
*/
99+
private function extractInnerFunctions(Function_ $outerFunction, array $siblingStmts): array
100+
{
101+
$innerFunctions = [];
102+
103+
foreach ($outerFunction->stmts as $key => $innerStmt) {
104+
if (! $innerStmt instanceof Function_) {
105+
continue;
106+
}
107+
108+
// avoid fatal error on name collision with an existing top-level function
109+
if ($this->hasSiblingFunctionOfSameName($innerStmt, $siblingStmts)) {
110+
continue;
111+
}
112+
113+
$innerFunctions[] = $innerStmt;
114+
unset($outerFunction->stmts[$key]);
115+
}
116+
117+
if ($innerFunctions !== []) {
118+
$outerFunction->stmts = array_values($outerFunction->stmts);
119+
}
120+
121+
return $innerFunctions;
122+
}
123+
124+
/**
125+
* @param Node\Stmt[] $siblingStmts
126+
*/
127+
private function hasSiblingFunctionOfSameName(Function_ $innerFunction, array $siblingStmts): bool
128+
{
129+
foreach ($siblingStmts as $siblingStmt) {
130+
if (! $siblingStmt instanceof Function_) {
131+
continue;
132+
}
133+
134+
if ($this->nodeNameResolver->areNamesEqual($siblingStmt, $innerFunction)) {
135+
return true;
136+
}
137+
}
138+
139+
return false;
140+
}
141+
}

‎src/Config/Level/CodeQualityLevel.php‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@
7070
use Rector\CodeQuality\Rector\New_\NewStaticToNewSelfRector;
7171
use Rector\CodeQuality\Rector\NotEqual\CommonNotEqualRector;
7272
use Rector\CodeQuality\Rector\NullsafeMethodCall\CleanupUnneededNullsafeOperatorRector;
73+
use Rector\CodeQuality\Rector\StmtsAwareInterface\MoveInnerFunctionToTopLevelRector;
7374
use Rector\CodeQuality\Rector\Switch_\SingularSwitchToIfRector;
7475
use Rector\CodeQuality\Rector\Switch_\SwitchTrueToIfRector;
7576
use Rector\CodeQuality\Rector\Ternary\ArrayKeyExistsTernaryThenValueToCoalescingRector;
@@ -185,6 +186,7 @@ final class CodeQualityLevel
185186
SortAttributeNamedArgsRector::class,
186187
RemoveReadonlyPropertyVisibilityOnReadonlyClassRector::class,
187188
SafeDeclareStrictTypesRector::class,
189+
MoveInnerFunctionToTopLevelRector::class,
188190
];
189191

190192
/**

0 commit comments

Comments
 (0)