From c8d9b6de49a84467cef4fe383c28f50b1af3f056 Mon Sep 17 00:00:00 2001 From: Sakshi Chitnis Date: Sun, 21 Jun 2026 12:50:03 +0530 Subject: [PATCH] Fix RemoveUnusedImports for inherited nested types --- .../java/RemoveUnusedImportsTest.java | 32 +++++++++ .../openrewrite/java/RemoveUnusedImports.java | 65 +++++++++++++++---- 2 files changed, 83 insertions(+), 14 deletions(-) diff --git a/rewrite-java-test/src/test/java/org/openrewrite/java/RemoveUnusedImportsTest.java b/rewrite-java-test/src/test/java/org/openrewrite/java/RemoveUnusedImportsTest.java index 562554880ec..a0b5afadd13 100755 --- a/rewrite-java-test/src/test/java/org/openrewrite/java/RemoveUnusedImportsTest.java +++ b/rewrite-java-test/src/test/java/org/openrewrite/java/RemoveUnusedImportsTest.java @@ -115,6 +115,38 @@ public abstract class MyMapEntry implements Entry { ); } + @Issue("https://github.com/openrewrite/rewrite/issues/7569") + @Test + void doNotRemoveStaticImportForInheritedNestedType() { + rewriteRun( + java( + """ + package com.helloworld; + + import static com.helloworld.FakeUserAccountClient.UserAccountDataAuth; + + import java.util.List; + + class Main { + List getUsers() { + return List.of(UserAccountDataAuth.create()); + } + } + + class FakeUserAccountClient implements UserAccountClient {} + + interface UserAccountClient { + record UserAccountDataAuth() { + static UserAccountDataAuth create() { + return new UserAccountDataAuth(); + } + } + } + """ + ) + ); + } + @Test void doNotRemoveImportUsedOnlyInNestedGeneric() { rewriteRun( diff --git a/rewrite-java/src/main/java/org/openrewrite/java/RemoveUnusedImports.java b/rewrite-java/src/main/java/org/openrewrite/java/RemoveUnusedImports.java index ddb294411c0..2fbb40fae6b 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/RemoveUnusedImports.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/RemoveUnusedImports.java @@ -153,17 +153,10 @@ public J.CompilationUnit visitCompilationUnit(J.CompilationUnit cu, ExecutionCon .orElse(target); SortedSet targetMethodsAndFields = methodsAndFieldsByTypeName.get(modifiedTarget); - Set staticClasses = null; - for (JavaType.FullyQualified maybeStatic : typesByPackage.getOrDefault(target, emptySet())) { - if (maybeStatic.getOwningClass() != null && outerType.startsWith(maybeStatic.getOwningClass().getFullyQualifiedName())) { - if (staticClasses == null) { - staticClasses = new HashSet<>(); - } - staticClasses.add(maybeStatic); - } - } + Set staticClasses = staticClassesInUse(typesByPackage, target, + qualid.getTarget().getType(), qualid.getSimpleName()); - if (methodsAndFields == null && targetMethodsAndFields == null && staticClasses == null) { + if (methodsAndFields == null && targetMethodsAndFields == null && staticClasses.isEmpty()) { anImport.used = false; changed = true; } else if ("*".equals(qualid.getSimpleName())) { @@ -171,7 +164,7 @@ public J.CompilationUnit visitCompilationUnit(J.CompilationUnit cu, ExecutionCon anImport.used = true; usedStaticWildcardImports.add(elem.getTypeName()); } else if (((methodsAndFields == null ? 0 : methodsAndFields.size()) + - (staticClasses == null ? 0 : staticClasses.size())) < layoutStyle.getNameCountToUseStarImport()) { + staticClasses.size()) < layoutStyle.getNameCountToUseStarImport()) { // replacing the star with a series of unfolded imports anImport.imports.clear(); @@ -185,7 +178,7 @@ public J.CompilationUnit visitCompilationUnit(J.CompilationUnit cu, ExecutionCon } } - if (staticClasses != null) { + if (!staticClasses.isEmpty()) { for (JavaType.FullyQualified fqn : staticClasses) { anImport.imports.add(new JRightPadded<>(elem .withId(randomId()) @@ -204,9 +197,10 @@ public J.CompilationUnit visitCompilationUnit(J.CompilationUnit cu, ExecutionCon } else { usedStaticWildcardImports.add(elem.getTypeName()); } - } else if (staticClasses != null && staticClasses.stream().anyMatch(c -> elem.getTypeName().equals(c.getFullyQualifiedName())) || + } else if (!staticClasses.isEmpty() || (methodsAndFields != null && methodsAndFields.contains(qualid.getSimpleName())) || - (targetMethodsAndFields != null && targetMethodsAndFields.contains(qualid.getSimpleName()))) { + (targetMethodsAndFields != null && + targetMethodsAndFields.contains(qualid.getSimpleName()))) { anImport.used = true; } else { anImport.used = false; @@ -335,6 +329,49 @@ public J.CompilationUnit visitCompilationUnit(J.CompilationUnit cu, ExecutionCon return cu; } + private static Set staticClassesInUse( + Map> typesByPackage, String target, JavaType targetType, + String importedName) { + Set targetTypes = staticClassTargets(target, targetType); + Set staticClasses = new HashSet<>(); + boolean wildcardImport = "*".equals(importedName); + for (String targetTypeName : targetTypes) { + for (JavaType.FullyQualified maybeStatic : typesByPackage.getOrDefault(targetTypeName, emptySet())) { + JavaType.FullyQualified owningClass = maybeStatic.getOwningClass(); + if (owningClass != null && + targetTypes.contains(toFullyQualifiedName(owningClass.getFullyQualifiedName())) && + (wildcardImport || importedName.equals(simpleClassName(maybeStatic)))) { + staticClasses.add(maybeStatic); + } + } + } + return staticClasses; + } + + private static Set staticClassTargets(String target, JavaType targetType) { + Set targets = new HashSet<>(); + targets.add(target); + collectInheritedTypeNames(TypeUtils.asFullyQualified(targetType), targets, new HashSet<>()); + return targets; + } + + private static void collectInheritedTypeNames(JavaType.FullyQualified type, Set targets, + Set seen) { + if (type == null || !seen.add(type.getFullyQualifiedName())) { + return; + } + targets.add(toFullyQualifiedName(type.getFullyQualifiedName())); + collectInheritedTypeNames(type.getSupertype(), targets, seen); + for (JavaType.FullyQualified anInterface : type.getInterfaces()) { + collectInheritedTypeNames(anInterface, targets, seen); + } + } + + private static String simpleClassName(JavaType.FullyQualified type) { + String className = type.getClassName(); + return className.contains(".") ? className.substring(className.lastIndexOf('.') + 1) : className; + } + private static Set getAmbiguousStaticImportNames(J.CompilationUnit cu) { Set typesWithWildcardImport = new HashSet<>(); for (J.Import elem : cu.getImports()) {