From 457b8fe1bfd93c308b9e0a7c1d0165f8df3e32ce Mon Sep 17 00:00:00 2001 From: Tim te Beek Date: Mon, 22 Jun 2026 11:38:42 +0200 Subject: [PATCH 1/5] Add `ExtractSuperConstructorArgument` recipe for JEP 513 Extracts complex `super(..)` arguments (method invocations and object creations) into local variables declared right before the `super(..)` call, now that JEP 513 permits statements before the explicit constructor invocation. The transformation is strictly behavior preserving: argument expressions already evaluate before the super constructor body, and a super argument can never reference the instance under construction. Arguments are extracted in their original left-to-right order; trivial side-effect-free arguments (literals and local/parameter references) are left in place. --- .../lang/ExtractSuperConstructorArgument.java | 224 ++++++++++++ .../META-INF/rewrite/java-version-25.yml | 1 + .../resources/META-INF/rewrite/recipes.csv | 7 +- .../ExtractSuperConstructorArgumentTest.java | 334 ++++++++++++++++++ 4 files changed, 564 insertions(+), 2 deletions(-) create mode 100644 src/main/java/org/openrewrite/java/migrate/lang/ExtractSuperConstructorArgument.java create mode 100644 src/test/java/org/openrewrite/java/migrate/lang/ExtractSuperConstructorArgumentTest.java diff --git a/src/main/java/org/openrewrite/java/migrate/lang/ExtractSuperConstructorArgument.java b/src/main/java/org/openrewrite/java/migrate/lang/ExtractSuperConstructorArgument.java new file mode 100644 index 0000000000..fdca243381 --- /dev/null +++ b/src/main/java/org/openrewrite/java/migrate/lang/ExtractSuperConstructorArgument.java @@ -0,0 +1,224 @@ +/* + * Copyright 2025 the original author or authors. + *

+ * 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 + *

+ * https://docs.moderne.io/licensing/moderne-source-available-license + *

+ * 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.lang; + +import lombok.Getter; +import org.jspecify.annotations.Nullable; +import org.openrewrite.ExecutionContext; +import org.openrewrite.Preconditions; +import org.openrewrite.Recipe; +import org.openrewrite.TreeVisitor; +import org.openrewrite.internal.StringUtils; +import org.openrewrite.java.JavaIsoVisitor; +import org.openrewrite.java.JavaTemplate; +import org.openrewrite.java.VariableNameUtils; +import org.openrewrite.java.search.UsesJavaVersion; +import org.openrewrite.java.tree.Expression; +import org.openrewrite.java.tree.J; +import org.openrewrite.java.tree.JavaType; +import org.openrewrite.java.tree.Statement; +import org.openrewrite.java.tree.TypeUtils; + +import java.util.ArrayList; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; + +import static org.openrewrite.java.VariableNameUtils.GenerationStrategy.INCREMENT_NUMBER; + +public class ExtractSuperConstructorArgument extends Recipe { + + @Getter + final String displayName = "Extract complex `super(..)` arguments into local variables"; + + @Getter + final String description = "[JEP 513](https://openjdk.org/jeps/513) allows statements before an explicit `super(..)` constructor " + + "invocation. When a `super(..)` call computes one of its arguments through a method invocation or object " + + "creation, this recipe extracts the non-trivial arguments into local variables declared right before the " + + "`super(..)` call, surfacing the work done before construction.\n\n" + + "This is a strictly behavior-preserving transformation: argument expressions are already evaluated " + + "before the super constructor body runs, and a super argument can never reference the instance under " + + "construction, so hoisting them into preceding statements changes neither the order of side effects nor " + + "the set of legal references. Arguments are extracted in their original left-to-right order, and trivial " + + "arguments (literals and local variable references, which have no side effects) are left in place. " + + "Statements that follow `super(..)` are deliberately *not* moved, as reordering them relative to the " + + "super constructor's side effects could change behavior."; + + @Override + public TreeVisitor getVisitor() { + return Preconditions.check(new UsesJavaVersion<>(25), new JavaIsoVisitor() { + @Override + public J.MethodDeclaration visitMethodDeclaration(J.MethodDeclaration method, ExecutionContext ctx) { + J.MethodDeclaration md = super.visitMethodDeclaration(method, ctx); + if (!md.isConstructor() || md.getBody() == null) { + return md; + } + + J.MethodInvocation superCall = findExplicitSuperCall(md.getBody().getStatements()); + if (superCall == null || superCall.getMethodType() == null) { + return md; + } + List args = superCall.getArguments(); + List paramTypes = superCall.getMethodType().getParameterTypes(); + List paramNames = superCall.getMethodType().getParameterNames(); + if (paramTypes.size() != args.size()) { + return md; + } + + // Only act when at least one argument actually does work worth surfacing + boolean anyComplex = false; + for (Expression arg : args) { + if (arg instanceof J.MethodInvocation || arg instanceof J.NewClass) { + anyComplex = true; + break; + } + } + if (!anyComplex) { + return md; + } + + // Plan the extraction: everything but trivial, side-effect-free arguments is hoisted, in order + Set usedNames = new LinkedHashSet<>(); + String[] names = new String[args.size()]; + Set imports = new LinkedHashSet<>(); + StringBuilder declarations = new StringBuilder(); + List declarationArgs = new ArrayList<>(); + for (int i = 0; i < args.size(); i++) { + Expression arg = args.get(i); + if (isInlineSafe(arg)) { + continue; + } + String typeName = denotableTypeName(paramTypes.get(i)); + if (typeName == null) { + // Cannot safely name this parameter's type; bail rather than partially extract and risk reordering + return md; + } + String name = uniqueName(baseName(arg, paramNames.get(i)), usedNames); + names[i] = name; + declarations.append(typeName).append(' ').append(name).append(" = #{any()};\n"); + declarationArgs.add(arg); + if (paramTypes.get(i) instanceof JavaType.FullyQualified) { + imports.add(((JavaType.FullyQualified) paramTypes.get(i)).getFullyQualifiedName()); + } + } + + // 1. Declare the extracted arguments right before the `super(..)` call, preserving their order + JavaTemplate.Builder declarationTemplate = JavaTemplate.builder(declarations.toString()).contextSensitive(); + for (String fqn : imports) { + declarationTemplate.imports(fqn); + maybeAddImport(fqn); + } + md = declarationTemplate.build() + .apply(getCursor(), superCall.getCoordinates().before(), declarationArgs.toArray()); + + // 2. Replace the now-redundant arguments with references to the new local variables + StringBuilder argumentList = new StringBuilder(); + List inlineArgs = new ArrayList<>(); + for (int i = 0; i < args.size(); i++) { + if (i > 0) { + argumentList.append(", "); + } + if (names[i] != null) { + argumentList.append(names[i]); + } else { + argumentList.append("#{any()}"); + inlineArgs.add(args.get(i)); + } + } + return (J.MethodDeclaration) new JavaIsoVisitor() { + @Override + public J.MethodInvocation visitMethodInvocation(J.MethodInvocation mi, ExecutionContext ctx2) { + mi = super.visitMethodInvocation(mi, ctx2); + if (isExplicitSuperCall(mi)) { + return JavaTemplate.builder(argumentList.toString()).contextSensitive().build() + .apply(getCursor(), mi.getCoordinates().replaceArguments(), inlineArgs.toArray()); + } + return mi; + } + }.visitNonNull(md, ctx, getCursor().getParentOrThrow()); + } + + private J.@Nullable MethodInvocation findExplicitSuperCall(List statements) { + for (Statement statement : statements) { + if (statement instanceof J.MethodInvocation && isExplicitSuperCall((J.MethodInvocation) statement)) { + return (J.MethodInvocation) statement; + } + } + return null; + } + + private boolean isExplicitSuperCall(J.MethodInvocation mi) { + return mi.getSelect() == null && "super".equals(mi.getSimpleName()); + } + + /** + * An argument is safe to leave in place only if it cannot be observably reordered relative to the + * extracted arguments, i.e. it has no side effects, cannot throw, and cannot trigger class initialization. + * That holds for literals and references to local variables or parameters, but not for field accesses + * (a static field read may trigger class initialization) or any compound expression. + */ + private boolean isInlineSafe(Expression arg) { + if (arg instanceof J.Literal) { + return true; + } + if (arg instanceof J.Identifier) { + JavaType.Variable fieldType = ((J.Identifier) arg).getFieldType(); + return fieldType != null && !(fieldType.getOwner() instanceof JavaType.FullyQualified); + } + return false; + } + + private @Nullable String denotableTypeName(JavaType type) { + if (type instanceof JavaType.Primitive) { + return ((JavaType.Primitive) type).getKeyword(); + } + // Only non-generic class types can be safely named without risking out-of-scope type variables + if (type instanceof JavaType.FullyQualified && !(type instanceof JavaType.Parameterized)) { + return ((JavaType.FullyQualified) type).getClassName().replace('$', '.'); + } + return null; + } + + private String baseName(Expression arg, @Nullable String paramName) { + if (isValidBaseName(paramName)) { + return paramName; + } + if (arg instanceof J.MethodInvocation) { + return ((J.MethodInvocation) arg).getSimpleName(); + } + JavaType.FullyQualified created = TypeUtils.asFullyQualified(arg.getType()); + if (created != null) { + return StringUtils.uncapitalize(created.getClassName()); + } + return "value"; + } + + private boolean isValidBaseName(@Nullable String name) { + return name != null && !name.isEmpty() && Character.isJavaIdentifierStart(name.charAt(0)) && + !"arg0".equals(name); + } + + private String uniqueName(String base, Set usedNames) { + String candidate = VariableNameUtils.generateVariableName(base, getCursor(), INCREMENT_NUMBER); + for (int n = 1; usedNames.contains(candidate); n++) { + candidate = VariableNameUtils.generateVariableName(base + n, getCursor(), INCREMENT_NUMBER); + } + usedNames.add(candidate); + return candidate; + } + }); + } +} diff --git a/src/main/resources/META-INF/rewrite/java-version-25.yml b/src/main/resources/META-INF/rewrite/java-version-25.yml index 8df8f20b03..8bf214bf5e 100644 --- a/src/main/resources/META-INF/rewrite/java-version-25.yml +++ b/src/main/resources/META-INF/rewrite/java-version-25.yml @@ -34,6 +34,7 @@ recipeList: - org.openrewrite.java.migrate.UpgradePluginsForJava25 - org.openrewrite.java.migrate.io.ReplaceSystemOutWithIOPrint - org.openrewrite.java.migrate.lang.MigrateProcessWaitForDuration + - org.openrewrite.java.migrate.lang.ExtractSuperConstructorArgument - org.openrewrite.java.migrate.lang.ReplaceUnusedVariablesWithUnderscore - org.openrewrite.java.migrate.util.MigrateInflaterDeflaterToClose - org.openrewrite.java.migrate.util.MigrateStringReaderToReaderOf diff --git a/src/main/resources/META-INF/rewrite/recipes.csv b/src/main/resources/META-INF/rewrite/recipes.csv index 0f8af09307..0f98b510cf 100644 --- a/src/main/resources/META-INF/rewrite/recipes.csv +++ b/src/main/resources/META-INF/rewrite/recipes.csv @@ -46,7 +46,7 @@ maven,org.openrewrite.recipe:rewrite-migrate-java,org.openrewrite.java.migrate.J maven,org.openrewrite.recipe:rewrite-migrate-java,org.openrewrite.java.migrate.JREThrowableFinalMethods,Rename final method declarations `getSuppressed()` and `addSuppressed(Throwable exception)` in classes that extend `Throwable`,The recipe renames `getSuppressed()` and `addSuppressed(Throwable exception)` methods in classes that extend `java.lang.Throwable` to `myGetSuppressed` and `myAddSuppressed(Throwable)`. These methods were added to Throwable in Java 7 and are marked final which cannot be overridden.,1,,,Modernize,Java,,,Modernize your code to best use the project's current JDK version. Take advantage of newly available APIs and reduce the dependency of your code on third party dependencies where there is equivalent functionality in the Java standard library.,Basic building blocks for transforming Java code.,, maven,org.openrewrite.recipe:rewrite-migrate-java,org.openrewrite.java.migrate.JREWrapperInterface,Add missing `isWrapperFor` and `unwrap` methods,Add method implementations stubs to classes that implement `java.sql.Wrapper`.,3,,,Modernize,Java,,,Modernize your code to best use the project's current JDK version. Take advantage of newly available APIs and reduce the dependency of your code on third party dependencies where there is equivalent functionality in the Java standard library.,Basic building blocks for transforming Java code.,, maven,org.openrewrite.recipe:rewrite-migrate-java,org.openrewrite.java.migrate.Java8toJava11,Migrate to Java 11,"This recipe will apply changes commonly needed when upgrading to Java 11. Specifically, for those applications that are built on Java 8, this recipe will update and add dependencies on J2EE libraries that are no longer directly bundled with the JDK. This recipe will also replace deprecated API with equivalents when there is a clear migration strategy. Build files will also be updated to use Java 11 as the target/source and plugins will be also be upgraded to versions that are compatible with Java 11.",255,,,Modernize,Java,,,Modernize your code to best use the project's current JDK version. Take advantage of newly available APIs and reduce the dependency of your code on third party dependencies where there is equivalent functionality in the Java standard library.,Basic building blocks for transforming Java code.,,"[{""name"":""org.openrewrite.maven.table.MavenMetadataFailures"",""displayName"":""Maven metadata failures"",""instanceName"":""Maven metadata failures"",""description"":""Attempts to resolve maven metadata that failed."",""columns"":[{""name"":""group"",""type"":""String"",""displayName"":""Group id"",""description"":""The groupId of the artifact for which the metadata download failed.""},{""name"":""artifactId"",""type"":""String"",""displayName"":""Artifact id"",""description"":""The artifactId of the artifact for which the metadata download failed.""},{""name"":""version"",""type"":""String"",""displayName"":""Version"",""description"":""The version of the artifact for which the metadata download failed.""},{""name"":""mavenRepositoryUri"",""type"":""String"",""displayName"":""Maven repository"",""description"":""The URL of the Maven repository that the metadata download failed on.""},{""name"":""snapshots"",""type"":""String"",""displayName"":""Snapshots"",""description"":""Does the repository support snapshots.""},{""name"":""releases"",""type"":""String"",""displayName"":""Releases"",""description"":""Does the repository support releases.""},{""name"":""failure"",""type"":""String"",""displayName"":""Failure"",""description"":""The reason the metadata download failed.""}]}]" -maven,org.openrewrite.recipe:rewrite-migrate-java,org.openrewrite.java.migrate.JavaBestPractices,Java best practices,"Applies opinionated best practices for Java projects targeting Java 25. This recipe includes the full Java 25 upgrade chain plus additional improvements to code style, API usage, and third-party dependency reduction that go beyond what the version migration recipes apply.",1504,,,Modernize,Java,,,Modernize your code to best use the project's current JDK version. Take advantage of newly available APIs and reduce the dependency of your code on third party dependencies where there is equivalent functionality in the Java standard library.,Basic building blocks for transforming Java code.,,"[{""name"":""org.openrewrite.maven.table.MavenMetadataFailures"",""displayName"":""Maven metadata failures"",""instanceName"":""Maven metadata failures"",""description"":""Attempts to resolve maven metadata that failed."",""columns"":[{""name"":""group"",""type"":""String"",""displayName"":""Group id"",""description"":""The groupId of the artifact for which the metadata download failed.""},{""name"":""artifactId"",""type"":""String"",""displayName"":""Artifact id"",""description"":""The artifactId of the artifact for which the metadata download failed.""},{""name"":""version"",""type"":""String"",""displayName"":""Version"",""description"":""The version of the artifact for which the metadata download failed.""},{""name"":""mavenRepositoryUri"",""type"":""String"",""displayName"":""Maven repository"",""description"":""The URL of the Maven repository that the metadata download failed on.""},{""name"":""snapshots"",""type"":""String"",""displayName"":""Snapshots"",""description"":""Does the repository support snapshots.""},{""name"":""releases"",""type"":""String"",""displayName"":""Releases"",""description"":""Does the repository support releases.""},{""name"":""failure"",""type"":""String"",""displayName"":""Failure"",""description"":""The reason the metadata download failed.""}]}]" +maven,org.openrewrite.recipe:rewrite-migrate-java,org.openrewrite.java.migrate.JavaBestPractices,Java best practices,"Applies opinionated best practices for Java projects targeting Java 25. This recipe includes the full Java 25 upgrade chain plus additional improvements to code style, API usage, and third-party dependency reduction that go beyond what the version migration recipes apply.",1505,,,Modernize,Java,,,Modernize your code to best use the project's current JDK version. Take advantage of newly available APIs and reduce the dependency of your code on third party dependencies where there is equivalent functionality in the Java standard library.,Basic building blocks for transforming Java code.,,"[{""name"":""org.openrewrite.maven.table.MavenMetadataFailures"",""displayName"":""Maven metadata failures"",""instanceName"":""Maven metadata failures"",""description"":""Attempts to resolve maven metadata that failed."",""columns"":[{""name"":""group"",""type"":""String"",""displayName"":""Group id"",""description"":""The groupId of the artifact for which the metadata download failed.""},{""name"":""artifactId"",""type"":""String"",""displayName"":""Artifact id"",""description"":""The artifactId of the artifact for which the metadata download failed.""},{""name"":""version"",""type"":""String"",""displayName"":""Version"",""description"":""The version of the artifact for which the metadata download failed.""},{""name"":""mavenRepositoryUri"",""type"":""String"",""displayName"":""Maven repository"",""description"":""The URL of the Maven repository that the metadata download failed on.""},{""name"":""snapshots"",""type"":""String"",""displayName"":""Snapshots"",""description"":""Does the repository support snapshots.""},{""name"":""releases"",""type"":""String"",""displayName"":""Releases"",""description"":""Does the repository support releases.""},{""name"":""failure"",""type"":""String"",""displayName"":""Failure"",""description"":""The reason the metadata download failed.""}]}]" maven,org.openrewrite.recipe:rewrite-migrate-java,org.openrewrite.java.migrate.JpaCacheProperties,Disable the persistence unit second-level cache,Sets an explicit value for the shared cache mode.,1,,,Modernize,Java,,,Modernize your code to best use the project's current JDK version. Take advantage of newly available APIs and reduce the dependency of your code on third party dependencies where there is equivalent functionality in the Java standard library.,Basic building blocks for transforming Java code.,, maven,org.openrewrite.recipe:rewrite-migrate-java,org.openrewrite.java.migrate.Jre17AgentMainPreMainPublic,Set visibility of `premain` and `agentmain` methods to `public`,Check for a behavior change in Java agents.,5,,,Modernize,Java,,,Modernize your code to best use the project's current JDK version. Take advantage of newly available APIs and reduce the dependency of your code on third party dependencies where there is equivalent functionality in the Java standard library.,Basic building blocks for transforming Java code.,, maven,org.openrewrite.recipe:rewrite-migrate-java,org.openrewrite.java.migrate.Krb5LoginModuleClass,Use `com.sun.security.auth.module.Krb5LoginModule` instead of `com.ibm.security.auth.module.Krb5LoginModule`,Do not use the `com.ibm.security.auth.module.Krb5LoginModule` class.,2,,,Modernize,Java,,,Modernize your code to best use the project's current JDK version. Take advantage of newly available APIs and reduce the dependency of your code on third party dependencies where there is equivalent functionality in the Java standard library.,Basic building blocks for transforming Java code.,, @@ -95,7 +95,7 @@ maven,org.openrewrite.recipe:rewrite-migrate-java,org.openrewrite.java.migrate.U maven,org.openrewrite.recipe:rewrite-migrate-java,org.openrewrite.java.migrate.UpgradePluginsForJava25,Upgrade plugins to Java 25 compatible versions,Updates plugins and dependencies to versions compatible with Java 25.,9,,,Modernize,Java,,,Modernize your code to best use the project's current JDK version. Take advantage of newly available APIs and reduce the dependency of your code on third party dependencies where there is equivalent functionality in the Java standard library.,Basic building blocks for transforming Java code.,,"[{""name"":""org.openrewrite.maven.table.MavenMetadataFailures"",""displayName"":""Maven metadata failures"",""instanceName"":""Maven metadata failures"",""description"":""Attempts to resolve maven metadata that failed."",""columns"":[{""name"":""group"",""type"":""String"",""displayName"":""Group id"",""description"":""The groupId of the artifact for which the metadata download failed.""},{""name"":""artifactId"",""type"":""String"",""displayName"":""Artifact id"",""description"":""The artifactId of the artifact for which the metadata download failed.""},{""name"":""version"",""type"":""String"",""displayName"":""Version"",""description"":""The version of the artifact for which the metadata download failed.""},{""name"":""mavenRepositoryUri"",""type"":""String"",""displayName"":""Maven repository"",""description"":""The URL of the Maven repository that the metadata download failed on.""},{""name"":""snapshots"",""type"":""String"",""displayName"":""Snapshots"",""description"":""Does the repository support snapshots.""},{""name"":""releases"",""type"":""String"",""displayName"":""Releases"",""description"":""Does the repository support releases.""},{""name"":""failure"",""type"":""String"",""displayName"":""Failure"",""description"":""The reason the metadata download failed.""}]}]" maven,org.openrewrite.recipe:rewrite-migrate-java,org.openrewrite.java.migrate.UpgradeToJava17,Migrate to Java 17,"This recipe will apply changes commonly needed when migrating to Java 17. Specifically, for those applications that are built on Java 8, this recipe will update and add dependencies on J2EE libraries that are no longer directly bundled with the JDK. This recipe will also replace deprecated API with equivalents when there is a clear migration strategy. Build files will also be updated to use Java 17 as the target/source and plugins will be also be upgraded to versions that are compatible with Java 17.",416,,,Modernize,Java,,,Modernize your code to best use the project's current JDK version. Take advantage of newly available APIs and reduce the dependency of your code on third party dependencies where there is equivalent functionality in the Java standard library.,Basic building blocks for transforming Java code.,,"[{""name"":""org.openrewrite.maven.table.MavenMetadataFailures"",""displayName"":""Maven metadata failures"",""instanceName"":""Maven metadata failures"",""description"":""Attempts to resolve maven metadata that failed."",""columns"":[{""name"":""group"",""type"":""String"",""displayName"":""Group id"",""description"":""The groupId of the artifact for which the metadata download failed.""},{""name"":""artifactId"",""type"":""String"",""displayName"":""Artifact id"",""description"":""The artifactId of the artifact for which the metadata download failed.""},{""name"":""version"",""type"":""String"",""displayName"":""Version"",""description"":""The version of the artifact for which the metadata download failed.""},{""name"":""mavenRepositoryUri"",""type"":""String"",""displayName"":""Maven repository"",""description"":""The URL of the Maven repository that the metadata download failed on.""},{""name"":""snapshots"",""type"":""String"",""displayName"":""Snapshots"",""description"":""Does the repository support snapshots.""},{""name"":""releases"",""type"":""String"",""displayName"":""Releases"",""description"":""Does the repository support releases.""},{""name"":""failure"",""type"":""String"",""displayName"":""Failure"",""description"":""The reason the metadata download failed.""}]}]" maven,org.openrewrite.recipe:rewrite-migrate-java,org.openrewrite.java.migrate.UpgradeToJava21,Migrate to Java 21,This recipe will apply changes commonly needed when migrating to Java 21. This recipe will also replace deprecated API with equivalents when there is a clear migration strategy. Build files will also be updated to use Java 21 as the target/source and plugins will be also be upgraded to versions that are compatible with Java 21.,592,,,Modernize,Java,,,Modernize your code to best use the project's current JDK version. Take advantage of newly available APIs and reduce the dependency of your code on third party dependencies where there is equivalent functionality in the Java standard library.,Basic building blocks for transforming Java code.,,"[{""name"":""org.openrewrite.maven.table.MavenMetadataFailures"",""displayName"":""Maven metadata failures"",""instanceName"":""Maven metadata failures"",""description"":""Attempts to resolve maven metadata that failed."",""columns"":[{""name"":""group"",""type"":""String"",""displayName"":""Group id"",""description"":""The groupId of the artifact for which the metadata download failed.""},{""name"":""artifactId"",""type"":""String"",""displayName"":""Artifact id"",""description"":""The artifactId of the artifact for which the metadata download failed.""},{""name"":""version"",""type"":""String"",""displayName"":""Version"",""description"":""The version of the artifact for which the metadata download failed.""},{""name"":""mavenRepositoryUri"",""type"":""String"",""displayName"":""Maven repository"",""description"":""The URL of the Maven repository that the metadata download failed on.""},{""name"":""snapshots"",""type"":""String"",""displayName"":""Snapshots"",""description"":""Does the repository support snapshots.""},{""name"":""releases"",""type"":""String"",""displayName"":""Releases"",""description"":""Does the repository support releases.""},{""name"":""failure"",""type"":""String"",""displayName"":""Failure"",""description"":""The reason the metadata download failed.""}]}]" -maven,org.openrewrite.recipe:rewrite-migrate-java,org.openrewrite.java.migrate.UpgradeToJava25,Migrate to Java 25,This recipe will apply changes commonly needed when migrating to Java 25. This recipe will also replace deprecated API with equivalents when there is a clear migration strategy. Build files will also be updated to use Java 25 as the target/source and plugins will be also be upgraded to versions that are compatible with Java 25.,1154,,,Modernize,Java,,,Modernize your code to best use the project's current JDK version. Take advantage of newly available APIs and reduce the dependency of your code on third party dependencies where there is equivalent functionality in the Java standard library.,Basic building blocks for transforming Java code.,,"[{""name"":""org.openrewrite.maven.table.MavenMetadataFailures"",""displayName"":""Maven metadata failures"",""instanceName"":""Maven metadata failures"",""description"":""Attempts to resolve maven metadata that failed."",""columns"":[{""name"":""group"",""type"":""String"",""displayName"":""Group id"",""description"":""The groupId of the artifact for which the metadata download failed.""},{""name"":""artifactId"",""type"":""String"",""displayName"":""Artifact id"",""description"":""The artifactId of the artifact for which the metadata download failed.""},{""name"":""version"",""type"":""String"",""displayName"":""Version"",""description"":""The version of the artifact for which the metadata download failed.""},{""name"":""mavenRepositoryUri"",""type"":""String"",""displayName"":""Maven repository"",""description"":""The URL of the Maven repository that the metadata download failed on.""},{""name"":""snapshots"",""type"":""String"",""displayName"":""Snapshots"",""description"":""Does the repository support snapshots.""},{""name"":""releases"",""type"":""String"",""displayName"":""Releases"",""description"":""Does the repository support releases.""},{""name"":""failure"",""type"":""String"",""displayName"":""Failure"",""description"":""The reason the metadata download failed.""}]}]" +maven,org.openrewrite.recipe:rewrite-migrate-java,org.openrewrite.java.migrate.UpgradeToJava25,Migrate to Java 25,This recipe will apply changes commonly needed when migrating to Java 25. This recipe will also replace deprecated API with equivalents when there is a clear migration strategy. Build files will also be updated to use Java 25 as the target/source and plugins will be also be upgraded to versions that are compatible with Java 25.,1155,,,Modernize,Java,,,Modernize your code to best use the project's current JDK version. Take advantage of newly available APIs and reduce the dependency of your code on third party dependencies where there is equivalent functionality in the Java standard library.,Basic building blocks for transforming Java code.,,"[{""name"":""org.openrewrite.maven.table.MavenMetadataFailures"",""displayName"":""Maven metadata failures"",""instanceName"":""Maven metadata failures"",""description"":""Attempts to resolve maven metadata that failed."",""columns"":[{""name"":""group"",""type"":""String"",""displayName"":""Group id"",""description"":""The groupId of the artifact for which the metadata download failed.""},{""name"":""artifactId"",""type"":""String"",""displayName"":""Artifact id"",""description"":""The artifactId of the artifact for which the metadata download failed.""},{""name"":""version"",""type"":""String"",""displayName"":""Version"",""description"":""The version of the artifact for which the metadata download failed.""},{""name"":""mavenRepositoryUri"",""type"":""String"",""displayName"":""Maven repository"",""description"":""The URL of the Maven repository that the metadata download failed on.""},{""name"":""snapshots"",""type"":""String"",""displayName"":""Snapshots"",""description"":""Does the repository support snapshots.""},{""name"":""releases"",""type"":""String"",""displayName"":""Releases"",""description"":""Does the repository support releases.""},{""name"":""failure"",""type"":""String"",""displayName"":""Failure"",""description"":""The reason the metadata download failed.""}]}]" maven,org.openrewrite.recipe:rewrite-migrate-java,org.openrewrite.java.migrate.UpgradeToJava6,Migrate to Java 6,This recipe will apply changes commonly needed when upgrading to Java 6. This recipe will also replace deprecated API with equivalents when there is a clear migration strategy.,7,,,Modernize,Java,,,Modernize your code to best use the project's current JDK version. Take advantage of newly available APIs and reduce the dependency of your code on third party dependencies where there is equivalent functionality in the Java standard library.,Basic building blocks for transforming Java code.,,"[{""name"":""org.openrewrite.maven.table.MavenMetadataFailures"",""displayName"":""Maven metadata failures"",""instanceName"":""Maven metadata failures"",""description"":""Attempts to resolve maven metadata that failed."",""columns"":[{""name"":""group"",""type"":""String"",""displayName"":""Group id"",""description"":""The groupId of the artifact for which the metadata download failed.""},{""name"":""artifactId"",""type"":""String"",""displayName"":""Artifact id"",""description"":""The artifactId of the artifact for which the metadata download failed.""},{""name"":""version"",""type"":""String"",""displayName"":""Version"",""description"":""The version of the artifact for which the metadata download failed.""},{""name"":""mavenRepositoryUri"",""type"":""String"",""displayName"":""Maven repository"",""description"":""The URL of the Maven repository that the metadata download failed on.""},{""name"":""snapshots"",""type"":""String"",""displayName"":""Snapshots"",""description"":""Does the repository support snapshots.""},{""name"":""releases"",""type"":""String"",""displayName"":""Releases"",""description"":""Does the repository support releases.""},{""name"":""failure"",""type"":""String"",""displayName"":""Failure"",""description"":""The reason the metadata download failed.""}]}]" maven,org.openrewrite.recipe:rewrite-migrate-java,org.openrewrite.java.migrate.UpgradeToJava7,Migrate to Java 7,This recipe will apply changes commonly needed when upgrading to Java 7. This recipe will also replace deprecated API with equivalents when there is a clear migration strategy.,28,,,Modernize,Java,,,Modernize your code to best use the project's current JDK version. Take advantage of newly available APIs and reduce the dependency of your code on third party dependencies where there is equivalent functionality in the Java standard library.,Basic building blocks for transforming Java code.,,"[{""name"":""org.openrewrite.maven.table.MavenMetadataFailures"",""displayName"":""Maven metadata failures"",""instanceName"":""Maven metadata failures"",""description"":""Attempts to resolve maven metadata that failed."",""columns"":[{""name"":""group"",""type"":""String"",""displayName"":""Group id"",""description"":""The groupId of the artifact for which the metadata download failed.""},{""name"":""artifactId"",""type"":""String"",""displayName"":""Artifact id"",""description"":""The artifactId of the artifact for which the metadata download failed.""},{""name"":""version"",""type"":""String"",""displayName"":""Version"",""description"":""The version of the artifact for which the metadata download failed.""},{""name"":""mavenRepositoryUri"",""type"":""String"",""displayName"":""Maven repository"",""description"":""The URL of the Maven repository that the metadata download failed on.""},{""name"":""snapshots"",""type"":""String"",""displayName"":""Snapshots"",""description"":""Does the repository support snapshots.""},{""name"":""releases"",""type"":""String"",""displayName"":""Releases"",""description"":""Does the repository support releases.""},{""name"":""failure"",""type"":""String"",""displayName"":""Failure"",""description"":""The reason the metadata download failed.""}]}]" maven,org.openrewrite.recipe:rewrite-migrate-java,org.openrewrite.java.migrate.UpgradeToJava8,Migrate to Java 8,This recipe will apply changes commonly needed when upgrading to Java 8. This recipe will also replace deprecated API with equivalents when there is a clear migration strategy.,54,,,Modernize,Java,,,Modernize your code to best use the project's current JDK version. Take advantage of newly available APIs and reduce the dependency of your code on third party dependencies where there is equivalent functionality in the Java standard library.,Basic building blocks for transforming Java code.,,"[{""name"":""org.openrewrite.maven.table.MavenMetadataFailures"",""displayName"":""Maven metadata failures"",""instanceName"":""Maven metadata failures"",""description"":""Attempts to resolve maven metadata that failed."",""columns"":[{""name"":""group"",""type"":""String"",""displayName"":""Group id"",""description"":""The groupId of the artifact for which the metadata download failed.""},{""name"":""artifactId"",""type"":""String"",""displayName"":""Artifact id"",""description"":""The artifactId of the artifact for which the metadata download failed.""},{""name"":""version"",""type"":""String"",""displayName"":""Version"",""description"":""The version of the artifact for which the metadata download failed.""},{""name"":""mavenRepositoryUri"",""type"":""String"",""displayName"":""Maven repository"",""description"":""The URL of the Maven repository that the metadata download failed on.""},{""name"":""snapshots"",""type"":""String"",""displayName"":""Snapshots"",""description"":""Does the repository support snapshots.""},{""name"":""releases"",""type"":""String"",""displayName"":""Releases"",""description"":""Does the repository support releases.""},{""name"":""failure"",""type"":""String"",""displayName"":""Failure"",""description"":""The reason the metadata download failed.""}]}]" @@ -350,6 +350,9 @@ maven,org.openrewrite.recipe:rewrite-migrate-java,org.openrewrite.java.migrate.j maven,org.openrewrite.recipe:rewrite-migrate-java,org.openrewrite.java.migrate.javax.openJPAToEclipseLink,Migrate from OpenJPA to EclipseLink JPA,These recipes help migrate Java Persistence applications using OpenJPA to EclipseLink JPA.,10,,`javax` APIs,Modernize,Java,,,Modernize your code to best use the project's current JDK version. Take advantage of newly available APIs and reduce the dependency of your code on third party dependencies where there is equivalent functionality in the Java standard library.,Basic building blocks for transforming Java code.,, maven,org.openrewrite.recipe:rewrite-migrate-java,org.openrewrite.java.migrate.jspecify.MoveAnnotationToArrayType,Move annotation to array type,"When an annotation like `@Nullable` is applied to an array type in declaration position, this recipe moves it to the array brackets. For example, `@Nullable byte[]` becomes `byte @Nullable[]`. Best used before `ChangeType` in a migration pipeline, targeting the pre-migration annotation type.",1,,Jspecify,Modernize,Java,,,Modernize your code to best use the project's current JDK version. Take advantage of newly available APIs and reduce the dependency of your code on third party dependencies where there is equivalent functionality in the Java standard library.,Basic building blocks for transforming Java code.,"[{""name"":""annotationType"",""type"":""String"",""displayName"":""Annotation type"",""description"":""The type of annotation to move to the array type. Should target the pre-migration annotation type to avoid changing the semantics of pre-existing type-use annotations on object arrays."",""example"":""javax.annotation.*ull*"",""required"":true}]", maven,org.openrewrite.recipe:rewrite-migrate-java,org.openrewrite.java.migrate.lang.ExplicitRecordImport,Add explicit import for `Record` classes,"Add explicit import for `Record` classes when upgrading past Java 14+, to avoid conflicts with `java.lang.Record`.",1,,`java.lang` APIs,Modernize,Java,,,Modernize your code to best use the project's current JDK version. Take advantage of newly available APIs and reduce the dependency of your code on third party dependencies where there is equivalent functionality in the Java standard library.,Basic building blocks for transforming Java code.,, +maven,org.openrewrite.recipe:rewrite-migrate-java,org.openrewrite.java.migrate.lang.ExtractSuperConstructorArgument,Extract complex `super(..)` arguments into local variables,"[JEP 513](https://openjdk.org/jeps/513) allows statements before an explicit `super(..)` constructor invocation. When a `super(..)` call computes one of its arguments through a method invocation or object creation, this recipe extracts the non-trivial arguments into local variables declared right before the `super(..)` call, surfacing the work done before construction. + +This is a strictly behavior-preserving transformation: argument expressions are already evaluated before the super constructor body runs, and a super argument can never reference the instance under construction, so hoisting them into preceding statements changes neither the order of side effects nor the set of legal references. Arguments are extracted in their original left-to-right order, and trivial arguments (literals and local variable references, which have no side effects) are left in place. Statements that follow `super(..)` are deliberately *not* moved, as reordering them relative to the super constructor's side effects could change behavior.",1,,`java.lang` APIs,Modernize,Java,,,Modernize your code to best use the project's current JDK version. Take advantage of newly available APIs and reduce the dependency of your code on third party dependencies where there is equivalent functionality in the Java standard library.,Basic building blocks for transforming Java code.,, maven,org.openrewrite.recipe:rewrite-migrate-java,org.openrewrite.java.migrate.lang.FindNonVirtualExecutors,Find non-virtual `ExecutorService` creation,Find all places where static `java.util.concurrent.Executors` method creates a non-virtual `java.util.concurrent.ExecutorService`. This recipe can be used to search fro `ExecutorService` that can be replaced by Virtual Thread executor.,7,,`java.lang` APIs,Modernize,Java,,,Modernize your code to best use the project's current JDK version. Take advantage of newly available APIs and reduce the dependency of your code on third party dependencies where there is equivalent functionality in the Java standard library.,Basic building blocks for transforming Java code.,,"[{""name"":""org.openrewrite.java.table.MethodCalls"",""displayName"":""Method calls"",""instanceName"":""Method calls"",""description"":""The text of matching method invocations."",""columns"":[{""name"":""sourceFile"",""type"":""String"",""displayName"":""Source file"",""description"":""The source file that the method call occurred in.""},{""name"":""method"",""type"":""String"",""displayName"":""Method call"",""description"":""The text of the method call.""},{""name"":""className"",""type"":""String"",""displayName"":""Class name"",""description"":""The class name of the method call.""},{""name"":""methodName"",""type"":""String"",""displayName"":""Method name"",""description"":""The method name of the method call.""},{""name"":""argumentTypes"",""type"":""String"",""displayName"":""Argument types"",""description"":""The argument types of the method call.""}]}]" maven,org.openrewrite.recipe:rewrite-migrate-java,org.openrewrite.java.migrate.lang.FindVirtualThreadOpportunities,Find Virtual Thread opportunities,Find opportunities to convert existing code to use Virtual Threads.,10,,`java.lang` APIs,Modernize,Java,,,Modernize your code to best use the project's current JDK version. Take advantage of newly available APIs and reduce the dependency of your code on third party dependencies where there is equivalent functionality in the Java standard library.,Basic building blocks for transforming Java code.,,"[{""name"":""org.openrewrite.java.table.MethodCalls"",""displayName"":""Method calls"",""instanceName"":""Method calls"",""description"":""The text of matching method invocations."",""columns"":[{""name"":""sourceFile"",""type"":""String"",""displayName"":""Source file"",""description"":""The source file that the method call occurred in.""},{""name"":""method"",""type"":""String"",""displayName"":""Method call"",""description"":""The text of the method call.""},{""name"":""className"",""type"":""String"",""displayName"":""Class name"",""description"":""The class name of the method call.""},{""name"":""methodName"",""type"":""String"",""displayName"":""Method name"",""description"":""The method name of the method call.""},{""name"":""argumentTypes"",""type"":""String"",""displayName"":""Argument types"",""description"":""The argument types of the method call.""}]}]" maven,org.openrewrite.recipe:rewrite-migrate-java,org.openrewrite.java.migrate.lang.IfElseIfConstructToSwitch,If-else-if-else to switch,"Replace if-else-if-else with switch statements. In order to be replaced with a switch, all conditions must be on the same variable and there must be at least three cases.",1,,`java.lang` APIs,Modernize,Java,,,Modernize your code to best use the project's current JDK version. Take advantage of newly available APIs and reduce the dependency of your code on third party dependencies where there is equivalent functionality in the Java standard library.,Basic building blocks for transforming Java code.,, diff --git a/src/test/java/org/openrewrite/java/migrate/lang/ExtractSuperConstructorArgumentTest.java b/src/test/java/org/openrewrite/java/migrate/lang/ExtractSuperConstructorArgumentTest.java new file mode 100644 index 0000000000..d965ece4c4 --- /dev/null +++ b/src/test/java/org/openrewrite/java/migrate/lang/ExtractSuperConstructorArgumentTest.java @@ -0,0 +1,334 @@ +/* + * Copyright 2025 the original author or authors. + *

+ * 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 + *

+ * https://docs.moderne.io/licensing/moderne-source-available-license + *

+ * 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.lang; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.condition.EnabledForJreRange; +import org.openrewrite.DocumentExample; +import org.openrewrite.test.RecipeSpec; +import org.openrewrite.test.RewriteTest; + +import static org.junit.jupiter.api.condition.JRE.JAVA_25; +import static org.openrewrite.java.Assertions.java; +import static org.openrewrite.java.Assertions.javaVersion; + +@EnabledForJreRange(min = JAVA_25) +class ExtractSuperConstructorArgumentTest implements RewriteTest { + + @Override + public void defaults(RecipeSpec spec) { + spec + .allSources(src -> src.markers(javaVersion(25))) + .recipe(new ExtractSuperConstructorArgument()); + } + + @DocumentExample + @Test + void extractMethodInvocationArgument() { + rewriteRun( + //language=java + java( + """ + import java.util.Objects; + + class Parent { + Parent(String name) { + } + } + + class Child extends Parent { + Child(String name) { + super(Objects.requireNonNull(name)); + } + } + """, + """ + import java.util.Objects; + + class Parent { + Parent(String name) { + } + } + + class Child extends Parent { + Child(String name) { + String name1 = Objects.requireNonNull(name); + super(name1); + } + } + """ + ) + ); + } + + @Test + void extractObjectCreationArgument() { + rewriteRun( + //language=java + java( + """ + class Holder { + Holder(StringBuilder sb) { + } + } + + class Child extends Holder { + Child(String value) { + super(new StringBuilder(value)); + } + } + """, + """ + class Holder { + Holder(StringBuilder sb) { + } + } + + class Child extends Holder { + Child(String value) { + StringBuilder sb = new StringBuilder(value); + super(sb); + } + } + """ + ) + ); + } + + @Test + void extractPrimitiveReturningInvocation() { + rewriteRun( + //language=java + java( + """ + class Parent { + Parent(int value) { + } + } + + class Child extends Parent { + Child(String value) { + super(Integer.parseInt(value)); + } + } + """, + """ + class Parent { + Parent(int value) { + } + } + + class Child extends Parent { + Child(String value) { + int value1 = Integer.parseInt(value); + super(value1); + } + } + """ + ) + ); + } + + @Test + void doNotExtractSimpleIdentifier() { + rewriteRun( + //language=java + java( + """ + class Parent { + Parent(String name) { + } + } + + class Child extends Parent { + Child(String name) { + super(name); + } + } + """ + ) + ); + } + + @Test + void doNotExtractLiteral() { + rewriteRun( + //language=java + java( + """ + class Parent { + Parent(String name) { + } + } + + class Child extends Parent { + Child() { + super("constant"); + } + } + """ + ) + ); + } + + @Test + void doNotChangeImplicitSuper() { + rewriteRun( + //language=java + java( + """ + class Child { + Child(String name) { + System.out.println(name); + } + } + """ + ) + ); + } + + @Test + void extractMultipleArgumentsInOrder() { + rewriteRun( + //language=java + java( + """ + class Parent { + Parent(String a, String b) { + } + } + + class Child extends Parent { + Child(String value) { + super(value.trim(), value.strip()); + } + } + """, + """ + class Parent { + Parent(String a, String b) { + } + } + + class Child extends Parent { + Child(String value) { + String a = value.trim(); + String b = value.strip(); + super(a, b); + } + } + """ + ) + ); + } + + @Test + void leaveTrivialArgumentsInlineWhenExtractingSiblings() { + rewriteRun( + //language=java + java( + """ + class Parent { + Parent(String first, int second, String third) { + } + } + + class Child extends Parent { + Child(String name) { + super(name, Integer.parseInt(name), "literal"); + } + } + """, + """ + class Parent { + Parent(String first, int second, String third) { + } + } + + class Child extends Parent { + Child(String name) { + int second = Integer.parseInt(name); + super(name, second, "literal"); + } + } + """ + ) + ); + } + + @Test + void doNotExtractWhenNoArgumentDoesWork() { + rewriteRun( + //language=java + java( + """ + class Parent { + Parent(String a, String b) { + } + } + + class Child extends Parent { + Child(String value) { + super(value, value); + } + } + """ + ) + ); + } + + @Test + void doNotExtractGenericParameterType() { + rewriteRun( + //language=java + java( + """ + import java.util.List; + + class Parent { + Parent(List values) { + } + } + + class Child extends Parent { + Child() { + super(java.util.Collections.singletonList("a")); + } + } + """ + ) + ); + } + + @Test + void leaveThisDelegationAlone() { + rewriteRun( + //language=java + java( + """ + class Child { + Child(int value) { + } + + Child(String value) { + this(Integer.parseInt(value)); + } + } + """ + ) + ); + } +} From a5396d587a9e1e3ee74ebc31a9dd0bc85db145c1 Mon Sep 17 00:00:00 2001 From: Tim te Beek Date: Mon, 22 Jun 2026 11:46:30 +0200 Subject: [PATCH 2/5] Simplify recipe: single-pass extraction plan and robust synthetic-name check --- .../lang/ExtractSuperConstructorArgument.java | 35 +++++++++---------- 1 file changed, 16 insertions(+), 19 deletions(-) diff --git a/src/main/java/org/openrewrite/java/migrate/lang/ExtractSuperConstructorArgument.java b/src/main/java/org/openrewrite/java/migrate/lang/ExtractSuperConstructorArgument.java index fdca243381..fc807feae0 100644 --- a/src/main/java/org/openrewrite/java/migrate/lang/ExtractSuperConstructorArgument.java +++ b/src/main/java/org/openrewrite/java/migrate/lang/ExtractSuperConstructorArgument.java @@ -71,37 +71,29 @@ public J.MethodDeclaration visitMethodDeclaration(J.MethodDeclaration method, Ex if (superCall == null || superCall.getMethodType() == null) { return md; } + JavaType.Method ctorType = superCall.getMethodType(); List args = superCall.getArguments(); - List paramTypes = superCall.getMethodType().getParameterTypes(); - List paramNames = superCall.getMethodType().getParameterNames(); + List paramTypes = ctorType.getParameterTypes(); + List paramNames = ctorType.getParameterNames(); if (paramTypes.size() != args.size()) { return md; } - // Only act when at least one argument actually does work worth surfacing - boolean anyComplex = false; - for (Expression arg : args) { - if (arg instanceof J.MethodInvocation || arg instanceof J.NewClass) { - anyComplex = true; - break; - } - } - if (!anyComplex) { - return md; - } - - // Plan the extraction: everything but trivial, side-effect-free arguments is hoisted, in order + // Plan the extraction: hoist everything but trivial, side-effect-free arguments, in order. + // Only act when at least one extracted argument actually does work worth surfacing. Set usedNames = new LinkedHashSet<>(); String[] names = new String[args.size()]; Set imports = new LinkedHashSet<>(); StringBuilder declarations = new StringBuilder(); List declarationArgs = new ArrayList<>(); + boolean anyComplex = false; for (int i = 0; i < args.size(); i++) { Expression arg = args.get(i); if (isInlineSafe(arg)) { continue; } - String typeName = denotableTypeName(paramTypes.get(i)); + JavaType paramType = paramTypes.get(i); + String typeName = denotableTypeName(paramType); if (typeName == null) { // Cannot safely name this parameter's type; bail rather than partially extract and risk reordering return md; @@ -110,9 +102,13 @@ public J.MethodDeclaration visitMethodDeclaration(J.MethodDeclaration method, Ex names[i] = name; declarations.append(typeName).append(' ').append(name).append(" = #{any()};\n"); declarationArgs.add(arg); - if (paramTypes.get(i) instanceof JavaType.FullyQualified) { - imports.add(((JavaType.FullyQualified) paramTypes.get(i)).getFullyQualifiedName()); + if (paramType instanceof JavaType.FullyQualified) { + imports.add(((JavaType.FullyQualified) paramType).getFullyQualifiedName()); } + anyComplex |= arg instanceof J.MethodInvocation || arg instanceof J.NewClass; + } + if (!anyComplex) { + return md; } // 1. Declare the extracted arguments right before the `super(..)` call, preserving their order @@ -207,8 +203,9 @@ private String baseName(Expression arg, @Nullable String paramName) { } private boolean isValidBaseName(@Nullable String name) { + // Reject synthetic parameter names (`arg0`, `arg1`, ...) from constructors compiled without `-parameters` return name != null && !name.isEmpty() && Character.isJavaIdentifierStart(name.charAt(0)) && - !"arg0".equals(name); + !name.matches("arg\\d+"); } private String uniqueName(String base, Set usedNames) { From c2435c9a7782e6b74ae310c1ca38458e977cbefc Mon Sep 17 00:00:00 2001 From: Tim te Beek Date: Mon, 22 Jun 2026 12:02:41 +0200 Subject: [PATCH 3/5] Also extract complex arguments from `this(..)` delegations --- .../lang/ExtractSuperConstructorArgument.java | 31 ++++++++++--------- .../ExtractSuperConstructorArgumentTest.java | 13 +++++++- 2 files changed, 28 insertions(+), 16 deletions(-) diff --git a/src/main/java/org/openrewrite/java/migrate/lang/ExtractSuperConstructorArgument.java b/src/main/java/org/openrewrite/java/migrate/lang/ExtractSuperConstructorArgument.java index fc807feae0..6b56d1a409 100644 --- a/src/main/java/org/openrewrite/java/migrate/lang/ExtractSuperConstructorArgument.java +++ b/src/main/java/org/openrewrite/java/migrate/lang/ExtractSuperConstructorArgument.java @@ -42,20 +42,20 @@ public class ExtractSuperConstructorArgument extends Recipe { @Getter - final String displayName = "Extract complex `super(..)` arguments into local variables"; + final String displayName = "Extract complex `super(..)` and `this(..)` arguments into local variables"; @Getter - final String description = "[JEP 513](https://openjdk.org/jeps/513) allows statements before an explicit `super(..)` constructor " + - "invocation. When a `super(..)` call computes one of its arguments through a method invocation or object " + - "creation, this recipe extracts the non-trivial arguments into local variables declared right before the " + - "`super(..)` call, surfacing the work done before construction.\n\n" + + final String description = "[JEP 513](https://openjdk.org/jeps/513) allows statements before an explicit `super(..)` or " + + "`this(..)` constructor invocation. When such a call computes one of its arguments through a method " + + "invocation or object creation, this recipe extracts the non-trivial arguments into local variables " + + "declared right before the call, surfacing the work done before construction.\n\n" + "This is a strictly behavior-preserving transformation: argument expressions are already evaluated " + - "before the super constructor body runs, and a super argument can never reference the instance under " + + "before the delegate constructor body runs, and such an argument can never reference the instance under " + "construction, so hoisting them into preceding statements changes neither the order of side effects nor " + "the set of legal references. Arguments are extracted in their original left-to-right order, and trivial " + "arguments (literals and local variable references, which have no side effects) are left in place. " + - "Statements that follow `super(..)` are deliberately *not* moved, as reordering them relative to the " + - "super constructor's side effects could change behavior."; + "Statements that follow the constructor invocation are deliberately *not* moved, as reordering them " + + "relative to the delegate constructor's side effects could change behavior."; @Override public TreeVisitor getVisitor() { @@ -67,7 +67,7 @@ public J.MethodDeclaration visitMethodDeclaration(J.MethodDeclaration method, Ex return md; } - J.MethodInvocation superCall = findExplicitSuperCall(md.getBody().getStatements()); + J.MethodInvocation superCall = findExplicitConstructorInvocation(md.getBody().getStatements()); if (superCall == null || superCall.getMethodType() == null) { return md; } @@ -111,7 +111,7 @@ public J.MethodDeclaration visitMethodDeclaration(J.MethodDeclaration method, Ex return md; } - // 1. Declare the extracted arguments right before the `super(..)` call, preserving their order + // 1. Declare the extracted arguments right before the constructor invocation, preserving their order JavaTemplate.Builder declarationTemplate = JavaTemplate.builder(declarations.toString()).contextSensitive(); for (String fqn : imports) { declarationTemplate.imports(fqn); @@ -138,7 +138,7 @@ public J.MethodDeclaration visitMethodDeclaration(J.MethodDeclaration method, Ex @Override public J.MethodInvocation visitMethodInvocation(J.MethodInvocation mi, ExecutionContext ctx2) { mi = super.visitMethodInvocation(mi, ctx2); - if (isExplicitSuperCall(mi)) { + if (isExplicitConstructorInvocation(mi)) { return JavaTemplate.builder(argumentList.toString()).contextSensitive().build() .apply(getCursor(), mi.getCoordinates().replaceArguments(), inlineArgs.toArray()); } @@ -147,17 +147,18 @@ public J.MethodInvocation visitMethodInvocation(J.MethodInvocation mi, Execution }.visitNonNull(md, ctx, getCursor().getParentOrThrow()); } - private J.@Nullable MethodInvocation findExplicitSuperCall(List statements) { + private J.@Nullable MethodInvocation findExplicitConstructorInvocation(List statements) { for (Statement statement : statements) { - if (statement instanceof J.MethodInvocation && isExplicitSuperCall((J.MethodInvocation) statement)) { + if (statement instanceof J.MethodInvocation && isExplicitConstructorInvocation((J.MethodInvocation) statement)) { return (J.MethodInvocation) statement; } } return null; } - private boolean isExplicitSuperCall(J.MethodInvocation mi) { - return mi.getSelect() == null && "super".equals(mi.getSimpleName()); + private boolean isExplicitConstructorInvocation(J.MethodInvocation mi) { + return mi.getSelect() == null && + ("super".equals(mi.getSimpleName()) || "this".equals(mi.getSimpleName())); } /** diff --git a/src/test/java/org/openrewrite/java/migrate/lang/ExtractSuperConstructorArgumentTest.java b/src/test/java/org/openrewrite/java/migrate/lang/ExtractSuperConstructorArgumentTest.java index d965ece4c4..be874031dd 100644 --- a/src/test/java/org/openrewrite/java/migrate/lang/ExtractSuperConstructorArgumentTest.java +++ b/src/test/java/org/openrewrite/java/migrate/lang/ExtractSuperConstructorArgumentTest.java @@ -314,7 +314,7 @@ class Child extends Parent { } @Test - void leaveThisDelegationAlone() { + void extractThisDelegationArgument() { rewriteRun( //language=java java( @@ -327,6 +327,17 @@ class Child { this(Integer.parseInt(value)); } } + """, + """ + class Child { + Child(int value) { + } + + Child(String value) { + int value1 = Integer.parseInt(value); + this(value1); + } + } """ ) ); From ea059e8ff0105658a73441aff5a0613ddebeeb1c Mon Sep 17 00:00:00 2001 From: Tim te Beek Date: Mon, 22 Jun 2026 12:13:06 +0200 Subject: [PATCH 4/5] Rename recipe to ExtractExplicitConstructorInvocationArguments The recipe handles both super(..) and this(..) explicit constructor invocations, so the name now reflects the JLS umbrella term rather than only the super case. --- ...ava => ExtractExplicitConstructorInvocationArguments.java} | 2 +- src/main/resources/META-INF/rewrite/java-version-25.yml | 2 +- src/main/resources/META-INF/rewrite/recipes.csv | 4 ++-- ...=> ExtractExplicitConstructorInvocationArgumentsTest.java} | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-) rename src/main/java/org/openrewrite/java/migrate/lang/{ExtractSuperConstructorArgument.java => ExtractExplicitConstructorInvocationArguments.java} (99%) rename src/test/java/org/openrewrite/java/migrate/lang/{ExtractSuperConstructorArgumentTest.java => ExtractExplicitConstructorInvocationArgumentsTest.java} (98%) diff --git a/src/main/java/org/openrewrite/java/migrate/lang/ExtractSuperConstructorArgument.java b/src/main/java/org/openrewrite/java/migrate/lang/ExtractExplicitConstructorInvocationArguments.java similarity index 99% rename from src/main/java/org/openrewrite/java/migrate/lang/ExtractSuperConstructorArgument.java rename to src/main/java/org/openrewrite/java/migrate/lang/ExtractExplicitConstructorInvocationArguments.java index 6b56d1a409..b996e5efbe 100644 --- a/src/main/java/org/openrewrite/java/migrate/lang/ExtractSuperConstructorArgument.java +++ b/src/main/java/org/openrewrite/java/migrate/lang/ExtractExplicitConstructorInvocationArguments.java @@ -39,7 +39,7 @@ import static org.openrewrite.java.VariableNameUtils.GenerationStrategy.INCREMENT_NUMBER; -public class ExtractSuperConstructorArgument extends Recipe { +public class ExtractExplicitConstructorInvocationArguments extends Recipe { @Getter final String displayName = "Extract complex `super(..)` and `this(..)` arguments into local variables"; diff --git a/src/main/resources/META-INF/rewrite/java-version-25.yml b/src/main/resources/META-INF/rewrite/java-version-25.yml index 8bf214bf5e..0cae73689e 100644 --- a/src/main/resources/META-INF/rewrite/java-version-25.yml +++ b/src/main/resources/META-INF/rewrite/java-version-25.yml @@ -34,7 +34,7 @@ recipeList: - org.openrewrite.java.migrate.UpgradePluginsForJava25 - org.openrewrite.java.migrate.io.ReplaceSystemOutWithIOPrint - org.openrewrite.java.migrate.lang.MigrateProcessWaitForDuration - - org.openrewrite.java.migrate.lang.ExtractSuperConstructorArgument + - org.openrewrite.java.migrate.lang.ExtractExplicitConstructorInvocationArguments - org.openrewrite.java.migrate.lang.ReplaceUnusedVariablesWithUnderscore - org.openrewrite.java.migrate.util.MigrateInflaterDeflaterToClose - org.openrewrite.java.migrate.util.MigrateStringReaderToReaderOf diff --git a/src/main/resources/META-INF/rewrite/recipes.csv b/src/main/resources/META-INF/rewrite/recipes.csv index 0f98b510cf..23bb2de555 100644 --- a/src/main/resources/META-INF/rewrite/recipes.csv +++ b/src/main/resources/META-INF/rewrite/recipes.csv @@ -350,9 +350,9 @@ maven,org.openrewrite.recipe:rewrite-migrate-java,org.openrewrite.java.migrate.j maven,org.openrewrite.recipe:rewrite-migrate-java,org.openrewrite.java.migrate.javax.openJPAToEclipseLink,Migrate from OpenJPA to EclipseLink JPA,These recipes help migrate Java Persistence applications using OpenJPA to EclipseLink JPA.,10,,`javax` APIs,Modernize,Java,,,Modernize your code to best use the project's current JDK version. Take advantage of newly available APIs and reduce the dependency of your code on third party dependencies where there is equivalent functionality in the Java standard library.,Basic building blocks for transforming Java code.,, maven,org.openrewrite.recipe:rewrite-migrate-java,org.openrewrite.java.migrate.jspecify.MoveAnnotationToArrayType,Move annotation to array type,"When an annotation like `@Nullable` is applied to an array type in declaration position, this recipe moves it to the array brackets. For example, `@Nullable byte[]` becomes `byte @Nullable[]`. Best used before `ChangeType` in a migration pipeline, targeting the pre-migration annotation type.",1,,Jspecify,Modernize,Java,,,Modernize your code to best use the project's current JDK version. Take advantage of newly available APIs and reduce the dependency of your code on third party dependencies where there is equivalent functionality in the Java standard library.,Basic building blocks for transforming Java code.,"[{""name"":""annotationType"",""type"":""String"",""displayName"":""Annotation type"",""description"":""The type of annotation to move to the array type. Should target the pre-migration annotation type to avoid changing the semantics of pre-existing type-use annotations on object arrays."",""example"":""javax.annotation.*ull*"",""required"":true}]", maven,org.openrewrite.recipe:rewrite-migrate-java,org.openrewrite.java.migrate.lang.ExplicitRecordImport,Add explicit import for `Record` classes,"Add explicit import for `Record` classes when upgrading past Java 14+, to avoid conflicts with `java.lang.Record`.",1,,`java.lang` APIs,Modernize,Java,,,Modernize your code to best use the project's current JDK version. Take advantage of newly available APIs and reduce the dependency of your code on third party dependencies where there is equivalent functionality in the Java standard library.,Basic building blocks for transforming Java code.,, -maven,org.openrewrite.recipe:rewrite-migrate-java,org.openrewrite.java.migrate.lang.ExtractSuperConstructorArgument,Extract complex `super(..)` arguments into local variables,"[JEP 513](https://openjdk.org/jeps/513) allows statements before an explicit `super(..)` constructor invocation. When a `super(..)` call computes one of its arguments through a method invocation or object creation, this recipe extracts the non-trivial arguments into local variables declared right before the `super(..)` call, surfacing the work done before construction. +maven,org.openrewrite.recipe:rewrite-migrate-java,org.openrewrite.java.migrate.lang.ExtractExplicitConstructorInvocationArguments,Extract complex `super(..)` and `this(..)` arguments into local variables,"[JEP 513](https://openjdk.org/jeps/513) allows statements before an explicit `super(..)` or `this(..)` constructor invocation. When such a call computes one of its arguments through a method invocation or object creation, this recipe extracts the non-trivial arguments into local variables declared right before the call, surfacing the work done before construction. -This is a strictly behavior-preserving transformation: argument expressions are already evaluated before the super constructor body runs, and a super argument can never reference the instance under construction, so hoisting them into preceding statements changes neither the order of side effects nor the set of legal references. Arguments are extracted in their original left-to-right order, and trivial arguments (literals and local variable references, which have no side effects) are left in place. Statements that follow `super(..)` are deliberately *not* moved, as reordering them relative to the super constructor's side effects could change behavior.",1,,`java.lang` APIs,Modernize,Java,,,Modernize your code to best use the project's current JDK version. Take advantage of newly available APIs and reduce the dependency of your code on third party dependencies where there is equivalent functionality in the Java standard library.,Basic building blocks for transforming Java code.,, +This is a strictly behavior-preserving transformation: argument expressions are already evaluated before the delegate constructor body runs, and such an argument can never reference the instance under construction, so hoisting them into preceding statements changes neither the order of side effects nor the set of legal references. Arguments are extracted in their original left-to-right order, and trivial arguments (literals and local variable references, which have no side effects) are left in place. Statements that follow the constructor invocation are deliberately *not* moved, as reordering them relative to the delegate constructor's side effects could change behavior.",1,,`java.lang` APIs,Modernize,Java,,,Modernize your code to best use the project's current JDK version. Take advantage of newly available APIs and reduce the dependency of your code on third party dependencies where there is equivalent functionality in the Java standard library.,Basic building blocks for transforming Java code.,, maven,org.openrewrite.recipe:rewrite-migrate-java,org.openrewrite.java.migrate.lang.FindNonVirtualExecutors,Find non-virtual `ExecutorService` creation,Find all places where static `java.util.concurrent.Executors` method creates a non-virtual `java.util.concurrent.ExecutorService`. This recipe can be used to search fro `ExecutorService` that can be replaced by Virtual Thread executor.,7,,`java.lang` APIs,Modernize,Java,,,Modernize your code to best use the project's current JDK version. Take advantage of newly available APIs and reduce the dependency of your code on third party dependencies where there is equivalent functionality in the Java standard library.,Basic building blocks for transforming Java code.,,"[{""name"":""org.openrewrite.java.table.MethodCalls"",""displayName"":""Method calls"",""instanceName"":""Method calls"",""description"":""The text of matching method invocations."",""columns"":[{""name"":""sourceFile"",""type"":""String"",""displayName"":""Source file"",""description"":""The source file that the method call occurred in.""},{""name"":""method"",""type"":""String"",""displayName"":""Method call"",""description"":""The text of the method call.""},{""name"":""className"",""type"":""String"",""displayName"":""Class name"",""description"":""The class name of the method call.""},{""name"":""methodName"",""type"":""String"",""displayName"":""Method name"",""description"":""The method name of the method call.""},{""name"":""argumentTypes"",""type"":""String"",""displayName"":""Argument types"",""description"":""The argument types of the method call.""}]}]" maven,org.openrewrite.recipe:rewrite-migrate-java,org.openrewrite.java.migrate.lang.FindVirtualThreadOpportunities,Find Virtual Thread opportunities,Find opportunities to convert existing code to use Virtual Threads.,10,,`java.lang` APIs,Modernize,Java,,,Modernize your code to best use the project's current JDK version. Take advantage of newly available APIs and reduce the dependency of your code on third party dependencies where there is equivalent functionality in the Java standard library.,Basic building blocks for transforming Java code.,,"[{""name"":""org.openrewrite.java.table.MethodCalls"",""displayName"":""Method calls"",""instanceName"":""Method calls"",""description"":""The text of matching method invocations."",""columns"":[{""name"":""sourceFile"",""type"":""String"",""displayName"":""Source file"",""description"":""The source file that the method call occurred in.""},{""name"":""method"",""type"":""String"",""displayName"":""Method call"",""description"":""The text of the method call.""},{""name"":""className"",""type"":""String"",""displayName"":""Class name"",""description"":""The class name of the method call.""},{""name"":""methodName"",""type"":""String"",""displayName"":""Method name"",""description"":""The method name of the method call.""},{""name"":""argumentTypes"",""type"":""String"",""displayName"":""Argument types"",""description"":""The argument types of the method call.""}]}]" maven,org.openrewrite.recipe:rewrite-migrate-java,org.openrewrite.java.migrate.lang.IfElseIfConstructToSwitch,If-else-if-else to switch,"Replace if-else-if-else with switch statements. In order to be replaced with a switch, all conditions must be on the same variable and there must be at least three cases.",1,,`java.lang` APIs,Modernize,Java,,,Modernize your code to best use the project's current JDK version. Take advantage of newly available APIs and reduce the dependency of your code on third party dependencies where there is equivalent functionality in the Java standard library.,Basic building blocks for transforming Java code.,, diff --git a/src/test/java/org/openrewrite/java/migrate/lang/ExtractSuperConstructorArgumentTest.java b/src/test/java/org/openrewrite/java/migrate/lang/ExtractExplicitConstructorInvocationArgumentsTest.java similarity index 98% rename from src/test/java/org/openrewrite/java/migrate/lang/ExtractSuperConstructorArgumentTest.java rename to src/test/java/org/openrewrite/java/migrate/lang/ExtractExplicitConstructorInvocationArgumentsTest.java index be874031dd..18c84ab7e6 100644 --- a/src/test/java/org/openrewrite/java/migrate/lang/ExtractSuperConstructorArgumentTest.java +++ b/src/test/java/org/openrewrite/java/migrate/lang/ExtractExplicitConstructorInvocationArgumentsTest.java @@ -26,13 +26,13 @@ import static org.openrewrite.java.Assertions.javaVersion; @EnabledForJreRange(min = JAVA_25) -class ExtractSuperConstructorArgumentTest implements RewriteTest { +class ExtractExplicitConstructorInvocationArgumentsTest implements RewriteTest { @Override public void defaults(RecipeSpec spec) { spec .allSources(src -> src.markers(javaVersion(25))) - .recipe(new ExtractSuperConstructorArgument()); + .recipe(new ExtractExplicitConstructorInvocationArguments()); } @DocumentExample From d09e755e20131254111b1a0cb4608c68d76fa48e Mon Sep 17 00:00:00 2001 From: Tim te Beek Date: Mon, 22 Jun 2026 13:45:37 +0200 Subject: [PATCH 5/5] Fix nested-class clobbering and move recipe to best-practices The argument-replacement visitor descended into nested/local classes and rewrote their super(..)/this(..) calls with the outer constructor's argument list. Guard against descending into nested class declarations. Also move ExtractExplicitConstructorInvocationArguments out of the default UpgradeToJava25 upgrade and into JavaBestPractices, since it restyles already-valid code rather than migrating it. Add tests for the nested-class case, varargs bail, statements before super, static-field extraction, and name-collision avoidance. --- ...xplicitConstructorInvocationArguments.java | 7 + .../META-INF/rewrite/java-best-practices.yml | 2 + .../META-INF/rewrite/java-version-25.yml | 1 - ...citConstructorInvocationArgumentsTest.java | 186 ++++++++++++++++++ 4 files changed, 195 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/openrewrite/java/migrate/lang/ExtractExplicitConstructorInvocationArguments.java b/src/main/java/org/openrewrite/java/migrate/lang/ExtractExplicitConstructorInvocationArguments.java index b996e5efbe..651807ebe2 100644 --- a/src/main/java/org/openrewrite/java/migrate/lang/ExtractExplicitConstructorInvocationArguments.java +++ b/src/main/java/org/openrewrite/java/migrate/lang/ExtractExplicitConstructorInvocationArguments.java @@ -135,6 +135,13 @@ public J.MethodDeclaration visitMethodDeclaration(J.MethodDeclaration method, Ex } } return (J.MethodDeclaration) new JavaIsoVisitor() { + @Override + public J.ClassDeclaration visitClassDeclaration(J.ClassDeclaration classDecl, ExecutionContext ctx2) { + // Do not descend into nested/local classes; their own `super(..)`/`this(..)` calls + // are handled by their own constructor visits, not this one's argument list. + return classDecl; + } + @Override public J.MethodInvocation visitMethodInvocation(J.MethodInvocation mi, ExecutionContext ctx2) { mi = super.visitMethodInvocation(mi, ctx2); diff --git a/src/main/resources/META-INF/rewrite/java-best-practices.yml b/src/main/resources/META-INF/rewrite/java-best-practices.yml index 4702715dd8..96c1f78173 100644 --- a/src/main/resources/META-INF/rewrite/java-best-practices.yml +++ b/src/main/resources/META-INF/rewrite/java-best-practices.yml @@ -34,6 +34,8 @@ recipeList: # Instance main methods (JEP 512); not moved into the version upgrade as it does not yet work for Spring Boot # https://github.com/spring-projects/spring-boot/issues/35785 - org.openrewrite.java.migrate.lang.MigrateMainMethodToInstanceMain + # Hoist complex super(..)/this(..) arguments into locals (JEP 513); opt-in, as it restyles working code + - org.openrewrite.java.migrate.lang.ExtractExplicitConstructorInvocationArguments # Text blocks for strings without newlines (upgrade chain only does convertStringsWithoutNewlines: false) - org.openrewrite.java.migrate.lang.UseTextBlocks: convertStringsWithoutNewlines: true diff --git a/src/main/resources/META-INF/rewrite/java-version-25.yml b/src/main/resources/META-INF/rewrite/java-version-25.yml index 0cae73689e..8df8f20b03 100644 --- a/src/main/resources/META-INF/rewrite/java-version-25.yml +++ b/src/main/resources/META-INF/rewrite/java-version-25.yml @@ -34,7 +34,6 @@ recipeList: - org.openrewrite.java.migrate.UpgradePluginsForJava25 - org.openrewrite.java.migrate.io.ReplaceSystemOutWithIOPrint - org.openrewrite.java.migrate.lang.MigrateProcessWaitForDuration - - org.openrewrite.java.migrate.lang.ExtractExplicitConstructorInvocationArguments - org.openrewrite.java.migrate.lang.ReplaceUnusedVariablesWithUnderscore - org.openrewrite.java.migrate.util.MigrateInflaterDeflaterToClose - org.openrewrite.java.migrate.util.MigrateStringReaderToReaderOf diff --git a/src/test/java/org/openrewrite/java/migrate/lang/ExtractExplicitConstructorInvocationArgumentsTest.java b/src/test/java/org/openrewrite/java/migrate/lang/ExtractExplicitConstructorInvocationArgumentsTest.java index 18c84ab7e6..b0d8175d5d 100644 --- a/src/test/java/org/openrewrite/java/migrate/lang/ExtractExplicitConstructorInvocationArgumentsTest.java +++ b/src/test/java/org/openrewrite/java/migrate/lang/ExtractExplicitConstructorInvocationArgumentsTest.java @@ -313,6 +313,192 @@ class Child extends Parent { ); } + @Test + void doNotClobberLocalClassConstructorInvocation() { + rewriteRun( + //language=java + java( + """ + class Base { + Base(String s) { + } + } + + class Parent { + Parent(String name) { + } + } + + class Child extends Parent { + Child(String value) { + super(value.trim()); + class Local extends Base { + Local() { + super("literal"); + } + } + } + } + """, + """ + class Base { + Base(String s) { + } + } + + class Parent { + Parent(String name) { + } + } + + class Child extends Parent { + Child(String value) { + String name = value.trim(); + super(name); + class Local extends Base { + Local() { + super("literal"); + } + } + } + } + """ + ) + ); + } + + @Test + void doNotExtractFromVarargsConstructor() { + rewriteRun( + //language=java + java( + """ + class Parent { + Parent(String... values) { + } + } + + class Child extends Parent { + Child(String value) { + super(value.trim(), value.strip()); + } + } + """ + ) + ); + } + + @Test + void insertDeclarationsAfterExistingStatementsBeforeSuper() { + rewriteRun( + //language=java + java( + """ + class Parent { + Parent(int value) { + } + } + + class Child extends Parent { + Child(String value) { + System.out.println("before"); + super(Integer.parseInt(value)); + } + } + """, + """ + class Parent { + Parent(int value) { + } + } + + class Child extends Parent { + Child(String value) { + System.out.println("before"); + int value1 = Integer.parseInt(value); + super(value1); + } + } + """ + ) + ); + } + + @Test + void extractStaticFieldArgumentToPreserveOrder() { + rewriteRun( + //language=java + java( + """ + class Parent { + Parent(int a, int b) { + } + } + + class Child extends Parent { + static final int CONST = 1; + + Child(String value) { + super(CONST, Integer.parseInt(value)); + } + } + """, + """ + class Parent { + Parent(int a, int b) { + } + } + + class Child extends Parent { + static final int CONST = 1; + + Child(String value) { + int a = CONST; + int b = Integer.parseInt(value); + super(a, b); + } + } + """ + ) + ); + } + + @Test + void avoidNameCollisionWithLaterDeclaredVariable() { + rewriteRun( + //language=java + java( + """ + class Parent { + Parent(int value) { + } + } + + class Child extends Parent { + Child(String value) { + super(Integer.parseInt(value)); + int value1 = 5; + } + } + """, + """ + class Parent { + Parent(int value) { + } + } + + class Child extends Parent { + Child(String value) { + int value2 = Integer.parseInt(value); + super(value2); + int value1 = 5; + } + } + """ + ) + ); + } + @Test void extractThisDelegationArgument() { rewriteRun(