From bd95a1af25fc7590fbb7e1b2baf620ed9eddad1a Mon Sep 17 00:00:00 2001 From: Tim te Beek Date: Sun, 11 Jan 2026 13:15:54 +0100 Subject: [PATCH 1/2] Use `var` for variables initialized with type casts --- .../migrate/lang/var/UseVarForTypeCast.java | 114 ++++++++ .../lang/var/UseVarForTypeCastTest.java | 272 ++++++++++++++++++ 2 files changed, 386 insertions(+) create mode 100644 src/main/java/org/openrewrite/java/migrate/lang/var/UseVarForTypeCast.java create mode 100644 src/test/java/org/openrewrite/java/migrate/lang/var/UseVarForTypeCastTest.java diff --git a/src/main/java/org/openrewrite/java/migrate/lang/var/UseVarForTypeCast.java b/src/main/java/org/openrewrite/java/migrate/lang/var/UseVarForTypeCast.java new file mode 100644 index 0000000000..da93cb6dc5 --- /dev/null +++ b/src/main/java/org/openrewrite/java/migrate/lang/var/UseVarForTypeCast.java @@ -0,0 +1,114 @@ +/* + * 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.var; + +import org.jspecify.annotations.Nullable; +import org.openrewrite.*; +import org.openrewrite.internal.ListUtils; +import org.openrewrite.java.JavaIsoVisitor; +import org.openrewrite.java.search.UsesJavaVersion; +import org.openrewrite.java.tree.*; +import org.openrewrite.marker.Markers; + +import java.util.List; + +import static java.util.Collections.emptyList; +import static java.util.Collections.singleton; +import static org.openrewrite.Tree.randomId; +import static org.openrewrite.java.tree.Space.EMPTY; + +public class UseVarForTypeCast extends Recipe { + + @Override + public String getDisplayName() { + return "Use `var` for variables initialized with type casts"; + } + + @Override + public String getDescription() { + return "Apply local variable type inference `var` to variables that are initialized by a cast expression " + + "where the cast type matches the declared variable type. This removes the redundant type duplication. " + + "For example, `String s = (String) obj;` becomes `var s = (String) obj;`."; + } + + @Override + public TreeVisitor getVisitor() { + return Preconditions.check(new UsesJavaVersion<>(10), new JavaIsoVisitor() { + @Override + public J.VariableDeclarations visitVariableDeclarations(J.VariableDeclarations variableDeclarations, ExecutionContext ctx) { + J.VariableDeclarations vd = super.visitVariableDeclarations(variableDeclarations, ctx); + if (usesVar(vd)) { + return vd; + } + + J.TypeCast typeCast = getSingleTypeCastInitializer(vd); + if (typeCast != null && typeCast.getType() != null && + TypeUtils.isOfType(typeCast.getType(), vd.getType()) && + isInsideMethod(getCursor())) { + return transformToVar(vd, typeCast); + } + + return vd; + } + + private boolean usesVar(J.VariableDeclarations vd) { + TypeTree typeExpression = vd.getTypeExpression(); + return typeExpression instanceof J.Identifier && + "var".equals(((J.Identifier) typeExpression).getSimpleName()); + } + + private J.@Nullable TypeCast getSingleTypeCastInitializer(J.VariableDeclarations vd) { + if (vd.getVariables().size() != 1) { + return null; + } + Expression initializer = vd.getVariables().get(0).getInitializer(); + if (initializer != null) { + initializer = initializer.unwrap(); + if (initializer instanceof J.TypeCast) { + return (J.TypeCast) initializer; + } + } + return null; + } + + private boolean isInsideMethod(Cursor cursor) { + return cursor.dropParentUntil(p -> p instanceof J.MethodDeclaration || + p instanceof J.ClassDeclaration || + Cursor.ROOT_VALUE.equals(p)) + .getValue() instanceof J.MethodDeclaration; + } + + private J.VariableDeclarations transformToVar(J.VariableDeclarations vd, J.TypeCast typeCast) { + List variables = ListUtils.mapFirst(vd.getVariables(), it -> { + JavaType.Variable variableType = it.getVariableType() == null ? + null : it.getVariableType().withOwner(null); + return it + .withName(it.getName().withType(typeCast.getType()).withFieldType(variableType)) + .withVariableType(variableType); + }); + J.Identifier typeExpression = new J.Identifier( + randomId(), + vd.getTypeExpression() == null ? EMPTY : vd.getTypeExpression().getPrefix(), + Markers.build(singleton(JavaVarKeyword.build())), + emptyList(), + "var", + typeCast.getType(), + null); + return vd.withVariables(variables).withTypeExpression(typeExpression); + } + }); + } +} diff --git a/src/test/java/org/openrewrite/java/migrate/lang/var/UseVarForTypeCastTest.java b/src/test/java/org/openrewrite/java/migrate/lang/var/UseVarForTypeCastTest.java new file mode 100644 index 0000000000..e1b0fd4e44 --- /dev/null +++ b/src/test/java/org/openrewrite/java/migrate/lang/var/UseVarForTypeCastTest.java @@ -0,0 +1,272 @@ +/* + * Copyright 2026 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.var; + +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.openrewrite.DocumentExample; +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 UseVarForTypeCastTest implements RewriteTest { + + @Override + public void defaults(RecipeSpec spec) { + spec.recipe(new UseVarForTypeCast()) + .allSources(s -> s.markers(javaVersion(10))); + } + + @DocumentExample + @Test + void simpleCast() { + rewriteRun( + java( + """ + class A { + void m(Object obj) { + String s = (String) obj; + } + } + """, + """ + class A { + void m(Object obj) { + var s = (String) obj; + } + } + """ + ) + ); + } + + @Test + void castWithFinalModifier() { + rewriteRun( + java( + """ + class A { + void m(Object obj) { + final String s = (String) obj; + } + } + """, + """ + class A { + void m(Object obj) { + final var s = (String) obj; + } + } + """ + ) + ); + } + + @Test + void castToFullyQualifiedType() { + rewriteRun( + java( + """ + import java.util.List; + + class A { + void m(Object obj) { + List list = (List) obj; + } + } + """, + """ + import java.util.List; + + class A { + void m(Object obj) { + var list = (List) obj; + } + } + """ + ) + ); + } + + @Test + void primitiveCast() { + rewriteRun( + java( + """ + class A { + void m(double d) { + int i = (int) d; + } + } + """, + """ + class A { + void m(double d) { + var i = (int) d; + } + } + """ + ) + ); + } + + @Test + void nullInitializerWithCast() { + rewriteRun( + java( + """ + class A { + void m() { + String s = (String) null; + } + } + """, + """ + class A { + void m() { + var s = (String) null; + } + } + """ + ) + ); + } + + @Nested + class NoChange { + @Test + void fieldDeclaration() { + rewriteRun( + java( + """ + class A { + static Object obj = new Object(); + String s = (String) obj; + } + """ + ) + ); + } + + @Test + void castTypeDiffersFromDeclaredType() { + rewriteRun( + java( + """ + class A { + void m(Object obj) { + Object o = (String) obj; + } + } + """ + ) + ); + } + + @Test + void alreadyUsesVar() { + rewriteRun( + java( + """ + class A { + void m(Object obj) { + var s = (String) obj; + } + } + """ + ) + ); + } + + @Test + void notACast() { + rewriteRun( + java( + """ + class A { + void m() { + String s = "hello"; + } + } + """ + ) + ); + } + + @Test + void noInitializer() { + rewriteRun( + java( + """ + class A { + void m() { + String s; + } + } + """ + ) + ); + } + + @Test + void nullInitializer() { + rewriteRun( + java( + """ + class A { + void m() { + String s = null; + } + } + """ + ) + ); + } + + @Test + void multipleVariables() { + rewriteRun( + java( + """ + class A { + void m(Object obj1, Object obj2) { + String s1, s2 = (String) obj2; + } + } + """ + ) + ); + } + + @Test + void java9NotSupported() { + rewriteRun( + spec -> spec.allSources(s -> s.markers(javaVersion(9))), + java( + """ + class A { + void m(Object obj) { + String s = (String) obj; + } + } + """ + ) + ); + } + } +} From 430d2296ee688a83088368fe1b76e9ec5d7c2d92 Mon Sep 17 00:00:00 2001 From: Tim te Beek Date: Mon, 12 Jan 2026 11:50:20 +0100 Subject: [PATCH 2/2] Update recipes.csv --- src/main/resources/META-INF/rewrite/recipes.csv | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/resources/META-INF/rewrite/recipes.csv b/src/main/resources/META-INF/rewrite/recipes.csv index c5758a53c7..8ec636813e 100644 --- a/src/main/resources/META-INF/rewrite/recipes.csv +++ b/src/main/resources/META-INF/rewrite/recipes.csv @@ -335,6 +335,7 @@ maven,org.openrewrite.recipe:rewrite-migrate-java,org.openrewrite.java.migrate.l maven,org.openrewrite.recipe:rewrite-migrate-java,org.openrewrite.java.migrate.lang.var.UseVarForGenericsConstructors,Apply `var` to Generic Constructors,Apply `var` to generics variables initialized by constructor calls.,1,Var,`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.var.UseVarForObject,Use `var` for reference-typed variables,Try to apply local variable type inference `var` to variables containing Objects where possible. This recipe will not touch variable declarations with generics or initializers containing ternary operators.,1,Var,`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.var.UseVarForPrimitive,Use `var` for primitive-typed variables,Try to apply local variable type inference `var` to primitive variables where possible. This recipe will not touch variable declarations with initializers containing ternary operators.,1,Var,`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.var.UseVarForTypeCast,Use `var` for variables initialized with type casts,"Apply local variable type inference `var` to variables that are initialized by a cast expression where the cast type matches the declared variable type. This removes the redundant type duplication. For example, `String s = (String) obj;` becomes `var s = (String) obj;`.",1,Var,`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.logging.MigrateGetLoggingMXBeanToGetPlatformMXBean,Use `ManagementFactory#getPlatformMXBean(PlatformLoggingMXBean.class)`,Use `ManagementFactory#getPlatformMXBean(PlatformLoggingMXBean.class)` instead of the deprecated `LogManager#getLoggingMXBean()` in Java 9 or higher.,1,,`java.util.logging` 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.logging.MigrateLogRecordSetMillisToSetInstant,Use `LogRecord#setInstant(Instant)`,Use `LogRecord#setInstant(Instant)` instead of the deprecated `LogRecord#setMillis(long)` in Java 9 or higher.,1,,`java.util.logging` 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.logging.MigrateLoggerGlobalToGetGlobal,Use `Logger#getGlobal()`,The preferred way to get the global logger object is via the call `Logger#getGlobal()` over direct field access to `java.util.logging.Logger.global`.,1,,`java.util.logging` 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.,,