mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
Sealed classes: update hierarchy fixes (IJ-CR-2554):
1. add missing same file inheritors to permits list during safe delete or create subclass refactoring 2. inline isParentSealed for inline to anonymous class refactoring 3. do not make sealed class final when removing last inheritor (just remove sealed modifier) GitOrigin-RevId: acf3652f12144446116c5d71ea1722b06b9f6b92
This commit is contained in:
committed by
intellij-monorepo-bot
parent
984fe0931e
commit
ec30bf3a97
+10
-6
@@ -56,13 +56,12 @@ import com.intellij.psi.util.PsiUtilCore;
|
||||
import com.intellij.psi.util.TypeConversionUtil;
|
||||
import com.intellij.util.ArrayUtil;
|
||||
import com.intellij.util.IncorrectOperationException;
|
||||
import com.intellij.util.containers.SmartHashSet;
|
||||
import com.siyeh.ig.psiutils.ClassUtils;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.*;
|
||||
|
||||
public class CreateSubclassAction extends BaseIntentionAction {
|
||||
private static final Logger LOG = Logger.getInstance(CreateSubclassAction.class);
|
||||
@@ -265,8 +264,13 @@ public class CreateSubclassAction extends BaseIntentionAction {
|
||||
ref = (PsiJavaCodeReferenceElement)targetClass.getExtendsList().add(ref);
|
||||
}
|
||||
if (psiClass.hasModifierProperty(PsiModifier.SEALED)) {
|
||||
String createdClassName = targetClass.getQualifiedName();
|
||||
FillPermitsListFix.fillPermitsList(psiClass, Collections.singleton(createdClassName));
|
||||
String createdClassName = Objects.requireNonNull(targetClass.getQualifiedName());
|
||||
SmartHashSet<String> missingInheritors = new SmartHashSet<>();
|
||||
missingInheritors.add(createdClassName);
|
||||
if (psiClass.getPermitsList() == null) {
|
||||
missingInheritors.addAll(ClassUtils.findSameFileInheritors(psiClass));
|
||||
}
|
||||
FillPermitsListFix.fillPermitsList(psiClass, missingInheritors);
|
||||
}
|
||||
if (psiClass.hasTypeParameters() || includeClassName) {
|
||||
final Editor editor = CodeInsightUtil.positionCursorAtLBrace(project, targetClass.getContainingFile(), targetClass);
|
||||
|
||||
+2
-6
@@ -76,7 +76,8 @@ public class InlineToAnonymousClassHandler extends JavaInlineActionHandler {
|
||||
if (element instanceof PsiAnonymousClass) return false;
|
||||
PsiClass psiClass = (PsiClass)element;
|
||||
if (!findClassInheritors(psiClass)) return false;
|
||||
return !isParentSealed(psiClass);
|
||||
boolean hasMethods = PsiTreeUtil.findChildOfType(psiClass, PsiMember.class) != null;
|
||||
return !hasMethods && !ClassUtils.hasSealedParent(psiClass);
|
||||
}
|
||||
|
||||
private static boolean findClassInheritors(final PsiClass element) {
|
||||
@@ -95,11 +96,6 @@ public class InlineToAnonymousClassHandler extends JavaInlineActionHandler {
|
||||
return inheritors.isEmpty();
|
||||
}
|
||||
|
||||
private static boolean isParentSealed(@NotNull PsiClass psiClass) {
|
||||
if (PsiTreeUtil.findChildOfType(psiClass, PsiMember.class) == null) return false;
|
||||
return ClassUtils.hasSealedParent(psiClass);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canInlineElementInEditor(PsiElement element, Editor editor) {
|
||||
if (canInlineElement(element)) {
|
||||
|
||||
+18
-4
@@ -23,9 +23,12 @@ import com.intellij.psi.util.TypeConversionUtil;
|
||||
import com.intellij.util.ArrayUtilRt;
|
||||
import com.intellij.util.IncorrectOperationException;
|
||||
import com.siyeh.ig.psiutils.ClassUtils;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* @author ven
|
||||
@@ -55,8 +58,8 @@ public class SafeDeleteExtendsClassUsageInfo extends SafeDeleteReferenceUsageInf
|
||||
final PsiElementFactory elementFactory = JavaPsiFacade.getElementFactory(refClass.getProject());
|
||||
|
||||
boolean targetTypeParameter = myExtendingClass instanceof PsiTypeParameter;
|
||||
copyExtendsList(refClass.getExtendsList(), refClass.isInterface() == myExtendingClass.isInterface() || targetTypeParameter, elementFactory);
|
||||
copyExtendsList(refClass.getImplementsList(), targetTypeParameter, elementFactory);
|
||||
copyExtendsList(refClass, refClass.getExtendsList(), refClass.isInterface() == myExtendingClass.isInterface() || targetTypeParameter, elementFactory);
|
||||
copyExtendsList(refClass, refClass.getImplementsList(), targetTypeParameter, elementFactory);
|
||||
|
||||
getElement().delete();
|
||||
|
||||
@@ -69,7 +72,8 @@ public class SafeDeleteExtendsClassUsageInfo extends SafeDeleteReferenceUsageInf
|
||||
}
|
||||
}
|
||||
|
||||
private void copyExtendsList(@Nullable PsiReferenceList sourceExtendsList,
|
||||
private void copyExtendsList(@NotNull PsiClass classToRemove,
|
||||
@Nullable PsiReferenceList sourceExtendsList,
|
||||
boolean targetExtends,
|
||||
PsiElementFactory elementFactory) {
|
||||
if (sourceExtendsList != null) {
|
||||
@@ -82,7 +86,17 @@ public class SafeDeleteExtendsClassUsageInfo extends SafeDeleteReferenceUsageInf
|
||||
PsiElement extendsRef = targetExtendsList.add(elementFactory.createReferenceElementByType(classType));
|
||||
PsiClass classToExtend = classType.resolve();
|
||||
if (classToExtend != null && classToExtend.hasModifierProperty(PsiModifier.SEALED)) {
|
||||
FillPermitsListFix.fillPermitsList(classToExtend, Collections.singleton(myExtendingClass.getQualifiedName()));
|
||||
String extendingClassName = Objects.requireNonNull(myExtendingClass.getQualifiedName());
|
||||
if (classToExtend.getPermitsList() == null) {
|
||||
if (classToExtend.getContainingFile() != myExtendingClass.getContainingFile()) {
|
||||
Collection<String> missingInheritors = ClassUtils.findSameFileInheritors(classToExtend, classToRemove);
|
||||
missingInheritors.add(extendingClassName);
|
||||
FillPermitsListFix.fillPermitsList(classToExtend, missingInheritors);
|
||||
}
|
||||
}
|
||||
else {
|
||||
FillPermitsListFix.fillPermitsList(classToExtend, Collections.singleton(extendingClassName));
|
||||
}
|
||||
}
|
||||
CodeStyleManager.getInstance(myExtendingClass.getProject()).reformat(extendsRef);
|
||||
}
|
||||
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
public class Subclass extends Superclass {
|
||||
}
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
public sealed class Superclass permits B, Subclass {
|
||||
public void foo() {}
|
||||
}
|
||||
|
||||
final class B extends Superclass {}
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
public sealed class Superclass {
|
||||
public void foo() {}
|
||||
}
|
||||
|
||||
final class B extends Superclass {}
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
// "Unimplement Class" "true"
|
||||
public sealed interface A permits B {}
|
||||
|
||||
final class C {}
|
||||
class C {}
|
||||
|
||||
non-sealed class B implements A {}
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
// "Unimplement Class" "true"
|
||||
final class A {
|
||||
class A {
|
||||
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
// "Unimplement Class" "true"
|
||||
final class A {}
|
||||
class A {}
|
||||
|
||||
final class B {}
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
// "Unimplement Class" "true"
|
||||
final class A {}
|
||||
class A {}
|
||||
|
||||
class B {}
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
// "Unimplement Class" "true"
|
||||
final class A {}
|
||||
class A {}
|
||||
|
||||
sealed class B permits C {}
|
||||
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
final class A {
|
||||
class A {
|
||||
void doSmth() {}
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
sealed interface SuperSuper permits Super, Test {}
|
||||
|
||||
final class Super implements SuperSuper {}
|
||||
class Super implements SuperSuper {}
|
||||
|
||||
non-sealed class Test implements SuperSuper {}
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
sealed interface SuperSuper permits Super, Test {}
|
||||
|
||||
final class Super implements SuperSuper {}
|
||||
class Super implements SuperSuper {}
|
||||
|
||||
non-sealed class Test implements SuperSuper {}
|
||||
+1
-1
@@ -1,3 +1,3 @@
|
||||
final class Super {}
|
||||
class Super {}
|
||||
|
||||
class Test {}
|
||||
@@ -1,4 +1,4 @@
|
||||
final class Parent {
|
||||
class Parent {
|
||||
|
||||
void test() {
|
||||
Object obj = new Parent();
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
public final class C implements GrandParent {
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
sealed interface GrandParent permits A, C, Child {
|
||||
}
|
||||
|
||||
final class Child implements GrandParent {}
|
||||
|
||||
final class A implements GrandParent {}
|
||||
@@ -0,0 +1,2 @@
|
||||
public final class C extends Parent {
|
||||
}
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
sealed interface GrandParent {
|
||||
}
|
||||
|
||||
public sealed class Parent<caret> implements GrandParent permits C, Child {}
|
||||
|
||||
final class Child extends Parent implements GrandParent {}
|
||||
|
||||
final class A implements GrandParent {}
|
||||
+4
@@ -54,6 +54,10 @@ public class CreateSubclassTest extends LightMultiFileTestCase {
|
||||
doTest();
|
||||
}
|
||||
|
||||
public void testSealedWithSameFileInheritors() {
|
||||
doTest();
|
||||
}
|
||||
|
||||
private void doTestInner() {
|
||||
doTest(() -> {
|
||||
PsiClass superClass = myFixture.findClass("Test");
|
||||
|
||||
@@ -393,6 +393,11 @@ public class SafeDeleteTest extends MultiFileTestCase {
|
||||
doSingleFileTest();
|
||||
}
|
||||
|
||||
public void testSealedGrandParent() {
|
||||
LanguageLevelProjectExtension.getInstance(getProject()).setLanguageLevel(LanguageLevel.JDK_15_PREVIEW);
|
||||
doTest("Parent");
|
||||
}
|
||||
|
||||
private void doTest(@NonNls final String qClassName) {
|
||||
doTest((rootDir, rootAfter) -> this.performAction(qClassName));
|
||||
}
|
||||
|
||||
+12
-2
@@ -18,10 +18,13 @@ package com.siyeh.ig.psiutils;
|
||||
import com.intellij.codeInspection.concurrencyAnnotations.JCiPUtil;
|
||||
import com.intellij.psi.*;
|
||||
import com.intellij.psi.impl.light.LightElement;
|
||||
import com.intellij.psi.search.GlobalSearchScope;
|
||||
import com.intellij.psi.search.searches.DirectClassInheritorsSearch;
|
||||
import com.intellij.psi.search.searches.MethodReferencesSearch;
|
||||
import com.intellij.psi.util.PsiTreeUtil;
|
||||
import com.intellij.psi.util.PsiUtil;
|
||||
import com.intellij.psi.util.TypeConversionUtil;
|
||||
import com.intellij.util.ArrayUtil;
|
||||
import com.intellij.util.Processor;
|
||||
import com.intellij.util.Query;
|
||||
import com.intellij.util.containers.ContainerUtil;
|
||||
@@ -341,7 +344,7 @@ public final class ClassUtils {
|
||||
|
||||
/**
|
||||
* Removes exChild class reference from permits list of a parent.
|
||||
* If this was the last element in permits list then parent class is converted to final if possible (sealed modifier is removed).
|
||||
* If this was the last element in permits list then sealed modifier of parent class is removed.
|
||||
*/
|
||||
public static void removeFromPermitsList(@NotNull PsiClass parent, @NotNull PsiClass exChild) {
|
||||
PsiReferenceList permitsList = parent.getPermitsList();
|
||||
@@ -354,7 +357,14 @@ public final class ClassUtils {
|
||||
PsiModifierList modifiers = parent.getModifierList();
|
||||
if (modifiers == null) return;
|
||||
modifiers.setModifierProperty(PsiModifier.SEALED, false);
|
||||
if (!parent.hasModifierProperty(PsiModifier.ABSTRACT)) modifiers.setModifierProperty(PsiModifier.FINAL, true);
|
||||
}
|
||||
|
||||
public static Collection<String> findSameFileInheritors(@NotNull PsiClass psiClass, PsiClass @NotNull ... classesToExclude) {
|
||||
GlobalSearchScope fileScope = GlobalSearchScope.fileScope(psiClass.getContainingFile());
|
||||
return DirectClassInheritorsSearch.search(psiClass, fileScope)
|
||||
.filtering(inheritor -> !ArrayUtil.contains(inheritor, classesToExclude))
|
||||
.mapping(inheritor -> inheritor.getQualifiedName())
|
||||
.findAll();
|
||||
}
|
||||
|
||||
public static boolean hasSealedParent(@NotNull PsiClass psiClass) {
|
||||
|
||||
Reference in New Issue
Block a user