mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
[java-intentions] ExtendSealedClassFix: ModCommand
IntentionActionWithChoice UI replaced with multi-step ModCommand, as we don't have IntentionActionWithChoice in ModCommand API, and it's unclear whether it's a good idea, and how to implement it better GitOrigin-RevId: a702bc672b56f4c51dd7e7b01cf5ee0ded457805
This commit is contained in:
committed by
intellij-monorepo-bot
parent
9ab2a2cd01
commit
2c12f7c579
@@ -426,7 +426,8 @@ remove.unreachable.branches=Remove unreachable branches
|
||||
simplify.boolean.expression.extracting.side.effects=\ extracting side effects
|
||||
intention.move.parenthesis.name=Fix closing parenthesis placement
|
||||
|
||||
extend.sealed.title=Make ''{0}'' {1, choice, 1#extend|2#implement} ''{2}'' and
|
||||
extend.sealed.title=Make ''{0}'' {1, choice, 1#Extend|2#Implement} ''{2}'' and
|
||||
extend.sealed.name=Make ''{0}'' {1, choice, 1#extend|2#implement} ''{2}''
|
||||
|
||||
implement.or.extend.fix.family=Implement/Extend required base class
|
||||
implement.or.extend.fix.implement.text=Make ''{0}'' implement ''{1}''
|
||||
|
||||
+39
-73
@@ -3,77 +3,72 @@ package com.intellij.codeInsight.daemon.impl.quickfix;
|
||||
|
||||
import com.intellij.codeInsight.daemon.QuickFixBundle;
|
||||
import com.intellij.codeInsight.intention.FileModifier;
|
||||
import com.intellij.codeInsight.intention.choice.ChoiceTitleIntentionAction;
|
||||
import com.intellij.codeInsight.intention.choice.ChoiceVariantIntentionAction;
|
||||
import com.intellij.codeInsight.intention.choice.DefaultIntentionActionWithChoice;
|
||||
import com.intellij.codeInspection.LocalQuickFix;
|
||||
import com.intellij.codeInspection.util.IntentionFamilyName;
|
||||
import com.intellij.codeInspection.util.IntentionName;
|
||||
import com.intellij.openapi.editor.Editor;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.openapi.util.NlsContexts;
|
||||
import com.intellij.modcommand.*;
|
||||
import com.intellij.openapi.util.NlsSafe;
|
||||
import com.intellij.psi.*;
|
||||
import com.intellij.psi.search.searches.DirectClassInheritorsSearch;
|
||||
import com.intellij.psi.util.PsiTreeUtil;
|
||||
import com.intellij.util.Query;
|
||||
import org.jetbrains.annotations.Nls;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
public class ExtendSealedClassFix implements DefaultIntentionActionWithChoice {
|
||||
public class ExtendSealedClassFix extends PsiBasedModCommandAction<PsiClass> {
|
||||
@FileModifier.SafeFieldForPreview private final SmartPsiElementPointer<PsiClass> myParentClassPointer;
|
||||
@FileModifier.SafeFieldForPreview private final SmartPsiElementPointer<PsiClass> mySubclassPointer;
|
||||
private final @Nls String myName;
|
||||
|
||||
private ExtendSealedClassFix(PsiClass parentClass, PsiClass subclass) {
|
||||
private ExtendSealedClassFix(@NotNull PsiClass parentClass, @NotNull PsiClass subclass) {
|
||||
super(subclass);
|
||||
myParentClassPointer = SmartPointerManager.createPointer(parentClass);
|
||||
mySubclassPointer = SmartPointerManager.createPointer(subclass);
|
||||
int extendsImplements = subclass.isInterface() || !parentClass.isInterface() ? 1 : 2;
|
||||
myName = QuickFixBundle.message("extend.sealed.title", subclass.getName(), extendsImplements, parentClass.getName());
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull ChoiceTitleIntentionAction getTitle() {
|
||||
return new ChoiceTitleIntentionAction(QuickFixBundle.message("implement.or.extend.fix.family"), myName);
|
||||
public @NotNull String getFamilyName() {
|
||||
return QuickFixBundle.message("implement.or.extend.fix.family");
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull List<@NotNull ChoiceVariantIntentionAction> getVariants() {
|
||||
PsiClass subclass = mySubclassPointer.getElement();
|
||||
protected @Nullable Presentation getPresentation(@NotNull ActionContext context, @NotNull PsiClass subclass) {
|
||||
PsiClass parentClass = myParentClassPointer.getElement();
|
||||
if (subclass == null || parentClass == null) return Collections.emptyList();
|
||||
if (parentClass == null) return null;
|
||||
int extendsImplements = subclass.isInterface() || !parentClass.isInterface() ? 1 : 2;
|
||||
String name = QuickFixBundle.message("extend.sealed.name", subclass.getName(), extendsImplements, parentClass.getName());
|
||||
return Presentation.of(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @NotNull ModCommand perform(@NotNull ActionContext context, @NotNull PsiClass subclass) {
|
||||
PsiClass parentClass = myParentClassPointer.getElement();
|
||||
if (parentClass == null) return ModCommands.nop();
|
||||
boolean parentIsInterface = parentClass.isInterface();
|
||||
boolean subclassIsInterface = subclass.isInterface();
|
||||
if (!parentIsInterface && subclassIsInterface) return Collections.emptyList();
|
||||
if (!parentIsInterface && subclassIsInterface) return ModCommands.nop();
|
||||
List<ModCommandAction> fixes;
|
||||
if (subclassIsInterface) {
|
||||
return Arrays.asList(new ExtendSealedClassVariantAction(0, PsiModifier.SEALED, myParentClassPointer, mySubclassPointer),
|
||||
new ExtendSealedClassVariantAction(1, PsiModifier.NON_SEALED, myParentClassPointer, mySubclassPointer));
|
||||
fixes = List.of(new ExtendSealedClassVariantAction(PsiModifier.SEALED, myParentClassPointer, subclass),
|
||||
new ExtendSealedClassVariantAction(PsiModifier.NON_SEALED, myParentClassPointer, subclass));
|
||||
}
|
||||
return Arrays.asList(new ExtendSealedClassVariantAction(0, PsiModifier.FINAL, myParentClassPointer, mySubclassPointer),
|
||||
new ExtendSealedClassVariantAction(1, PsiModifier.SEALED, myParentClassPointer, mySubclassPointer),
|
||||
new ExtendSealedClassVariantAction(2, PsiModifier.NON_SEALED, myParentClassPointer, mySubclassPointer));
|
||||
else {
|
||||
fixes = List.of(new ExtendSealedClassVariantAction(PsiModifier.FINAL, myParentClassPointer, subclass),
|
||||
new ExtendSealedClassVariantAction(PsiModifier.SEALED, myParentClassPointer, subclass),
|
||||
new ExtendSealedClassVariantAction(PsiModifier.NON_SEALED, myParentClassPointer, subclass));
|
||||
}
|
||||
int extendsImplements = subclass.isInterface() || !parentClass.isInterface() ? 1 : 2;
|
||||
//noinspection DialogTitleCapitalization
|
||||
String name = QuickFixBundle.message("extend.sealed.title", subclass.getName(), extendsImplements, parentClass.getName());
|
||||
return new ModChooseAction(name, fixes);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return fixes or null if given case is not supported (but probably valid)
|
||||
*/
|
||||
static LocalQuickFix @Nullable [] createFixes(@NotNull PsiClass parentClass, @NotNull PsiClass subclass) {
|
||||
static @Nullable ModCommandAction createFix(@NotNull PsiClass parentClass, @NotNull PsiClass subclass) {
|
||||
if (!parentClass.hasModifierProperty(PsiModifier.SEALED) || !parentClass.getManager().isInProject(parentClass)) return null;
|
||||
boolean parentIsInterface = parentClass.isInterface();
|
||||
if (parentIsInterface && (subclass.isRecord() || subclass.isEnum())) return null;
|
||||
PsiModifierList modifiers = subclass.getModifierList();
|
||||
if (modifiers == null || hasSealedClassSubclassModifier(modifiers)) return null;
|
||||
ExtendSealedClassFix extendSealedClassFix = new ExtendSealedClassFix(parentClass, subclass);
|
||||
List<LocalQuickFix> actions = new ArrayList<>();
|
||||
actions.add(extendSealedClassFix.getTitle());
|
||||
actions.addAll(extendSealedClassFix.getVariants());
|
||||
return actions.toArray(LocalQuickFix.EMPTY_ARRAY);
|
||||
return new ExtendSealedClassFix(parentClass, subclass);
|
||||
}
|
||||
|
||||
private static boolean hasSealedClassSubclassModifier(PsiClass psiClass) {
|
||||
@@ -88,30 +83,21 @@ public class ExtendSealedClassFix implements DefaultIntentionActionWithChoice {
|
||||
modifiers.hasExplicitModifier(PsiModifier.NON_SEALED);
|
||||
}
|
||||
|
||||
private static class ExtendSealedClassVariantAction extends ChoiceVariantIntentionAction {
|
||||
|
||||
private final int myIndex;
|
||||
private static class ExtendSealedClassVariantAction extends PsiUpdateModCommandAction<PsiClass> {
|
||||
private final @NlsSafe String myModifier;
|
||||
private final SmartPsiElementPointer<PsiClass> myParentClassPointer;
|
||||
private final SmartPsiElementPointer<PsiClass> mySubclassPointer;
|
||||
|
||||
private ExtendSealedClassVariantAction(int index, @NotNull String modifier,
|
||||
private ExtendSealedClassVariantAction(@NotNull String modifier,
|
||||
SmartPsiElementPointer<PsiClass> parentClassPointer,
|
||||
SmartPsiElementPointer<PsiClass> subclassPointer) {
|
||||
myIndex = index;
|
||||
PsiClass subclass) {
|
||||
super(subclass);
|
||||
myModifier = modifier;
|
||||
myParentClassPointer = parentClassPointer;
|
||||
mySubclassPointer = subclassPointer;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getIndex() {
|
||||
return myIndex;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @IntentionName @NotNull String getName() {
|
||||
return myModifier;
|
||||
protected @Nullable Presentation getPresentation(@NotNull ActionContext context, @NotNull PsiClass element) {
|
||||
return Presentation.of(myModifier);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -120,21 +106,9 @@ public class ExtendSealedClassFix implements DefaultIntentionActionWithChoice {
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NlsContexts.Tooltip String getTooltipText() {
|
||||
return getFamilyName() + " " + myModifier;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable PsiElement getElementToMakeWritable(@NotNull PsiFile currentFile) {
|
||||
return mySubclassPointer.getContainingFile();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void applyFix(@NotNull Project project, PsiFile file, @Nullable Editor editor) {
|
||||
protected void invoke(@NotNull ActionContext context, @NotNull PsiClass subclass, @NotNull ModPsiUpdater updater) {
|
||||
PsiClass parentClass = myParentClassPointer.getElement();
|
||||
if (parentClass == null || !parentClass.hasModifierProperty(PsiModifier.SEALED)) return;
|
||||
PsiClass subclass = mySubclassPointer.getElement();
|
||||
if (subclass == null) return;
|
||||
if (ImplementOrExtendFix.implementOrExtend(parentClass, subclass) == null) return;
|
||||
PsiModifierList modifiers = subclass.getModifierList();
|
||||
if (modifiers == null) return;
|
||||
@@ -146,13 +120,5 @@ public class ExtendSealedClassFix implements DefaultIntentionActionWithChoice {
|
||||
subclass.navigate(true);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable FileModifier getFileModifierForPreview(@NotNull PsiFile target) {
|
||||
PsiClass subclass = mySubclassPointer.getElement();
|
||||
PsiClass copy = PsiTreeUtil.findSameElementInCopy(subclass, target);
|
||||
if (copy == null) return null;
|
||||
return new ExtendSealedClassVariantAction(myIndex, myModifier, myParentClassPointer, SmartPointerManager.createPointer(copy));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+11
-27
@@ -2,18 +2,14 @@
|
||||
package com.intellij.codeInsight.daemon.impl.quickfix;
|
||||
|
||||
import com.intellij.codeInsight.daemon.QuickFixBundle;
|
||||
import com.intellij.codeInsight.intention.IntentionAction;
|
||||
import com.intellij.modcommand.ModPsiUpdater;
|
||||
import com.intellij.codeInspection.LocalQuickFix;
|
||||
import com.intellij.modcommand.PsiUpdateModCommandAction;
|
||||
import com.intellij.codeInspection.util.IntentionFamilyName;
|
||||
import com.intellij.codeInspection.util.IntentionName;
|
||||
import com.intellij.modcommand.ModCommandAction;
|
||||
import com.intellij.modcommand.ModCommandService;
|
||||
import com.intellij.modcommand.ModPsiUpdater;
|
||||
import com.intellij.modcommand.PsiUpdateModCommandAction;
|
||||
import com.intellij.psi.*;
|
||||
import com.intellij.psi.codeStyle.JavaCodeStyleManager;
|
||||
import com.siyeh.ig.psiutils.TypeUtils;
|
||||
import one.util.streamex.StreamEx;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
@@ -53,37 +49,25 @@ public final class ImplementOrExtendFix extends PsiUpdateModCommandAction<PsiCla
|
||||
return QuickFixBundle.message("implement.or.extend.fix.family");
|
||||
}
|
||||
|
||||
public static IntentionAction[] createActions(@NotNull PsiClass subclass,
|
||||
@NotNull PsiClass parentClass) {
|
||||
return StreamEx.of(createFixes(subclass, parentClass))
|
||||
.map(fix -> {
|
||||
if (fix instanceof IntentionAction action) return action;
|
||||
ModCommandAction modCommandAction = ModCommandService.getInstance().unwrap(fix);
|
||||
return modCommandAction == null ? null : modCommandAction.asIntention();
|
||||
})
|
||||
.nonNull()
|
||||
.toArray(IntentionAction.EMPTY_ARRAY);
|
||||
}
|
||||
|
||||
public static LocalQuickFix @NotNull [] createFixes(@NotNull PsiClass subclass,
|
||||
public static @Nullable ModCommandAction createFix(@NotNull PsiClass subclass,
|
||||
@NotNull PsiClass parentClass) {
|
||||
if (!parentClass.isInterface() && (subclass.isInterface() || subclass.isRecord() || subclass.isEnum())) {
|
||||
return LocalQuickFix.EMPTY_ARRAY;
|
||||
return null;
|
||||
}
|
||||
if (subclass.isAnnotationType()) return LocalQuickFix.EMPTY_ARRAY;
|
||||
if (subclass.isAnnotationType()) return null;
|
||||
PsiModifierList modifiers = subclass.getModifierList();
|
||||
if (modifiers == null) return LocalQuickFix.EMPTY_ARRAY;
|
||||
if (modifiers == null) return null;
|
||||
if (parentClass.isInterface()) {
|
||||
PsiReferenceList targetList = subclass.isInterface() ? subclass.getExtendsList() : subclass.getImplementsList();
|
||||
if (targetList == null) return LocalQuickFix.EMPTY_ARRAY;
|
||||
if (targetList == null) return null;
|
||||
}
|
||||
else if (subclass.getExtendsList() == null || hasNonObjectParent(subclass)) {
|
||||
return LocalQuickFix.EMPTY_ARRAY;
|
||||
return null;
|
||||
}
|
||||
|
||||
LocalQuickFix[] fixes = ExtendSealedClassFix.createFixes(parentClass, subclass);
|
||||
if (fixes != null) return fixes;
|
||||
return new LocalQuickFix[]{new ImplementOrExtendFix(subclass, parentClass).asQuickFix()};
|
||||
ModCommandAction fix = ExtendSealedClassFix.createFix(parentClass, subclass);
|
||||
if (fix != null) return fix;
|
||||
return new ImplementOrExtendFix(subclass, parentClass);
|
||||
}
|
||||
|
||||
static boolean hasNonObjectParent(@NotNull PsiClass psiClass) {
|
||||
|
||||
+6
-2
@@ -60,7 +60,10 @@ import org.jetbrains.annotations.Nls;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.function.Function;
|
||||
|
||||
public final class QuickFixFactoryImpl extends QuickFixFactory {
|
||||
@@ -1050,7 +1053,8 @@ public final class QuickFixFactoryImpl extends QuickFixFactory {
|
||||
public @NotNull List<IntentionAction> createExtendSealedClassFixes(@NotNull PsiJavaCodeReferenceElement subclassRef,
|
||||
@NotNull PsiClass parentClass,
|
||||
@NotNull PsiClass subClass) {
|
||||
return Arrays.asList(ImplementOrExtendFix.createActions(subClass, parentClass));
|
||||
ModCommandAction fix = ImplementOrExtendFix.createFix(subClass, parentClass);
|
||||
return fix == null ? List.of() : List.of(fix.asIntention());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
// "final" "true-preview"
|
||||
// "Make 'Child' extend 'Parent'|->final" "true-preview"
|
||||
sealed class Parent permits Child {}
|
||||
|
||||
final class Child extends Parent {}
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
// "non-sealed" "true-preview"
|
||||
// "Make 'Child' extend 'Parent'|->non-sealed" "true-preview"
|
||||
sealed interface Parent permits Child, Foo {}
|
||||
|
||||
interface OtherParent {}
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
// "final" "true-preview"
|
||||
// "Make 'Child' extend 'Parent'|->final" "true-preview"
|
||||
sealed class Parent permits Child {}
|
||||
|
||||
final class Child extends Parent {}
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
// "final" "false"
|
||||
// "Make 'Child' extend 'Parent'|->final" "false"
|
||||
sealed interface Parent permits C<caret>hild {}
|
||||
|
||||
interface Child {}
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
// "final" "true-preview"
|
||||
// "Make 'Child' extend 'Parent'|->final" "true-preview"
|
||||
sealed class Parent permits C<caret>hild {}
|
||||
|
||||
class Child {}
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
// "non-sealed" "true-preview"
|
||||
// "Make 'Child' extend 'Parent'|->non-sealed" "true-preview"
|
||||
sealed interface Parent permits Child<caret>, Foo {}
|
||||
|
||||
interface OtherParent {}
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
// "final" "false"
|
||||
// "Make 'Child' extend 'Parent'" "false"
|
||||
sealed class Parent permits C<caret>hild {}
|
||||
|
||||
class OtherParent {}
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
// "final" "true-preview"
|
||||
// "Make 'Child' extend 'Parent'|->final" "true-preview"
|
||||
sealed class Parent permits C<caret>hild {}
|
||||
|
||||
class Child extends Object {}
|
||||
-4
@@ -2,10 +2,6 @@
|
||||
package com.intellij.java.codeInsight.intention;
|
||||
|
||||
import com.intellij.codeInsight.daemon.LightIntentionActionTestCase;
|
||||
import com.intellij.ui.ChooserInterceptor;
|
||||
import com.intellij.ui.UiInterceptors;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class ExpandStaticImportActionTest extends LightIntentionActionTestCase {
|
||||
@Override
|
||||
|
||||
-4
@@ -17,10 +17,6 @@ package com.intellij.java.codeInsight.intention;
|
||||
|
||||
import com.intellij.codeInsight.daemon.LightIntentionActionTestCase;
|
||||
import com.intellij.pom.java.LanguageLevel;
|
||||
import com.intellij.ui.ChooserInterceptor;
|
||||
import com.intellij.ui.UiInterceptors;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class ExtractSetFromComparisonChainActionTest extends LightIntentionActionTestCase {
|
||||
@Override
|
||||
|
||||
@@ -6,6 +6,7 @@ import com.intellij.codeInsight.daemon.impl.quickfix.ImplementOrExtendFix;
|
||||
import com.intellij.codeInsight.intention.preview.IntentionPreviewInfo;
|
||||
import com.intellij.codeInspection.*;
|
||||
import com.intellij.lang.java.JavaLanguage;
|
||||
import com.intellij.modcommand.ModCommandAction;
|
||||
import com.intellij.openapi.diagnostic.Logger;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.openapi.ui.Messages;
|
||||
@@ -52,13 +53,13 @@ public class RegistrationProblemsInspection extends DevKitUastInspectionBase {
|
||||
for (PsiClass componentClass : componentClasses) {
|
||||
if (ActionType.ACTION.myClassName.equals(componentClass.getQualifiedName()) &&
|
||||
!checkedClass.isInheritor(componentClass, true)) {
|
||||
LocalQuickFix[] fixes = sourcePsi.getLanguage().is(JavaLanguage.INSTANCE) ?
|
||||
ImplementOrExtendFix.createFixes(checkedClass, componentClass) :
|
||||
LocalQuickFix.EMPTY_ARRAY;
|
||||
ModCommandAction fix = sourcePsi.getLanguage().is(JavaLanguage.INSTANCE) ?
|
||||
ImplementOrExtendFix.createFix(checkedClass, componentClass) :
|
||||
null;
|
||||
ProblemHolderUtilKt.registerUProblem(holder, uClass,
|
||||
DevKitBundle.message("inspections.registration.problems.incompatible.message",
|
||||
componentClass.getQualifiedName()),
|
||||
fixes);
|
||||
fix == null ? LocalQuickFix.EMPTY_ARRAY : new LocalQuickFix[] {fix.asQuickFix()});
|
||||
}
|
||||
}
|
||||
if (ActionType.ACTION.isOfType(checkedClass) && !hasNoArgConstructor(checkedClass)) {
|
||||
|
||||
Reference in New Issue
Block a user