diff --git a/src/main/java/org/openrewrite/java/migrate/lang/var/DeclarationCheck.java b/src/main/java/org/openrewrite/java/migrate/lang/var/DeclarationCheck.java index 434eee2e3c..b58571ab59 100644 --- a/src/main/java/org/openrewrite/java/migrate/lang/var/DeclarationCheck.java +++ b/src/main/java/org/openrewrite/java/migrate/lang/var/DeclarationCheck.java @@ -15,20 +15,27 @@ */ package org.openrewrite.java.migrate.lang.var; +import lombok.experimental.UtilityClass; import org.jspecify.annotations.Nullable; import org.openrewrite.Cursor; +import org.openrewrite.internal.ListUtils; import org.openrewrite.java.tree.*; +import org.openrewrite.marker.Markers; +import java.util.List; +import java.util.function.UnaryOperator; + +import static java.util.Collections.emptyList; +import static java.util.Collections.singleton; import static java.util.Objects.requireNonNull; +import static org.openrewrite.Tree.randomId; +import static org.openrewrite.java.tree.Space.EMPTY; +@UtilityClass final class DeclarationCheck { - private DeclarationCheck() { - - } - /** - * Determine if var is applicable with regard to location and decleation type. + * Determine if var is applicable with regard to location and declaration type. *

* Var is applicable inside methods and initializer blocks for single variable definition. * Var is *not* applicable to method definitions. @@ -119,7 +126,7 @@ public static boolean useGenerics(J.VariableDeclarations vd) { } /** - * Determin if the initilizer uses the ternary operator Expression ? if-then : else + * Determine if the initializer uses the ternary operator Expression ? if-then : else * * @param vd variable declaration at hand * @return true iff the ternary operator is used in the initialization @@ -225,4 +232,29 @@ public static boolean initializedByStaticMethod(@Nullable Expression initializer return invocation.getMethodType().hasFlags(Flag.Static); } + + public static J.VariableDeclarations transformToVar(J.VariableDeclarations vd) { + return transformToVar(vd, it -> it); + } + + public static J.VariableDeclarations transformToVar(J.VariableDeclarations vd, UnaryOperator transformerInitializer) { + T initializer = (T) vd.getVariables().get(0).getInitializer(); + if (initializer == null) { + return vd; + } + + Expression transformedInitializer = transformerInitializer.apply(initializer); + + List variables = ListUtils.mapFirst(vd.getVariables(), it -> { + JavaType.Variable variableType = it.getVariableType() == null ? null : it.getVariableType().withOwner(null); + return it + .withName(it.getName().withType(transformedInitializer.getType()).withFieldType(variableType)) + .withInitializer(transformedInitializer) + .withVariableType(variableType); + }); + J.Identifier typeExpression = new J.Identifier(randomId(), vd.getTypeExpression() == null ? EMPTY : vd.getTypeExpression().getPrefix(), + Markers.build(singleton(JavaVarKeyword.build())), emptyList(), "var", transformedInitializer.getType(), null); + + return vd.withVariables(variables).withTypeExpression(typeExpression); + } } diff --git a/src/main/java/org/openrewrite/java/migrate/lang/var/UseVarForGenericMethodInvocations.java b/src/main/java/org/openrewrite/java/migrate/lang/var/UseVarForGenericMethodInvocations.java index 5754427dbf..376c67d018 100644 --- a/src/main/java/org/openrewrite/java/migrate/lang/var/UseVarForGenericMethodInvocations.java +++ b/src/main/java/org/openrewrite/java/migrate/lang/var/UseVarForGenericMethodInvocations.java @@ -15,18 +15,15 @@ */ package org.openrewrite.java.migrate.lang.var; -import org.openrewrite.*; +import org.openrewrite.ExecutionContext; +import org.openrewrite.Preconditions; +import org.openrewrite.Recipe; +import org.openrewrite.TreeVisitor; import org.openrewrite.java.JavaIsoVisitor; -import org.openrewrite.java.JavaParser; -import org.openrewrite.java.JavaTemplate; import org.openrewrite.java.search.UsesJavaVersion; -import org.openrewrite.java.tree.*; -import org.openrewrite.marker.Markers; - -import java.util.ArrayList; -import java.util.List; - -import static java.util.Collections.emptyList; +import org.openrewrite.java.tree.Expression; +import org.openrewrite.java.tree.J; +import org.openrewrite.java.tree.JavaType; public class UseVarForGenericMethodInvocations extends Recipe { @Override @@ -50,9 +47,6 @@ public TreeVisitor getVisitor() { } static final class UseVarForGenericsVisitor extends JavaIsoVisitor { - private final JavaTemplate template = JavaTemplate.builder("var #{} = #{any()}") - .javaParser(JavaParser.fromJavaVersion()).build(); - @Override public J.VariableDeclarations visitVariableDeclarations(J.VariableDeclarations vd, ExecutionContext ctx) { vd = super.visitVariableDeclarations(vd, ctx); @@ -62,7 +56,7 @@ public J.VariableDeclarations visitVariableDeclarations(J.VariableDeclarations v return vd; } - // recipe specific + // Recipe specific boolean isPrimitive = DeclarationCheck.isPrimitive(vd); boolean usesNoGenerics = !DeclarationCheck.useGenerics(vd); boolean usesTernary = DeclarationCheck.initializedByTernary(vd); @@ -70,26 +64,34 @@ public J.VariableDeclarations visitVariableDeclarations(J.VariableDeclarations v return vd; } - //now we deal with generics, check for method invocations + // Now we deal with generics, check for method invocations Expression initializer = vd.getVariables().get(0).getInitializer(); boolean isMethodInvocation = initializer != null && initializer.unwrap() instanceof J.MethodInvocation; if (!isMethodInvocation) { return vd; } - //if no type paramters are present and no arguments we assume the type is hard to determine a needs manual action + // If no type parameters and no arguments are present, we assume the type is too hard to determine boolean hasNoTypeParams = ((J.MethodInvocation) initializer).getTypeParameters() == null; boolean argumentsEmpty = allArgumentsEmpty((J.MethodInvocation) initializer); if (hasNoTypeParams && argumentsEmpty) { return vd; } - // mark imports for removal if unused if (vd.getType() instanceof JavaType.FullyQualified) { - maybeRemoveImport( (JavaType.FullyQualified) vd.getType() ); + maybeRemoveImport((JavaType.FullyQualified) vd.getType()); } - return transformToVar(vd, new ArrayList<>(), new ArrayList<>()); + return DeclarationCheck.transformToVar(vd); + // TODO implement to support cases like `var strs = List.of();` + /*J.VariableDeclarations finalVd = vd; + return DeclarationCheck.transformToVar(vd, it -> { + // If left has generics but right has not, copy types parameters + if (finalVd.getTypeExpression() instanceof J.ParameterizedType && !((J.ParameterizedType) finalVd.getTypeExpression()).getTypeParameters().isEmpty() && it.getTypeParameters() == null) { + return it.withTypeParameters(((J.ParameterizedType) finalVd.getTypeExpression()).getPadding().getTypeParameters()); + } + return it; + });*/ } private static boolean allArgumentsEmpty(J.MethodInvocation invocation) { @@ -100,40 +102,5 @@ private static boolean allArgumentsEmpty(J.MethodInvocation invocation) { } return true; } - - private J.VariableDeclarations transformToVar(J.VariableDeclarations vd, List leftTypes, List rightTypes) { - Expression initializer = vd.getVariables().get(0).getInitializer(); - String simpleName = vd.getVariables().get(0).getSimpleName(); - - // if left is defined but not right, copy types to initializer - if (rightTypes.isEmpty() && !leftTypes.isEmpty()) { - // we need to switch type infos from left to right here - List typeArgument = new ArrayList<>(); - for (JavaType t : leftTypes) { - typeArgument.add(new J.Identifier(Tree.randomId(), Space.EMPTY, Markers.EMPTY, emptyList(), ((JavaType.Class) t).getClassName(), t, null)); - } - J.ParameterizedType typedInitializerClazz = ((J.ParameterizedType) ((J.NewClass) initializer).getClazz()).withTypeParameters(typeArgument); - initializer = ((J.NewClass) initializer).withClazz(typedInitializerClazz); - } - - J.VariableDeclarations result = template.apply(getCursor(), vd.getCoordinates().replace(), simpleName, initializer) - .withPrefix(vd.getPrefix()); - - // apply modifiers like final - List modifiers = vd.getModifiers(); - boolean hasModifiers = !modifiers.isEmpty(); - if (hasModifiers) { - result = result.withModifiers(modifiers); - } - - // apply prefix to type expression - TypeTree resultingTypeExpression = result.getTypeExpression(); - boolean resultHasTypeExpression = resultingTypeExpression != null; - if (resultHasTypeExpression) { - result = result.withTypeExpression(resultingTypeExpression.withPrefix(vd.getTypeExpression().getPrefix())); - } - - return result; - } } } diff --git a/src/main/java/org/openrewrite/java/migrate/lang/var/UseVarForGenericsConstructors.java b/src/main/java/org/openrewrite/java/migrate/lang/var/UseVarForGenericsConstructors.java index 1c7257a383..5a85f3fbf4 100644 --- a/src/main/java/org/openrewrite/java/migrate/lang/var/UseVarForGenericsConstructors.java +++ b/src/main/java/org/openrewrite/java/migrate/lang/var/UseVarForGenericsConstructors.java @@ -20,19 +20,15 @@ import org.openrewrite.Preconditions; import org.openrewrite.Recipe; import org.openrewrite.TreeVisitor; -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 org.openrewrite.java.tree.Expression; +import org.openrewrite.java.tree.J; +import org.openrewrite.java.tree.JavaType; +import org.openrewrite.java.tree.TypeTree; import java.util.ArrayList; import java.util.List; -import java.util.Objects; - -import static java.util.Collections.emptyList; -import static java.util.Collections.singleton; -import static org.openrewrite.Tree.randomId; public class UseVarForGenericsConstructors extends Recipe { @Override @@ -87,12 +83,20 @@ public J.VariableDeclarations visitVariableDeclarations(J.VariableDeclarations v return vd; } - // Mark imports for removal if unused if (vd.getType() instanceof JavaType.FullyQualified) { maybeRemoveImport((JavaType.FullyQualified) vd.getType()); } - return transformToVar(vd, leftTypes, rightTypes); + J.VariableDeclarations finalVd = vd; + return DeclarationCheck.transformToVar(vd, it -> { + // If left is defined but right is not, copy types from typeExpression to initializer + if (rightTypes.isEmpty() && !leftTypes.isEmpty() && finalVd.getTypeExpression() instanceof J.ParameterizedType && it.getClazz() instanceof J.ParameterizedType) { + J.ParameterizedType typedInitializerClazz = ((J.ParameterizedType) it.getClazz()) + .withTypeParameters(((J.ParameterizedType) finalVd.getTypeExpression()).getTypeParameters()); + return it.withClazz(typedInitializerClazz); + } + return it; + }); } private static Boolean anyTypeHasBounds(List leftTypes) { @@ -141,30 +145,5 @@ private List extractTypeParameters(JavaType.@Nullable Variable variabl } return new ArrayList<>(); } - - private J.VariableDeclarations transformToVar(J.VariableDeclarations vd, List leftTypes, List rightTypes) { - J.NewClass initializer = Objects.requireNonNull((J.NewClass) vd.getVariables().get(0).getInitializer()); - - // If left is defined but right is not, copy types from typeExpression to initializer - if (rightTypes.isEmpty() && !leftTypes.isEmpty() && vd.getTypeExpression() instanceof J.ParameterizedType && initializer.getClazz() instanceof J.ParameterizedType) { - J.ParameterizedType typedInitializerClazz = ((J.ParameterizedType) initializer.getClazz()) - .withTypeParameters(((J.ParameterizedType) vd.getTypeExpression()).getTypeParameters()); - initializer = initializer.withClazz(typedInitializerClazz); - } - - // Replace actual type by `var` keyword and replace the first variable's name, initializer and type - J.NewClass finalInitializer = initializer; - List variables = ListUtils.mapFirst(vd.getVariables(), it -> { - JavaType.Variable variableType = it.getVariableType() == null ? null : it.getVariableType().withOwner(null); - return it - .withName(it.getName().withType(finalInitializer.getType()).withFieldType(variableType)) - .withInitializer(finalInitializer) - .withVariableType(variableType); - }); - J.Identifier typeExpression = new J.Identifier(randomId(), vd.getTypeExpression().getPrefix(), - Markers.build(singleton(JavaVarKeyword.build())), emptyList(), "var", initializer.getType(), null); - - return vd.withVariables(variables).withTypeExpression(typeExpression); - } } } diff --git a/src/main/java/org/openrewrite/java/migrate/lang/var/UseVarForObject.java b/src/main/java/org/openrewrite/java/migrate/lang/var/UseVarForObject.java index 1b2d9b1feb..1f3de88cd0 100644 --- a/src/main/java/org/openrewrite/java/migrate/lang/var/UseVarForObject.java +++ b/src/main/java/org/openrewrite/java/migrate/lang/var/UseVarForObject.java @@ -22,13 +22,10 @@ import org.openrewrite.Recipe; import org.openrewrite.TreeVisitor; import org.openrewrite.java.JavaIsoVisitor; -import org.openrewrite.java.JavaParser; -import org.openrewrite.java.JavaTemplate; 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.TypeTree; @EqualsAndHashCode(callSuper = false) @Value @@ -58,11 +55,6 @@ public TreeVisitor getVisitor() { static final class UseVarForObjectVisitor extends JavaIsoVisitor { - private final JavaTemplate template = JavaTemplate.builder("var #{} = #{any()};") - .contextSensitive() - .javaParser(JavaParser.fromJavaVersion()).build(); - - @Override public J.VariableDeclarations visitVariableDeclarations(J.VariableDeclarations vd, ExecutionContext ctx) { vd = super.visitVariableDeclarations(vd, ctx); @@ -82,30 +74,11 @@ public J.VariableDeclarations visitVariableDeclarations(J.VariableDeclarations v return vd; } - // mark imports for removal if unused if (vd.getType() instanceof JavaType.FullyQualified) { maybeRemoveImport( (JavaType.FullyQualified) vd.getType() ); } - return transformToVar(vd); - } - - - private J.VariableDeclarations transformToVar(J.VariableDeclarations vd) { - Expression initializer = vd.getVariables().get(0).getInitializer(); - String simpleName = vd.getVariables().get(0).getSimpleName(); - - if (vd.getModifiers().isEmpty()) { - return template.apply(getCursor(), vd.getCoordinates().replace(), simpleName, initializer) - .withPrefix(vd.getPrefix()); - } else { - J.VariableDeclarations result = template.apply(getCursor(), vd.getCoordinates().replace(), simpleName, initializer) - .withModifiers(vd.getModifiers()) - .withPrefix(vd.getPrefix()); - TypeTree typeExpression = result.getTypeExpression(); - //noinspection DataFlowIssue - return typeExpression != null ? result.withTypeExpression(typeExpression.withPrefix(vd.getTypeExpression().getPrefix())) : vd; - } + return DeclarationCheck.transformToVar(vd); } } } diff --git a/src/main/java/org/openrewrite/java/migrate/lang/var/UseVarForPrimitive.java b/src/main/java/org/openrewrite/java/migrate/lang/var/UseVarForPrimitive.java index ac15dc2c6e..03a30ae964 100644 --- a/src/main/java/org/openrewrite/java/migrate/lang/var/UseVarForPrimitive.java +++ b/src/main/java/org/openrewrite/java/migrate/lang/var/UseVarForPrimitive.java @@ -22,14 +22,12 @@ import org.openrewrite.Recipe; import org.openrewrite.TreeVisitor; import org.openrewrite.java.JavaIsoVisitor; -import org.openrewrite.java.JavaParser; -import org.openrewrite.java.JavaTemplate; 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 static java.lang.String.format; +import static org.openrewrite.java.tree.JavaType.Primitive.*; @EqualsAndHashCode(callSuper = false) @Value @@ -58,14 +56,6 @@ public TreeVisitor getVisitor() { } static final class VarForPrimitivesVisitor extends JavaIsoVisitor { - - private final JavaType.Primitive SHORT_TYPE = JavaType.Primitive.Short; - private final JavaType.Primitive BYTE_TYPE = JavaType.Primitive.Byte; - - private final JavaTemplate template = JavaTemplate.builder("var #{} = #{any()}") - .javaParser(JavaParser.fromJavaVersion()).build(); - - @Override public J.VariableDeclarations visitVariableDeclarations(J.VariableDeclarations vd, ExecutionContext ctx) { vd = super.visitVariableDeclarations(vd, ctx); @@ -75,41 +65,18 @@ public J.VariableDeclarations visitVariableDeclarations(J.VariableDeclarations v return vd; } - // recipe specific + // Recipe specific boolean isNoPrimitive = !DeclarationCheck.isPrimitive(vd); - boolean isByteVariable = DeclarationCheck.declarationHasType(vd, BYTE_TYPE); - boolean isShortVariable = DeclarationCheck.declarationHasType(vd, SHORT_TYPE); + boolean isByteVariable = DeclarationCheck.declarationHasType(vd, Byte); + boolean isShortVariable = DeclarationCheck.declarationHasType(vd, Short); if (isNoPrimitive || isByteVariable || isShortVariable) { return vd; } - // no need to remove imports, because primitives are never imported - - return transformToVar(vd); + J.VariableDeclarations finalVd = vd; + return DeclarationCheck.transformToVar(vd, it -> it instanceof J.Literal ? expandWithPrimitivTypeHint(finalVd, it) : it); } - - private J.VariableDeclarations transformToVar(J.VariableDeclarations vd) { - Expression initializer = vd.getVariables().get(0).getInitializer(); - String simpleName = vd.getVariables().get(0).getSimpleName(); - - if (initializer instanceof J.Literal) { - initializer = expandWithPrimitivTypeHint(vd, initializer); - } - - if (vd.getModifiers().isEmpty()) { - return template.apply(getCursor(), vd.getCoordinates().replace(), simpleName, initializer) - .withPrefix(vd.getPrefix()); - } else { - J.VariableDeclarations result = template.apply(getCursor(), vd.getCoordinates().replace(), simpleName, initializer) - .withModifiers(vd.getModifiers()) - .withPrefix(vd.getPrefix()); - //noinspection DataFlowIssue - return result.withTypeExpression(result.getTypeExpression().withPrefix(vd.getTypeExpression().getPrefix())); - } - } - - private Expression expandWithPrimitivTypeHint(J.VariableDeclarations vd, Expression initializer) { String valueSource = ((J.Literal) initializer).getValueSource(); @@ -117,11 +84,11 @@ private Expression expandWithPrimitivTypeHint(J.VariableDeclarations vd, Express return initializer; } - boolean isLongLiteral = JavaType.Primitive.Long == vd.getType(); + boolean isLongLiteral = Long == vd.getType(); boolean inferredAsLong = valueSource.endsWith("l") || valueSource.endsWith("L"); - boolean isFloatLiteral = JavaType.Primitive.Float == vd.getType(); + boolean isFloatLiteral = Float == vd.getType(); boolean inferredAsFloat = valueSource.endsWith("f") || valueSource.endsWith("F"); - boolean isDoubleLiteral = JavaType.Primitive.Double == vd.getType(); + boolean isDoubleLiteral = Double == vd.getType(); boolean inferredAsDouble = valueSource.endsWith("d") || valueSource.endsWith("D") || valueSource.contains("."); String typNotation = null; diff --git a/src/test/java/org/openrewrite/java/migrate/lang/var/UseVarForGenericMethodInvocationsTest.java b/src/test/java/org/openrewrite/java/migrate/lang/var/UseVarForGenericMethodInvocationsTest.java index a71e35514a..78d830b959 100644 --- a/src/test/java/org/openrewrite/java/migrate/lang/var/UseVarForGenericMethodInvocationsTest.java +++ b/src/test/java/org/openrewrite/java/migrate/lang/var/UseVarForGenericMethodInvocationsTest.java @@ -85,7 +85,7 @@ void m() { @Test void withEmptyOnwNonStaticFactoryMethods() { - //if detectable this could be `var strs = this.myList();` + // TODO: this could be `var strs = this.myList();` //language=java rewriteRun( version( @@ -110,7 +110,7 @@ void m() { @Test void withEmptyOnwFactoryMethods() { - // if detectable this could be `var strs = A.myList();` + // TODO: this could be `var strs = A.myList();` //language=java rewriteRun( version( @@ -135,7 +135,7 @@ void m() { @Test void forEmptyJDKFactoryMethod() { - // if detectable this could be `var strs = List.of();` + // TODO: this could be `var strs = List.of();` //language=java rewriteRun( version(