[java-inspections] RemoveInitializerFix2: mod-command action, with chooser

IDEA-323888 Replace 'side effect' dialog in Java quick-fixes with chooser

GitOrigin-RevId: 99e43ec42520ff407ad755c1b05cd5c9320bd273
This commit is contained in:
Tagir Valeev
2023-06-28 16:59:55 +02:00
committed by intellij-monorepo-bot
parent 51c5999d0b
commit 5b29f251c1
13 changed files with 212 additions and 29 deletions

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.intellij.codeInspection.defUse;
import com.intellij.codeInsight.ExpressionUtil;
@@ -157,7 +157,7 @@ public class DefUseInspection extends AbstractBaseJavaLocalInspectionTool {
private static void reportInitializerProblem(PsiVariable psiVariable, ProblemsHolder holder) {
List<LocalQuickFix> fixes = ContainerUtil.createMaybeSingletonList(
isOnTheFlyOrNoSideEffects(holder.isOnTheFly(), psiVariable, psiVariable.getInitializer()) ? new RemoveInitializerFix() : null);
isOnTheFlyOrNoSideEffects(holder.isOnTheFly(), psiVariable, psiVariable.getInitializer()) ? new RemoveInitializerFix2() : null);
holder.registerProblem(ObjectUtils.notNull(psiVariable.getInitializer(), psiVariable),
JavaBundle.message("inspection.unused.assignment.problem.descriptor2", psiVariable.getName()),
fixes.toArray(LocalQuickFix.EMPTY_ARRAY)

View File

@@ -5,13 +5,15 @@ import com.intellij.codeInsight.BlockUtils;
import com.intellij.codeInsight.daemon.QuickFixBundle;
import com.intellij.codeInsight.intention.PriorityAction;
import com.intellij.codeInspection.CommonQuickFixBundle;
import com.intellij.modcommand.ModPsiUpdater;
import com.intellij.codeInspection.PsiUpdateModCommandAction;
import com.intellij.codeInspection.util.IntentionName;
import com.intellij.modcommand.ModPsiUpdater;
import com.intellij.openapi.util.TextRange;
import com.intellij.psi.*;
import com.intellij.psi.util.JavaElementKind;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.psi.util.PsiUtil;
import com.intellij.util.containers.ContainerUtil;
import com.siyeh.ig.psiutils.CommentTracker;
import com.siyeh.ig.psiutils.SideEffectChecker;
import com.siyeh.ig.psiutils.StatementExtractor;
@@ -24,8 +26,7 @@ import java.util.Objects;
public class DeleteSideEffectsAwareFix extends PsiUpdateModCommandAction<PsiStatement> {
private final SmartPsiElementPointer<PsiExpression> myExpressionPtr;
private final @IntentionName String myMessage;
private final boolean myIsAvailable;
private final boolean myAlwaysAvailable;
public DeleteSideEffectsAwareFix(@NotNull PsiStatement statement, PsiExpression expression) {
this(statement, expression, false);
@@ -33,26 +34,9 @@ public class DeleteSideEffectsAwareFix extends PsiUpdateModCommandAction<PsiStat
public DeleteSideEffectsAwareFix(@NotNull PsiStatement statement, PsiExpression expression, boolean alwaysAvailable) {
super(statement);
myAlwaysAvailable = alwaysAvailable;
SmartPointerManager manager = SmartPointerManager.getInstance(statement.getProject());
myExpressionPtr = manager.createSmartPsiElementPointer(expression);
List<PsiExpression> sideEffects = SideEffectChecker.extractSideEffectExpressions(expression);
if (sideEffects.isEmpty()) {
JavaElementKind kind = statement instanceof PsiExpressionStatement ? JavaElementKind.EXPRESSION : JavaElementKind.STATEMENT;
myMessage = CommonQuickFixBundle.message("fix.remove.title", kind.object());
}
else {
PsiStatement[] statements = StatementExtractor.generateStatements(sideEffects, expression);
if (statements.length == 1 && statements[0] instanceof PsiIfStatement) {
myMessage = QuickFixBundle.message("extract.side.effects.convert.to.if");
}
else {
myMessage = QuickFixBundle.message("extract.side.effects", statements.length);
}
}
myIsAvailable = alwaysAvailable ||
// "Remove unnecessary parentheses" action is already present which will do the same
sideEffects.size() != 1 || !(statement instanceof PsiExpressionStatement) ||
sideEffects.get(0) != PsiUtil.skipParenthesizedExprDown(expression);
}
@Nls
@@ -63,8 +47,37 @@ public class DeleteSideEffectsAwareFix extends PsiUpdateModCommandAction<PsiStat
}
@Override
protected @Nullable Presentation getPresentation(@NotNull ActionContext context, @NotNull PsiStatement element) {
return myIsAvailable ? Presentation.of(myMessage).withPriority(PriorityAction.Priority.LOW) : null;
protected @Nullable Presentation getPresentation(@NotNull ActionContext context, @NotNull PsiStatement statement) {
PsiExpression expression = myExpressionPtr.getElement();
if (expression == null) return null;
List<PsiExpression> sideEffects = SideEffectChecker.extractSideEffectExpressions(expression);
String message = getMessage(expression, sideEffects);
if (!myAlwaysAvailable &&
// "Remove unnecessary parentheses" action is already present which will do the same
sideEffects.size() == 1 && statement instanceof PsiExpressionStatement &&
sideEffects.get(0) == PsiUtil.skipParenthesizedExprDown(expression)) {
return null;
}
return Presentation.of(message).withPriority(PriorityAction.Priority.LOW)
.withHighlighting(ContainerUtil.map2Array(sideEffects, TextRange.EMPTY_ARRAY, effect -> effect.getTextRange()));
}
/**
* @param expression expression to remove
* @param sideEffects side effects
* @return inspection message
*/
@IntentionName
public static @NotNull String getMessage(@NotNull PsiExpression expression, @NotNull List<@NotNull PsiExpression> sideEffects) {
if (sideEffects.isEmpty()) {
JavaElementKind kind = expression.getParent() instanceof PsiExpressionStatement ? JavaElementKind.EXPRESSION : JavaElementKind.STATEMENT;
return CommonQuickFixBundle.message("fix.remove.title", kind.object());
}
PsiStatement[] statements = StatementExtractor.generateStatements(sideEffects, expression);
if (statements.length == 1 && statements[0] instanceof PsiIfStatement) {
return QuickFixBundle.message("extract.side.effects.convert.to.if");
}
return QuickFixBundle.message("extract.side.effects", statements.length);
}
@Override

View File

@@ -27,7 +27,7 @@ public class RemoveInitializerFix implements LocalQuickFix {
@Override
@NotNull
public String getFamilyName() {
return JavaBundle.message("inspection.unused.assignment.remove.quickfix");
return JavaBundle.message("inspection.unused.assignment.remove.initializer.quickfix");
}
@Override

View File

@@ -0,0 +1,91 @@
// 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.codeInspection;
import com.intellij.codeInsight.BlockUtils;
import com.intellij.codeInsight.daemon.QuickFixBundle;
import com.intellij.codeInsight.daemon.impl.quickfix.DeleteElementFix;
import com.intellij.codeInsight.daemon.impl.quickfix.DeleteSideEffectsAwareFix;
import com.intellij.java.JavaBundle;
import com.intellij.modcommand.*;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.TextRange;
import com.intellij.psi.*;
import com.intellij.util.containers.ContainerUtil;
import com.siyeh.ig.psiutils.CodeBlockSurrounder;
import com.siyeh.ig.psiutils.CommentTracker;
import com.siyeh.ig.psiutils.SideEffectChecker;
import com.siyeh.ig.psiutils.StatementExtractor;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.List;
public class RemoveInitializerFix2 extends ModCommandQuickFix {
@Override
@NotNull
public String getFamilyName() {
return JavaBundle.message("inspection.unused.assignment.remove.initializer.quickfix");
}
@Override
public @NotNull ModCommand perform(@NotNull Project project, @NotNull ProblemDescriptor descriptor) {
if (!(descriptor.getPsiElement() instanceof PsiExpression initializer)) return ModCommands.nop();
if (!(initializer.getParent() instanceof PsiVariable variable)) return ModCommands.nop();
List<ModCommandAction> subActions;
List<PsiExpression> sideEffects = SideEffectChecker.extractSideEffectExpressions(initializer);
if (!sideEffects.isEmpty()) {
subActions = List.of(new DeleteElementFix(initializer, JavaBundle.message("delete.initializer.completely")),
new SideEffectAwareRemove(variable));
}
else {
subActions = List.of(new DeleteElementFix(initializer));
}
return new ModChooseAction(JavaBundle.message("inspection.unused.assignment.remove.initializer.quickfix.title"), subActions);
}
static class SideEffectAwareRemove extends PsiUpdateModCommandAction<PsiVariable> {
SideEffectAwareRemove(@NotNull PsiVariable variable) {
super(variable);
}
@Override
protected void invoke(@NotNull ActionContext context, @NotNull PsiVariable variable, @NotNull ModPsiUpdater updater) {
PsiExpression initializer = variable.getInitializer();
if (initializer == null) return;
CodeBlockSurrounder surrounder = CodeBlockSurrounder.forExpression(initializer);
if (surrounder == null) return;
CodeBlockSurrounder.SurroundResult result = surrounder.surround();
PsiStatement anchor = result.getAnchor();
initializer = result.getExpression();
List<PsiExpression> sideEffects = SideEffectChecker.extractSideEffectExpressions(initializer);
CommentTracker ct = new CommentTracker();
sideEffects.forEach(ct::markUnchanged);
PsiStatement[] statements = StatementExtractor.generateStatements(sideEffects, initializer);
if (statements.length > 0) {
BlockUtils.addBefore(anchor, statements);
}
PsiElement parent = initializer.getParent();
if (parent instanceof PsiVariable) {
ct.deleteAndRestoreComments(initializer);
} else if (parent instanceof PsiAssignmentExpression) {
ct.deleteAndRestoreComments(parent.getParent());
}
}
@Override
protected @Nullable Presentation getPresentation(@NotNull ActionContext context, @NotNull PsiVariable variable) {
PsiExpression initializer = variable.getInitializer();
if (initializer == null) return null;
if (!CodeBlockSurrounder.canSurround(initializer)) return null;
List<PsiExpression> sideEffects = SideEffectChecker.extractSideEffectExpressions(initializer);
return Presentation.of(DeleteSideEffectsAwareFix.getMessage(initializer, sideEffects))
.withHighlighting(ContainerUtil.map2Array(sideEffects, TextRange.EMPTY_ARRAY, PsiExpression::getTextRange));
}
@Override
public @NotNull String getFamilyName() {
return QuickFixBundle.message("extract.side.effects.family.name");
}
}
}

View File

@@ -0,0 +1,15 @@
// "Remove redundant initializer" "true-preview"
class A {
void test() {
if (!isA() || !isB()) {
isC();
}
boolean b;
b = 10;
System.out.println(b);
}
native boolean isA();
native boolean isB();
native boolean isC();
}

View File

@@ -0,0 +1,12 @@
// "Remove redundant initializer" "true-preview"
class A {
void test() {
boolean b = isA() <caret>&& isB() || isC();
b = 10;
System.out.println(b);
}
native boolean isA();
native boolean isB();
native boolean isC();
}

View File

@@ -0,0 +1,12 @@
// "Remove redundant initializer" "true-preview"
class A {
private String myFoo;
protected String abc() {
return "";
}
public A(String myFoo) {
this.myFoo = myFoo;
}
}

View File

@@ -0,0 +1,15 @@
// "Remove redundant initializer" "true-preview"
class A {
void testFor() {
if (true)
for(int i;; i++) {
i = 10;
System.out.println("Hello!");
}
}
private static int read() {
System.out.println();
return 0;
}}

View File

@@ -0,0 +1,12 @@
// "Remove redundant initializer" "true-preview"
class A {
void test() {
boolean b;
b = 10;
System.out.println(b);
}
native boolean isA();
native boolean isB();
native boolean isC();
}

View File

@@ -1,13 +1,24 @@
// 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.intellij.codeInsight.daemon.impl.quickfix;
import com.intellij.codeInsight.daemon.quickFix.LightQuickFixParameterizedTestCase;
import com.intellij.codeInspection.LocalInspectionTool;
import com.intellij.codeInspection.defUse.DefUseInspection;
import com.intellij.codeInspection.sillyAssignment.SillyAssignmentInspection;
import com.intellij.ui.ChooserInterceptor;
import com.intellij.ui.UiInterceptors;
import org.jetbrains.annotations.NotNull;
public class RemoveUnusedAssignmentTest extends LightQuickFixParameterizedTestCase {
@Override
protected void setUp() throws Exception {
super.setUp();
String name = getTestName(false);
if (name.endsWith("WithSideEffect.java")) {
UiInterceptors.register(new ChooserInterceptor(null, "Extract side effect.*"));
}
}
@Override
protected LocalInspectionTool @NotNull [] configureLocalInspectionTools() {
return new LocalInspectionTool[] {new DefUseInspection(), new SillyAssignmentInspection()};

View File

@@ -753,7 +753,8 @@ inspection.unused.assignment.problem.descriptor4=The value changed at <code>#ref
inspection.unused.assignment.problem.descriptor5=The value of pattern variable <code>#ref</code> #loc is never used
inspection.unused.assignment.problem.descriptor6=The value of foreach iteration parameter <code>#ref</code> #loc is never used
inspection.unused.assignment.remove.assignment.quickfix=Remove redundant assignment
inspection.unused.assignment.remove.quickfix=Remove redundant initializer
inspection.unused.assignment.remove.initializer.quickfix=Remove redundant initializer
inspection.unused.assignment.remove.initializer.quickfix.title=Remove Redundant Initializer
inspection.unused.parameter.problem.descriptor=Parameter <code>#ref</code> is not used
inspection.unused.parameter.composer=Parameter <code>#ref</code> is not used in any implementation
inspection.unused.parameter.composer1=Parameter <code>#ref</code> is not used in this method nor in any of its overriding methods
@@ -1860,4 +1861,5 @@ convert.number.binary=binary
convert.number.octal=octal
convert.number.decimal=decimal
convert.number.plain.format=plain format
convert.number.scientific.format=scientific format
convert.number.scientific.format=scientific format
delete.initializer.completely=Delete initializer completely