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

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

GitOrigin-RevId: 0ceadfeebf9bf294d5201a641922efba3b8a66be
This commit is contained in:
Tagir Valeev
2023-06-28 18:48:48 +02:00
committed by intellij-monorepo-bot
parent 5b29f251c1
commit 640306d59f
19 changed files with 271 additions and 379 deletions

View File

@@ -1,18 +1,16 @@
// 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.FileModificationService;
import com.intellij.codeInsight.editorActions.DeclarationJoinLinesHandler;
import com.intellij.codeInsight.intention.preview.IntentionPreviewInfo;
import com.intellij.java.JavaBundle;
import com.intellij.openapi.application.WriteAction;
import com.intellij.modcommand.*;
import com.intellij.openapi.project.Project;
import com.intellij.profile.codeInspection.InspectionProjectProfileManager;
import com.intellij.psi.*;
import com.intellij.psi.util.PsiExpressionTrimRenderer;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.psi.util.PsiUtil;
import com.intellij.util.ObjectUtils;
import com.intellij.util.containers.ContainerUtil;
import com.siyeh.ig.psiutils.ExpressionUtils;
import com.siyeh.ig.psiutils.SideEffectChecker;
import org.jetbrains.annotations.Contract;
@@ -20,6 +18,7 @@ import org.jetbrains.annotations.Nls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.List;
import java.util.function.BiFunction;
import java.util.function.Function;
@@ -186,8 +185,30 @@ public class JoinDeclarationAndAssignmentJavaInspection extends AbstractBaseJava
return initializer == null || !SideEffectChecker.checkSideEffects(initializer, null);
}
private static class JoinDeclarationAndAssignmentAction extends PsiUpdateModCommandAction<PsiElement> {
protected JoinDeclarationAndAssignmentAction(@NotNull PsiElement element) {
super(element);
}
private static class JoinDeclarationAndAssignmentFix implements LocalQuickFix {
@Override
public @NotNull String getFamilyName() {
return JavaBundle.message("delete.initializer.completely");
}
@Override
protected void invoke(@NotNull ActionContext actionContext, @NotNull PsiElement element, @NotNull ModPsiUpdater updater) {
join(element);
}
private static void join(@NotNull PsiElement element) {
Context context = getContext(element);
if (context != null) {
DeclarationJoinLinesHandler.joinDeclarationAndAssignment(context.myVariable, context.myAssignment);
}
}
}
private static class JoinDeclarationAndAssignmentFix extends ModCommandQuickFix {
@Nls(capitalization = Nls.Capitalization.Sentence)
@NotNull
@Override
@@ -196,36 +217,24 @@ public class JoinDeclarationAndAssignmentJavaInspection extends AbstractBaseJava
}
@Override
public boolean startInWriteAction() {
return false;
}
@Override
public void applyFix(@NotNull Project project, @NotNull ProblemDescriptor descriptor) {
Context context = getContext(descriptor.getPsiElement());
if (context != null) {
PsiLocalVariable variable = context.myVariable;
PsiAssignmentExpression assignmentExpression = context.myAssignment;
PsiExpression initializer = variable.getInitializer();
if (initializer != null && assignmentExpression.getOperationTokenType() == JavaTokenType.EQ) {
String textAfter = PsiExpressionTrimRenderer.render(initializer) + ";<br>" +
variable.getTypeElement().getText() + ' ' + variable.getName();
if (!RemoveInitializerFix.sideEffectAwareRemove(project, initializer, initializer, variable, textAfter)) return;
}
if (!FileModificationService.getInstance().prepareFileForWrite(assignmentExpression.getContainingFile())) return;
WriteAction.run(() -> DeclarationJoinLinesHandler.joinDeclarationAndAssignment(context.myVariable, context.myAssignment));
public @NotNull ModCommand perform(@NotNull Project project, @NotNull ProblemDescriptor descriptor) {
PsiElement element = descriptor.getPsiElement();
Context context = getContext(element);
if (context == null) return ModCommands.nop();
PsiLocalVariable variable = context.myVariable;
PsiExpression initializer = variable.getInitializer();
List<PsiExpression> sideEffects = initializer == null ||
context.myAssignment.getOperationTokenType() != JavaTokenType.EQ ?
List.of() : SideEffectChecker.extractSideEffectExpressions(initializer);
JoinDeclarationAndAssignmentAction action = new JoinDeclarationAndAssignmentAction(element);
if (!sideEffects.isEmpty() && !ContainerUtil.exists(sideEffects, se -> variableIsUsed(variable, se))) {
List<ModCommandAction> subActions = List.of(
new RemoveInitializerFix.SideEffectAwareRemove(initializer, var -> JoinDeclarationAndAssignmentAction.join(var)),
action);
return new ModChooseAction(JavaBundle.message("inspection.join.declaration.and.assignment.fix.title"), subActions);
}
}
@Override
public @NotNull IntentionPreviewInfo generatePreview(@NotNull Project project, @NotNull ProblemDescriptor previewDescriptor) {
Context context = getContext(previewDescriptor.getPsiElement());
if (context != null) {
DeclarationJoinLinesHandler.joinDeclarationAndAssignment(context.myVariable, context.myAssignment);
return IntentionPreviewInfo.DIFF;
} else {
return IntentionPreviewInfo.EMPTY;
else {
return action.perform(ModCommandAction.ActionContext.from(descriptor));
}
}
}

View File

@@ -2,7 +2,6 @@
package com.intellij.codeInspection.dataFlow;
import com.intellij.codeInsight.daemon.impl.analysis.HighlightingFeature;
import com.intellij.codeInsight.daemon.impl.quickfix.DeleteSideEffectsAwareFix;
import com.intellij.codeInsight.daemon.impl.quickfix.UnwrapSwitchLabelFix;
import com.intellij.codeInsight.options.JavaInspectionButtons;
import com.intellij.codeInsight.options.JavaInspectionControls;
@@ -85,7 +84,7 @@ public class DataFlowInspection extends DataFlowInspectionBase {
if (assignment == null || assignment.getRExpression() == null || !(assignment.getParent() instanceof PsiExpressionStatement)) {
return null;
}
return new DeleteSideEffectsAwareFix((PsiStatement)assignment.getParent(), assignment.getRExpression(), true).asQuickFix();
return new RemoveAssignmentFix();
}
@Override

View File

@@ -4,7 +4,6 @@ package com.intellij.codeInspection.defUse;
import com.intellij.codeInsight.ExpressionUtil;
import com.intellij.codeInsight.daemon.impl.analysis.HighlightControlFlowUtil;
import com.intellij.codeInsight.daemon.impl.analysis.JavaHighlightUtil;
import com.intellij.codeInsight.daemon.impl.quickfix.RemoveUnusedVariableUtil;
import com.intellij.codeInspection.*;
import com.intellij.codeInspection.dataFlow.java.ControlFlowAnalyzer;
import com.intellij.codeInspection.dataFlow.java.inst.AssignInstruction;
@@ -106,7 +105,7 @@ public class DefUseInspection extends AbstractBaseJavaLocalInspectionTool {
// x = x = 5; reported by "Variable is assigned to itself"
continue;
}
reportAssignmentProblem(psiVariable, (PsiAssignmentExpression)context, holder);
reportAssignmentProblem((PsiAssignmentExpression)context, holder);
}
else if (context instanceof PsiPrefixExpression && REPORT_PREFIX_EXPRESSIONS ||
context instanceof PsiPostfixExpression && REPORT_POSTFIX_EXPRESSIONS) {
@@ -149,30 +148,24 @@ public class DefUseInspection extends AbstractBaseJavaLocalInspectionTool {
// Due to implementation quirk, array initializers are reassigned in CFG, so false warnings appear there
continue;
}
reportAssignmentProblem(field, (PsiAssignmentExpression)parent, holder);
reportAssignmentProblem((PsiAssignmentExpression)parent, holder);
}
}
}
}
private static void reportInitializerProblem(PsiVariable psiVariable, ProblemsHolder holder) {
List<LocalQuickFix> fixes = ContainerUtil.createMaybeSingletonList(
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)
);
new RemoveInitializerFix());
}
private static void reportAssignmentProblem(PsiVariable psiVariable,
PsiAssignmentExpression assignment,
private static void reportAssignmentProblem(PsiAssignmentExpression assignment,
ProblemsHolder holder) {
List<LocalQuickFix> fixes = ContainerUtil.createMaybeSingletonList(
isOnTheFlyOrNoSideEffects(holder.isOnTheFly(), psiVariable, assignment.getRExpression()) ? new RemoveAssignmentFix() : null);
holder.registerProblem(assignment.getLExpression(),
JavaBundle.message("inspection.unused.assignment.problem.descriptor3",
Objects.requireNonNull(assignment.getRExpression()).getText()),
fixes.toArray(LocalQuickFix.EMPTY_ARRAY)
new RemoveAssignmentFix()
);
}
@@ -230,7 +223,7 @@ public class DefUseInspection extends AbstractBaseJavaLocalInspectionTool {
}
else {
for (PsiAssignmentExpression assignment : fieldWrite.getAssignments()) {
reportAssignmentProblem(field, assignment, holder);
reportAssignmentProblem(assignment, holder);
}
}
}
@@ -351,12 +344,6 @@ public class DefUseInspection extends AbstractBaseJavaLocalInspectionTool {
return assignmentExpressions;
}
private static boolean isOnTheFlyOrNoSideEffects(boolean isOnTheFly,
PsiVariable psiVariable,
PsiExpression initializer) {
return isOnTheFly || !RemoveUnusedVariableUtil.checkSideEffects(initializer, psiVariable, new ArrayList<>());
}
@Override
public @NotNull OptPane getOptionsPane() {
return OptPane.pane(

View File

@@ -119,9 +119,10 @@ public class DeclarationJoinLinesHandler implements JoinLinesHandlerDelegate {
final Project project = assignment.getProject();
final String rightText = rExpression.getText();
String initializerText;
if ("+".equals(opSign) && ExpressionUtils.isZero(initializer) ||
"*".equals(opSign) && ExpressionUtils.isOne(initializer)) {
if (isIdentity(initializer, opSign)) {
initializerText = rightText;
} else if (isIdentity(rExpression, opSign)) {
initializerText = initializer.getText();
} else {
boolean parenthesesForLhs = PsiPrecedenceUtil.getPrecedence(initializer) > PsiPrecedenceUtil.getPrecedenceForOperator(simpleOp);
boolean parenthesesForRhs = PsiPrecedenceUtil.areParenthesesNeeded(sign, rExpression);
@@ -132,6 +133,14 @@ public class DeclarationJoinLinesHandler implements JoinLinesHandlerDelegate {
return (PsiExpression)CodeStyleManager.getInstance(project).reformat(initializerExpression);
}
private static boolean isIdentity(PsiExpression operand, String opSign) {
return "+".equals(opSign) && ExpressionUtils.isZero(operand) ||
"*".equals(opSign) && ExpressionUtils.isOne(operand) ||
"^".equals(opSign) && ExpressionUtils.isLiteral(operand, false) ||
"|".equals(opSign) && ExpressionUtils.isLiteral(operand, false) ||
"&".equals(opSign) && ExpressionUtils.isLiteral(operand, true);
}
@Nullable
public static PsiLocalVariable copyVarWithInitializer(PsiLocalVariable origVar, PsiExpression initializer) {
// Don't normalize the original declaration: it may declare many variables

View File

@@ -1,35 +1,26 @@
/*
* Copyright 2000-2016 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.
*/
// 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.FileModificationService;
import com.intellij.codeInsight.daemon.impl.quickfix.RemoveUnusedVariableUtil;
import com.intellij.codeInsight.daemon.impl.quickfix.DeleteElementFix;
import com.intellij.codeInsight.editorActions.DeclarationJoinLinesHandler;
import com.intellij.codeInsight.intention.preview.IntentionPreviewInfo;
import com.intellij.java.JavaBundle;
import com.intellij.openapi.application.WriteAction;
import com.intellij.modcommand.ModChooseAction;
import com.intellij.modcommand.ModCommand;
import com.intellij.modcommand.ModCommandAction;
import com.intellij.modcommand.ModCommandQuickFix;
import com.intellij.openapi.project.Project;
import com.intellij.psi.*;
import com.intellij.psi.tree.IElementType;
import com.intellij.psi.util.PsiUtil;
import com.intellij.util.ObjectUtils;
import com.siyeh.ig.psiutils.ExpressionUtils;
import com.siyeh.ig.psiutils.SideEffectChecker;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public class RemoveAssignmentFix extends RemoveInitializerFix {
import java.util.List;
public class RemoveAssignmentFix extends ModCommandQuickFix {
@NotNull
@Override
public String getFamilyName() {
@@ -37,29 +28,37 @@ public class RemoveAssignmentFix extends RemoveInitializerFix {
}
@Override
public void applyFix(@NotNull Project project, @NotNull ProblemDescriptor descriptor) {
public @NotNull ModCommand perform(@NotNull Project project, @NotNull ProblemDescriptor descriptor) {
final PsiElement element = descriptor.getPsiElement();
PsiAssignmentExpression parentExpr = getAssignment(descriptor);
if (parentExpr == null) return;
PsiElement gParentExpr = parentExpr.getParent();
PsiExpression initializer = getInitializer(parentExpr);
if (initializer == null) return;
if (mayBeFixedWithoutSideEffect(gParentExpr)) {
if (!FileModificationService.getInstance().prepareFileForWrite(gParentExpr.getContainingFile())) return;
WriteAction.run(() -> {
if (gParentExpr instanceof PsiParenthesizedExpression) {
gParentExpr.replace(initializer);
if (parentExpr == null) return ModCommands.nop();
if (!ExpressionUtils.isVoidContext(parentExpr)) {
return ModCommands.psiUpdate(parentExpr, p -> {
PsiExpression initializer = getInitializer(p);
if (initializer == null) return;
PsiElement gp = p.getParent();
if (gp instanceof PsiParenthesizedExpression) {
gp.replace(initializer);
} else {
parentExpr.replace(initializer);
p.replace(initializer);
}
});
return;
}
PsiExpression initializer = parentExpr.getRExpression();
if (initializer == null) return ModCommands.nop();
PsiElement resolve = resolveExpression(element, parentExpr);
if (!(resolve instanceof PsiVariable)) return;
sideEffectAwareRemove(project, initializer, parentExpr, (PsiVariable)resolve);
if (!(resolve instanceof PsiVariable)) return ModCommands.nop();
List<PsiExpression> sideEffects = SideEffectChecker.extractSideEffectExpressions(initializer);
List<ModCommandAction> subActions;
if (!sideEffects.isEmpty()) {
subActions = List.of(new RemoveInitializerFix.SideEffectAwareRemove(initializer),
new DeleteElementFix(parentExpr, JavaBundle.message("delete.assignment.completely")));
}
else {
subActions = List.of(new DeleteElementFix(parentExpr));
}
return new ModChooseAction(JavaBundle.message("inspection.unused.assignment.remove.assignment.quickfix.title"), subActions);
}
PsiAssignmentExpression getAssignment(@NotNull ProblemDescriptor descriptor) {
@@ -68,28 +67,6 @@ public class RemoveAssignmentFix extends RemoveInitializerFix {
return ObjectUtils.tryCast(parent, PsiAssignmentExpression.class);
}
@Override
public @NotNull IntentionPreviewInfo generatePreview(@NotNull Project project, @NotNull ProblemDescriptor previewDescriptor) {
final PsiElement element = previewDescriptor.getPsiElement();
PsiAssignmentExpression parentExpr = getAssignment(previewDescriptor);
if (parentExpr == null) return IntentionPreviewInfo.EMPTY;
PsiElement gParentExpr = parentExpr.getParent();
PsiExpression initializer = getInitializer(parentExpr);
if (initializer == null) return IntentionPreviewInfo.EMPTY;
if (mayBeFixedWithoutSideEffect(gParentExpr)) {
PsiElement target = gParentExpr instanceof PsiParenthesizedExpression ? gParentExpr : parentExpr;
target.replace(initializer);
return IntentionPreviewInfo.DIFF;
}
PsiVariable resolve = ObjectUtils.tryCast(resolveExpression(element, parentExpr), PsiVariable.class);
if (resolve == null) return IntentionPreviewInfo.EMPTY;
RemoveUnusedVariableUtil.RemoveMode res = RemoveUnusedVariableUtil.getModeForPreview(initializer, resolve);
doRemove(project, initializer, parentExpr, resolve, resolve.getParent(), res);
return IntentionPreviewInfo.DIFF;
}
@Nullable
private static PsiExpression getInitializer(@NotNull PsiAssignmentExpression assignmentExpr) {
final IElementType operationSign = assignmentExpr.getOperationTokenType();
@@ -100,11 +77,6 @@ public class RemoveAssignmentFix extends RemoveInitializerFix {
return result;
}
private static boolean mayBeFixedWithoutSideEffect(@NotNull PsiElement expr) {
return expr instanceof PsiExpression || expr instanceof PsiExpressionList || expr instanceof PsiReturnStatement
|| expr instanceof PsiLocalVariable;
}
@Nullable
private static PsiElement resolveExpression(@NotNull PsiElement expr, @NotNull PsiAssignmentExpression parentExpr) {
PsiElement result = null;

View File

@@ -2,27 +2,23 @@
package com.intellij.codeInspection;
import com.intellij.codeInsight.BlockUtils;
import com.intellij.codeInsight.FileModificationService;
import com.intellij.codeInsight.daemon.impl.quickfix.RemoveUnusedVariableFix;
import com.intellij.codeInsight.daemon.impl.quickfix.RemoveUnusedVariableUtil;
import com.intellij.codeInsight.intention.preview.IntentionPreviewInfo;
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.openapi.application.WriteAction;
import com.intellij.openapi.fileEditor.FileEditorManager;
import com.intellij.modcommand.*;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.TextRange;
import com.intellij.psi.*;
import com.intellij.psi.util.PsiExpressionTrimRenderer;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.psi.util.PsiUtil;
import com.intellij.util.ObjectUtils;
import com.siyeh.ig.psiutils.CommentTracker;
import com.siyeh.ig.psiutils.ExpressionUtils;
import com.intellij.util.containers.ContainerUtil;
import com.siyeh.ig.psiutils.*;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;
public class RemoveInitializerFix implements LocalQuickFix {
public class RemoveInitializerFix extends ModCommandQuickFix {
@Override
@NotNull
@@ -31,106 +27,69 @@ public class RemoveInitializerFix implements LocalQuickFix {
}
@Override
public void applyFix(@NotNull Project project, @NotNull ProblemDescriptor descriptor) {
final PsiElement psiInitializer = descriptor.getPsiElement();
if (!(psiInitializer instanceof PsiExpression)) return;
if (!(psiInitializer.getParent() instanceof PsiVariable variable)) return;
sideEffectAwareRemove(project, (PsiExpression)psiInitializer, psiInitializer, variable);
}
@Override
public boolean startInWriteAction() {
return false;
}
public static void sideEffectAwareRemove(Project project,
PsiExpression psiInitializer,
PsiElement elementToDelete,
PsiVariable variable) {
PsiTypeElement typeElement = variable.getTypeElement();
sideEffectAwareRemove(project, psiInitializer, elementToDelete, variable,
(typeElement != null ? typeElement.getText() + " " + variable.getName() + ";<br>" : "") +
PsiExpressionTrimRenderer.render(psiInitializer));
}
/**
* Remove an element. Ask user what to do if the element has a side effect: keep the side effect, ignore it, or cancel removal.
* @return <code>true</code> if the element was actually removed, <code>false</code> if removal was cancelled or is not possible.
* */
public static boolean sideEffectAwareRemove(Project project,
PsiExpression psiInitializer,
PsiElement elementToDelete,
PsiVariable variable,
String afterText) {
if (!FileModificationService.getInstance().prepareFileForWrite(elementToDelete.getContainingFile())) return false;
final List<PsiElement> sideEffects = new ArrayList<>();
boolean hasSideEffects = RemoveUnusedVariableUtil.checkSideEffects(psiInitializer, variable, sideEffects);
final PsiElement declaration = variable.getParent();
RemoveUnusedVariableUtil.RemoveMode res;
if (hasSideEffects) {
hasSideEffects = PsiUtil.isStatement(psiInitializer);
res = RemoveUnusedVariableFix.showSideEffectsWarning(sideEffects, variable,
FileEditorManager.getInstance(project).getSelectedTextEditor(),
hasSideEffects, sideEffects.get(0).getText(), afterText);
if (res == RemoveUnusedVariableUtil.RemoveMode.CANCEL) {
return false;
}
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() && !ContainerUtil.exists(sideEffects, se -> VariableAccessUtils.variableIsUsed(variable, se))) {
subActions = List.of(new SideEffectAwareRemove(initializer),
new DeleteElementFix(initializer, JavaBundle.message("delete.initializer.completely")));
}
else {
res = RemoveUnusedVariableUtil.RemoveMode.DELETE_ALL;
subActions = List.of(new DeleteElementFix(initializer));
}
WriteAction.run(() -> doRemove(project, psiInitializer, elementToDelete, variable, declaration, res));
return true;
return new ModChooseAction(JavaBundle.message("inspection.unused.assignment.remove.initializer.quickfix.title"), subActions);
}
public static class SideEffectAwareRemove extends PsiUpdateModCommandAction<PsiExpression> {
private final @Nullable Consumer<PsiVariable> myAction;
@Override
public @NotNull IntentionPreviewInfo generatePreview(@NotNull Project project, @NotNull ProblemDescriptor previewDescriptor) {
PsiExpression psiInitializer = ObjectUtils.tryCast(previewDescriptor.getPsiElement(), PsiExpression.class);
if (psiInitializer == null) return IntentionPreviewInfo.EMPTY;
PsiVariable variable = ObjectUtils.tryCast(psiInitializer.getParent(), PsiVariable.class);
if (variable == null) return IntentionPreviewInfo.EMPTY;
RemoveUnusedVariableUtil.RemoveMode res = RemoveUnusedVariableUtil.getModeForPreview(psiInitializer, variable);
doRemove(project, psiInitializer, psiInitializer, variable, variable.getParent(), res);
return IntentionPreviewInfo.DIFF;
}
static void doRemove(@NotNull Project project,
@NotNull PsiExpression psiInitializer,
@NotNull PsiElement elementToDelete,
@NotNull PsiVariable variable,
PsiElement declaration,
@NotNull RemoveUnusedVariableUtil.RemoveMode res) {
if (res == RemoveUnusedVariableUtil.RemoveMode.DELETE_ALL) {
if (elementToDelete instanceof PsiExpression && !ExpressionUtils.isVoidContext((PsiExpression)elementToDelete) &&
!PsiTreeUtil.isAncestor(variable, elementToDelete, true)) {
String name = variable.getName();
if (name != null) {
new CommentTracker().replaceAndRestoreComments(elementToDelete, name);
return;
}
}
elementToDelete.delete();
public SideEffectAwareRemove(@NotNull PsiExpression initializer) {
this(initializer, null);
}
else if (res == RemoveUnusedVariableUtil.RemoveMode.MAKE_STATEMENT) {
final PsiElementFactory factory = JavaPsiFacade.getElementFactory(project);
final PsiStatement statementFromText = factory.createStatementFromText(psiInitializer.getText() + ";", null);
final PsiElement parent = elementToDelete.getParent();
if (parent instanceof PsiExpressionStatement) {
parent.replace(statementFromText);
public SideEffectAwareRemove(@NotNull PsiExpression initializer, @Nullable Consumer<PsiVariable> postAction) {
super(initializer);
myAction = postAction;
}
@Override
protected void invoke(@NotNull ActionContext context, @NotNull PsiExpression initializer, @NotNull ModPsiUpdater updater) {
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);
}
else {
elementToDelete.delete();
if (declaration instanceof PsiClass) {
PsiClassInitializer initializer = factory.createClassInitializer();
initializer = (PsiClassInitializer)declaration.addAfter(initializer, variable);
initializer.getBody().add(statementFromText);
return;
PsiElement parent = initializer.getParent();
if (parent instanceof PsiVariable var) {
ct.deleteAndRestoreComments(initializer);
if (myAction != null) {
myAction.accept(var);
}
PsiElement grandParent = declaration.getParent();
BlockUtils.addBefore(((PsiStatement)(grandParent instanceof PsiForStatement ? grandParent : declaration)), statementFromText);
} else if (parent instanceof PsiAssignmentExpression) {
ct.deleteAndRestoreComments(parent.getParent());
}
}
@Override
protected @Nullable Presentation getPresentation(@NotNull ActionContext context, @NotNull PsiExpression initializer) {
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, expression -> expression.getTextRange()));
}
@Override
public @NotNull String getFamilyName() {
return QuickFixBundle.message("extract.side.effects.family.name");
}
}
}

View File

@@ -1,91 +0,0 @@
// 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

@@ -1,4 +1,4 @@
// "Remove expression" "true-preview"
// "Remove redundant assignment" "true-preview"
class X {
void test() {
for (int i = 0; i < Integer.MAX_VALUE; ) {

View File

@@ -1,4 +1,4 @@
// "Remove expression" "true-preview"
// "Remove redundant assignment" "true-preview"
class X {
void test() {
for (int i = 0; i < Integer.MAX_VALUE; i<caret> *= 2) {

View File

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

View File

@@ -1,15 +0,0 @@
// "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

@@ -1,12 +0,0 @@
// "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

@@ -0,0 +1,87 @@
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public class RedundantAssignment {
void test(int x) {
boolean flag = false;
if(x > 2) {
if(x % 3 == 1) {
flag = true;
} else {
flag = false;
}
}
System.out.println(flag);
}
void fillArray(int[] arr) {
arr[0] = 1;
arr[1] = 2;
arr[2] = 3;
arr[0] = 1;
arr[4] += 0;
}
void withTest(int x) {
if(x != 0) {
System.out.println(x);
} else {
x = 0;
}
System.out.println("oops");
}
void var(Object b) {
Object a = b;
if(b.hashCode() > 10) {
a = null;
} else {
a = b;
}
}
void testOneMinusOne(int a) {
int b = 0;
if(a < 1 && a > -1) {
b = a;
}
System.out.println(b);
}
class X {
int a;
int b;
{
a = 0;
((b)) = 0;
}
}
}
class A {
String typePath;
void f() {
StringBuilder typePath = new StringBuilder(this.typePath);
for (int index = 0; index < 10; index++) {
<caret> typePath.append(Integer.valueOf(index));
}
}
}
class TestConstant {
static final byte NONE = 0;
byte x;
TestConstant() {
x = NONE;
}
}
// IDEA-258765
interface Intersection {
interface I { }
final class A { }
class Data<T extends A & I> {
final T value;
private Data(T value) { this.value = value; }
}
}

View File

@@ -1,12 +1,23 @@
// 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.JoinDeclarationAndAssignmentJavaInspection;
import com.intellij.codeInspection.LocalInspectionTool;
import com.intellij.ui.ChooserInterceptor;
import com.intellij.ui.UiInterceptors;
import org.jetbrains.annotations.NotNull;
public class JoinDeclarationAndAssignmentTest extends LightQuickFixParameterizedTestCase {
@Override
protected void setUp() throws Exception {
super.setUp();
String name = getTestName(false);
if (name.endsWith("SideEffect.java")) {
UiInterceptors.register(new ChooserInterceptor(null, "Extract side effect.*"));
}
}
@Override
protected LocalInspectionTool @NotNull [] configureLocalInspectionTools() {

View File

@@ -13,6 +13,8 @@ import com.intellij.psi.PsiField;
import com.intellij.testFramework.LightProjectDescriptor;
import com.intellij.testFramework.PsiTestUtil;
import com.intellij.testFramework.fixtures.JavaCodeInsightTestFixture;
import com.intellij.ui.ChooserInterceptor;
import com.intellij.ui.UiInterceptors;
import com.intellij.util.containers.ContainerUtil;
import org.jetbrains.annotations.NotNull;
@@ -582,7 +584,10 @@ public class DataFlowInspectionTest extends DataFlowInspectionTestCase {
public void testPureNoArgMethodAsVariable() { doTest(); }
public void testRedundantAssignment() {
doTest();
assertIntentionAvailable("Extract side effect");
UiInterceptors.register(new ChooserInterceptor(List.of("Delete assignment completely", "Extract side effect"), "Extract side effect"));
IntentionAction action = myFixture.findSingleIntention("Remove redundant assignment");
myFixture.launchAction(action);
myFixture.checkResultByFile(getTestName(false) + "_after.java");
}
public void testXorNullity() { doTest(); }
public void testPrimitiveNull() { doTest(); }

View File

@@ -1,25 +1,10 @@
/*
* 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.
*/
// 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.java.codeInspection;
import com.intellij.codeInspection.dataFlow.ConstantValueInspection;
import com.intellij.codeInspection.dataFlow.DataFlowInspection;
import com.intellij.testFramework.fixtures.JavaCodeInsightTestFixture;
import com.intellij.testFramework.fixtures.LightJavaCodeInsightFixtureTestCase;
import com.intellij.util.containers.ContainerUtil;
import java.util.function.BiConsumer;
@@ -39,10 +24,6 @@ public abstract class DataFlowInspectionTestCase extends LightJavaCodeInsightFix
myFixture.testHighlighting(true, false, true, getTestName(false) + ".java");
}
public void assertIntentionAvailable(String intentionName) {
assertTrue(ContainerUtil.exists(myFixture.getAvailableIntentions(), action -> action.getText().equals(intentionName)));
}
static void addCheckerAnnotations(JavaCodeInsightTestFixture fixture) {
fixture.addClass("package org.checkerframework.checker.nullness.qual;import java.lang.annotation.*;" +
"@Target(ElementType.TYPE_USE)public @interface NonNull {}");

View File

@@ -523,6 +523,7 @@ inspection.javadoc.link.as.plain.text.display.name=Link specified as plain text
inspection.javadoc.link.as.plain.text.message=Link specified as plain text
inspection.join.declaration.and.assignment.display.name=Assignment can be joined with declaration
inspection.join.declaration.and.assignment.fix.family.name=Join declaration and assignment
inspection.join.declaration.and.assignment.fix.title=Join Declaration and Assignment
inspection.join.declaration.and.assignment.message=Assignment can be joined with declaration of ''{0}''
inspection.labeled.switch.rule.redundant.code.block.display.name=Labeled switch rule has redundant code block
inspection.labeled.switch.rule.redundant.code.block.message=Labeled rule's code block is redundant
@@ -753,6 +754,7 @@ 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.assignment.quickfix.title=Remove Redundant Assignment
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
@@ -1862,4 +1864,5 @@ convert.number.octal=octal
convert.number.decimal=decimal
convert.number.plain.format=plain format
convert.number.scientific.format=scientific format
delete.initializer.completely=Delete initializer completely
delete.initializer.completely=Delete initializer completely
delete.assignment.completely=Delete assignment completely