Skip to content

Commit d64f8d8

Browse files
authored
[Renaming] Do not rename extends its name on RenameClassRector (#7328)
* [Renaming] Do not rename extends its name on RenameClassRector * Fix
1 parent ffe171f commit d64f8d8

File tree

4 files changed

+63
-7
lines changed

4 files changed

+63
-7
lines changed
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
<?php
2+
3+
namespace MyNamespace;
4+
5+
abstract class AbstractMigration {}
6+
7+
abstract class BaseMigration extends AbstractMigration {}
8+
9+
class MyMigration extends AbstractMigration {}
10+
11+
?>
12+
-----
13+
<?php
14+
15+
namespace MyNamespace;
16+
17+
abstract class AbstractMigration {}
18+
19+
abstract class BaseMigration extends AbstractMigration {}
20+
21+
class MyMigration extends \MyNamespace\BaseMigration {}
22+
23+
?>

rules-tests/Renaming/Rector/Name/RenameClassRector/config/configured_rule.php

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,5 +62,13 @@
6262
// test skip rename class const fetch when target rename is interface
6363
// and interface class const fetch not found
6464
'Symfony\Component\Serializer\Normalizer\ObjectNormalizer' => 'Symfony\Component\Serializer\Normalizer\NormalizerInterface',
65+
66+
/**
67+
* // avoid error extends itself
68+
* namespace MyNamespace;
69+
*
70+
* class MyMigration extends \MyNamespace\BaseMigration {}
71+
*/
72+
'MyNamespace\AbstractMigration' => 'MyNamespace\BaseMigration',
6573
]);
6674
};

rules/Renaming/NodeManipulator/ClassRenamer.php

Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -54,14 +54,14 @@ public function renameNode(Node $node, array $oldToNewClasses, ?Scope $scope): ?
5454

5555
// execute FullyQualified before Name on purpose so next Name check is pure Name node
5656
if ($node instanceof FullyQualified) {
57-
return $this->refactorName($node, $oldToNewClasses);
57+
return $this->refactorName($node, $oldToNewClasses, $scope);
5858
}
5959

6060
// Name as parent of FullyQualified executed for fallback annotation to attribute rename to Name
6161
if ($node instanceof Name) {
6262
$phpAttributeName = $node->getAttribute(AttributeKey::PHP_ATTRIBUTE_NAME);
6363
if (is_string($phpAttributeName)) {
64-
return $this->refactorName(new FullyQualified($phpAttributeName), $oldToNewClasses);
64+
return $this->refactorName(new FullyQualified($phpAttributeName), $oldToNewClasses, $scope);
6565
}
6666

6767
return null;
@@ -133,8 +133,11 @@ private function shouldSkip(string $newName, FullyQualified $fullyQualified): bo
133133
/**
134134
* @param array<string, string> $oldToNewClasses
135135
*/
136-
private function refactorName(FullyQualified $fullyQualified, array $oldToNewClasses): ?FullyQualified
137-
{
136+
private function refactorName(
137+
FullyQualified $fullyQualified,
138+
array $oldToNewClasses,
139+
?Scope $scope
140+
): ?FullyQualified {
138141
if ($fullyQualified->getAttribute(AttributeKey::IS_FUNCCALL_NAME) === true) {
139142
return null;
140143
}
@@ -146,7 +149,7 @@ private function refactorName(FullyQualified $fullyQualified, array $oldToNewCla
146149
return null;
147150
}
148151

149-
if (! $this->isClassToInterfaceValidChange($fullyQualified, $newName)) {
152+
if (! $this->isClassToInterfaceValidChange($fullyQualified, $newName, $scope)) {
150153
return null;
151154
}
152155

@@ -202,15 +205,26 @@ private function refactorClassLike(ClassLike $classLike, array $oldToNewClasses,
202205
* - implements SomeInterface
203206
* - implements SomeClass
204207
*/
205-
private function isClassToInterfaceValidChange(FullyQualified $fullyQualified, string $newClassName): bool
206-
{
208+
private function isClassToInterfaceValidChange(
209+
FullyQualified $fullyQualified,
210+
string $newClassName,
211+
?Scope $scope
212+
): bool {
207213
if (! $this->reflectionProvider->hasClass($newClassName)) {
208214
return true;
209215
}
210216

211217
$classReflection = $this->reflectionProvider->getClass($newClassName);
212218
// ensure new is not with interface
213219
if ($fullyQualified->getAttribute(AttributeKey::IS_NEW_INSTANCE_NAME) !== true) {
220+
if ($fullyQualified->getAttribute(AttributeKey::IS_CLASS_EXTENDS) === true && $scope instanceof Scope) {
221+
$currentClassReflection = $scope->getClassReflection();
222+
223+
if ($currentClassReflection instanceof ClassReflection && $currentClassReflection->getName() === $newClassName) {
224+
return false;
225+
}
226+
}
227+
214228
return $this->isValidClassNameChange($fullyQualified, $classReflection);
215229
}
216230

src/NodeTypeResolver/PHPStan/Scope/PHPStanNodeScopeResolver.php

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@
5555
use PhpParser\Node\Identifier;
5656
use PhpParser\Node\IntersectionType;
5757
use PhpParser\Node\Name;
58+
use PhpParser\Node\Name\FullyQualified;
5859
use PhpParser\Node\NullableType;
5960
use PhpParser\Node\Param;
6061
use PhpParser\Node\Stmt;
@@ -168,6 +169,16 @@ public function processNodes(
168169
$mutatingScope = $this->resolveClassOrInterfaceScope($node, $mutatingScope);
169170
$node->setAttribute(AttributeKey::SCOPE, $mutatingScope);
170171

172+
if ($node instanceof Class_) {
173+
if ($node->extends instanceof FullyQualified) {
174+
$node->extends->setAttribute(AttributeKey::SCOPE, $mutatingScope);
175+
}
176+
177+
foreach ($node->implements as $implement) {
178+
$implement->setAttribute(AttributeKey::SCOPE, $mutatingScope);
179+
}
180+
}
181+
171182
return;
172183
}
173184

0 commit comments

Comments
 (0)