Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
/*
* Copyright 2025 the original author or authors.
* <p>
* Licensed under the Moderne Source Available License (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* https://docs.moderne.io/licensing/moderne-source-available-license
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.openrewrite.java.migrate.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<?, ExecutionContext> getVisitor() {
return Preconditions.check(new UsesJavaVersion<>(10), new JavaIsoVisitor<ExecutionContext>() {
@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<J.VariableDeclarations.NamedVariable> 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);
}
});
}
}
1 change: 1 addition & 0 deletions src/main/resources/META-INF/rewrite/recipes.csv
Original file line number Diff line number Diff line change
Expand Up @@ -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.,,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,272 @@
/*
* Copyright 2026 the original author or authors.
* <p>
* Licensed under the Moderne Source Available License (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* https://docs.moderne.io/licensing/moderne-source-available-license
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.openrewrite.java.migrate.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;
}
}
"""
)
);
}
}
}