[java-imports] IDEA-370756 Optimize Imports adds unused import

GitOrigin-RevId: e14b210b781a3134d03c988d27f7ddb7a5e65f63
This commit is contained in:
Mikhail Pyltsin
2025-05-12 12:20:09 +00:00
committed by intellij-monorepo-bot
parent ac7d0aad0a
commit 294d6bb6c9
6 changed files with 100 additions and 10 deletions
@@ -683,6 +683,7 @@ public final class ImportUtils {
private final @NotNull Set<PsiImportModuleStatement> myModulesStatements = new HashSet<>();
private final @NotNull Map<String, PsiImportStatement> myPackageStatements = new HashMap<>();
private final @NotNull Map<ImplicitCacheItem, Boolean> myCache = new HashMap<>();
private ImplicitImportChecker(@NotNull PsiJavaFile file) {
for (PsiImportStatementBase anImport : getAllImplicitImports(file)) {
if(anImport instanceof PsiImportStaticStatement staticStatement) {
@@ -703,12 +704,18 @@ public final class ImportUtils {
}
}
record ImplicitCacheItem(String qName, boolean isStatic,
@NotNull ImportUtils.OnDemandImportConflict conflicts) { }
public boolean isImplicitlyImported(String qName, boolean isStatic) {
return isImplicitlyImported(qName, isStatic, OnDemandImportConflict.NO_CONFLICTS);
}
public boolean isImplicitlyImported(String qName, boolean isStatic,
@NotNull ImportUtils.OnDemandImportConflict conflicts) {
ImplicitCacheItem cacheItem = new ImplicitCacheItem(qName, isStatic, conflicts);
Boolean result = myCache.get(cacheItem);
if (result != null) return result;
String packageOrClassName = StringUtil.getPackageName(qName);
String className = ClassUtil.extractClassName(qName);
if (!isStatic) {
@@ -720,24 +727,39 @@ public final class ImportUtils {
if (reference == null) continue;
PsiElement resolved = reference.resolve();
if (resolved instanceof PsiPackage psiPackage) {
if (psiPackage.containsClassNamed(className)) return true;
if (psiPackage.containsClassNamed(className)) {
myCache.put(cacheItem, true);
return true;
}
}
}
}
if (!conflicts.hasConflictForOnDemand() && myPackageStatements.containsKey(packageOrClassName)) return true;
if (!conflicts.hasConflictForOnDemand() && myPackageStatements.containsKey(packageOrClassName)) {
myCache.put(cacheItem, true);
return true;
}
}
else {
if (!conflicts.hasConflictForOnDemand()) {
PsiImportStaticStatement psiImportStaticStatement = myStaticImportStatements.get(packageOrClassName);
if (psiImportStaticStatement != null) {
if (psiImportStaticStatement.isOnDemand()) return true;
if (psiImportStaticStatement.isOnDemand()) {
myCache.put(cacheItem, true);
return true;
}
PsiJavaCodeReferenceElement reference = psiImportStaticStatement.getImportReference();
if (reference == null) return false;
if (reference == null) {
myCache.put(cacheItem, false);
return false;
}
String qualifiedName = reference.getQualifiedName();
return qName.equals(qualifiedName);
boolean equals = qName.equals(qualifiedName);
myCache.put(cacheItem, equals);
return equals;
}
}
}
myCache.put(cacheItem, false);
return false;
}
}
@@ -128,6 +128,8 @@ public final class ImportHelper extends ImportHelperBase {
moduleStatementMap.keySet());
classesToUseSingle.addAll(toReimport);
deleteOnDemandIfAllIsSingle(classesOrPackagesToImportOnDemand, resultList, classesToUseSingle, checker);
try {
boolean onDemandFirst = mySettings.isLayoutOnDemandImportFromSamePackageFirst();
StringBuilder text =
@@ -159,6 +161,33 @@ public final class ImportHelper extends ImportHelperBase {
}
}
/**
* Removes on-demand imports from the given map if all corresponding elements are present in the singles set
* (individually imported) or implicitly imported.
*
* @param onDemand a map of package or class names (keys) and boolean values associating them with on-demand imports.
* @param allList a list of Import objects representing all current imports in the file.
* @param singles a set of individual imports to retain.
* @param checker an ImplicitImportChecker used to determine if a name is implicitly imported.
*/
private static void deleteOnDemandIfAllIsSingle(@NotNull Map<String, Boolean> onDemand,
@NotNull List<Import> allList,
@NotNull Set<String> singles,
@NotNull ImportUtils.ImplicitImportChecker checker) {
Set<String> save = new HashSet<>();
if (singles.isEmpty()) return;
for (Import anImport : allList) {
String name = anImport.name();
boolean isStatic = anImport.isStatic();
String packageOrClassName = StringUtil.getPackageName(name);
boolean implicitlyImported = checker.isImplicitlyImported(name, isStatic);
if (!singles.contains(name) && !implicitlyImported && onDemand.containsKey(packageOrClassName)) {
save.add(packageOrClassName);
}
}
onDemand.keySet().retainAll(save);
}
/**
* Collects the names of classes that are imported by modules specified implicitly in the given Java file and in the import list.
*
@@ -168,8 +197,8 @@ public final class ImportHelper extends ImportHelperBase {
* @return a map of class names and used module imports.
*/
private static @NotNull Map<String, PsiImportModuleStatement> collectNamesImportedByModules(@NotNull PsiJavaFile file,
@NotNull List<PsiImportModuleStatement> statements,
@NotNull List<Import> list) {
@NotNull List<PsiImportModuleStatement> statements,
@NotNull List<Import> list) {
List<PsiImportStatementBase> implicitImports = ImportsUtil.getAllImplicitImports(file);
List<PsiImportModuleStatement> moduleImports =
new ArrayList<>(ContainerUtil.filterIsInstance(implicitImports, PsiImportModuleStatement.class));
@@ -986,11 +1015,11 @@ public final class ImportHelper extends ImportHelperBase {
}
private static boolean isOnDemandStaticImported(@NotNull PsiJavaFile file, @NotNull Import anImport) {
if(!anImport.isStatic()) return false;
if (!anImport.isStatic()) return false;
PsiImportList importList = file.getImportList();
if(importList==null)return false;
if (importList == null) return false;
for (PsiImportStaticStatement statement : importList.getImportStaticStatements()) {
if(!statement.isOnDemand()) return false;
if (!statement.isOnDemand()) return false;
String packageName = StringUtil.getPackageName(anImport.name);
if (statement.getImportReference() != null &&
packageName.equals(statement.getImportReference().getQualifiedName())) {
@@ -0,0 +1,10 @@
package p3;
import p2.*;
import p1.List;
public class A {
public static void main(String[] args) {
List<Something> frameList = List.of();
}
}
@@ -0,0 +1,10 @@
package p3;
import p1.List;
import p2.*;
public class A {
public static void main(String[] args) {
List<Something> frameList = List.of();
}
}
@@ -15,6 +15,7 @@ public class LightOptimizeImportsTest extends LightJavaCodeInsightFixtureTestCas
public void testLayoutOnDemandImportsFromTheSamePackageFirst() {
myFixture.addClass("package a.a; public class A {}");
myFixture.addClass("package a.a; public class C {}");
myFixture.addClass("package a.b; public class A {}");
myFixture.addClass("package a.b; public class B {}");
myFixture.addClass("package a.b; public class Boolean {}");
@@ -22,12 +23,14 @@ public class LightOptimizeImportsTest extends LightJavaCodeInsightFixtureTestCas
package a;
import a.a.A;
import a.a.C;
import a.b.B;
import a.b.Boolean;
class Main {
A a;
B b;
C c;
Boolean bool;
}""";
myFixture.configureByText(JavaFileType.INSTANCE, code);
@@ -48,6 +51,7 @@ public class LightOptimizeImportsTest extends LightJavaCodeInsightFixtureTestCas
class Main {
A a;
B b;
C c;
Boolean bool;
}""";
myFixture.checkResult(result);
@@ -63,11 +63,15 @@ public class OptimizeImportsTest extends OptimizeImportsTestCase {
JavaCodeStyleSettings javaSettings = JavaCodeStyleSettings.getInstance(getProject());
boolean preserveModuleImports = javaSettings.isPreserveModuleImports();
PackageEntryTable table = javaSettings.IMPORT_LAYOUT_TABLE;
int classOnDemand = javaSettings.CLASS_COUNT_TO_USE_IMPORT_ON_DEMAND;
int NamesOnDemand = javaSettings.NAMES_COUNT_TO_USE_IMPORT_ON_DEMAND;
Disposer.register(getTestRootDisposable(), new Disposable() {
@Override
public void dispose() {
javaSettings.setPreserveModuleImports(preserveModuleImports);
javaSettings.IMPORT_LAYOUT_TABLE = table;
javaSettings.CLASS_COUNT_TO_USE_IMPORT_ON_DEMAND = classOnDemand;
javaSettings.NAMES_COUNT_TO_USE_IMPORT_ON_DEMAND = NamesOnDemand;
}
});
myFixture.enableInspections(new UnusedDeclarationInspection());
@@ -220,6 +224,17 @@ public class OptimizeImportsTest extends OptimizeImportsTestCase {
});
}
public void testDontShowOnDemandIfAllSingles() {
myFixture.addClass("package p1; public class List<T> {}");
myFixture.addClass("package p2; public class List<T> {}");
myFixture.addClass("package p2; public class Something {}");
JavaCodeStyleSettings javaSettings = JavaCodeStyleSettings.getInstance(getProject());
javaSettings.CLASS_COUNT_TO_USE_IMPORT_ON_DEMAND = 1;
javaSettings.NAMES_COUNT_TO_USE_IMPORT_ON_DEMAND = 1;
doTest();
}
public void testConflictStaticImport(){
myFixture.addClass(
"""