2020use Rector \BetterPhpDocParser \PhpDocManipulator \PhpDocClassRenamer ;
2121use Rector \BetterPhpDocParser \ValueObject \NodeTypes ;
2222use Rector \Comments \NodeDocBlock \DocBlockUpdater ;
23+ use Rector \NodeNameResolver \NodeNameResolver ;
2324use Rector \NodeTypeResolver \Node \AttributeKey ;
2425use Rector \NodeTypeResolver \PhpDoc \NodeAnalyzer \DocBlockClassRenamer ;
2526use Rector \NodeTypeResolver \ValueObject \OldToNewType ;
@@ -44,7 +45,8 @@ public function __construct(
4445 private readonly DocBlockClassRenamer $ docBlockClassRenamer ,
4546 private readonly ReflectionProvider $ reflectionProvider ,
4647 private readonly DocBlockUpdater $ docBlockUpdater ,
47- private readonly RenamedNameCollector $ renamedNameCollector
48+ private readonly RenamedNameCollector $ renamedNameCollector ,
49+ private readonly NodeNameResolver $ nodeNameResolver
4850 ) {
4951 }
5052
@@ -56,14 +58,14 @@ public function renameNode(Node $node, array $oldToNewClasses, ?Scope $scope): ?
5658 {
5759 // execute FullyQualified before Name on purpose so next Name check is pure Name node
5860 if ($ node instanceof FullyQualified) {
59- return $ this ->refactorName ($ node , $ oldToNewClasses, $ scope );
61+ return $ this ->refactorName ($ node , $ oldToNewClasses );
6062 }
6163
6264 // Name as parent of FullyQualified executed for fallback annotation to attribute rename to Name
6365 if ($ node instanceof Name) {
6466 $ phpAttributeName = $ node ->getAttribute (AttributeKey::PHP_ATTRIBUTE_NAME );
6567 if (is_string ($ phpAttributeName )) {
66- return $ this ->refactorName (new FullyQualified ($ phpAttributeName ), $ oldToNewClasses, $ scope );
68+ return $ this ->refactorName (new FullyQualified ($ phpAttributeName ), $ oldToNewClasses );
6769 }
6870
6971 return null ;
@@ -141,21 +143,25 @@ private function shouldSkip(string $newName, FullyQualified $fullyQualified): bo
141143 */
142144 private function refactorName (
143145 FullyQualified $ fullyQualified ,
144- array $ oldToNewClasses ,
145- ?Scope $ scope
146+ array $ oldToNewClasses
146147 ): ?FullyQualified {
147148 if ($ fullyQualified ->getAttribute (AttributeKey::IS_FUNCCALL_NAME ) === true ) {
148149 return null ;
149150 }
150151
152+ // class extends is handled on Class_ node level, to resolve self-extends without scope
153+ if ($ fullyQualified ->getAttribute (AttributeKey::IS_CLASS_EXTENDS ) === true ) {
154+ return null ;
155+ }
156+
151157 $ stringName = $ fullyQualified ->toString ();
152158 $ newName = $ oldToNewClasses [$ stringName ] ?? null ;
153159
154160 if ($ newName === null ) {
155161 return null ;
156162 }
157163
158- if (! $ this ->isClassToInterfaceValidChange ($ fullyQualified , $ newName, $ scope )) {
164+ if (! $ this ->isClassToInterfaceValidChange ($ fullyQualified , $ newName )) {
159165 return null ;
160166 }
161167
@@ -178,6 +184,22 @@ private function refactorClassLike(ClassLike $classLike, array $oldToNewClasses,
178184 }
179185
180186 $ hasChanged = false ;
187+
188+ if ($ classLike ->extends instanceof FullyQualified) {
189+ $ extendsName = $ classLike ->extends ->toString ();
190+ $ newName = $ oldToNewClasses [$ extendsName ] ?? null ;
191+
192+ // skip when the class would extend itself after rename; this might happen when you run a rename class on new child class that extends the deprecated one
193+ if ($ newName !== null
194+ && $ newName !== $ this ->nodeNameResolver ->getName ($ classLike )
195+ && $ this ->isClassToInterfaceValidChange ($ classLike ->extends , $ newName )
196+ ) {
197+ $ this ->renamedNameCollector ->add ($ extendsName );
198+ $ classLike ->extends = new FullyQualified ($ newName );
199+ $ hasChanged = true ;
200+ }
201+ }
202+
181203 $ classLike ->implements = array_unique ($ classLike ->implements );
182204 foreach ($ classLike ->implements as $ key => $ implementName ) {
183205 $ namespaceName = $ scope instanceof Scope ? $ scope ->getNamespace () : null ;
@@ -213,8 +235,7 @@ private function refactorClassLike(ClassLike $classLike, array $oldToNewClasses,
213235 */
214236 private function isClassToInterfaceValidChange (
215237 FullyQualified $ fullyQualified ,
216- string $ newClassName ,
217- ?Scope $ scope
238+ string $ newClassName
218239 ): bool {
219240 if (! $ this ->reflectionProvider ->hasClass ($ newClassName )) {
220241 return true ;
@@ -223,14 +244,6 @@ private function isClassToInterfaceValidChange(
223244 $ classReflection = $ this ->reflectionProvider ->getClass ($ newClassName );
224245 // ensure new is not with interface
225246 if ($ fullyQualified ->getAttribute (AttributeKey::IS_NEW_INSTANCE_NAME ) !== true ) {
226- if ($ fullyQualified ->getAttribute (AttributeKey::IS_CLASS_EXTENDS ) === true && $ scope instanceof Scope) {
227- $ currentClassReflection = $ scope ->getClassReflection ();
228-
229- if ($ currentClassReflection instanceof ClassReflection && $ currentClassReflection ->getName () === $ newClassName ) {
230- return false ;
231- }
232- }
233-
234247 return $ this ->isValidClassNameChange ($ fullyQualified , $ classReflection );
235248 }
236249
0 commit comments