[java-highlighting] IDEA-263473 'Fix all' action for (at least some) compilation errors (initial support for several actions)

GitOrigin-RevId: 9462a17952f9d97eefbf27058ac36ff7b9902df6
This commit is contained in:
Tagir Valeev
2021-03-03 17:05:43 +07:00
committed by intellij-monorepo-bot
parent 97feb54751
commit a0b14b0e24
22 changed files with 336 additions and 29 deletions

View File

@@ -4,6 +4,7 @@ package com.intellij.codeInsight.daemon.impl.quickfix;
import com.intellij.codeInsight.daemon.QuickFixActionRegistrar;
import com.intellij.codeInsight.daemon.QuickFixBundle;
import com.intellij.codeInsight.daemon.impl.actions.IntentionActionWithFixAllOption;
import com.intellij.codeInsight.guess.GuessManager;
import com.intellij.codeInsight.intention.HighPriorityAction;
import com.intellij.codeInsight.intention.impl.BaseIntentionAction;
@@ -24,7 +25,8 @@ import org.jetbrains.annotations.PropertyKey;
import java.util.List;
import java.util.Objects;
public class AddTypeCastFix extends LocalQuickFixAndIntentionActionOnPsiElement implements HighPriorityAction {
public class AddTypeCastFix extends LocalQuickFixAndIntentionActionOnPsiElement
implements HighPriorityAction, IntentionActionWithFixAllOption {
@SafeFieldForPreview
private final PsiType myType;
private final @IntentionName String myName;

View File

@@ -28,7 +28,7 @@ add.new.array.text=Add ''new {0}[]''
add.return.statement.text=Add 'return' statement
add.runtime.exception.to.throws.text=Add runtime exception(s) to method signature
add.runtime.exception.to.throws.family=Add Runtime Exception to Method Signature
add.typecast.family=Add TypeCast
add.typecast.family=Add type cast
add.typecast.text=Cast to ''{0}''
add.qualifier.typecast.text=Cast qualifier to ''{0}''
add.docTag.to.custom.tags=Add {0} to custom tags
@@ -150,6 +150,8 @@ anonymous.class.presentation=Anonymous class derived from {0}
class.initializer.presentation={0} class initializer
add.modifier.fix=Make ''{0}'' {1}
remove.modifier.fix=Make ''{0}'' not {1}
add.modifier.fix.family=Make {0}
remove.modifier.fix.family=Make not {0}
change.inheritors.visibility.warning.text=Do you want to change inheritors' visibility to visibility of the base method?
change.inheritors.visibility.warning.title=Change Inheritors
@@ -258,7 +260,7 @@ fix.single.character.string.to.char.literal.family=Fix literal type
change.to.append.family=Fix StringBuilder append
change.to.append.text=Change to ''{0}''
convert.to.string.family=Fix Character Literal
convert.to.string.family=Fix character literal
convert.to.string.text=Convert to string literal
initialize.final.field.in.constructor.name=Initialize in constructor

View File

@@ -2,8 +2,8 @@
package com.intellij.codeInsight.daemon.impl.quickfix;
import com.intellij.codeInsight.daemon.QuickFixBundle;
import com.intellij.codeInsight.daemon.impl.actions.IntentionActionWithFixAllOption;
import com.intellij.codeInsight.intention.FileModifier;
import com.intellij.codeInsight.intention.IntentionAction;
import com.intellij.codeInsight.intention.impl.BaseIntentionAction;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.project.Project;
@@ -16,7 +16,7 @@ import com.intellij.psi.util.PsiUtil;
import org.jetbrains.annotations.Nls;
import org.jetbrains.annotations.NotNull;
public final class AddMethodBodyFix implements IntentionAction {
public final class AddMethodBodyFix implements IntentionActionWithFixAllOption {
private final PsiMethod myMethod;
private final @Nls String myText;

View File

@@ -16,7 +16,7 @@
package com.intellij.codeInsight.daemon.impl.quickfix;
import com.intellij.codeInsight.daemon.QuickFixBundle;
import com.intellij.codeInsight.intention.IntentionAction;
import com.intellij.codeInsight.daemon.impl.actions.IntentionActionWithFixAllOption;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.text.StringUtil;
@@ -25,7 +25,7 @@ import com.intellij.psi.util.PsiUtil;
import com.intellij.util.IncorrectOperationException;
import org.jetbrains.annotations.NotNull;
public class ConvertToStringLiteralAction implements IntentionAction {
public class ConvertToStringLiteralAction implements IntentionActionWithFixAllOption {
@NotNull
@Override
public String getText() {

View File

@@ -16,8 +16,8 @@
package com.intellij.codeInsight.daemon.impl.quickfix;
import com.intellij.codeInsight.daemon.QuickFixBundle;
import com.intellij.codeInsight.daemon.impl.actions.IntentionActionWithFixAllOption;
import com.intellij.codeInsight.intention.FileModifier;
import com.intellij.codeInsight.intention.IntentionAction;
import com.intellij.codeInsight.intention.impl.BaseIntentionAction;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.project.Project;
@@ -32,7 +32,7 @@ import org.jetbrains.annotations.NotNull;
/**
* @author ven
*/
public final class DeleteMethodBodyFix implements IntentionAction {
public final class DeleteMethodBodyFix implements IntentionActionWithFixAllOption {
private final PsiMethod myMethod;
public DeleteMethodBodyFix(@NotNull PsiMethod method) {

View File

@@ -16,8 +16,8 @@
package com.intellij.codeInsight.daemon.impl.quickfix;
import com.intellij.codeInsight.daemon.QuickFixBundle;
import com.intellij.codeInsight.daemon.impl.actions.IntentionActionWithFixAllOption;
import com.intellij.codeInsight.intention.HighPriorityAction;
import com.intellij.codeInsight.intention.IntentionAction;
import com.intellij.codeInsight.intention.impl.BaseIntentionAction;
import com.intellij.openapi.command.undo.UndoUtil;
import com.intellij.openapi.editor.Editor;
@@ -28,7 +28,7 @@ import com.intellij.psi.util.PsiMatchers;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
public class InsertConstructorCallFix implements IntentionAction, HighPriorityAction {
public class InsertConstructorCallFix implements IntentionActionWithFixAllOption, HighPriorityAction {
protected final PsiMethod myConstructor;
private final String myCall;

View File

@@ -3,6 +3,7 @@ package com.intellij.codeInsight.daemon.impl.quickfix;
import com.intellij.codeInsight.FileModificationService;
import com.intellij.codeInsight.daemon.QuickFixBundle;
import com.intellij.codeInsight.daemon.impl.actions.IntentionActionWithFixAllOption;
import com.intellij.codeInsight.intention.impl.BaseIntentionAction;
import com.intellij.codeInspection.LocalQuickFixAndIntentionActionOnPsiElement;
import com.intellij.codeInspection.util.IntentionName;
@@ -28,7 +29,7 @@ import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.List;
public class ModifierFix extends LocalQuickFixAndIntentionActionOnPsiElement {
public class ModifierFix extends LocalQuickFixAndIntentionActionOnPsiElement implements IntentionActionWithFixAllOption {
private static final Logger LOG = Logger.getInstance(ModifierFix.class);
@PsiModifier.ModifierConstant private final String myModifier;
@@ -101,10 +102,18 @@ public class ModifierFix extends LocalQuickFixAndIntentionActionOnPsiElement {
return myName;
}
@Override
public boolean belongsToMyFamily(@NotNull IntentionActionWithFixAllOption action) {
return action instanceof ModifierFix &&
((ModifierFix)action).myModifier.equals(myModifier) &&
((ModifierFix)action).myShouldHave == myShouldHave;
}
@NotNull
@Override
public String getFamilyName() {
return QuickFixBundle.message("fix.modifiers.family");
return myShouldHave ? QuickFixBundle.message("add.modifier.fix.family", myModifier)
: QuickFixBundle.message("remove.modifier.fix.family", myModifier);
}
@Override

View File

@@ -0,0 +1,15 @@
// "Apply all 'Delete method body' fixes in file" "true"
public interface InterfaceToClass {
void foo();
static void bar() {
class Qux {
abstract static void qux() {}
abstract static void qux1() {}
abstract static void qux2() {}
abstract static void qux3() {}
}
}
void baz();
}

View File

@@ -0,0 +1,18 @@
// "Apply all 'Make default' fixes in file" "true"
public interface InterfaceToClass {
default void foo() {
}
static void bar() {
class Qux {
abstract static void qux() {}
abstract static void qux1() {}
abstract static void qux2() {}
abstract static void qux3() {}
}
}
default void baz() {
}
}

View File

@@ -0,0 +1,18 @@
// "Apply all 'Make not abstract' fixes in file" "true"
public interface InterfaceToClass {
void foo() {
}
static void bar() {
class Qux {
static void qux() {}
static void qux1() {}
static void qux2() {}
static void qux3() {}
}
}
void baz() {
}
}

View File

@@ -0,0 +1,5 @@
// "Apply all 'Fix character literal' fixes in file" "true"
class Test {
String s = "abc";
String s1 = "xyz";
}

View File

@@ -0,0 +1,18 @@
// "Apply all 'Delete method body' fixes in file" "true"
public interface InterfaceToClass {
<caret>void foo() {
}
static void bar() {
class Qux {
abstract static void qux() {}
abstract static void qux1() {}
abstract static void qux2() {}
abstract static void qux3() {}
}
}
void baz() {
}
}

View File

@@ -0,0 +1,18 @@
// "Apply all 'Make default' fixes in file" "true"
public interface InterfaceToClass {
<caret>void foo() {
}
static void bar() {
class Qux {
abstract static void qux() {}
abstract static void qux1() {}
abstract static void qux2() {}
abstract static void qux3() {}
}
}
void baz() {
}
}

View File

@@ -0,0 +1,18 @@
// "Apply all 'Make not abstract' fixes in file" "true"
public interface InterfaceToClass {
void foo() {
}
static void bar() {
class Qux {
<caret>abstract static void qux() {}
abstract static void qux1() {}
abstract static void qux2() {}
abstract static void qux3() {}
}
}
void baz() {
}
}

View File

@@ -0,0 +1,5 @@
// "Apply all 'Fix character literal' fixes in file" "true"
class Test {
String s = '<caret>abc';
String s1 = 'xyz';
}

View File

@@ -0,0 +1,25 @@
/*
* Copyright 2000-2017 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.intellij.java.codeInsight.daemon.quickFix;
import com.intellij.codeInsight.daemon.quickFix.LightQuickFixParameterizedTestCase;
public class MassCompilationErrorFixTest extends LightQuickFixParameterizedTestCase {
@Override
protected String getBasePath() {
return "/codeInsight/daemonCodeAnalyzer/quickFix/massFix";
}
}

View File

@@ -198,3 +198,6 @@ MsBuiltinInspection.inspection.message.second.argument.isnull.implicitly.truncat
MsOrderByInspection.inspection.message.order.by.clause.invalid=ORDER BY clause is invalid in subqueries and views unless TOP, OFFSET, or FOR XML clause is specified
OraErrorHandler.set.locale=Set locale
notification.title.unix.socket.connection=Unix socket connection
command.name.apply.fixes=Apply Fixes
intention.family.name.fix.all.problems.like.this=Fix all problems like this
intention.name.apply.all.fixes.in.file=Apply all ''{0}'' fixes in file

View File

@@ -4,9 +4,8 @@ package com.intellij.codeInsight.daemon.impl;
import com.intellij.codeHighlighting.Pass;
import com.intellij.codeInsight.daemon.GutterMark;
import com.intellij.codeInsight.daemon.HighlightDisplayKey;
import com.intellij.codeInsight.intention.EmptyIntentionAction;
import com.intellij.codeInsight.intention.IntentionAction;
import com.intellij.codeInsight.intention.IntentionManager;
import com.intellij.codeInsight.daemon.impl.actions.IntentionActionWithFixAllOption;
import com.intellij.codeInsight.intention.*;
import com.intellij.codeInspection.*;
import com.intellij.codeInspection.ex.GlobalInspectionToolWrapper;
import com.intellij.codeInspection.ex.InspectionToolWrapper;
@@ -834,6 +833,9 @@ public class HighlightInfo implements Segment {
return null;
}
List<IntentionAction> options = myOptions;
if (options != null) {
return options;
}
HighlightDisplayKey key = myKey;
if (myProblemGroup != null) {
String problemName = myProblemGroup.getProblemName();
@@ -842,8 +844,15 @@ public class HighlightInfo implements Segment {
key = problemGroupKey;
}
}
if (options != null || key == null) {
return options;
if (key == null) {
IntentionAction action = IntentionActionDelegate.unwrap(myAction);
if (action instanceof IntentionActionWithOptions) {
options = ((IntentionActionWithOptions)action).getOptions();
if (!options.isEmpty()) {
return updateOptions(options);
}
}
return null;
}
IntentionManager intentionManager = IntentionManager.getInstance();
List<IntentionAction> newOptions = intentionManager.getStandardIntentionOptions(key, element);
@@ -856,7 +865,6 @@ public class HighlightInfo implements Segment {
}
}
if (toolWrapper != null) {
myCanCleanup = toolWrapper.isCleanupTool();
IntentionAction fixAllIntention = intentionManager.createFixAllIntention(toolWrapper, myAction);
@@ -898,15 +906,15 @@ public class HighlightInfo implements Segment {
ContainerUtil.addAll(newOptions, suppressActions);
}
//noinspection SynchronizeOnThis
synchronized (this) {
options = myOptions;
if (options == null) {
myOptions = options = newOptions;
}
myKey = null;
}
return updateOptions(newOptions);
}
private synchronized List<IntentionAction> updateOptions(List<IntentionAction> newOptions) {
List<IntentionAction> options = myOptions;
if (options == null) {
myOptions = options = newOptions;
}
myKey = null;
return options;
}
@@ -985,4 +993,14 @@ public class HighlightInfo implements Segment {
quickFixActionRanges.removeIf(pair -> condition.value(pair.first.getAction()));
}
}
public @Nullable IntentionAction getSameFamilyFix(IntentionActionWithFixAllOption action) {
if (quickFixActionRanges == null) return null;
for (Pair<IntentionActionDescriptor, TextRange> range : quickFixActionRanges) {
IntentionAction other = IntentionActionDelegate.unwrap(range.first.myAction);
if (other instanceof IntentionActionWithFixAllOption &&
action.belongsToMyFamily((IntentionActionWithFixAllOption)other)) return other;
}
return null;
}
}

View File

@@ -0,0 +1,91 @@
// Copyright 2000-2021 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.
package com.intellij.codeInsight.daemon.impl.actions;
import com.intellij.analysis.AnalysisBundle;
import com.intellij.codeInsight.FileModificationService;
import com.intellij.codeInsight.daemon.impl.DaemonCodeAnalyzerEx;
import com.intellij.codeInsight.daemon.impl.DaemonProgressIndicator;
import com.intellij.codeInsight.intention.IntentionAction;
import com.intellij.lang.annotation.HighlightSeverity;
import com.intellij.openapi.application.ex.ApplicationManagerEx;
import com.intellij.openapi.command.CommandProcessor;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.progress.ProgressManager;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Pair;
import com.intellij.psi.PsiDocumentManager;
import com.intellij.psi.PsiFile;
import com.intellij.util.IncorrectOperationException;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.List;
public class FixAllHighlightingProblems implements IntentionAction {
private final IntentionActionWithFixAllOption myAction;
public FixAllHighlightingProblems(IntentionActionWithFixAllOption action) {
myAction = action;
}
@Override
public @NotNull String getText() {
return AnalysisBundle.message("intention.name.apply.all.fixes.in.file", myAction.getFamilyName());
}
@Override
public @NotNull String getFamilyName() {
return AnalysisBundle.message("intention.family.name.fix.all.problems.like.this");
}
@Override
public boolean isAvailable(@NotNull Project project,
Editor editor,
PsiFile file) {
return true;
}
@Override
public void invoke(@NotNull Project project, Editor editor, PsiFile file) throws IncorrectOperationException {
// IntentionAction, offset
List<Pair<IntentionAction, Integer>> actions = new ArrayList<>();
Document document = editor.getDocument();
ProgressManager.getInstance().runProcess(() -> {
DaemonCodeAnalyzerEx.processHighlights(document, project, HighlightSeverity.ERROR, 0, document.getTextLength(),
info -> {
ProgressManager.checkCanceled();
IntentionAction fix = info.getSameFamilyFix(myAction);
if (fix != null) {
actions.add(Pair.create(fix, info.getActualStartOffset()));
}
return true;
});
}, new DaemonProgressIndicator());
if (actions.isEmpty() || !FileModificationService.getInstance().preparePsiElementForWrite(file)) return;
String message = AnalysisBundle.message("command.name.apply.fixes");
CommandProcessor.getInstance().executeCommand(project, () -> {
ApplicationManagerEx.getApplicationEx()
.runWriteActionWithCancellableProgressInDispatchThread(message, project, null, indicator -> {
PsiDocumentManager psiDocumentManager = PsiDocumentManager.getInstance(project);
for (Pair<IntentionAction, Integer> pair : actions) {
IntentionAction action = pair.getFirst();
// Some actions rely on the caret position
editor.getCaretModel().moveToOffset(pair.getSecond());
if (action.isAvailable(project, editor, file)) {
action.invoke(project, editor, file);
psiDocumentManager.doPostponedOperationsAndUnblockDocument(document);
psiDocumentManager.commitDocument(document);
}
}
});
}, message, null);
}
@Override
public boolean startInWriteAction() {
return false;
}
}

View File

@@ -0,0 +1,36 @@
// Copyright 2000-2021 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.
package com.intellij.codeInsight.daemon.impl.actions;
import com.intellij.codeInsight.intention.IntentionAction;
import com.intellij.codeInsight.intention.IntentionActionWithOptions;
import org.jetbrains.annotations.NotNull;
import java.util.Collections;
import java.util.List;
/**
* An intention action that is used to fix the compilation error
* with the ability to apply to the current file in batch.
* <p>
* If the action is used as an inspection quick-fix or
* as a usual intention action, using this interface will have no effect.
*/
public interface IntentionActionWithFixAllOption extends IntentionActionWithOptions {
@Override
default @NotNull List<IntentionAction> getOptions() {
return Collections.singletonList(new FixAllHighlightingProblems(this));
}
/**
* Tests if the supplied action belongs to the same family as this action.
* Default implementation assumes that actions from the same family
* must have the same runtime class. Could be overridden to provide more specific
* behavior. Must be symmetrical (x.belongsToMyFamily(y) == y.belongsToMyFamily(x))
*
* @param action action to check
* @return true if the action belongs to the same family as this action.
*/
default boolean belongsToMyFamily(@NotNull IntentionActionWithFixAllOption action) {
return action.getClass().equals(getClass());
}
}

View File

@@ -3,6 +3,7 @@ package com.intellij.codeInsight.intention.impl;
import com.intellij.codeInsight.intention.FileModifier;
import com.intellij.codeInsight.intention.IntentionAction;
import com.intellij.codeInsight.intention.IntentionActionDelegate;
import com.intellij.codeInsight.intention.PriorityAction;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.project.Project;
@@ -16,7 +17,7 @@ import org.jetbrains.annotations.Nullable;
/**
* @author Danila Ponomarenko
*/
public final class PriorityIntentionActionWrapper implements IntentionAction, PriorityAction {
public final class PriorityIntentionActionWrapper implements IntentionAction, PriorityAction, IntentionActionDelegate {
private final IntentionAction myAction;
private final Priority myPriority;
@@ -70,6 +71,11 @@ public final class PriorityIntentionActionWrapper implements IntentionAction, Pr
new PriorityIntentionActionWrapper(delegate, myPriority);
}
@Override
public @NotNull IntentionAction getDelegate() {
return myAction;
}
@NotNull
public static IntentionAction highPriority(@NotNull IntentionAction action) {
return new PriorityIntentionActionWrapper(action, Priority.HIGH);

View File

@@ -345,7 +345,7 @@ public final class CachedIntentions {
private int getWeight(@NotNull IntentionActionWithTextCaching action) {
IntentionAction a = action.getAction();
int group = getGroup(action).getPriority();
while (a instanceof IntentionActionDelegate) {
while (a instanceof IntentionActionDelegate && !(a instanceof PriorityAction)) {
a = ((IntentionActionDelegate)a).getDelegate();
}
if (a instanceof PriorityAction) {