diff --git a/java/java-analysis-impl/intellij.java.analysis.impl.iml b/java/java-analysis-impl/intellij.java.analysis.impl.iml index 2574fb64a1f1..6b0a31999446 100644 --- a/java/java-analysis-impl/intellij.java.analysis.impl.iml +++ b/java/java-analysis-impl/intellij.java.analysis.impl.iml @@ -29,5 +29,6 @@ + \ No newline at end of file diff --git a/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/PostHighlightingVisitor.java b/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/PostHighlightingVisitor.java index 9d11b0cea735..f02878550faf 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/PostHighlightingVisitor.java +++ b/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/PostHighlightingVisitor.java @@ -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 custom = InspectionProfileWrapper.getCustomInspectionProfileWrapper(myFile); - InspectionProfileImpl currentProfile = InspectionProjectProfileManager.getInstance(myProject).getCurrentProfile(); + private static InspectionProfile getCurrentProfile(@NotNull PsiFile file) { + Function 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()); + } } diff --git a/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/RemoveAllUnusedImportsFix.java b/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/RemoveAllUnusedImportsFix.java new file mode 100644 index 000000000000..5e1101010967 --- /dev/null +++ b/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/RemoveAllUnusedImportsFix.java @@ -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 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; + } +} diff --git a/java/java-psi-impl/src/messages/JavaErrorBundle.properties b/java/java-psi-impl/src/messages/JavaErrorBundle.properties index ae90eba4d504..a1fe42e24d58 100644 --- a/java/java-psi-impl/src/messages/JavaErrorBundle.properties +++ b/java/java-psi-impl/src/messages/JavaErrorBundle.properties @@ -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 \ No newline at end of file +remove.unused.imports.quickfix.text=Remove unused imports \ No newline at end of file diff --git a/java/java-tests/testSrc/com/intellij/java/psi/OptimizeImportsTest.java b/java/java-tests/testSrc/com/intellij/java/psi/OptimizeImportsTest.java index c61f44963d3f..af1fc30eed91 100644 --- a/java/java-tests/testSrc/com/intellij/java/psi/OptimizeImportsTest.java +++ b/java/java-tests/testSrc/com/intellij/java/psi/OptimizeImportsTest.java @@ -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.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.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.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());