From 6baf2c056f02a42c818b3aa19311da8d67e64a69 Mon Sep 17 00:00:00 2001 From: martinfrancois Date: Tue, 11 Aug 2026 19:56:43 +0200 Subject: [PATCH] ChangeMethodTargetToStatic: add failing tests for receiver effects lost when dropping the select staticallyImportedFieldReceiverNotChanged pins that deleting a bare statically imported field receiver (INSTANCE.stat() -> B.stat()) loses the class initialization its read triggers per JLS 12.4.1. chainedCallsNotCollapsedWhenArgumentsWouldBeDropped pins that collapsing a chain matched at both calls drops the intermediate call's arguments, so argument() is never evaluated. Both tests are marked @ExpectedToFail as known failing. --- .../java/ChangeMethodTargetToStaticTest.java | 89 +++++++++++++++++++ 1 file changed, 89 insertions(+) diff --git a/rewrite-java-test/src/test/java/org/openrewrite/java/ChangeMethodTargetToStaticTest.java b/rewrite-java-test/src/test/java/org/openrewrite/java/ChangeMethodTargetToStaticTest.java index 78fd3f41a9e..5ce0d65236c 100644 --- a/rewrite-java-test/src/test/java/org/openrewrite/java/ChangeMethodTargetToStaticTest.java +++ b/rewrite-java-test/src/test/java/org/openrewrite/java/ChangeMethodTargetToStaticTest.java @@ -17,6 +17,7 @@ import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; +import org.junitpioneer.jupiter.ExpectedToFail; import org.openrewrite.Issue; import org.openrewrite.test.RewriteTest; @@ -185,6 +186,94 @@ public void test() { ); } + @ExpectedToFail("Deleting the read of a statically imported field also deletes the class initialization of its declaring class (JLS 12.4.1)") + @Test + void staticallyImportedFieldReceiverNotChanged() { + rewriteRun( + spec -> spec.recipe(new ChangeMethodTargetToStatic("a.A stat()", "b.B", null, null, false)), + java( + """ + package a; + public class A { + public void stat() {} + } + """ + ), + java( + """ + package b; + public class B { + public static void stat() {} + } + """ + ), + java( + """ + package holder; + import a.A; + public class Holder { + public static final A INSTANCE = new A(); + static { + System.setProperty("holder.initialized", "true"); + } + } + """ + ), + java( + """ + import static holder.Holder.INSTANCE; + + class C { + public void test() { + INSTANCE.stat(); + } + } + """ + ) + ); + } + + @ExpectedToFail("Collapsing a chain where the pattern matches both calls drops the intermediate call's arguments and their side effects") + @Test + void chainedCallsNotCollapsedWhenArgumentsWouldBeDropped() { + rewriteRun( + spec -> spec.recipe(new ChangeMethodTargetToStatic("a.A *(..)", "b.B", null, null, false)), + java( + """ + package a; + public class A { + public A combine(String s) { return this; } + public String value() { return "x"; } + } + """ + ), + java( + """ + package b; + public class B { + public static B combine(String s) { return null; } + public static String value() { return "x"; } + } + """ + ), + java( + """ + import a.A; + + class C { + String test(A legacy) { + return legacy.combine(argument()).value(); + } + + String argument() { + return "arg"; + } + } + """ + ) + ); + } + @Test void memberReferenceTargetToStatic() { rewriteRun(