[mod-commands] ModShowConflicts; use in MakeClassFinalFix

GitOrigin-RevId: 50c4332cf67c60462446c4b470526301c076412b
This commit is contained in:
Tagir Valeev
2023-07-11 13:24:33 +02:00
committed by intellij-monorepo-bot
parent edc2082508
commit e26af76325
9 changed files with 98 additions and 71 deletions

View File

@@ -232,3 +232,4 @@ modcommand.result.action.completed.successfully=Action completed successfully
modcommand.result.action.is.interactive.only.cannot.be.executed.in.batch=Action is interactive only; cannot be executed in batch
modcommand.result.action.has.no.effect=Action has no effect
modcommand.result.action.was.aborted=Action was aborted
modcommand.result.conflict=There are conflicts; user interaction required

View File

@@ -183,6 +183,9 @@ public final class IntentionPreviewUtils {
else if (command instanceof ModChooseAction target) {
return getChoosePreview(context, target);
}
else if (command instanceof ModShowConflicts showConflicts) {
return getModCommandPreview(showConflicts.nextStep(), context);
}
else if (command instanceof ModDisplayMessage message) {
if (message.kind() == ModDisplayMessage.MessageKind.ERROR) {
return new IntentionPreviewInfo.Html(new HtmlBuilder().append(

View File

@@ -16,7 +16,7 @@ import java.util.Set;
*/
public sealed interface ModCommand
permits ModChooseAction, ModCompositeCommand, ModCopyToClipboard, ModCreateFile, ModDeleteFile, ModDisplayMessage, ModHighlight,
ModNavigate, ModNothing, ModRenameSymbol, ModUpdateFileText {
ModNavigate, ModNothing, ModRenameSymbol, ModShowConflicts, ModUpdateFileText {
/**
* @return true if the command does nothing

View File

@@ -44,8 +44,8 @@ public interface ModCommandExecutor {
sealed interface BatchExecutionResult {
default @NotNull BatchExecutionResult compose(@NotNull BatchExecutionResult next) {
if (next == Result.NOTHING || next.equals(this) || this instanceof Error) return this;
if (this == Result.NOTHING || next instanceof Error) return next;
if (next == Result.NOTHING || next.equals(this) || this instanceof Error || this == Result.CONFLICTS) return this;
if (this == Result.NOTHING || next instanceof Error || next == Result.CONFLICTS) return next;
if (this == Result.ABORT || next == Result.ABORT) return Result.ABORT;
return Result.SUCCESS;
}
@@ -69,8 +69,11 @@ public interface ModCommandExecutor {
/**
* Action was aborted
*/
ABORT;
ABORT,
/**
* Conflicts should be displayed and confirmed
*/
CONFLICTS;
@Nls
@Override
@@ -80,6 +83,7 @@ public interface ModCommandExecutor {
case INTERACTIVE -> AnalysisBundle.message("modcommand.result.action.is.interactive.only.cannot.be.executed.in.batch");
case NOTHING -> AnalysisBundle.message("modcommand.result.action.has.no.effect");
case ABORT -> AnalysisBundle.message("modcommand.result.action.was.aborted");
case CONFLICTS -> AnalysisBundle.message("modcommand.result.conflict");
};
}
}

View File

@@ -0,0 +1,27 @@
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.modcommand;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.PsiElement;
import org.jetbrains.annotations.Nls;
import org.jetbrains.annotations.NotNull;
import java.util.List;
import java.util.Map;
import java.util.Set;
public record ModShowConflicts(@NotNull Map<@NotNull PsiElement, @NotNull Conflict> conflicts,
@NotNull ModCommand nextStep) implements ModCommand {
public record Conflict(@NotNull List<@NotNull @Nls String> messages) {
}
@Override
public boolean isEmpty() {
return conflicts().isEmpty() && nextStep().isEmpty();
}
@Override
public @NotNull Set<@NotNull VirtualFile> modifiedFiles() {
return nextStep.modifiedFiles();
}
}

View File

@@ -42,9 +42,11 @@ import com.intellij.psi.search.LocalSearchScope;
import com.intellij.psi.search.searches.ReferencesSearch;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.refactoring.RefactoringBundle;
import com.intellij.refactoring.ui.ConflictsDialog;
import com.intellij.util.concurrency.AppExecutorUtil;
import com.intellij.util.concurrency.annotations.RequiresEdt;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.containers.MultiMap;
import one.util.streamex.StreamEx;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -106,6 +108,12 @@ public class ModCommandExecutorImpl implements ModCommandExecutor {
command instanceof ModCopyToClipboard || command instanceof ModRenameSymbol) {
return Result.INTERACTIVE;
}
if (command instanceof ModShowConflicts showConflicts) {
if (showConflicts.conflicts().isEmpty()) {
return executeInBatch(context, showConflicts.nextStep());
}
return Result.CONFLICTS;
}
if (command instanceof ModDisplayMessage message) {
if (message.kind() == ModDisplayMessage.MessageKind.ERROR) {
return new Error(message.messageText());
@@ -174,9 +182,25 @@ public class ModCommandExecutorImpl implements ModCommandExecutor {
if (command instanceof ModDeleteFile deleteFile) {
return executeDelete(project, deleteFile);
}
if (command instanceof ModShowConflicts showConflicts) {
return executeShowConflicts(context, showConflicts, editor);
}
throw new IllegalArgumentException("Unknown command: " + command);
}
private boolean executeShowConflicts(@NotNull ActionContext context, @NotNull ModShowConflicts conflicts, @Nullable Editor editor) {
MultiMap<PsiElement, String> conflictData = new MultiMap<>();
conflicts.conflicts().forEach((e, c) -> conflictData.put(e, c.messages()));
if (!conflictData.isEmpty()) {
var conflictsDialog =
new ConflictsDialog(context.project(), conflictData, () -> doExecuteInteractively(context, conflicts.nextStep(), editor));
if (!conflictsDialog.showAndGet()) {
return false;
}
}
return doExecuteInteractively(context, conflicts.nextStep(), editor);
}
private static boolean executeCopyToClipboard(@NotNull ModCopyToClipboard clipboard) {
CopyPasteManager.getInstance().setContents(new StringSelection(clipboard.content()));
return true;

View File

@@ -1,34 +1,35 @@
/*
* Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
*/
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.siyeh.ig.fixes;
import com.intellij.codeInsight.intention.preview.IntentionPreviewInfo;
import com.intellij.codeInsight.intention.preview.IntentionPreviewUtils;
import com.intellij.codeInspection.ModCommands;
import com.intellij.codeInspection.ProblemDescriptor;
import com.intellij.openapi.application.WriteAction;
import com.intellij.modcommand.ModCommand;
import com.intellij.modcommand.ModCommandQuickFix;
import com.intellij.modcommand.ModShowConflicts;
import com.intellij.openapi.project.Project;
import com.intellij.psi.PsiClass;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.intellij.psi.PsiModifierList;
import com.intellij.psi.search.searches.ClassInheritorsSearch;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.refactoring.ui.ConflictsDialog;
import com.intellij.refactoring.util.RefactoringUIUtil;
import com.intellij.util.Query;
import com.intellij.util.containers.MultiMap;
import com.siyeh.InspectionGadgetsBundle;
import com.siyeh.ig.InspectionGadgetsFix;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import static com.intellij.psi.PsiModifier.ABSTRACT;
import static com.intellij.psi.PsiModifier.FINAL;
/**
* @author Bas Leijdekkers
*/
public class MakeClassFinalFix extends InspectionGadgetsFix {
public class MakeClassFinalFix extends ModCommandQuickFix {
private final String className;
@@ -50,41 +51,26 @@ public class MakeClassFinalFix extends InspectionGadgetsFix {
}
@Override
protected void doFix(@NotNull Project project, @NotNull ProblemDescriptor descriptor) {
public @NotNull ModCommand perform(@NotNull Project project, @NotNull ProblemDescriptor descriptor) {
final PsiElement element = descriptor.getPsiElement();
final PsiClass containingClass = findClassToFix(element);
if (containingClass == null) {
return;
return ModCommands.nop();
}
final PsiModifierList modifierList = containingClass.getModifierList();
assert modifierList != null;
if (!isOnTheFly()) {
if (ClassInheritorsSearch.search(containingClass).findFirst() != null) {
return;
}
WriteAction.run(() -> doMakeFinal(modifierList));
return;
}
final MultiMap<PsiElement, String> conflicts = new MultiMap<>();
final Query<PsiClass> search = ClassInheritorsSearch.search(containingClass);
search.forEach(aClass -> {
conflicts.putValue(containingClass, InspectionGadgetsBundle
.message("0.will.no.longer.be.overridable.by.1", RefactoringUIUtil.getDescription(containingClass, false),
RefactoringUIUtil.getDescription(aClass, false)));
return true;
});
final boolean conflictsDialogOK;
if (!conflicts.isEmpty()) {
ConflictsDialog conflictsDialog =
new ConflictsDialog(element.getProject(), conflicts, () -> WriteAction.run(() -> doMakeFinal(modifierList)));
conflictsDialogOK = conflictsDialog.showAndGet();
}
else {
conflictsDialogOK = true;
}
if (conflictsDialogOK) {
WriteAction.run(() -> doMakeFinal(modifierList));
final List<String> conflictMessages = new ArrayList<>();
if (!IntentionPreviewUtils.isIntentionPreviewActive()) {
final Query<PsiClass> search = ClassInheritorsSearch.search(containingClass);
search.forEach(aClass -> {
conflictMessages.add(InspectionGadgetsBundle
.message("0.will.no.longer.be.overridable.by.1", RefactoringUIUtil.getDescription(containingClass, false),
RefactoringUIUtil.getDescription(aClass, false)));
return true;
});
}
ModShowConflicts.Conflict conflict = new ModShowConflicts.Conflict(conflictMessages);
return new ModShowConflicts(Map.of(containingClass, conflict), ModCommands.psiUpdate(modifierList, list -> doMakeFinal(list)));
}
private static void doMakeFinal(PsiModifierList modifierList) {
@@ -99,27 +85,4 @@ public class MakeClassFinalFix extends InspectionGadgetsFix {
}
return containingClass.getModifierList() == null ? null : containingClass;
}
@Override
public @NotNull IntentionPreviewInfo generatePreview(@NotNull Project project, @NotNull ProblemDescriptor previewDescriptor) {
final PsiClass aClass = findClassToFix(previewDescriptor.getPsiElement());
if (aClass == null) {
return IntentionPreviewInfo.EMPTY;
}
final PsiModifierList modifierList = aClass.getModifierList();
assert modifierList != null;
doMakeFinal(modifierList);
return IntentionPreviewInfo.DIFF;
}
@Nullable
@Override
public PsiElement getElementToMakeWritable(@NotNull PsiFile currentFile) {
return currentFile;
}
@Override
public boolean startInWriteAction() {
return false;
}
}

View File

@@ -15,6 +15,7 @@
*/
package com.siyeh.ig.initialization;
import com.intellij.codeInspection.LocalQuickFix;
import com.intellij.psi.*;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.psi.util.PsiUtil;
@@ -35,21 +36,21 @@ import java.util.List;
public class OverridableMethodCallDuringObjectConstructionInspection extends BaseInspection {
@Override
protected InspectionGadgetsFix @NotNull [] buildFixes(Object... infos) {
protected LocalQuickFix @NotNull [] buildFixes(Object... infos) {
final PsiMethodCallExpression methodCallExpression = (PsiMethodCallExpression)infos[0];
final PsiClass callClass = ClassUtils.getContainingClass(methodCallExpression);
final PsiMethod method = methodCallExpression.resolveMethod();
if (method == null || callClass == null || MethodUtils.isOverriddenInHierarchy(method, callClass)) {
return InspectionGadgetsFix.EMPTY_ARRAY;
}
final List<InspectionGadgetsFix> fixes = new SmartList<>();
final List<LocalQuickFix> fixes = new SmartList<>();
if (!callClass.hasModifierProperty(PsiModifier.ABSTRACT)) {
fixes.add(new MakeClassFinalFix(callClass));
}
if (!(method instanceof PsiCompiledElement) && !method.hasModifierProperty(PsiModifier.ABSTRACT)) {
fixes.add(new MakeMethodFinalFix(method.getName()));
}
return fixes.toArray(InspectionGadgetsFix.EMPTY_ARRAY);
return fixes.toArray(LocalQuickFix.EMPTY_ARRAY);
}
@Override

View File

@@ -1,4 +1,4 @@
// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.siyeh.ig.initialization;
import com.intellij.codeInsight.intention.IntentionAction;
@@ -30,7 +30,11 @@ public class OverridableMethodCallDuringObjectConstructionInspectionTest extends
final IntentionAction intention = myFixture.getAvailableIntention(InspectionGadgetsBundle.message("make.method.final.fix.name", "a"));
assertNotNull(intention);
String customPreviewText = myFixture.getIntentionPreviewText(intention);
assertEquals("public final void a() {}", customPreviewText);
assertEquals("""
class One {
public final void a() {}
}
""", customPreviewText);
}
public void testNoQuickFixes() {