From ab582c44b1b5d84a9700ad411df9b0fd31c3861c Mon Sep 17 00:00:00 2001 From: lingenj Date: Tue, 5 Aug 2025 17:41:08 +0200 Subject: [PATCH 01/17] RemoveImport should compare supertypes as well --- .../org/openrewrite/java/RemoveImport.java | 42 +++++++++++++------ .../openrewrite/kotlin/RemoveImportTest.java | 37 ++++++++++++++++ 2 files changed, 67 insertions(+), 12 deletions(-) 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 22787f8689..220a27dcec 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/RemoveImport.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/RemoveImport.java @@ -18,7 +18,6 @@ import com.fasterxml.jackson.annotation.JsonCreator; import lombok.EqualsAndHashCode; import org.jspecify.annotations.Nullable; -import org.openrewrite.SourceFile; import org.openrewrite.internal.ListUtils; import org.openrewrite.java.internal.FormatFirstClassPrefix; import org.openrewrite.java.style.ImportLayoutStyle; @@ -28,6 +27,7 @@ import java.util.*; import java.util.concurrent.atomic.AtomicReference; +import static java.util.Collections.singleton; import static org.openrewrite.Tree.randomId; import static org.openrewrite.java.style.ImportLayoutStyle.isPackageAlwaysFolded; @@ -58,24 +58,33 @@ public RemoveImport(String type, boolean force) { J j = tree; if (tree instanceof JavaSourceFile) { JavaSourceFile cu = (JavaSourceFile) tree; - ImportLayoutStyle importLayoutStyle = Optional.ofNullable(((SourceFile) cu).getStyle(ImportLayoutStyle.class)) + ImportLayoutStyle importLayoutStyle = Optional.ofNullable(cu.getStyle(ImportLayoutStyle.class)) .orElse(IntelliJ.importLayout()); boolean typeUsed = false; + Set typeWithSuperTypes = new HashSet<>(singleton(type)); Set otherTypesInPackageUsed = new TreeSet<>(); Set methodsAndFieldsUsed = new HashSet<>(); Set otherMethodsAndFieldsInTypeUsed = new TreeSet<>(); Set originalImports = new HashSet<>(); for (J.Import cuImport : cu.getImports()) { - if (cuImport.getQualid().getType() != null) { - originalImports.add(((JavaType.FullyQualified) cuImport.getQualid().getType()).getFullyQualifiedName().replace("$", ".")); + JavaType.FullyQualified fq = TypeUtils.asFullyQualified(cuImport.getQualid().getType()); + if (fq != null) { + originalImports.add(fq.getFullyQualifiedName().replace("$", ".")); + // For Kotlin, there is the option star imports reference types of superclasses + if (type.equals(fq.getFullyQualifiedName())) { + while (fq.getSupertype() != null) { + fq = fq.getSupertype(); + typeWithSuperTypes.add(fq.toString()); + } + } } } for (JavaType.Variable variable : cu.getTypesInUse().getVariables()) { JavaType.FullyQualified fq = TypeUtils.asFullyQualified(variable.getOwner()); - if (fq != null && (TypeUtils.fullyQualifiedNamesAreEqual(fq.getFullyQualifiedName(), type) || + if (fq != null && (fullyQualifiedNamesAreEqual(fq.getFullyQualifiedName(), typeWithSuperTypes) || TypeUtils.fullyQualifiedNamesAreEqual(fq.getFullyQualifiedName(), owner))) { methodsAndFieldsUsed.add(variable.getName()); } @@ -84,7 +93,7 @@ public RemoveImport(String type, boolean force) { for (JavaType.Method method : cu.getTypesInUse().getUsedMethods()) { if (method.hasFlags(Flag.Static)) { String declaringType = method.getDeclaringType().getFullyQualifiedName(); - if (TypeUtils.fullyQualifiedNamesAreEqual(declaringType, type)) { + if (fullyQualifiedNamesAreEqual(declaringType, typeWithSuperTypes)) { methodsAndFieldsUsed.add(method.getName()); } else if (declaringType.equals(owner)) { if (method.getName().equals(type.substring(type.lastIndexOf('.') + 1))) { @@ -99,7 +108,7 @@ public RemoveImport(String type, boolean force) { for (JavaType javaType : cu.getTypesInUse().getTypesInUse()) { if (javaType instanceof JavaType.FullyQualified) { JavaType.FullyQualified fullyQualified = (JavaType.FullyQualified) javaType; - if (TypeUtils.fullyQualifiedNamesAreEqual(fullyQualified.getFullyQualifiedName(), type)) { + if (fullyQualifiedNamesAreEqual(fullyQualified.getFullyQualifiedName(), typeWithSuperTypes)) { typeUsed = true; } else if (TypeUtils.fullyQualifiedNamesAreEqual(fullyQualified.getFullyQualifiedName(), owner) || TypeUtils.fullyQualifiedNamesAreEqual(fullyQualified.getPackageName(), owner)) { @@ -128,12 +137,12 @@ public RemoveImport(String type, boolean force) { String typeName = import_.getTypeName(); if (import_.isStatic()) { String imported = import_.getQualid().getSimpleName(); - if (TypeUtils.fullyQualifiedNamesAreEqual(typeName + "." + imported, type) && (force || !methodsAndFieldsUsed.contains(imported))) { + if (fullyQualifiedNamesAreEqual(typeName + "." + imported, typeWithSuperTypes) && (force || !methodsAndFieldsUsed.contains(imported))) { // e.g. remove java.util.Collections.emptySet when type is java.util.Collections.emptySet spaceForNextImport.set(import_.getPrefix()); return null; - } else if ("*".equals(imported) && (TypeUtils.fullyQualifiedNamesAreEqual(typeName, type) || - TypeUtils.fullyQualifiedNamesAreEqual(typeName + type.substring(type.lastIndexOf('.')), type))) { + } else if ("*".equals(imported) && (fullyQualifiedNamesAreEqual(typeName, typeWithSuperTypes) || + fullyQualifiedNamesAreEqual(typeName + type.substring(type.lastIndexOf('.')), typeWithSuperTypes))) { if (methodsAndFieldsUsed.isEmpty() && otherMethodsAndFieldsInTypeUsed.isEmpty()) { spaceForNextImport.set(import_.getPrefix()); return null; @@ -142,12 +151,12 @@ public RemoveImport(String type, boolean force) { methodsAndFieldsUsed.addAll(otherMethodsAndFieldsInTypeUsed); return unfoldStarImport(import_, methodsAndFieldsUsed); } - } else if (TypeUtils.fullyQualifiedNamesAreEqual(typeName, type) && !methodsAndFieldsUsed.contains(imported)) { + } else if (fullyQualifiedNamesAreEqual(typeName, typeWithSuperTypes) && !methodsAndFieldsUsed.contains(imported)) { // e.g. remove java.util.Collections.emptySet when type is java.util.Collections spaceForNextImport.set(import_.getPrefix()); return null; } - } else if (!keepImport && TypeUtils.fullyQualifiedNamesAreEqual(typeName, type)) { + } else if (!keepImport && fullyQualifiedNamesAreEqual(typeName, typeWithSuperTypes)) { spaceForNextImport.set(import_.getPrefix()); return null; } else if (!keepImport && import_.getPackageName().equals(owner) && @@ -175,6 +184,15 @@ public RemoveImport(String type, boolean force) { return j; } + private boolean fullyQualifiedNamesAreEqual(String declaringType, Collection typeWithSuperTypes) { + for (String type : typeWithSuperTypes) { + if (TypeUtils.fullyQualifiedNamesAreEqual(declaringType, type)) { + return true; + } + } + return false; + } + 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 28e43a9a56..98f0212bb0 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; @@ -23,6 +24,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; @@ -223,4 +225,39 @@ class A ) ); } + + @Disabled("We cannot use Java sources as dependencies in Kotlin sources yet") + @Test + void keepStarFoldWhenUsingStaticChildAndParentMembersFromJavaClasses() { + rewriteRun( + // This kind of setup is only possible in Java, 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.* + + class A { + fun test() { + a() + b() + x() + y() + } + } + """ + ) + ); + } } From c81b3400e660dc42d9fb3e7fa97c4e3bdcfc413c Mon Sep 17 00:00:00 2001 From: lingenj Date: Tue, 5 Aug 2025 17:43:27 +0200 Subject: [PATCH 02/17] RemoveImport should compare supertypes as well --- .../src/main/java/org/openrewrite/java/RemoveImport.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 220a27dcec..14a72e7470 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/RemoveImport.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/RemoveImport.java @@ -147,7 +147,7 @@ public RemoveImport(String type, boolean force) { spaceForNextImport.set(import_.getPrefix()); return null; } else if (!isPackageAlwaysFolded(importLayoutStyle.getPackagesToFold(), import_) && - methodsAndFieldsUsed.size() + otherMethodsAndFieldsInTypeUsed.size() < importLayoutStyle.getNameCountToUseStarImport()) { + methodsAndFieldsUsed.size() + otherMethodsAndFieldsInTypeUsed.size() < importLayoutStyle.getNameCountToUseStarImport()) { methodsAndFieldsUsed.addAll(otherMethodsAndFieldsInTypeUsed); return unfoldStarImport(import_, methodsAndFieldsUsed); } From 4f6b6a26ec29184f274f5bc5854b506a2d87eec7 Mon Sep 17 00:00:00 2001 From: lingenj Date: Tue, 5 Aug 2025 18:00:57 +0200 Subject: [PATCH 03/17] Polish --- .../src/main/java/org/openrewrite/java/RemoveImport.java | 2 +- .../src/test/java/org/openrewrite/kotlin/RemoveImportTest.java | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) 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 14a72e7470..5282545c5b 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/RemoveImport.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/RemoveImport.java @@ -72,7 +72,7 @@ public RemoveImport(String type, boolean force) { JavaType.FullyQualified fq = TypeUtils.asFullyQualified(cuImport.getQualid().getType()); if (fq != null) { originalImports.add(fq.getFullyQualifiedName().replace("$", ".")); - // For Kotlin, there is the option star imports reference types of superclasses + // For Kotlin, there is the option star imports reference to types of superclasses if (type.equals(fq.getFullyQualifiedName())) { while (fq.getSupertype() != null) { fq = fq.getSupertype(); 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 98f0212bb0..b1af2d53ad 100644 --- a/rewrite-kotlin/src/test/java/org/openrewrite/kotlin/RemoveImportTest.java +++ b/rewrite-kotlin/src/test/java/org/openrewrite/kotlin/RemoveImportTest.java @@ -247,6 +247,7 @@ public static void y() {} kotlin( """ import org.example.Child.* + import org.example.Child.a class A { fun test() { From f2ca5aae23881d96eb54601a5b54a7d6512ed3e5 Mon Sep 17 00:00:00 2001 From: lingenj Date: Wed, 6 Aug 2025 08:07:59 +0200 Subject: [PATCH 04/17] Polish --- .../src/main/java/org/openrewrite/java/RemoveImport.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 5282545c5b..f8281204cf 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/RemoveImport.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/RemoveImport.java @@ -142,12 +142,12 @@ public RemoveImport(String type, boolean force) { spaceForNextImport.set(import_.getPrefix()); return null; } else if ("*".equals(imported) && (fullyQualifiedNamesAreEqual(typeName, typeWithSuperTypes) || - fullyQualifiedNamesAreEqual(typeName + type.substring(type.lastIndexOf('.')), typeWithSuperTypes))) { + fullyQualifiedNamesAreEqual(typeName + type.substring(type.lastIndexOf('.')), typeWithSuperTypes))) { if (methodsAndFieldsUsed.isEmpty() && otherMethodsAndFieldsInTypeUsed.isEmpty()) { spaceForNextImport.set(import_.getPrefix()); return null; } else if (!isPackageAlwaysFolded(importLayoutStyle.getPackagesToFold(), import_) && - methodsAndFieldsUsed.size() + otherMethodsAndFieldsInTypeUsed.size() < importLayoutStyle.getNameCountToUseStarImport()) { + methodsAndFieldsUsed.size() + otherMethodsAndFieldsInTypeUsed.size() < importLayoutStyle.getNameCountToUseStarImport()) { methodsAndFieldsUsed.addAll(otherMethodsAndFieldsInTypeUsed); return unfoldStarImport(import_, methodsAndFieldsUsed); } From 91c6089d18559b3121715c457aad67c47346e61a Mon Sep 17 00:00:00 2001 From: lingenj Date: Wed, 6 Aug 2025 09:00:49 +0200 Subject: [PATCH 05/17] Polish --- .../org/openrewrite/java/RemoveImport.java | 4 +- .../openrewrite/kotlin/RemoveImportTest.java | 40 +++++++++++++++++++ 2 files changed, 42 insertions(+), 2 deletions(-) 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 f8281204cf..23beb218bc 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/RemoveImport.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/RemoveImport.java @@ -72,7 +72,7 @@ public RemoveImport(String type, boolean force) { JavaType.FullyQualified fq = TypeUtils.asFullyQualified(cuImport.getQualid().getType()); if (fq != null) { originalImports.add(fq.getFullyQualifiedName().replace("$", ".")); - // For Kotlin, there is the option star imports reference to types of superclasses + // For Kotlin, there is the option star imports reference to Java types of superclasses if (type.equals(fq.getFullyQualifiedName())) { while (fq.getSupertype() != null) { fq = fq.getSupertype(); @@ -122,7 +122,7 @@ public RemoveImport(String type, boolean force) { JavaSourceFile c = cu; boolean keepImport = !force && (typeUsed || !otherTypesInPackageUsed.isEmpty() && type.endsWith(".*")); - AtomicReference spaceForNextImport = new AtomicReference<>(); + AtomicReference<@Nullable Space> spaceForNextImport = new AtomicReference<>(); c = c.withImports(ListUtils.flatMap(c.getImports(), import_ -> { if (spaceForNextImport.get() != null) { Space removedPrefix = spaceForNextImport.get(); 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 b1af2d53ad..c2a79f846a 100644 --- a/rewrite-kotlin/src/test/java/org/openrewrite/kotlin/RemoveImportTest.java +++ b/rewrite-kotlin/src/test/java/org/openrewrite/kotlin/RemoveImportTest.java @@ -226,6 +226,46 @@ class A ); } + @Disabled("link-to-companion-object-fixes") + @Test + void dontRemoveUsedParentMembers() { + 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" + fun three() = "three" + } + } + """ + ), + kotlin( + """ + import org.example.Child.Companion.one + import org.example.Child.Companion.two + import org.example.Child.Companion.three + + class A { + fun test() { + one() + two() + three() + } + } + """ + ) + ); + } + @Disabled("We cannot use Java sources as dependencies in Kotlin sources yet") @Test void keepStarFoldWhenUsingStaticChildAndParentMembersFromJavaClasses() { From 1fe6a1533fe1c2a77f9ecf9818a3a9ffbecbc095 Mon Sep 17 00:00:00 2001 From: lingenj Date: Wed, 6 Aug 2025 09:46:11 +0200 Subject: [PATCH 06/17] Polish --- .../org/openrewrite/java/RemoveImport.java | 23 +++++++++---------- .../openrewrite/kotlin/RemoveImportTest.java | 2 +- 2 files changed, 12 insertions(+), 13 deletions(-) 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 23beb218bc..9fde0e2f9f 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/RemoveImport.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/RemoveImport.java @@ -71,12 +71,13 @@ public RemoveImport(String type, boolean force) { for (J.Import cuImport : cu.getImports()) { JavaType.FullyQualified fq = TypeUtils.asFullyQualified(cuImport.getQualid().getType()); if (fq != null) { - originalImports.add(fq.getFullyQualifiedName().replace("$", ".")); + String fqnType = TypeUtils.toFullyQualifiedName(fq.getFullyQualifiedName()); + originalImports.add(fqnType); // For Kotlin, there is the option star imports reference to Java types of superclasses - if (type.equals(fq.getFullyQualifiedName())) { + if (type.equals(fqnType)) { while (fq.getSupertype() != null) { fq = fq.getSupertype(); - typeWithSuperTypes.add(fq.toString()); + typeWithSuperTypes.add(TypeUtils.toFullyQualifiedName(fq.getFullyQualifiedName())); } } } @@ -91,16 +92,14 @@ public RemoveImport(String type, boolean force) { } for (JavaType.Method method : cu.getTypesInUse().getUsedMethods()) { - if (method.hasFlags(Flag.Static)) { - String declaringType = method.getDeclaringType().getFullyQualifiedName(); - if (fullyQualifiedNamesAreEqual(declaringType, typeWithSuperTypes)) { + String declaringType = TypeUtils.toFullyQualifiedName(method.getDeclaringType().getFullyQualifiedName()); + if (fullyQualifiedNamesAreEqual(declaringType, typeWithSuperTypes)) { + methodsAndFieldsUsed.add(method.getName()); + } else if (declaringType.equals(owner)) { + if (method.getName().equals(type.substring(type.lastIndexOf('.') + 1))) { methodsAndFieldsUsed.add(method.getName()); - } else if (declaringType.equals(owner)) { - if (method.getName().equals(type.substring(type.lastIndexOf('.') + 1))) { - methodsAndFieldsUsed.add(method.getName()); - } else { - otherMethodsAndFieldsInTypeUsed.add(method.getName()); - } + } else { + otherMethodsAndFieldsInTypeUsed.add(method.getName()); } } } 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 c2a79f846a..384ef97356 100644 --- a/rewrite-kotlin/src/test/java/org/openrewrite/kotlin/RemoveImportTest.java +++ b/rewrite-kotlin/src/test/java/org/openrewrite/kotlin/RemoveImportTest.java @@ -226,7 +226,7 @@ class A ); } - @Disabled("link-to-companion-object-fixes") + @Disabled("First fix: https://github.com/openrewrite/rewrite/pull/5862") @Test void dontRemoveUsedParentMembers() { rewriteRun( From 941fad32f4652740d3000ba2978e08783d9ecc15 Mon Sep 17 00:00:00 2001 From: lingenj Date: Wed, 6 Aug 2025 09:47:35 +0200 Subject: [PATCH 07/17] Polish --- .../src/test/java/org/openrewrite/kotlin/RemoveImportTest.java | 3 --- 1 file changed, 3 deletions(-) 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 384ef97356..ffdb486a54 100644 --- a/rewrite-kotlin/src/test/java/org/openrewrite/kotlin/RemoveImportTest.java +++ b/rewrite-kotlin/src/test/java/org/openrewrite/kotlin/RemoveImportTest.java @@ -243,7 +243,6 @@ open class Parent { class Child : Parent() { companion object : Shared { fun two() = "two" - fun three() = "three" } } """ @@ -252,13 +251,11 @@ fun three() = "three" """ import org.example.Child.Companion.one import org.example.Child.Companion.two - import org.example.Child.Companion.three class A { fun test() { one() two() - three() } } """ From f6cab9cb358b1a9174ee2ce5ee76358fc9452b2f Mon Sep 17 00:00:00 2001 From: lingenj Date: Thu, 7 Aug 2025 16:31:23 +0200 Subject: [PATCH 08/17] Add `isKotlin` check --- .../src/main/java/org/openrewrite/java/RemoveImport.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) 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 9fde0e2f9f..f2e36a13bd 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/RemoveImport.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/RemoveImport.java @@ -58,6 +58,7 @@ public RemoveImport(String type, boolean force) { J j = tree; if (tree instanceof JavaSourceFile) { JavaSourceFile cu = (JavaSourceFile) tree; + boolean isKotlin = tree.toString().contains("K.CompilationUnit"); // poor man's `cu instanceof K.CompilationUnit` ImportLayoutStyle importLayoutStyle = Optional.ofNullable(cu.getStyle(ImportLayoutStyle.class)) .orElse(IntelliJ.importLayout()); @@ -74,7 +75,7 @@ public RemoveImport(String type, boolean force) { String fqnType = TypeUtils.toFullyQualifiedName(fq.getFullyQualifiedName()); originalImports.add(fqnType); // For Kotlin, there is the option star imports reference to Java types of superclasses - if (type.equals(fqnType)) { + if (isKotlin && type.equals(fqnType)) { while (fq.getSupertype() != null) { fq = fq.getSupertype(); typeWithSuperTypes.add(TypeUtils.toFullyQualifiedName(fq.getFullyQualifiedName())); From 4f295075407402fc27cf6c5471584a9ed509c0d3 Mon Sep 17 00:00:00 2001 From: lingenj Date: Thu, 7 Aug 2025 16:37:38 +0200 Subject: [PATCH 09/17] Use `isKotlin` check --- .../java/org/openrewrite/java/RemoveImport.java | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) 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 f2e36a13bd..d01a7933fb 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/RemoveImport.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/RemoveImport.java @@ -93,14 +93,16 @@ public RemoveImport(String type, boolean force) { } for (JavaType.Method method : cu.getTypesInUse().getUsedMethods()) { - String declaringType = TypeUtils.toFullyQualifiedName(method.getDeclaringType().getFullyQualifiedName()); - if (fullyQualifiedNamesAreEqual(declaringType, typeWithSuperTypes)) { - methodsAndFieldsUsed.add(method.getName()); - } else if (declaringType.equals(owner)) { - if (method.getName().equals(type.substring(type.lastIndexOf('.') + 1))) { + if (method.hasFlags(Flag.Static) || isKotlin) { + String declaringType = TypeUtils.toFullyQualifiedName(method.getDeclaringType().getFullyQualifiedName()); + if (fullyQualifiedNamesAreEqual(declaringType, typeWithSuperTypes)) { methodsAndFieldsUsed.add(method.getName()); - } else { - otherMethodsAndFieldsInTypeUsed.add(method.getName()); + } else if (declaringType.equals(owner)) { + if (method.getName().equals(type.substring(type.lastIndexOf('.') + 1))) { + methodsAndFieldsUsed.add(method.getName()); + } else { + otherMethodsAndFieldsInTypeUsed.add(method.getName()); + } } } } From 54e4e2698e12f2861c912dc563685370dbe5c363 Mon Sep 17 00:00:00 2001 From: lingenj Date: Thu, 7 Aug 2025 16:45:21 +0200 Subject: [PATCH 10/17] Improve `isKotlin` check --- .../src/main/java/org/openrewrite/java/RemoveImport.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 d01a7933fb..aa26d11d83 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/RemoveImport.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/RemoveImport.java @@ -58,7 +58,7 @@ public RemoveImport(String type, boolean force) { J j = tree; if (tree instanceof JavaSourceFile) { JavaSourceFile cu = (JavaSourceFile) tree; - boolean isKotlin = tree.toString().contains("K.CompilationUnit"); // poor man's `cu instanceof K.CompilationUnit` + boolean isKotlin = !(cu instanceof J.CompilationUnit) && tree.toString().contains("K.CompilationUnit"); // poor man's `cu instanceof K.CompilationUnit` ImportLayoutStyle importLayoutStyle = Optional.ofNullable(cu.getStyle(ImportLayoutStyle.class)) .orElse(IntelliJ.importLayout()); From e1d2bd083cb4a22a495078a728f74722eb4dede4 Mon Sep 17 00:00:00 2001 From: lingenj Date: Thu, 7 Aug 2025 16:51:41 +0200 Subject: [PATCH 11/17] Improve `isKotlin` check --- .../src/main/java/org/openrewrite/java/RemoveImport.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 aa26d11d83..6e60aa9356 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/RemoveImport.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/RemoveImport.java @@ -58,7 +58,7 @@ public RemoveImport(String type, boolean force) { J j = tree; if (tree instanceof JavaSourceFile) { JavaSourceFile cu = (JavaSourceFile) tree; - boolean isKotlin = !(cu instanceof J.CompilationUnit) && tree.toString().contains("K.CompilationUnit"); // poor man's `cu instanceof K.CompilationUnit` + boolean isKotlin = !(cu instanceof J.CompilationUnit) && (cu.getSourcePath().toString().endsWith("kt") || cu.getSourcePath().toString().endsWith("kts")); // poor man's `cu instanceof K.CompilationUnit` ImportLayoutStyle importLayoutStyle = Optional.ofNullable(cu.getStyle(ImportLayoutStyle.class)) .orElse(IntelliJ.importLayout()); From 185cf43a48dc40c195a7ad1cd5d461693036fddb Mon Sep 17 00:00:00 2001 From: lingenj Date: Wed, 20 Aug 2025 11:26:15 +0200 Subject: [PATCH 12/17] Add owningClass.getInterfaces() check --- .../org/openrewrite/java/RemoveImport.java | 38 +++++++++++++------ .../openrewrite/kotlin/RemoveImportTest.java | 3 +- 2 files changed, 28 insertions(+), 13 deletions(-) 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 6e60aa9356..17b59dc95f 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/RemoveImport.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/RemoveImport.java @@ -63,7 +63,7 @@ public RemoveImport(String type, boolean force) { .orElse(IntelliJ.importLayout()); boolean typeUsed = false; - Set typeWithSuperTypes = new HashSet<>(singleton(type)); + Set types = new HashSet<>(singleton(type)); Set otherTypesInPackageUsed = new TreeSet<>(); Set methodsAndFieldsUsed = new HashSet<>(); @@ -74,11 +74,27 @@ public RemoveImport(String type, boolean force) { if (fq != null) { String fqnType = TypeUtils.toFullyQualifiedName(fq.getFullyQualifiedName()); originalImports.add(fqnType); - // For Kotlin, there is the option star imports reference to Java types of superclasses if (isKotlin && type.equals(fqnType)) { + // For Kotlin, the owning class interfaces with methods can be used directly without actually importing those interfaces + JavaType.Class owningClass = TypeUtils.asClass(fq.getOwningClass()); + if (owningClass != null) { + Queue toVisit = new LinkedList<>(owningClass.getInterfaces()); + Set visited = new HashSet<>(); + while (!toVisit.isEmpty()) { + JavaType.FullyQualified current = toVisit.poll(); + if (!visited.add(current)) { + continue; + } + toVisit.addAll(current.getInterfaces()); + } + for (JavaType.FullyQualified current : visited) { + types.add(TypeUtils.toFullyQualifiedName(current.getFullyQualifiedName())); + } + } + // And there is the option to star imports references of Java sourced superclasses types while (fq.getSupertype() != null) { fq = fq.getSupertype(); - typeWithSuperTypes.add(TypeUtils.toFullyQualifiedName(fq.getFullyQualifiedName())); + types.add(TypeUtils.toFullyQualifiedName(fq.getFullyQualifiedName())); } } } @@ -86,7 +102,7 @@ public RemoveImport(String type, boolean force) { for (JavaType.Variable variable : cu.getTypesInUse().getVariables()) { JavaType.FullyQualified fq = TypeUtils.asFullyQualified(variable.getOwner()); - if (fq != null && (fullyQualifiedNamesAreEqual(fq.getFullyQualifiedName(), typeWithSuperTypes) || + if (fq != null && (fullyQualifiedNamesAreEqual(fq.getFullyQualifiedName(), types) || TypeUtils.fullyQualifiedNamesAreEqual(fq.getFullyQualifiedName(), owner))) { methodsAndFieldsUsed.add(variable.getName()); } @@ -95,7 +111,7 @@ public RemoveImport(String type, boolean force) { for (JavaType.Method method : cu.getTypesInUse().getUsedMethods()) { if (method.hasFlags(Flag.Static) || isKotlin) { String declaringType = TypeUtils.toFullyQualifiedName(method.getDeclaringType().getFullyQualifiedName()); - if (fullyQualifiedNamesAreEqual(declaringType, typeWithSuperTypes)) { + if (fullyQualifiedNamesAreEqual(declaringType, types)) { methodsAndFieldsUsed.add(method.getName()); } else if (declaringType.equals(owner)) { if (method.getName().equals(type.substring(type.lastIndexOf('.') + 1))) { @@ -110,7 +126,7 @@ public RemoveImport(String type, boolean force) { for (JavaType javaType : cu.getTypesInUse().getTypesInUse()) { if (javaType instanceof JavaType.FullyQualified) { JavaType.FullyQualified fullyQualified = (JavaType.FullyQualified) javaType; - if (fullyQualifiedNamesAreEqual(fullyQualified.getFullyQualifiedName(), typeWithSuperTypes)) { + if (fullyQualifiedNamesAreEqual(fullyQualified.getFullyQualifiedName(), types)) { typeUsed = true; } else if (TypeUtils.fullyQualifiedNamesAreEqual(fullyQualified.getFullyQualifiedName(), owner) || TypeUtils.fullyQualifiedNamesAreEqual(fullyQualified.getPackageName(), owner)) { @@ -139,12 +155,12 @@ public RemoveImport(String type, boolean force) { String typeName = import_.getTypeName(); if (import_.isStatic()) { String imported = import_.getQualid().getSimpleName(); - if (fullyQualifiedNamesAreEqual(typeName + "." + imported, typeWithSuperTypes) && (force || !methodsAndFieldsUsed.contains(imported))) { + if (fullyQualifiedNamesAreEqual(typeName + "." + imported, types) && (force || !methodsAndFieldsUsed.contains(imported))) { // e.g. remove java.util.Collections.emptySet when type is java.util.Collections.emptySet spaceForNextImport.set(import_.getPrefix()); return null; - } else if ("*".equals(imported) && (fullyQualifiedNamesAreEqual(typeName, typeWithSuperTypes) || - fullyQualifiedNamesAreEqual(typeName + type.substring(type.lastIndexOf('.')), typeWithSuperTypes))) { + } else if ("*".equals(imported) && (fullyQualifiedNamesAreEqual(typeName, types) || + fullyQualifiedNamesAreEqual(typeName + type.substring(type.lastIndexOf('.')), types))) { if (methodsAndFieldsUsed.isEmpty() && otherMethodsAndFieldsInTypeUsed.isEmpty()) { spaceForNextImport.set(import_.getPrefix()); return null; @@ -153,12 +169,12 @@ public RemoveImport(String type, boolean force) { methodsAndFieldsUsed.addAll(otherMethodsAndFieldsInTypeUsed); return unfoldStarImport(import_, methodsAndFieldsUsed); } - } else if (fullyQualifiedNamesAreEqual(typeName, typeWithSuperTypes) && !methodsAndFieldsUsed.contains(imported)) { + } else if (fullyQualifiedNamesAreEqual(typeName, types) && !methodsAndFieldsUsed.contains(imported)) { // e.g. remove java.util.Collections.emptySet when type is java.util.Collections spaceForNextImport.set(import_.getPrefix()); return null; } - } else if (!keepImport && fullyQualifiedNamesAreEqual(typeName, typeWithSuperTypes)) { + } else if (!keepImport && fullyQualifiedNamesAreEqual(typeName, types)) { spaceForNextImport.set(import_.getPrefix()); return null; } else if (!keepImport && import_.getPackageName().equals(owner) && 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 ffdb486a54..ac6b6f4ad5 100644 --- a/rewrite-kotlin/src/test/java/org/openrewrite/kotlin/RemoveImportTest.java +++ b/rewrite-kotlin/src/test/java/org/openrewrite/kotlin/RemoveImportTest.java @@ -226,7 +226,6 @@ class A ); } - @Disabled("First fix: https://github.com/openrewrite/rewrite/pull/5862") @Test void dontRemoveUsedParentMembers() { rewriteRun( @@ -267,7 +266,7 @@ fun test() { @Test void keepStarFoldWhenUsingStaticChildAndParentMembersFromJavaClasses() { rewriteRun( - // This kind of setup is only possible in Java, as you cannot use star imports for companion object members + // 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; From 290fb26f47c06fe8c04c3c209f213eaf2bc495e2 Mon Sep 17 00:00:00 2001 From: lingenj Date: Wed, 20 Aug 2025 12:12:18 +0200 Subject: [PATCH 13/17] Add keepStarFoldWhenUsingTopLevelFunctions test --- .../org/openrewrite/java/RemoveImport.java | 8 +++--- .../openrewrite/kotlin/RemoveImportTest.java | 27 +++++++++++++++++++ 2 files changed, 31 insertions(+), 4 deletions(-) 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 17b59dc95f..60d940c3da 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/RemoveImport.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/RemoveImport.java @@ -75,7 +75,7 @@ public RemoveImport(String type, boolean force) { String fqnType = TypeUtils.toFullyQualifiedName(fq.getFullyQualifiedName()); originalImports.add(fqnType); if (isKotlin && type.equals(fqnType)) { - // For Kotlin, the owning class interfaces with methods can be used directly without actually importing those interfaces + // For Kotlin, the owning class interfaces with methods can be used without actually importing those interfaces directly... JavaType.Class owningClass = TypeUtils.asClass(fq.getOwningClass()); if (owningClass != null) { Queue toVisit = new LinkedList<>(owningClass.getInterfaces()); @@ -91,7 +91,7 @@ public RemoveImport(String type, boolean force) { types.add(TypeUtils.toFullyQualifiedName(current.getFullyQualifiedName())); } } - // And there is the option to star imports references of Java sourced superclasses types + // ... and there is the option to star imports references of Java sourced superclasses types while (fq.getSupertype() != null) { fq = fq.getSupertype(); types.add(TypeUtils.toFullyQualifiedName(fq.getFullyQualifiedName())); @@ -202,8 +202,8 @@ public RemoveImport(String type, boolean force) { return j; } - private boolean fullyQualifiedNamesAreEqual(String declaringType, Collection typeWithSuperTypes) { - for (String type : typeWithSuperTypes) { + private boolean fullyQualifiedNamesAreEqual(String declaringType, Collection types) { + for (String type : types) { if (TypeUtils.fullyQualifiedNamesAreEqual(declaringType, type)) { return true; } 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 ac6b6f4ad5..69ea954902 100644 --- a/rewrite-kotlin/src/test/java/org/openrewrite/kotlin/RemoveImportTest.java +++ b/rewrite-kotlin/src/test/java/org/openrewrite/kotlin/RemoveImportTest.java @@ -262,6 +262,33 @@ fun test() { ); } + @Test + void keepStarFoldWhenUsingTopLevelFunctions() { + rewriteRun( + spec -> spec.recipe(removeTypeImportRecipe("org.example.one")), + kotlin( + """ + package org.example + + fun one() = "one" + fun two() = "two" + """ + ), + kotlin( + """ + import org.example.* + + class A { + fun test() { + one() + two() + } + } + """ + ) + ); + } + @Disabled("We cannot use Java sources as dependencies in Kotlin sources yet") @Test void keepStarFoldWhenUsingStaticChildAndParentMembersFromJavaClasses() { From 157fa641671f642ab48c02603efce570d5e4e744 Mon Sep 17 00:00:00 2001 From: lingenj Date: Wed, 20 Aug 2025 15:29:12 +0200 Subject: [PATCH 14/17] Improve test --- .../src/main/java/org/openrewrite/java/RemoveImport.java | 2 ++ .../test/java/org/openrewrite/kotlin/RemoveImportTest.java | 7 +++++-- 2 files changed, 7 insertions(+), 2 deletions(-) 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 60d940c3da..8bc00ec0bd 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/RemoveImport.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/RemoveImport.java @@ -119,6 +119,8 @@ public RemoveImport(String type, boolean force) { } else { otherMethodsAndFieldsInTypeUsed.add(method.getName()); } + } else if (declaringType.endsWith(".kt") || declaringType.endsWith(".kts")) { // Kotlin top level function + // TODO: } } } 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 69ea954902..ab7a2e8aeb 100644 --- a/rewrite-kotlin/src/test/java/org/openrewrite/kotlin/RemoveImportTest.java +++ b/rewrite-kotlin/src/test/java/org/openrewrite/kotlin/RemoveImportTest.java @@ -276,9 +276,12 @@ fun two() = "two" ), kotlin( """ - import org.example.* + package org.example2 - class A { + import org.example.one + import org.example.two + + class Aassss { fun test() { one() two() From 6a0107080d1810523c71b755309791eff46c5f74 Mon Sep 17 00:00:00 2001 From: lingenj Date: Tue, 26 Aug 2025 11:30:38 +0200 Subject: [PATCH 15/17] Implement `keepStarFoldWhenUsingTopLevelFunctions` --- .../java/org/openrewrite/java/RemoveImport.java | 16 ++++++++++++---- .../org/openrewrite/kotlin/RemoveImportTest.java | 2 +- 2 files changed, 13 insertions(+), 5 deletions(-) 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 8bc00ec0bd..efde691f29 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/RemoveImport.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/RemoveImport.java @@ -119,8 +119,16 @@ public RemoveImport(String type, boolean force) { } else { otherMethodsAndFieldsInTypeUsed.add(method.getName()); } - } else if (declaringType.endsWith(".kt") || declaringType.endsWith(".kts")) { // Kotlin top level function - // TODO: + } else if (declaringType.endsWith("Kt") || declaringType.endsWith("Kts")) { // Kotlin top level function + for (JavaType.Method m : method.getDeclaringType().getMethods()) { + if (m.getDeclaringType().getOwningClass() != null) { + String declaringDeclaringType = m.getDeclaringType().getOwningClass().getFullyQualifiedName() + "." + m.getName(); + if (fullyQualifiedNamesAreEqual(declaringDeclaringType, types)) { + methodsAndFieldsUsed.add(method.getName()); + break; + } + } + } } } } @@ -155,8 +163,8 @@ public RemoveImport(String type, boolean force) { } String typeName = import_.getTypeName(); - if (import_.isStatic()) { - String imported = import_.getQualid().getSimpleName(); + String imported = import_.getQualid().getSimpleName(); + if (import_.isStatic() || (isKotlin && !"*".equals(imported))) { if (fullyQualifiedNamesAreEqual(typeName + "." + imported, types) && (force || !methodsAndFieldsUsed.contains(imported))) { // e.g. remove java.util.Collections.emptySet when type is java.util.Collections.emptySet spaceForNextImport.set(import_.getPrefix()); 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 ab7a2e8aeb..e73d2a1622 100644 --- a/rewrite-kotlin/src/test/java/org/openrewrite/kotlin/RemoveImportTest.java +++ b/rewrite-kotlin/src/test/java/org/openrewrite/kotlin/RemoveImportTest.java @@ -77,7 +77,7 @@ class A @Test void removeStarFoldPackage() { rewriteRun( - spec -> spec.recipe(removeTypeImportRecipe("java.io.OutputStream")).expectedCyclesThatMakeChanges(2), + spec -> spec.recipe(removeTypeImportRecipe("java.io.OutputStream")), kotlin( """ import java.io.* From a48a5ced6c4847712a34e420af856c1b556492d7 Mon Sep 17 00:00:00 2001 From: lingenj Date: Thu, 4 Sep 2025 11:00:24 +0200 Subject: [PATCH 16/17] Improve naming --- .../src/test/java/org/openrewrite/kotlin/RemoveImportTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 e73d2a1622..3750c1400c 100644 --- a/rewrite-kotlin/src/test/java/org/openrewrite/kotlin/RemoveImportTest.java +++ b/rewrite-kotlin/src/test/java/org/openrewrite/kotlin/RemoveImportTest.java @@ -263,7 +263,7 @@ fun test() { } @Test - void keepStarFoldWhenUsingTopLevelFunctions() { + void keepImportWhenUsingTopLevelFunctions() { rewriteRun( spec -> spec.recipe(removeTypeImportRecipe("org.example.one")), kotlin( From a79d0c83b40284b0ee441e9e62a66214e92a9b30 Mon Sep 17 00:00:00 2001 From: lingenj Date: Thu, 4 Sep 2025 11:02:06 +0200 Subject: [PATCH 17/17] Improve naming --- .../test/java/org/openrewrite/kotlin/RemoveImportTest.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 3750c1400c..ea150a2d01 100644 --- a/rewrite-kotlin/src/test/java/org/openrewrite/kotlin/RemoveImportTest.java +++ b/rewrite-kotlin/src/test/java/org/openrewrite/kotlin/RemoveImportTest.java @@ -227,7 +227,7 @@ class A } @Test - void dontRemoveUsedParentMembers() { + void keepWhenParentMembersAreUsed() { rewriteRun( spec -> spec.recipe(removeTypeImportRecipe("org.example.Child.Companion.one")), kotlin( @@ -263,7 +263,7 @@ fun test() { } @Test - void keepImportWhenUsingTopLevelFunctions() { + void keepWhenUsingTopLevelFunctions() { rewriteRun( spec -> spec.recipe(removeTypeImportRecipe("org.example.one")), kotlin(