mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-04-19 04:51:24 +07:00
[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:
committed by
intellij-monorepo-bot
parent
12607d120a
commit
c55dfd035c
@@ -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();
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
|
||||
@@ -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)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -4,7 +4,7 @@ import java.io.*;
|
||||
class a {
|
||||
int k;
|
||||
private int run() {
|
||||
Object o0, oo;<caret>
|
||||
Object o0, oo<caret>;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -5,6 +5,9 @@ class a {
|
||||
<caret>private void run() {
|
||||
int j;
|
||||
int k = 9;
|
||||
if ((k = 9) == 0) {
|
||||
k = 8;
|
||||
}
|
||||
if (3 ==0) ;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,9 @@ class a {
|
||||
int k;
|
||||
private void run() {
|
||||
k = 9;
|
||||
if ((k = 9) == 0) {
|
||||
k = 8;
|
||||
}
|
||||
while (1 > 0) ;
|
||||
for (;; ) ;
|
||||
}
|
||||
@@ -4,7 +4,6 @@ import java.io.*;
|
||||
class a {
|
||||
int k;
|
||||
private int run() {
|
||||
new a();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ import java.io.*;
|
||||
class a {
|
||||
int k;
|
||||
private int run() {
|
||||
<caret>Object i = null;
|
||||
Object <caret>i = null;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,8 @@ class C {
|
||||
|
||||
void case01() {
|
||||
int i = 10;
|
||||
for(foo(); (--i) > 0; ) {
|
||||
foo();
|
||||
for(; (--i) > 0; ) {
|
||||
System.out.println("index = " + i);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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()));
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user