diff --git a/rewrite-java/src/main/java/org/openrewrite/java/RemoveImport.java b/rewrite-java/src/main/java/org/openrewrite/java/RemoveImport.java index 2f14c5d3bb2..d6ad732cf44 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/RemoveImport.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/RemoveImport.java @@ -67,9 +67,15 @@ public RemoveImport(String type, boolean force) { Set methodsAndFieldsUsed = new HashSet<>(); Set otherMethodsAndFieldsInTypeUsed = new TreeSet<>(); Set originalImports = new HashSet<>(); + Set ownerAndSupertypes = new HashSet<>(); for (J.Import cuImport : cu.getImports()) { - if (cuImport.getQualid().getType() != null) { - originalImports.add(((JavaType.FullyQualified) cuImport.getQualid().getType()).getFullyQualifiedName().replace("$", ".")); + JavaType.FullyQualified qualidType = TypeUtils.asFullyQualified(cuImport.getQualid().getType()); + if (qualidType != null) { + originalImports.add(qualidType.getFullyQualifiedName().replace("$", ".")); + if (TypeUtils.fullyQualifiedNamesAreEqual(qualidType.getFullyQualifiedName(), type) && + qualidType.getOwningClass() != null) { + collectSupertypeNames(qualidType.getOwningClass(), ownerAndSupertypes); + } } } @@ -81,8 +87,16 @@ public RemoveImport(String type, boolean force) { } } + String importedName = type.substring(type.lastIndexOf('.') + 1); for (JavaType.Method method : cu.getTypesInUse().getUsedMethods()) { - if (method.hasFlags(Flag.Static)) { + JavaType.FullyQualified methodDeclaringType = method.getDeclaringType(); + if (methodDeclaringType != null && + TypeUtils.fullyQualifiedNamesAreEqual(packageQualifiedName(methodDeclaringType, method.getName()), type)) { + methodsAndFieldsUsed.add(method.getName()); + } else if (methodDeclaringType != null && method.getName().equals(importedName) && + ownerAndSupertypes.contains(TypeUtils.toFullyQualifiedName(methodDeclaringType.getFullyQualifiedName()))) { + methodsAndFieldsUsed.add(method.getName()); + } else if (method.hasFlags(Flag.Static)) { String declaringType = method.getDeclaringType().getFullyQualifiedName(); if (TypeUtils.fullyQualifiedNamesAreEqual(declaringType, type)) { methodsAndFieldsUsed.add(method.getName()); @@ -143,7 +157,8 @@ public RemoveImport(String type, boolean force) { spaceForNextImport.set(import_.getPrefix()); return null; } - } else if (!keepImport && TypeUtils.fullyQualifiedNamesAreEqual(typeName, type)) { + } else if (!keepImport && TypeUtils.fullyQualifiedNamesAreEqual(typeName, type) && + !methodsAndFieldsUsed.contains(import_.getQualid().getSimpleName())) { spaceForNextImport.set(import_.getPrefix()); return null; } else if (!keepImport && import_.getPackageName().equals(owner) && @@ -184,6 +199,23 @@ public RemoveImport(String type, boolean force) { return j; } + private static String packageQualifiedName(JavaType.FullyQualified declaringType, String name) { + String packageName = declaringType.getPackageName(); + return packageName.isEmpty() ? name : packageName + "." + name; + } + + private static void collectSupertypeNames(JavaType.FullyQualified type, Set names) { + if (!names.add(TypeUtils.toFullyQualifiedName(type.getFullyQualifiedName()))) { + return; + } + for (JavaType.FullyQualified anInterface : type.getInterfaces()) { + collectSupertypeNames(anInterface, names); + } + if (type.getSupertype() != null) { + collectSupertypeNames(type.getSupertype(), names); + } + } + private long countTrailingLinebreaks(Space space) { return space.getLastWhitespace().chars().filter(s -> s == '\n').count(); } diff --git a/rewrite-kotlin/src/test/java/org/openrewrite/kotlin/RemoveImportTest.java b/rewrite-kotlin/src/test/java/org/openrewrite/kotlin/RemoveImportTest.java index 793443da92c..ef324d6b69f 100644 --- a/rewrite-kotlin/src/test/java/org/openrewrite/kotlin/RemoveImportTest.java +++ b/rewrite-kotlin/src/test/java/org/openrewrite/kotlin/RemoveImportTest.java @@ -15,6 +15,7 @@ */ package org.openrewrite.kotlin; +import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.openrewrite.DocumentExample; import org.openrewrite.ExecutionContext; @@ -24,6 +25,7 @@ import org.openrewrite.test.RewriteTest; import org.openrewrite.test.TypeValidation; +import static org.openrewrite.java.Assertions.java; import static org.openrewrite.kotlin.Assertions.kotlin; import static org.openrewrite.test.RewriteTest.toRecipe; @@ -268,4 +270,106 @@ class A ) ); } + + @Test + void keepWhenParentMembersAreUsed() { + rewriteRun( + spec -> spec.recipe(removeTypeImportRecipe("org.example.Child.Companion.one")), + kotlin( + """ + package org.example + interface Shared { + fun one() = "one" + } + open class Parent { + companion object : Shared + } + class Child : Parent() { + companion object : Shared { + fun two() = "two" + } + } + """ + ), + kotlin( + """ + import org.example.Child.Companion.one + import org.example.Child.Companion.two + + class A { + fun test() { + one() + two() + } + } + """ + ) + ); + } + + @Test + void keepWhenUsingTopLevelFunctions() { + rewriteRun( + spec -> spec.recipe(removeTypeImportRecipe("org.example.one")), + kotlin( + """ + package org.example + + fun one() = "one" + fun two() = "two" + """ + ), + kotlin( + """ + package org.example2 + + import org.example.one + import org.example.two + + class Aassss { + fun test() { + one() + two() + } + } + """ + ) + ); + } + + @Disabled("We cannot use Java sources as dependencies in Kotlin sources yet") + @Test + void keepStarFoldWhenUsingStaticChildAndParentMembersFromJavaClasses() { + rewriteRun( + // This kind of setup is only possible with a Java <> Kotlin mix, as you cannot use star imports for companion object members + java( + """ + package org.example; + public class Parent { + public static void a() {} + public static void b() {} + } + public class Child extends Parent { + public static void x() {} + public static void y() {} + } + """ + ), + kotlin( + """ + import org.example.Child.* + import org.example.Child.a + + class A { + fun test() { + a() + b() + x() + y() + } + } + """ + ) + ); + } }