Skip to content
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
/*
* Copyright 2025 the original author or authors.
* <p>
* Licensed under the Moderne Source Available License (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* https://docs.moderne.io/licensing/moderne-source-available-license
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.openrewrite.java.migrate.util;


import lombok.EqualsAndHashCode;
import lombok.Value;
import org.openrewrite.ExecutionContext;
import org.openrewrite.Preconditions;
import org.openrewrite.Recipe;
import org.openrewrite.TreeVisitor;
import org.openrewrite.java.JavaIsoVisitor;
import org.openrewrite.java.search.UsesJavaVersion;
import org.openrewrite.java.tree.J;
import org.openrewrite.java.tree.Statement;

import java.util.List;

@EqualsAndHashCode(callSuper = false)
@Value
public class RelocateSuperCall extends Recipe {

@Override
public String getDisplayName() {
return "Move `super()` after conditionals (Java 25+)";
}

@Override
public String getDescription() {
return "Relocates `super()` calls to take advantage of the early construction context introduced by JEP 513 in Java 25+, allowing statements before constructor calls.";
}

@Override
public TreeVisitor<?, ExecutionContext> getVisitor() {
return Preconditions.check(
new UsesJavaVersion<>(25),
new RelocateSuperCallVisitor());
}

private static class RelocateSuperCallVisitor extends JavaIsoVisitor<ExecutionContext> {

@Override
public J.MethodDeclaration visitMethodDeclaration(J.MethodDeclaration method, ExecutionContext ctx) {
if (!method.isConstructor() || method.getBody() == null) {
return method;
}

List<Statement> statements = method.getBody().getStatements();
if (statements.size() < 2) {
return method;
}

Statement first = statements.get(0);
if (!(first instanceof J.MethodInvocation)) {
return method;
}
J.MethodInvocation methodInvocation = (J.MethodInvocation) first;
if (!"super".equals(methodInvocation.getSimpleName())) {
return method;
}

// Move super() to the end
List<Statement> updated = new java.util.ArrayList<>(statements);
updated.remove(0);
updated.add(methodInvocation);

return method.withBody(method.getBody().withStatements(updated));
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,222 @@
/*
* Copyright 2025 the original author or authors.
* <p>
* Licensed under the Moderne Source Available License (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* https://docs.moderne.io/licensing/moderne-source-available-license
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.openrewrite.java.migrate.util;

import org.openrewrite.DocumentExample;

import org.junit.jupiter.api.Test;
import org.openrewrite.test.RecipeSpec;
import org.openrewrite.test.RewriteTest;

import static org.openrewrite.java.Assertions.java;
import static org.openrewrite.java.Assertions.javaVersion;

class RelocateSuperCallTest implements RewriteTest {

@Override
public void defaults(RecipeSpec spec) {
spec.recipe(new RelocateSuperCall())
.allSources(src -> src.markers(javaVersion(25)));

}

@DocumentExample
@Test
void relocateSuperAfterIf() {
rewriteRun(
java(
"""
class A {
public A(String bar) {
super();
if(bar.equals("test"))
throw new RuntimeException();
}
}
""",
"""
class A {
public A(String bar) {
if(bar.equals("test"))
throw new RuntimeException();
super();
}
}
"""
)
);
}

@Test
void relocateSuperAfterIfStatement() {
rewriteRun(
java(
// language=java
"""
class Person {
final int age;
public Person(int age) {
if (age < 0) {
throw new IllegalArgumentException("Invalid age");
}
this.age = age;
}
}

class Employee extends Person {
public Employee(int age) {
super(age);
if (age < 18 || age > 67) {
throw new IllegalArgumentException("Invalid employee age");
}
}
}
""",
// Expected output
"""
class Person {
final int age;
public Person(int age) {
if (age < 0) {
throw new IllegalArgumentException("Invalid age");
}
this.age = age;
}
}

class Employee extends Person {
public Employee(int age) {
if (age < 18 || age > 67) {
throw new IllegalArgumentException("Invalid employee age");
}
super(age);
}
}
"""
)
);
}

@Test
void relocateSuperWithSafeFieldAssignmentOnly() {
rewriteRun(
java(
"""
class Outer {
class Inner {
int x;
int y = 100;

Inner(int input) {
super();
x = input;
y = 200;
}
}
}
""",
// Note: `y = 200` is illegal under early construction context since `y` has initializer
// So the expected result is same as input if `super()` is already last.
"""
class Outer {
class Inner {
int x;
int y = 100;

Inner(int input) {
x = input;
y = 200;
super();
}
}
}
"""
)
);
}

@Test
void nprelocateSuperInInnerClassIfAlreadyAsLast_withSafeAssignments() {
rewriteRun(
java(
// language=java (before transformation)
"""
class Outer {
class Inner {
int x;
int y;

Inner(int input) {
var tmp = input * 2;
x = tmp;
y = 42;
super();
}
}
}
""",
// // No Change expected as super() valid with JDK25 version
spec -> spec.markers(javaVersion(25))
)
);
}

@Test
void noRelocateSuperAfterIf_givenBelowJDK25Version() {
rewriteRun(
java(
// Input
"""
class A {
public A(String bar) {
super();
if(bar.equals("test"))
throw new RuntimeException();
}
}
""",
// Simulate Java 8 environment
spec -> spec.markers(javaVersion(8))
)
);
}

@Test
void relocateSuperInInnerClass_withSafeAssignments() {
rewriteRun(
java(
// language=java (before transformation)
"""
class Outer {
class Inner {
int x;
int y;

Inner(int input) {
super();
var tmp = input * 2;
x = tmp;
y = 42;
}
}
}
""",
spec -> spec.markers(javaVersion(8))
)
);
}
}

Loading