InlineSuperClass: support sealed classes (IDEA-243902)

GitOrigin-RevId: 9e9d53ec7fa1ade7e0d1890fb5682c41da7c4f97
This commit is contained in:
Artemiy Sartakov
2020-07-29 10:15:32 +00:00
committed by intellij-monorepo-bot
parent 341bbc7bc1
commit fccea59571
24 changed files with 153 additions and 23 deletions
@@ -85,7 +85,7 @@ public class FillPermitsListFix extends LocalQuickFixAndIntentionActionOnPsiElem
return missingInheritors;
}
static void fillPermitsList(@NotNull PsiClass parent, @NotNull Collection<String> missingInheritors) {
public static void fillPermitsList(@NotNull PsiClass parent, @NotNull Collection<String> missingInheritors) {
PsiReferenceList permitsList = parent.getPermitsList();
PsiFileFactory factory = PsiFileFactory.getInstance(parent.getProject());
if (permitsList == null) {
@@ -24,6 +24,7 @@ import com.intellij.psi.*;
import com.intellij.psi.util.MethodSignatureUtil;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.util.IncorrectOperationException;
import com.siyeh.ig.psiutils.ClassUtils;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -120,17 +121,10 @@ public class UnimplementInterfaceAction implements IntentionAction {
if (target == psiClass) return;
if (targetClass.hasModifierProperty(PsiModifier.SEALED)) {
PsiReferenceList permitsList = targetClass.getPermitsList();
if (permitsList != null) {
Arrays.stream(permitsList.getReferenceElements())
.filter(r -> r.resolve() == psiClass).findFirst()
.ifPresent(r -> r.delete());
PsiModifierList modifiers = psiClass.getModifierList();
if (modifiers != null && modifiers.hasExplicitModifier(PsiModifier.NON_SEALED)) {
boolean hasAnotherSealedParent = hasSealedParent(psiClass.getExtendsListTypes(), targetClass);
if (!hasAnotherSealedParent) hasAnotherSealedParent = hasSealedParent(psiClass.getImplementsListTypes(), targetClass);
if (!hasAnotherSealedParent) modifiers.setModifierProperty(PsiModifier.NON_SEALED, false);
}
ClassUtils.removeFromPermitsList(targetClass, psiClass);
final PsiModifierList modifiers = psiClass.getModifierList();
if (modifiers != null && modifiers.hasExplicitModifier(PsiModifier.NON_SEALED) && !ClassUtils.hasSealedParent(psiClass)) {
modifiers.setModifierProperty(PsiModifier.NON_SEALED, false);
}
}
@@ -146,12 +140,6 @@ public class UnimplementInterfaceAction implements IntentionAction {
}
}
private static boolean hasSealedParent(PsiClassType[] types, PsiClass toExclude) {
return Arrays.stream(types)
.map(t -> t.resolve())
.anyMatch(parent -> parent != null && parent != toExclude && parent.hasModifierProperty(PsiModifier.SEALED));
}
@Override
public boolean startInWriteAction() {
return true;
@@ -15,14 +15,18 @@
*/
package com.intellij.refactoring.safeDelete.usageInfo;
import com.intellij.codeInsight.intention.impl.FillPermitsListFix;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.psi.*;
import com.intellij.psi.codeStyle.CodeStyleManager;
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.Nullable;
import java.util.Collections;
/**
* @author ven
*/
@@ -55,6 +59,14 @@ public class SafeDeleteExtendsClassUsageInfo extends SafeDeleteReferenceUsageInf
copyExtendsList(refClass.getImplementsList(), targetTypeParameter, elementFactory);
getElement().delete();
if (!refClass.hasModifierProperty(PsiModifier.SEALED)) return;
ClassUtils.removeFromPermitsList(refClass, myExtendingClass);
final PsiModifierList modifiers = myExtendingClass.getModifierList();
if (modifiers == null || !modifiers.hasModifierProperty(PsiModifier.NON_SEALED)) return;
if (!ClassUtils.hasSealedParent(myExtendingClass)) {
modifiers.setModifierProperty(PsiModifier.NON_SEALED, false);
}
}
private void copyExtendsList(@Nullable PsiReferenceList sourceExtendsList,
@@ -68,6 +80,10 @@ public class SafeDeleteExtendsClassUsageInfo extends SafeDeleteReferenceUsageInf
if (ArrayUtilRt.find(existingRefTypes, referenceType) > -1) continue;
PsiClassType classType = (PsiClassType)mySubstitutor.substitute(referenceType);
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()));
}
CodeStyleManager.getInstance(myExtendingClass.getProject()).reformat(extendsRef);
}
}
@@ -1,6 +1,6 @@
// "Unimplement Class" "true"
public sealed interface A permits B {}
sealed class C {}
final class C {}
non-sealed class B implements A {}
@@ -1,5 +1,5 @@
// "Unimplement Class" "true"
sealed class A {
final class A {
}
@@ -1,4 +1,4 @@
// "Unimplement Class" "true"
sealed class A {}
final class A {}
final class B {}
@@ -1,4 +1,4 @@
// "Unimplement Class" "true"
sealed class A {}
final class A {}
class B {}
@@ -1,5 +1,5 @@
// "Unimplement Class" "true"
sealed class A {}
final class A {}
sealed class B permits C {}
@@ -0,0 +1,5 @@
sealed interface SuperSuper permits Super, Test {}
final class Super implements SuperSuper {}
non-sealed class Test implements SuperSuper {}
@@ -0,0 +1,5 @@
sealed interface SuperSuper permits Super, Test {}
sealed class Super implements SuperSuper permits Test {}
non-sealed class Test extends Super implements SuperSuper {}
@@ -1,3 +1,4 @@
import java.util.HashSet;
import java.util.List;
import java.util.Set;
@@ -0,0 +1,8 @@
abstract class Super {
abstract void doSmth();
}
final class Test {
void doSmth() {
}
}
@@ -0,0 +1,9 @@
abstract sealed class Super permits Test {
abstract void doSmth();
}
final class Test extends Super {
@Override
void doSmth() {
}
}
@@ -0,0 +1,5 @@
sealed interface SuperSuper permits Super, Test {}
final class Super implements SuperSuper {}
non-sealed class Test implements SuperSuper {}
@@ -0,0 +1,5 @@
sealed interface SuperSuper permits Super {}
sealed class Super implements SuperSuper permits Test {}
non-sealed class Test extends Super {}
@@ -0,0 +1,20 @@
final class Test {
void doSmth() {
System.out.println("hello");
}
}
class Test1 {
void doSmth() {
System.out.println("hello");
}
}
sealed class Test2 permits Test3 {
void doSmth() {
System.out.println("hello");
}
}
final class Test3 extends Test2 {}
@@ -0,0 +1,5 @@
sealed class Super permits Test, Test1, Test2 {
void doSmth() {
System.out.println("hello");
}
}
@@ -0,0 +1,8 @@
final class Test extends Super {}
non-sealed class Test1 extends Super {}
sealed class Test2 extends Super permits Test3 {}
final class Test3 extends Test2 {}
@@ -0,0 +1,5 @@
sealed class Super permits Test1 {}
final class Test {}
final class Test1 extends Super {}
@@ -0,0 +1,5 @@
sealed class Super permits Test, Test1 {}
final class Test extends Super {}
final class Test1 extends Super {}
@@ -0,0 +1,3 @@
final class Super {}
class Test {}
@@ -0,0 +1,3 @@
sealed class Super permits Test {}
non-sealed class Test extends Super {}
@@ -22,6 +22,8 @@ import com.intellij.refactoring.BaseRefactoringProcessor;
import com.intellij.refactoring.LightMultiFileTestCase;
import com.intellij.refactoring.inlineSuperClass.InlineSuperClassRefactoringProcessor;
import com.intellij.refactoring.util.DocCommentPolicy;
import com.intellij.testFramework.LightProjectDescriptor;
import org.jetbrains.annotations.NotNull;
/**
* @author anna
@@ -32,6 +34,11 @@ public class InlineSuperClassTest extends LightMultiFileTestCase {
return JavaTestUtil.getJavaTestDataPath() + "/refactoring/inlineSuperClass/";
}
@Override
protected @NotNull LightProjectDescriptor getProjectDescriptor() {
return JAVA_15;
}
public void testInlineOneClass() { doTest(false, true); }
public void testInlineOneClassWithConflicts() { doTest(true, true); }
public void testAbstractOverrides() { doTest(); }
@@ -77,6 +84,12 @@ public class InlineSuperClassTest extends LightMultiFileTestCase {
public void testThisQualificationInsideAnonymous() { doTest(); }
public void testOrderOfInnerClasses() { doTest(); }
public void testSuperMethodWithoutBody() { doTest(); }
public void testSealedAbstractParentOneInheritor() { doTest(false, true); }
public void testSealedParentManyInheritors() { doTest(false, true); }
public void testSealedParentNonSealedInheritor() { doTest(false, true); }
public void testSealedGrandParentNonSealedInheritor() { doTest(false, true); }
public void testSealedParentInlineAll() { doTest(); }
public void testMultipleSealedParents() { doTest(false, true); }
private void doTest() {
doTest(false, false);
@@ -24,6 +24,7 @@ import com.intellij.psi.util.PsiUtil;
import com.intellij.psi.util.TypeConversionUtil;
import com.intellij.util.Processor;
import com.intellij.util.Query;
import com.intellij.util.containers.ContainerUtil;
import one.util.streamex.StreamEx;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NonNls;
@@ -338,6 +339,31 @@ public final class ClassUtils {
return selfInstance != null && newOnlyAssignsToStaticSelfInstance(getIfOnlyInvisibleConstructors(aClass)[0], selfInstance);
}
/**
* 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).
*/
public static void removeFromPermitsList(@NotNull PsiClass parent, @NotNull PsiClass exChild) {
PsiReferenceList permitsList = parent.getPermitsList();
if (permitsList == null) return;
PsiJavaCodeReferenceElement[] childRefs = permitsList.getReferenceElements();
PsiJavaCodeReferenceElement exChildRef = ContainerUtil.find(childRefs, ref -> ref.resolve() == exChild);
if (exChildRef == null) return;
exChildRef.delete();
if (childRefs.length != 1) return;
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 boolean hasSealedParent(@NotNull PsiClass psiClass) {
return StreamEx.of(psiClass.getExtendsListTypes())
.append(psiClass.getImplementsListTypes())
.map(r -> r.resolve())
.anyMatch(parent -> parent != null && parent.hasModifierProperty(PsiModifier.SEALED));
}
private static PsiField getIfOneStaticSelfInstance(PsiClass aClass) {
Stream<PsiField> fieldStream = Arrays.stream(aClass.getFields());