"remove unused imports" quick fix to alleviate pain of IDEA-334084 No "Optimize imports" action is displayed

GitOrigin-RevId: 1c39342f6012a129605f662c427a4b9456f1114f
This commit is contained in:
Alexey Kudravtsev
2023-11-07 12:59:15 +00:00
committed by intellij-monorepo-bot
parent ab1b4cb4c3
commit d9a3efc3dd
5 changed files with 107 additions and 13 deletions
@@ -29,5 +29,6 @@
<orderEntry type="module" module-name="intellij.platform.workspace.jps" />
<orderEntry type="module" module-name="intellij.platform.backend.workspace" />
<orderEntry type="module" module-name="intellij.java.frontback.impl" exported="" />
<orderEntry type="module" module-name="intellij.platform.editor.ex" />
</component>
</module>
@@ -156,16 +156,16 @@ class PostHighlightingVisitor extends JavaElementVisitor {
if (!(myFile instanceof PsiJavaFile) || myUnusedSymbolInspection == null) {
return false;
}
InspectionProfile profile = getCurrentProfile();
InspectionProfile profile = getCurrentProfile(myFile);
return profile.isToolEnabled(displayKey, myFile) &&
HighlightingLevelManager.getInstance(myProject).shouldInspect(myFile) &&
!HighlightingLevelManager.getInstance(myProject).runEssentialHighlightingOnly(myFile);
}
@NotNull
private InspectionProfile getCurrentProfile() {
Function<? super InspectionProfile, ? extends InspectionProfileWrapper> custom = InspectionProfileWrapper.getCustomInspectionProfileWrapper(myFile);
InspectionProfileImpl currentProfile = InspectionProjectProfileManager.getInstance(myProject).getCurrentProfile();
private static InspectionProfile getCurrentProfile(@NotNull PsiFile file) {
Function<? super InspectionProfile, ? extends InspectionProfileWrapper> custom = InspectionProfileWrapper.getCustomInspectionProfileWrapper(file);
InspectionProfileImpl currentProfile = InspectionProjectProfileManager.getInstance(file.getProject()).getCurrentProfile();
return custom != null ? custom.apply(currentProfile).getInspectionProfile() : currentProfile;
}
@@ -552,7 +552,7 @@ class PostHighlightingVisitor extends JavaElementVisitor {
boolean predefinedImport = imports != null && imports.contains(importStatement.getText());
String description = !predefinedImport ? JavaAnalysisBundle.message("unused.import.statement") :
JavaAnalysisBundle.message("text.unused.import.in.template");
InspectionProfile profile = getCurrentProfile();
InspectionProfile profile = getCurrentProfile(myFile);
TextAttributesKey key = ObjectUtils.notNull(profile.getEditorAttributes(unusedImportKey.toString(), myFile),
JavaHighlightInfoTypes.UNUSED_IMPORT.getAttributesKey());
HighlightInfoType.HighlightInfoTypeImpl configHighlightType =
@@ -563,8 +563,7 @@ class PostHighlightingVisitor extends JavaElementVisitor {
.descriptionAndTooltip(description)
.group(GeneralHighlightingPass.POST_UPDATE_ALL);
IntentionAction removeFix = QuickFixFactory.getInstance().createDeleteFix(importStatement, JavaErrorBundle.message("remove.unused.import.quickfix.text"));
builder.registerFix(removeFix, null, HighlightDisplayKey.getDisplayNameByKey(unusedImportKey), null, unusedImportKey);
builder.registerFix(new RemoveAllUnusedImportsFix(), null, HighlightDisplayKey.getDisplayNameByKey(unusedImportKey), null, unusedImportKey);
IntentionAction switchFix = QuickFixFactory.getInstance().createEnableOptimizeImportsOnTheFlyFix();
builder.registerFix(switchFix, null, HighlightDisplayKey.getDisplayNameByKey(unusedImportKey), null, unusedImportKey);
@@ -573,4 +572,10 @@ class PostHighlightingVisitor extends JavaElementVisitor {
}
addInfo(holder, builder);
}
static boolean isUnusedImportHighlightInfo(@NotNull PsiFile psiFile, @NotNull HighlightInfo info) {
TextAttributesKey key = info.type.getAttributesKey();
InspectionProfile profile = getCurrentProfile(psiFile);
return key.equals(profile.getEditorAttributes(UnusedImportInspection.SHORT_NAME, psiFile))
|| key.equals(JavaHighlightInfoTypes.UNUSED_IMPORT.getAttributesKey());
}
}
@@ -0,0 +1,61 @@
// 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.analysis;
import com.intellij.codeInsight.daemon.JavaErrorBundle;
import com.intellij.codeInsight.daemon.impl.DaemonCodeAnalyzerEx;
import com.intellij.codeInsight.intention.IntentionAction;
import com.intellij.codeInsight.intention.QuickFixFactory;
import com.intellij.lang.annotation.HighlightSeverity;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.project.Project;
import com.intellij.psi.*;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.util.IncorrectOperationException;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.List;
public class RemoveAllUnusedImportsFix implements IntentionAction {
@Override
public @NotNull String getFamilyName() {
return JavaErrorBundle.message("remove.unused.imports.quickfix.text");
}
@Override
public @NotNull String getText() {
return getFamilyName();
}
@Override
public boolean isAvailable(@NotNull Project project, Editor editor, PsiFile file) {
return file instanceof PsiJavaFile && editor != null;
}
@Override
public void invoke(@NotNull Project project, Editor editor, PsiFile psiFile) throws IncorrectOperationException {
if (!(psiFile instanceof PsiJavaFile javaFile) || editor == null) return;
PsiImportList importList = javaFile.getImportList();
if (importList == null) return;
List<PsiImportStatement> importStatements = new ArrayList<>();
DaemonCodeAnalyzerEx.processHighlights(editor.getDocument(), project, HighlightSeverity.INFORMATION, importList.getTextRange().getStartOffset(), importList.getTextRange().getEndOffset(), info -> {
if (PostHighlightingVisitor.isUnusedImportHighlightInfo(psiFile, info)) {
PsiImportStatement importStatement = PsiTreeUtil.findElementOfClassAtOffset(psiFile, info.getActualStartOffset(), PsiImportStatement.class, false);
if (importStatement != null) {
importStatements.add(importStatement);
}
}
return true;
});
if (!importStatements.isEmpty()) {
IntentionAction deleteAll = QuickFixFactory.getInstance().createDeleteFix(importStatements.toArray(PsiElement.EMPTY_ARRAY));
deleteAll.invoke(project, editor, psiFile);
}
}
@Override
public boolean startInWriteAction() {
return false;
}
}
@@ -582,4 +582,4 @@ error.unnamed.class.contains.no.main.method=Unnamed class contains no 'main' met
error.unnamed.class.has.invalid.file.name=Unnamed class's file name is not a valid identifier
error.package.statement.not.allowed.for.unnamed.class=Package statement is not allowed for unnamed class
error.initializers.are.not.allowed.in.unnamed.classes=Initializers are not allowed in unnamed classes
remove.unused.import.quickfix.text=Remove unused import
remove.unused.imports.quickfix.text=Remove unused imports
@@ -4,6 +4,7 @@ package com.intellij.java.psi;
import com.intellij.application.options.CodeStyle;
import com.intellij.application.options.codeStyle.excludedFiles.NamedScopeDescriptor;
import com.intellij.codeInsight.CodeInsightWorkspaceSettings;
import com.intellij.codeInsight.daemon.JavaErrorBundle;
import com.intellij.codeInsight.intention.IntentionAction;
import com.intellij.codeInsight.intention.QuickFixFactory;
import com.intellij.codeInspection.deadCode.UnusedDeclarationInspection;
@@ -21,7 +22,6 @@ import com.intellij.openapi.application.ReadAction;
import com.intellij.openapi.application.ex.PathManagerEx;
import com.intellij.openapi.command.WriteCommandAction;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.pom.java.LanguageLevel;
import com.intellij.psi.PsiDocumentManager;
import com.intellij.psi.PsiFile;
import com.intellij.psi.codeStyle.CodeStyleSettings;
@@ -214,7 +214,7 @@ public class OptimizeImportsTest extends OptimizeImportsTestCase {
import java.util.Collection;
class Foo {}""");
myFixture.launchAction(myFixture.findSingleIntention("Remove unused import"));
myFixture.launchAction(myFixture.findSingleIntention(JavaErrorBundle.message("remove.unused.imports.quickfix.text")));
// whatever: main thing it didn't throw
myFixture.checkResult("""
@@ -233,7 +233,7 @@ public class OptimizeImportsTest extends OptimizeImportsTestCase {
import java.util.Map;
class Foo {}""");
myFixture.launchAction(myFixture.findSingleIntention("Remove unused import"));
myFixture.launchAction(myFixture.findSingleIntention(JavaErrorBundle.message("remove.unused.imports.quickfix.text")));
// whatever: main thing it didn't throw
assertNotEmpty(myFixture.doHighlighting(HighlightSeverity.ERROR));
@@ -259,7 +259,7 @@ public class OptimizeImportsTest extends OptimizeImportsTestCase {
import java.<caret>util.Set;
""");
myFixture.launchAction(myFixture.findSingleIntention("Remove unused import"));
myFixture.launchAction(myFixture.findSingleIntention(JavaErrorBundle.message("remove.unused.imports.quickfix.text")));
myFixture.checkResult("package p;\n\n");
}
public void testRemoveUnusedImportFixShownEvenForUnresolvedImport() {
@@ -270,9 +270,36 @@ public class OptimizeImportsTest extends OptimizeImportsTestCase {
import java.<caret>blahblah.Set;
""");
myFixture.launchAction(myFixture.findSingleIntention("Remove unused import"));
myFixture.launchAction(myFixture.findSingleIntention(JavaErrorBundle.message("remove.unused.imports.quickfix.text")));
myFixture.checkResult("package p;\n\n");
}
public void testRemoveUnusedImportFixMustDeleteAllUnusedImports() {
myFixture.enableInspections(new UnusedImportInspection());
myFixture.configureByText("a.java", """
package p;
// just remove, not reorganize
import java.util.List;
import java.util.HashSet;
// this for sure
import java.<caret>util.Set;
import java.util.ArrayList;
import java.util.HashMap;
class X { List a = new ArrayList(); }
""");
myFixture.launchAction(myFixture.findSingleIntention(JavaErrorBundle.message("remove.unused.imports.quickfix.text")));
myFixture.checkResult("""
package p;
// just remove, not reorganize
import java.util.List;
// this for sure
import java.util.ArrayList;
class X { List a = new ArrayList(); }
""");
}
public void testPerFileImportSettings() {
CodeStyle.dropTemporarySettings(getProject());