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
Expand Up @@ -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.
* <p>
* Var is applicable inside methods and initializer blocks for single variable definition.
* Var is *not* applicable to method definitions.
Expand Down Expand Up @@ -119,7 +126,7 @@ public static boolean useGenerics(J.VariableDeclarations vd) {
}

/**
* Determin if the initilizer uses the ternary operator <code>Expression ? if-then : else</code>
* Determine if the initializer uses the ternary operator <code>Expression ? if-then : else</code>
*
* @param vd variable declaration at hand
* @return true iff the ternary operator is used in the initialization
Expand Down Expand Up @@ -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 <T extends Expression> J.VariableDeclarations transformToVar(J.VariableDeclarations vd, UnaryOperator<T> transformerInitializer) {
T initializer = (T) vd.getVariables().get(0).getInitializer();
if (initializer == null) {
return vd;
}

Expression transformedInitializer = transformerInitializer.apply(initializer);

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(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);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -50,9 +47,6 @@ public TreeVisitor<?, ExecutionContext> getVisitor() {
}

static final class UseVarForGenericsVisitor extends JavaIsoVisitor<ExecutionContext> {
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);
Expand All @@ -62,34 +56,42 @@ 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);
if (isPrimitive || usesTernary || usesNoGenerics) {
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.<String>of();`
/*J.VariableDeclarations finalVd = vd;
return DeclarationCheck.<J.MethodInvocation>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) {
Expand All @@ -100,40 +102,5 @@ private static boolean allArgumentsEmpty(J.MethodInvocation invocation) {
}
return true;
}

private J.VariableDeclarations transformToVar(J.VariableDeclarations vd, List<JavaType> leftTypes, List<JavaType> 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<Expression> 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.<J.VariableDeclarations>apply(getCursor(), vd.getCoordinates().replace(), simpleName, initializer)
.withPrefix(vd.getPrefix());

// apply modifiers like final
List<J.Modifier> 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;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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.<J.NewClass>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<JavaType> leftTypes) {
Expand Down Expand Up @@ -141,30 +145,5 @@ private List<JavaType> extractTypeParameters(JavaType.@Nullable Variable variabl
}
return new ArrayList<>();
}

private J.VariableDeclarations transformToVar(J.VariableDeclarations vd, List<JavaType> leftTypes, List<JavaType> 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<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(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);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -58,11 +55,6 @@ public TreeVisitor<?, ExecutionContext> getVisitor() {


static final class UseVarForObjectVisitor extends JavaIsoVisitor<ExecutionContext> {
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);
Expand All @@ -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.<J.VariableDeclarations>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);
}
}
}
Loading