[java-inspections] RemoveUnusedVariableFix: mod-command

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

GitOrigin-RevId: c89ef0ad65e69348de86e10a20686dbf9f8e0d57
This commit is contained in:
Tagir Valeev
2023-06-29 15:25:13 +02:00
committed by intellij-monorepo-bot
parent 12607d120a
commit c55dfd035c
31 changed files with 306 additions and 331 deletions

View File

@@ -4,7 +4,6 @@ package com.intellij.codeInsight.daemon.impl.quickfix;
import com.intellij.psi.*;
import com.intellij.psi.util.PsiUtil;
import com.intellij.util.IncorrectOperationException;
import com.siyeh.ig.psiutils.CommentTracker;
import com.siyeh.ig.psiutils.ExpressionUtils;
import com.siyeh.ig.psiutils.SideEffectChecker;
import org.jetbrains.annotations.Contract;
@@ -41,17 +40,9 @@ public final class RemoveUnusedVariableUtil {
return !writes.isEmpty();
}
public static RemoveMode getModeForPreview(PsiExpression element, @Nullable PsiVariable variableToIgnore) {
List<PsiElement> sideEffects = new ArrayList<>();
boolean hasSideEffects = checkSideEffects(element, variableToIgnore, sideEffects);
if (!hasSideEffects || sideEffects.isEmpty()) return RemoveMode.DELETE_ALL;
if (!PsiUtil.isStatement(element)) return RemoveMode.DELETE_ALL;
return RemoveMode.MAKE_STATEMENT;
}
private static PsiElement replaceElementWithExpression(PsiExpression expression,
PsiElementFactory factory,
PsiElement element) throws IncorrectOperationException {
private static void replaceElementWithExpression(PsiExpression expression,
PsiElementFactory factory,
PsiElement element) throws IncorrectOperationException {
PsiElement elementToReplace = element;
PsiElement expressionToReplaceWith = expression;
if (element.getParent() instanceof PsiExpressionStatement || element.getParent() instanceof PsiExpressionListStatement) {
@@ -67,30 +58,10 @@ public final class RemoveUnusedVariableUtil {
else if (element.getParent() instanceof PsiDeclarationStatement) {
expressionToReplaceWith = factory.createStatementFromText((expression == null ? "" : expression.getText()) + ";", null);
}
return elementToReplace.replace(expressionToReplaceWith);
elementToReplace.replace(expressionToReplaceWith);
}
private static PsiElement createStatementIfNeeded(PsiExpression expression,
PsiElementFactory factory,
PsiElement element) throws IncorrectOperationException {
// if element used in expression, subexpression will do
PsiElement parent = element.getParent();
if (!(parent instanceof PsiExpressionStatement) && !(parent instanceof PsiDeclarationStatement)) {
return expression;
}
String replacement;
if (expression == null) {
boolean needBlock = parent instanceof PsiExpressionStatement && parent.getParent() instanceof PsiSwitchLabeledRuleStatement;
replacement = needBlock ? "{}" : ";";
}
else {
replacement = expression.getText() + ";";
}
return factory.createStatementFromText(replacement, null);
}
private static void deleteWholeStatement(PsiElement element, PsiElementFactory factory)
throws IncorrectOperationException {
public static void deleteWholeStatement(@NotNull PsiElement element) {
// just delete it altogether
PsiElement parent = element.getParent();
if (parent instanceof PsiExpressionStatement) {
@@ -99,152 +70,30 @@ public final class RemoveUnusedVariableUtil {
}
else {
// replace with empty statement (to handle with 'if (..) i=0;' )
parent.replace(createStatementIfNeeded(null, factory, element));
// if element used in expression, subexpression will do
String replacement = parent.getParent() instanceof PsiSwitchLabeledRuleStatement ? "{}" : ";";
PsiElement result = JavaPsiFacade.getElementFactory(parent.getProject()).createStatementFromText(replacement, null);
parent.replace(result);
}
}
else if (parent instanceof PsiExpressionList list && parent.getParent() instanceof PsiExpressionListStatement) {
PsiExpression[] expressions = list.getExpressions();
if (expressions.length == 2) {
PsiExpression other = expressions[0] == element ? expressions[1] : expressions[0];
replaceElementWithExpression(other, factory, parent);
replaceElementWithExpression(other, JavaPsiFacade.getElementFactory(parent.getProject()), parent);
}
else {
element.delete();
}
}
else if (element.getParent() instanceof PsiLambdaExpression) {
element.replace(factory.createCodeBlock());
element.replace(JavaPsiFacade.getElementFactory(parent.getProject()).createCodeBlock());
}
else {
element.delete();
}
}
static void deleteReferences(PsiVariable variable, List<? extends PsiElement> references, @NotNull RemoveMode mode) throws IncorrectOperationException {
for (PsiElement expression : references) {
processUsage(expression, variable, null, mode);
}
}
static void collectReferences(@NotNull PsiElement context, final PsiVariable variable, final List<? super PsiElement> references) {
context.accept(new JavaRecursiveElementWalkingVisitor() {
@Override public void visitReferenceExpression(@NotNull PsiReferenceExpression expression) {
if (expression.resolve() == variable) references.add(expression);
super.visitReferenceExpression(expression);
}
});
}
/**
* @param sideEffects if null, delete usages, otherwise collect side effects
* @return true if there are at least one unrecoverable side effect found, false if no side effects,
* null if read usage found (may happen if interval between fix creation in invoke() call was long enough)
*/
static Boolean processUsage(PsiElement element, PsiVariable variable, List<? super PsiElement> sideEffects, @NotNull RemoveMode deleteMode)
throws IncorrectOperationException {
if (!element.isValid()) return null;
PsiElementFactory factory = JavaPsiFacade.getElementFactory(variable.getProject());
while (element != null) {
if (element instanceof PsiAssignmentExpression) {
PsiAssignmentExpression expression = (PsiAssignmentExpression)element;
PsiExpression lExpression = expression.getLExpression();
// there should not be read access to the variable, otherwise it is not unused
if (!(lExpression instanceof PsiReferenceExpression) || variable != ((PsiReferenceExpression)lExpression).resolve()) {
return null;
}
PsiExpression rExpression = expression.getRExpression();
rExpression = PsiUtil.skipParenthesizedExprDown(rExpression);
if (rExpression == null) return true;
// replace assignment with expression and re-simplify
boolean sideEffectFound = checkSideEffects(rExpression, variable, sideEffects);
if (!ExpressionUtils.isVoidContext(expression) || PsiUtil.isStatement(rExpression)) {
if (deleteMode == RemoveMode.MAKE_STATEMENT ||
deleteMode == RemoveMode.DELETE_ALL && !ExpressionUtils.isVoidContext(expression)) {
element = replaceElementWithExpression(rExpression, factory, element);
element = eraseUnnecessaryOuterParentheses(element);
List<PsiElement> references = new ArrayList<>();
collectReferences(element, variable, references);
deleteReferences(variable, references, deleteMode);
}
else if (deleteMode == RemoveMode.DELETE_ALL) {
deleteWholeStatement(element, factory);
}
return true;
}
else {
if (deleteMode != RemoveMode.CANCEL) {
deleteWholeStatement(element, factory);
}
return !sideEffectFound;
}
}
else if (element instanceof PsiExpressionStatement && deleteMode != RemoveMode.CANCEL) {
final PsiElement parent = element.getParent();
if (parent instanceof PsiIfStatement || parent instanceof PsiLoopStatement && ((PsiLoopStatement)parent).getBody() == element) {
element.replace(JavaPsiFacade.getElementFactory(element.getProject()).createStatementFromText(";", element));
} else {
element.delete();
}
break;
}
else if (element instanceof PsiVariable && element == variable) {
PsiExpression expression = variable.getInitializer();
if (expression != null) {
expression = PsiUtil.deparenthesizeExpression(expression);
}
boolean sideEffectsFound = checkSideEffects(expression, variable, sideEffects);
if (expression != null && PsiUtil.isStatement(expression) && variable instanceof PsiLocalVariable
&&
!(variable.getParent() instanceof PsiDeclarationStatement &&
((PsiDeclarationStatement)variable.getParent()).getDeclaredElements().length > 1)) {
if (deleteMode == RemoveMode.MAKE_STATEMENT) {
element = element.getParent().replace(createStatementIfNeeded(expression, factory, element));
List<PsiElement> references = new ArrayList<>();
collectReferences(element, variable, references);
deleteReferences(variable, references, deleteMode);
}
else if (deleteMode == RemoveMode.DELETE_ALL) {
deleteVariable(variable);
}
return true;
}
else {
if (deleteMode != RemoveMode.CANCEL) {
if (element instanceof PsiField) {
((PsiField)element).normalizeDeclaration();
}
deleteVariable(variable);
}
return !sideEffectsFound;
}
}
element = element.getParent();
}
return true;
}
private static void deleteVariable(PsiVariable variable) {
CommentTracker tracker = new CommentTracker();
tracker.markUnchanged(variable.getInitializer()); // assume that initializer is used (e.g. inlined)
if (variable instanceof PsiJavaDocumentedElement) {
tracker.markUnchanged(((PsiJavaDocumentedElement)variable).getDocComment());
}
tracker.deleteAndRestoreComments(variable);
}
@NotNull
private static PsiElement eraseUnnecessaryOuterParentheses(@NotNull PsiElement element) {
PsiElement parenthesizedParent = element;
while (parenthesizedParent.getParent() instanceof PsiParenthesizedExpression) {
parenthesizedParent = parenthesizedParent.getParent();
}
if (parenthesizedParent != element) {
// replace() will preserve the parentheses if they're mandatory due to operator precedence
return parenthesizedParent.replace(element);
}
return element;
}
public static boolean isForLoopUpdate(@Nullable PsiElement element) {
if(element == null) return false;
PsiElement parent = element.getParent();

View File

@@ -151,6 +151,7 @@ public class InlineLocalHandler extends JavaInlineActionHandler {
() -> inlineOccurrences(project, pattern, defToInline, refsToInline));
if (inlineAll && ReferencesSearch.search(pattern).findFirst() == null) {
WriteAction.run(() -> PsiDocumentManager.getInstance(project).doPostponedOperationsAndUnblockDocument(editor.getDocument()));
QuickFixFactory.getInstance().createRemoveUnusedVariableFix(pattern).invoke(project, editor, pattern.getContainingFile());
}
@@ -342,6 +343,7 @@ public class InlineLocalHandler extends JavaInlineActionHandler {
});
if (inlineAll && ReferencesSearch.search(local).findFirst() == null) {
WriteAction.run(() -> PsiDocumentManager.getInstance(project).doPostponedOperationsAndUnblockDocument(editor.getDocument()));
QuickFixFactory.getInstance().createRemoveUnusedVariableFix(local).invoke(project, editor, local.getContainingFile());
}

View File

@@ -1,44 +1,35 @@
// 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.FileModificationService;
import com.intellij.codeInsight.daemon.QuickFixBundle;
import com.intellij.codeInsight.daemon.impl.quickfix.RemoveUnusedVariableUtil.RemoveMode;
import com.intellij.codeInsight.highlighting.HighlightManager;
import com.intellij.codeInsight.intention.IntentionAction;
import com.intellij.codeInsight.intention.impl.BaseIntentionAction;
import com.intellij.codeInsight.intention.preview.IntentionPreviewInfo;
import com.intellij.codeInspection.CommonQuickFixBundle;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.application.ModalityState;
import com.intellij.openapi.application.ReadAction;
import com.intellij.openapi.command.WriteCommandAction;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.editor.colors.EditorColors;
import com.intellij.openapi.project.Project;
import com.intellij.codeInspection.PsiUpdateModCommandAction;
import com.intellij.codeInspection.RemoveInitializerFix;
import com.intellij.java.JavaBundle;
import com.intellij.modcommand.ModChooseAction;
import com.intellij.modcommand.ModCommand;
import com.intellij.modcommand.ModPsiUpdater;
import com.intellij.modcommand.PsiBasedModCommandAction;
import com.intellij.openapi.util.TextRange;
import com.intellij.psi.*;
import com.intellij.psi.util.*;
import com.intellij.util.IncorrectOperationException;
import com.intellij.util.concurrency.AppExecutorUtil;
import org.jetbrains.annotations.NonNls;
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.*;
import one.util.streamex.StreamEx;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
public class RemoveUnusedVariableFix implements IntentionAction {
private static final Logger LOG = Logger.getInstance(RemoveUnusedVariableFix.class);
private final PsiVariable myVariable;
public class RemoveUnusedVariableFix extends PsiBasedModCommandAction<PsiVariable> {
public RemoveUnusedVariableFix(PsiVariable variable) {
myVariable = variable;
}
@Override
@NotNull
public String getText() {
return CommonQuickFixBundle.message("fix.remove.title.x", JavaElementKind.fromElement(myVariable).object(), myVariable.getName());
super(variable);
}
@Override
@@ -48,121 +39,137 @@ public class RemoveUnusedVariableFix implements IntentionAction {
}
@Override
public boolean isAvailable(@NotNull Project project, Editor editor, PsiFile file) {
return
myVariable != null
&& myVariable.isValid()
&& BaseIntentionAction.canModify(myVariable)
;
protected @Nullable Presentation getPresentation(@NotNull ActionContext context, @NotNull PsiVariable variable) {
String message = CommonQuickFixBundle.message("fix.remove.title.x", JavaElementKind.fromElement(variable).object(), variable.getName());
return Presentation.of(message);
}
@Override
public void invoke(@NotNull Project project, Editor editor, PsiFile file) {
if (!FileModificationService.getInstance().prepareFileForWrite(myVariable.getContainingFile())) return;
removeVariableAndReferencingStatements(editor);
}
@Override
public @NotNull IntentionPreviewInfo generatePreview(@NotNull Project project, @NotNull Editor editor, @NotNull PsiFile file) {
PsiVariable variable = PsiTreeUtil.findSameElementInCopy(myVariable, file);
List<PsiElement> references = collectReferences(variable);
// check for side effects
final List<PsiElement> sideEffects = new ArrayList<>();
boolean canCopeWithSideEffects = true;
for (PsiElement element : references) {
Boolean result = RemoveUnusedVariableUtil.processUsage(element, variable, sideEffects, RemoveMode.CANCEL);
if (result == null) return IntentionPreviewInfo.EMPTY;
canCopeWithSideEffects &= result;
}
RemoveMode mode = canCopeWithSideEffects && !sideEffects.isEmpty() ? RemoveMode.MAKE_STATEMENT : RemoveMode.DELETE_ALL;
RemoveUnusedVariableUtil.deleteReferences(variable, references, mode);
return IntentionPreviewInfo.DIFF;
}
private void removeVariableAndReferencingStatements(Editor editor) {
record Context(List<PsiElement> sideEffects, List<PsiElement> references, boolean canCopeWithSideEffects) {}
ReadAction.nonBlocking(() -> {
if (!myVariable.isValid()) return null;
final List<PsiElement> references = collectReferences(myVariable);
final List<PsiElement> sideEffects = new ArrayList<>();
boolean canCopeWithSideEffects = true;
// check for side effects
for (PsiElement element : references) {
Boolean result = RemoveUnusedVariableUtil.processUsage(element, myVariable, sideEffects, RemoveMode.CANCEL);
if (result == null) return null;
canCopeWithSideEffects &= result;
static Map<PsiExpression, List<PsiExpression>> collectSideEffects(@NotNull PsiVariable variable,
@NotNull List<@NotNull PsiReferenceExpression> references) {
PsiExpression initializer = variable.getInitializer();
Map<PsiExpression, List<PsiExpression>> result = new HashMap<>();
if (initializer != null) {
List<PsiExpression> expressions = SideEffectChecker.extractSideEffectExpressions(initializer);
if (!expressions.isEmpty()) {
result.put(initializer, expressions);
}
return new Context(sideEffects, references, canCopeWithSideEffects);
}).finishOnUiThread(ModalityState.nonModal(), context -> {
if (context == null) return;
final RemoveMode deleteMode = showSideEffectsWarning(context.sideEffects, myVariable, editor, context.canCopeWithSideEffects);
WriteCommandAction.writeCommandAction(myVariable.getProject()).run(() -> {
try {
RemoveUnusedVariableUtil.deleteReferences(myVariable, context.references, deleteMode);
}
catch (IncorrectOperationException e) {
LOG.error(e);
}
});
}).submit(AppExecutorUtil.getAppExecutorService());
}
private static List<PsiElement> collectReferences(@NotNull PsiVariable variable) {
List<PsiElement> references = new ArrayList<>();
PsiElement context = variable instanceof PsiField ? ((PsiField)variable).getContainingClass() : PsiUtil.getVariableCodeBlock(variable, null);
if (context != null) {
RemoveUnusedVariableUtil.collectReferences(context, variable, references);
}
// do not forget to delete variable declaration
references.add(variable);
return references;
}
public static RemoveMode showSideEffectsWarning(List<? extends PsiElement> sideEffects,
PsiVariable variable,
Editor editor,
boolean canCopeWithSideEffects,
@NonNls String beforeText,
@NonNls String afterText) {
if (sideEffects.isEmpty()) return RemoveMode.DELETE_ALL;
if (ApplicationManager.getApplication().isUnitTestMode()) {
return canCopeWithSideEffects ? RemoveMode.MAKE_STATEMENT : RemoveMode.DELETE_ALL;
for (PsiReferenceExpression reference : references) {
if (reference.getParent() instanceof PsiAssignmentExpression assignment &&
ExpressionUtils.isVoidContext(assignment)) {
PsiExpression rExpression = assignment.getRExpression();
if (rExpression != null) {
List<PsiExpression> expressions = SideEffectChecker.extractSideEffectExpressions(rExpression);
if (!expressions.isEmpty()) {
result.put(rExpression, expressions);
}
}
}
}
Project project = editor.getProject();
HighlightManager highlightManager = HighlightManager.getInstance(project);
PsiElement[] elements = PsiUtilCore.toPsiElementArray(sideEffects);
highlightManager.addOccurrenceHighlights(editor, elements, EditorColors.SEARCH_RESULT_ATTRIBUTES, true, null);
SideEffectWarningDialog dialog = new SideEffectWarningDialog(project, false, variable, beforeText, afterText, canCopeWithSideEffects);
dialog.show();
int code = dialog.getExitCode();
return RemoveMode.values()[code];
return result;
}
private static RemoveMode showSideEffectsWarning(List<? extends PsiElement> sideEffects,
PsiVariable variable,
Editor editor,
boolean canCopeWithSideEffects) {
String text;
if (sideEffects.isEmpty()) {
text = "";
@Override
protected @NotNull ModCommand perform(@NotNull ActionContext context, @NotNull PsiVariable variable) {
final List<PsiReferenceExpression> references = collectReferences(variable);
Map<PsiExpression, List<PsiExpression>> effects = collectSideEffects(variable, references);
if (effects.isEmpty()) {
return new RemoveVariableSideEffectAware(variable, false).perform(context);
}
else {
final PsiElement sideEffect = sideEffects.get(0);
if (sideEffect instanceof PsiExpression) {
text = PsiExpressionTrimRenderer.render((PsiExpression)sideEffect);
}
else {
text = sideEffect.getText();
}
return new ModChooseAction(JavaBundle.message("popup.title.remove.unused.variable"),
List.of(new RemoveVariableSideEffectAware(variable, true),
new RemoveVariableSideEffectAware(variable, false)));
}
return showSideEffectsWarning(sideEffects, variable, editor, canCopeWithSideEffects, text, text);
}
@Override
public boolean startInWriteAction() {
return false;
private static class RemoveVariableSideEffectAware extends PsiUpdateModCommandAction<PsiVariable> {
private final boolean myKeepSideEffects;
protected RemoveVariableSideEffectAware(@NotNull PsiVariable variable, boolean keepSideEffects) {
super(variable);
myKeepSideEffects = keepSideEffects;
}
@Override
protected @Nullable Presentation getPresentation(@NotNull ActionContext context, @NotNull PsiVariable variable) {
Presentation presentation = Presentation.of(getFamilyName());
if (myKeepSideEffects) {
final List<PsiReferenceExpression> references = collectReferences(variable);
Map<PsiExpression, List<PsiExpression>> effects = collectSideEffects(variable, references);
return presentation.withHighlighting(StreamEx.ofValues(effects).flatCollection(Function.identity())
.map(PsiExpression::getTextRange).toArray(TextRange.EMPTY_ARRAY));
}
return presentation;
}
@Override
public @NotNull String getFamilyName() {
return myKeepSideEffects ?
JavaBundle.message("intention.family.name.extract.possible.side.effects") :
JavaBundle.message("intention.family.name.delete.possible.side.effects");
}
@Override
protected void invoke(@NotNull ActionContext context, @NotNull PsiVariable variable, @NotNull ModPsiUpdater updater) {
boolean retry = true;
while(retry) {
retry = false;
List<PsiReferenceExpression> refs = collectReferences(variable);
for (PsiReferenceExpression ref : refs) {
if (!ref.isValid()) {
retry = true;
break;
}
PsiElement parent = PsiUtil.skipParenthesizedExprUp(ref.getParent());
if (parent instanceof PsiUnaryExpression unary && ExpressionUtils.isVoidContext(unary)) {
RemoveUnusedVariableUtil.deleteWholeStatement(parent);
}
else if (parent instanceof PsiAssignmentExpression assignment) {
PsiExpression rExpression = assignment.getRExpression();
if (ExpressionUtils.isVoidContext(assignment)) {
if (rExpression != null && myKeepSideEffects) {
RemoveInitializerFix.SideEffectAwareRemove.remove(rExpression);
}
else {
RemoveUnusedVariableUtil.deleteWholeStatement(assignment);
}
}
else if (rExpression != null) {
PsiElement result = new CommentTracker().replaceAndRestoreComments(assignment, rExpression);
if (result.getParent() instanceof PsiParenthesizedExpression parens &&
!ParenthesesUtils.areParenthesesNeeded(parens, true)) {
parens.replace(result);
}
}
else {
RemoveUnusedVariableUtil.deleteWholeStatement(assignment);
}
}
}
}
if (variable instanceof PsiField) {
variable.normalizeDeclaration();
}
PsiExpression initializer = variable.getInitializer();
if (initializer != null && myKeepSideEffects) {
RemoveInitializerFix.SideEffectAwareRemove.remove(initializer);
}
CommentTracker tracker = new CommentTracker();
tracker.markUnchanged(variable.getInitializer()); // assume that initializer is used (e.g. inlined)
if (variable instanceof PsiJavaDocumentedElement docElement) {
tracker.markUnchanged(docElement.getDocComment());
}
tracker.deleteAndRestoreComments(variable);
}
}
private static List<PsiReferenceExpression> collectReferences(@NotNull PsiVariable variable) {
PsiElement context = variable instanceof PsiField ? ((PsiField)variable).getContainingClass() : PsiUtil.getVariableCodeBlock(variable, null);
List<PsiReferenceExpression> references = new ArrayList<>(VariableAccessUtils.getVariableReferences(variable, context));
references.removeIf(ref -> !PsiUtil.isAccessedForWriting(ref));
return ContainerUtil.filter(references, r1 ->
(r1.getParent() instanceof PsiAssignmentExpression assignment && !ExpressionUtils.isVoidContext(assignment)) ||
!ContainerUtil.exists(references, r2 -> r1 != r2 && PsiTreeUtil.isAncestor(r2.getParent(), r1, true)));
}
}

View File

@@ -169,7 +169,7 @@ public final class QuickFixFactoryImpl extends QuickFixFactory {
@NotNull
@Override
public IntentionAction createRemoveUnusedVariableFix(@NotNull PsiVariable variable) {
return new RemoveUnusedVariableFix(variable);
return new RemoveUnusedVariableFix(variable).asIntention();
}
@Override

View File

@@ -5,6 +5,7 @@ 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.codeInsight.daemon.impl.quickfix.RemoveUnusedVariableUtil;
import com.intellij.java.JavaBundle;
import com.intellij.modcommand.*;
import com.intellij.openapi.project.Project;
@@ -56,27 +57,78 @@ public class RemoveInitializerFix extends ModCommandQuickFix {
@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);
invoke(initializer);
}
private void invoke(@NotNull PsiExpression initializer) {
PsiVariable origVar = initializer.getParent() instanceof PsiVariable v ? v:
initializer.getParent() instanceof PsiAssignmentExpression assignment &&
assignment.getLExpression() instanceof PsiReferenceExpression ref &&
ref.resolve() instanceof PsiVariable v ? v : null;
List<PsiExpression> sideEffects = SideEffectChecker.extractSideEffectExpressions(
initializer, e -> e instanceof PsiUnaryExpression unary && ExpressionUtils.isReferenceTo(unary.getOperand(), origVar) ||
e instanceof PsiAssignmentExpression assignment &&
ExpressionUtils.isReferenceTo(assignment.getLExpression(), origVar));
CodeBlockSurrounder.SurroundResult result = null;
if (!sideEffects.isEmpty()) {
PsiStatement[] statements = StatementExtractor.generateStatements(sideEffects, initializer);
CodeBlockSurrounder surrounder = CodeBlockSurrounder.forExpression(initializer);
if (surrounder == null) {
tryProcessExpressionList(initializer, sideEffects);
return;
}
result = surrounder.surround();
PsiStatement anchor = result.getAnchor();
initializer = result.getExpression();
if (statements.length > 0) {
BlockUtils.addBefore(anchor, statements);
}
}
PsiElement parent = initializer.getParent();
if (parent instanceof PsiVariable var) {
ct.deleteAndRestoreComments(initializer);
initializer.delete();
if (myAction != null) {
myAction.accept(var);
}
} else if (parent instanceof PsiAssignmentExpression) {
ct.deleteAndRestoreComments(parent.getParent());
RemoveUnusedVariableUtil.deleteWholeStatement(parent);
}
if (result != null) {
result.collapse();
}
}
private static void tryProcessExpressionList(@NotNull PsiExpression initializer, List<PsiExpression> sideEffects) {
if (initializer.getParent() instanceof PsiAssignmentExpression assignment) {
if (assignment.getParent() instanceof PsiExpressionList list) {
for (PsiExpression effect : sideEffects) {
list.addBefore(effect, assignment);
}
assignment.delete();
}
if (assignment.getParent() instanceof PsiExpressionStatement statement && statement.getParent() instanceof PsiForStatement) {
if (sideEffects.size() == 1) {
assignment.replace(sideEffects.get(0));
} else {
PsiExpressionListStatement listStatement = (PsiExpressionListStatement)JavaPsiFacade.getElementFactory(statement.getProject())
.createStatementFromText("a,b", null);
PsiExpressionList list = listStatement.getExpressionList();
PsiExpression[] mockExpressions = list.getExpressions();
PsiExpression first = mockExpressions[0];
for (PsiExpression effect : sideEffects) {
list.addBefore(effect, first);
}
for (PsiExpression expression : mockExpressions) {
expression.delete();
}
statement.replace(listStatement);
}
}
}
}
public static void remove(@NotNull PsiExpression expression) {
new SideEffectAwareRemove(expression).invoke(expression);
}
@Override

View File

@@ -4,7 +4,7 @@ import java.io.*;
class a {
int k;
private int run() {
Object o0, oo;<caret>
Object o0, oo<caret>;
return 0;
}

View File

@@ -5,8 +5,11 @@ class a {
private int run() {
<caret>int j;
int k = 9;
if ((k = 9) == 0) {
k = 8;
}
if (3 ==0) ;
else return k;
else return (k);
return 0;
}
}

View File

@@ -4,7 +4,6 @@ import java.io.*;
class a {
int k;
private int run() {
new a();
return 0;
}

View File

@@ -4,7 +4,7 @@ import java.io.*;
class a {
int k;
private int run() {
<caret>Object i = null;
Object <caret>i = null;
return 0;
}

View File

@@ -0,0 +1,8 @@
// "Remove local variable 'problematic'" "true-preview"
class C {
native int foo();
void case01() {
if (1 > 2) System.out.println(foo() + foo());
}
}

View File

@@ -4,7 +4,8 @@ class C {
void case01() {
int i = 10;
for(foo(); (--i) > 0; ) {
foo();
for(; (--i) > 0; ) {
System.out.println("index = " + i);
}
}

View File

@@ -0,0 +1,10 @@
// "Remove local variable 'problematic'" "true-preview"
class C {
native int foo();
void case01() {
for(int i = 10; i > 0; --i, foo(), foo()) {
System.out.println("index = " + i);
}
}
}

View File

@@ -0,0 +1,10 @@
// "Remove local variable 'problematic'" "true-preview"
class C {
native int foo();
void case01() {
for(int i = 10; i > 0; foo(), foo()) {
System.out.println("index = " + i);
}
}
}

View File

@@ -0,0 +1,9 @@
// "Remove local variable 'problematic'" "true-preview"
class C {
native int foo();
void case01() {
int <caret>problematic;
if (1 > 2) System.out.println((problematic = foo()) + (problematic = foo()));
}
}

View File

@@ -0,0 +1,11 @@
// "Remove local variable 'problematic'" "true-preview"
class C {
native int foo();
void case01() {
Object <caret>problematic;
for(int i = 10; i > 0; --i, problematic = foo() + foo()) {
System.out.println("index = " + i);
}
}
}

View File

@@ -0,0 +1,11 @@
// "Remove local variable 'problematic'" "true-preview"
class C {
native int foo();
void case01() {
Object <caret>problematic;
for(int i = 10; i > 0; problematic = foo() + foo()) {
System.out.println("index = " + i);
}
}
}

View File

@@ -1,14 +1,19 @@
// 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.deadCode.UnusedDeclarationInspection;
import com.intellij.ui.ChooserInterceptor;
import com.intellij.ui.UiInterceptors;
public class RemoveUnusedVariableTest extends LightQuickFixParameterizedTestCase {
@Override
protected void setUp() throws Exception {
super.setUp();
enableInspectionTool(new UnusedDeclarationInspection());
if (getTestName(false).endsWith("SideEffect.java")) {
UiInterceptors.register(new ChooserInterceptor(null, "Extract possible side effects"));
}
}
@Override

View File

@@ -1,18 +1,4 @@
/*
* 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.java18api;
import com.intellij.codeInsight.daemon.quickFix.LightQuickFixParameterizedTestCase;
@@ -20,7 +6,10 @@ import com.intellij.codeInspection.LocalInspectionTool;
import com.intellij.codeInspection.java18api.Java8MapApiInspection;
import org.jetbrains.annotations.NotNull;
import org.junit.Test;
import org.junit.experimental.runners.Enclosed;
import org.junit.runner.RunWith;
@RunWith(Enclosed.class)
public class Java8MapApiInspectionTest extends LightQuickFixParameterizedTestCase {
@Override
protected LocalInspectionTool @NotNull [] configureLocalInspectionTools() {

View File

@@ -1865,4 +1865,7 @@ convert.number.decimal=decimal
convert.number.plain.format=plain format
convert.number.scientific.format=scientific format
delete.initializer.completely=Delete initializer completely
delete.assignment.completely=Delete assignment completely
delete.assignment.completely=Delete assignment completely
popup.title.remove.unused.variable=Remove Unused Variable
intention.family.name.extract.possible.side.effects=Extract possible side effects
intention.family.name.delete.possible.side.effects=Delete possible side effects