No rewrite-static-analysis dependency. Adopt latest Apache license OR

This commit is contained in:
aboyko
2024-12-17 10:20:16 -05:00
parent f66a5f3c2b
commit 1c642049ff
18 changed files with 4269 additions and 14 deletions

View File

@@ -28,14 +28,6 @@
<version>${project.version}</version>
</dependency>
<!-- ObjectDiff object becomes unresolved all of a sudden for JDT compiler as of rewrite 7.32.1. The dependency should come from rewrite -->
<!-- Try to remove in the future as it only seems to be required by Eclipse JDT. Maven build is happy without it -->
<dependency>
<groupId>de.danielbechler</groupId>
<artifactId>java-object-diff</artifactId>
<version>0.95</version>
</dependency>
<dependency>
<groupId>io.github.classgraph</groupId>
<artifactId>classgraph</artifactId>
@@ -58,10 +50,6 @@
<groupId>org.openrewrite</groupId>
<artifactId>rewrite-groovy</artifactId>
</dependency>
<dependency>
<groupId>org.openrewrite</groupId>
<artifactId>rewrite-kotlin</artifactId>
</dependency>
<dependency>
<groupId>org.openrewrite</groupId>
<artifactId>rewrite-gradle</artifactId>
@@ -113,6 +101,12 @@
<dependency>
<groupId>org.openrewrite.recipe</groupId>
<artifactId>rewrite-spring</artifactId>
<exclusions>
<exclusion>
<groupId>org.openrewrite.recipe</groupId>
<artifactId>rewrite-static-analysis</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
@@ -133,6 +127,11 @@
<version>${project.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.openrewrite</groupId>
<artifactId>rewrite-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

View File

@@ -0,0 +1,93 @@
/*
* Copyright 2024 the original author or authors.
* <p>
* Licensed under the Apache License, Version 2.0 (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://www.apache.org/licenses/LICENSE-2.0
* <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.staticanalysis;
import org.jspecify.annotations.NonNull;
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.JavaTemplate;
import org.openrewrite.java.search.FindAnnotations;
import org.openrewrite.java.search.UsesJavaVersion;
import org.openrewrite.java.search.UsesType;
import org.openrewrite.java.tree.J;
import org.openrewrite.java.tree.JavaType;
import org.openrewrite.java.tree.TypeUtils;
import java.time.Duration;
import java.util.Comparator;
public class AddSerialAnnotationToSerialVersionUID extends Recipe {
@Override
public String getDisplayName() {
return "Add `@Serial` annotation to `serialVersionUID`";
}
@Override
public String getDescription() {
return "Annotation any `serialVersionUID` fields with `@Serial` to indicate it's part of the serialization mechanism.";
}
@Override
public Duration getEstimatedEffortPerOccurrence() {
return Duration.ofMinutes(1);
}
@Override
@NonNull
public TreeVisitor<?, ExecutionContext> getVisitor() {
return Preconditions.check(
Preconditions.and(
new UsesJavaVersion<>(14),
new UsesType<>("java.io.Serializable", true)
),
new JavaIsoVisitor<ExecutionContext>() {
@Override
public J.ClassDeclaration visitClassDeclaration(J.ClassDeclaration classDecl, ExecutionContext ctx) {
if (TypeUtils.isAssignableTo("java.io.Serializable", classDecl.getType())) {
return super.visitClassDeclaration(classDecl, ctx);
}
return classDecl;
}
@Override
public J.VariableDeclarations visitVariableDeclarations(J.VariableDeclarations multiVariable, ExecutionContext ctx) {
J.VariableDeclarations vd = super.visitVariableDeclarations(multiVariable, ctx);
if (isPrivateStaticFinalLongSerialVersionUID(vd) &&
FindAnnotations.find(vd, "@java.io.Serial").isEmpty()) {
maybeAddImport("java.io.Serial");
return JavaTemplate.builder("@Serial")
.imports("java.io.Serial")
.build()
.apply(getCursor(), vd.getCoordinates().addAnnotation(Comparator.comparing(J.Annotation::getSimpleName)));
}
return vd;
}
private boolean isPrivateStaticFinalLongSerialVersionUID(J.VariableDeclarations vd) {
return vd.hasModifier(J.Modifier.Type.Private) &&
vd.hasModifier(J.Modifier.Type.Static) &&
vd.hasModifier(J.Modifier.Type.Final) &&
TypeUtils.asPrimitive(vd.getType()) == JavaType.Primitive.Long &&
vd.getVariables().size() == 1 &&
"serialVersionUID".equals(vd.getVariables().get(0).getSimpleName());
}
}
);
}
}

View File

@@ -0,0 +1,149 @@
/*
* Copyright 2021 the original author or authors.
* <p>
* Licensed under the Apache License, Version 2.0 (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://www.apache.org/licenses/LICENSE-2.0
* <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.staticanalysis;
import org.jspecify.annotations.Nullable;
import org.openrewrite.*;
import org.openrewrite.internal.ListUtils;
import org.openrewrite.java.JavaIsoVisitor;
import org.openrewrite.java.JavaTemplate;
import org.openrewrite.java.tree.J;
import org.openrewrite.java.tree.JavaType;
import org.openrewrite.java.tree.Space;
import org.openrewrite.java.tree.TypeUtils;
import org.openrewrite.marker.Markers;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;
public class AddSerialVersionUidToSerializable extends Recipe {
@Override
public String getDisplayName() {
return "Add `serialVersionUID` to a `Serializable` class when missing";
}
@Override
public String getDescription() {
return "A `serialVersionUID` field is strongly recommended in all `Serializable` classes. If this is not " +
"defined on a `Serializable` class, the compiler will generate this value. If a change is later made " +
"to the class, the generated value will change and attempts to deserialize the class will fail.";
}
@Override
public Set<String> getTags() {
return Collections.singleton("RSPEC-S2057");
}
@Override
public TreeVisitor<?, ExecutionContext> getVisitor() {
return new JavaIsoVisitor<ExecutionContext>() {
final JavaTemplate template = JavaTemplate.builder("private static final long serialVersionUID = 1;").build();
@Override
public J.MethodDeclaration visitMethodDeclaration(J.MethodDeclaration method, ExecutionContext ctx) {
// Anonymous classes are not of interest
return method;
}
@Override
public J.VariableDeclarations visitVariableDeclarations(J.VariableDeclarations multiVariable, ExecutionContext ctx) {
// Anonymous classes are not of interest
return multiVariable;
}
@Override
public J.ClassDeclaration visitClassDeclaration(J.ClassDeclaration classDecl, ExecutionContext ctx) {
J.ClassDeclaration c = super.visitClassDeclaration(classDecl, ctx);
if (c.getKind() != J.ClassDeclaration.Kind.Type.Class || !requiresSerialVersionField(classDecl.getType())) {
return c;
}
AtomicBoolean needsSerialVersionId = new AtomicBoolean(true);
J.Block body = c.getBody();
c = c.withBody(c.getBody().withStatements(ListUtils.map(c.getBody().getStatements(), s -> {
if (!(s instanceof J.VariableDeclarations)) {
return s;
}
J.VariableDeclarations varDecls = (J.VariableDeclarations) s;
for (J.VariableDeclarations.NamedVariable v : varDecls.getVariables()) {
if ("serialVersionUID".equals(v.getSimpleName())) {
needsSerialVersionId.set(false);
return maybeAutoFormat(varDecls, maybeFixVariableDeclarations(varDecls), ctx, new Cursor(getCursor(), body));
}
}
return s;
})));
if (needsSerialVersionId.get()) {
c = template.apply(updateCursor(c), c.getBody().getCoordinates().firstStatement());
}
return c;
}
private J.VariableDeclarations maybeFixVariableDeclarations(J.VariableDeclarations varDecls) {
List<J.Modifier> modifiers = varDecls.getModifiers();
if (!J.Modifier.hasModifier(modifiers, J.Modifier.Type.Private) ||
!J.Modifier.hasModifier(modifiers, J.Modifier.Type.Static) ||
!J.Modifier.hasModifier(modifiers, J.Modifier.Type.Final)) {
varDecls = varDecls.withModifiers(Arrays.asList(
new J.Modifier(Tree.randomId(), Space.EMPTY, Markers.EMPTY, null, J.Modifier.Type.Private, Collections.emptyList()),
new J.Modifier(Tree.randomId(), Space.SINGLE_SPACE, Markers.EMPTY, null, J.Modifier.Type.Static, Collections.emptyList()),
new J.Modifier(Tree.randomId(), Space.SINGLE_SPACE, Markers.EMPTY, null, J.Modifier.Type.Final, Collections.emptyList())
));
}
if (TypeUtils.asPrimitive(varDecls.getType()) != JavaType.Primitive.Long) {
varDecls = varDecls.withTypeExpression(new J.Primitive(Tree.randomId(), Space.EMPTY, Markers.EMPTY, JavaType.Primitive.Long));
}
return varDecls;
}
private boolean requiresSerialVersionField(@Nullable JavaType type) {
if (type == null) {
return false;
} else if (type instanceof JavaType.Primitive) {
return true;
} else if (type instanceof JavaType.Array) {
return requiresSerialVersionField(((JavaType.Array) type).getElemType());
} else if (type instanceof JavaType.Parameterized) {
JavaType.Parameterized parameterized = (JavaType.Parameterized) type;
if (parameterized.isAssignableTo("java.util.Collection") || parameterized.isAssignableTo("java.util.Map")) {
//If the type is either a collection or a map, make sure the type parameters are serializable. We
//force all type parameters to be checked to correctly scoop up all non-serializable candidates.
boolean typeParametersSerializable = true;
for (JavaType typeParameter : parameterized.getTypeParameters()) {
typeParametersSerializable = typeParametersSerializable && requiresSerialVersionField(typeParameter);
}
return typeParametersSerializable;
}
//All other parameterized types fall through
} else if (type instanceof JavaType.FullyQualified) {
JavaType.FullyQualified fq = (JavaType.FullyQualified) type;
if (fq.getKind() == JavaType.Class.Kind.Enum) {
return false;
}
if (fq.getKind() != JavaType.Class.Kind.Interface &&
!fq.isAssignableTo("java.lang.Throwable")) {
return fq.isAssignableTo("java.io.Serializable");
}
}
return false;
}
};
}
}

View File

@@ -0,0 +1,199 @@
/*
* Copyright 2023 the original author or authors.
* <p>
* Licensed under the Apache License, Version 2.0 (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://www.apache.org/licenses/LICENSE-2.0
* <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.staticanalysis;
import org.jspecify.annotations.Nullable;
import org.openrewrite.*;
import org.openrewrite.java.JavaIsoVisitor;
import org.openrewrite.java.JavaParser;
import org.openrewrite.java.MethodMatcher;
import org.openrewrite.java.search.UsesMethod;
import org.openrewrite.java.tree.*;
import java.time.Duration;
import java.util.ArrayList;
import java.util.List;
import static java.util.Collections.emptyList;
import static java.util.Collections.singletonList;
public class ChainStringBuilderAppendCalls extends Recipe {
private static final MethodMatcher STRING_BUILDER_APPEND = new MethodMatcher("java.lang.StringBuilder append(String)");
@SuppressWarnings("ALL") // Stop NoMutableStaticFieldsInRecipes from suggesting to remove this mutable static field
private static J.Binary additiveBinaryTemplate = null;
@Override
public String getDisplayName() {
return "Chain `StringBuilder.append()` calls";
}
@Override
public String getDescription() {
return "String concatenation within calls to `StringBuilder.append()` causes unnecessary memory allocation. Except for concatenations of String literals, which are joined together at compile time. Replaces inefficient concatenations with chained calls to `StringBuilder.append()`.";
}
@Override
public @Nullable Duration getEstimatedEffortPerOccurrence() {
return Duration.ofMinutes(2);
}
@Override
public TreeVisitor<?, ExecutionContext> getVisitor() {
return Preconditions.check(new UsesMethod<>(STRING_BUILDER_APPEND), Repeat.repeatUntilStable(new JavaIsoVisitor<ExecutionContext>() {
@Override
public J.MethodInvocation visitMethodInvocation(J.MethodInvocation method, ExecutionContext ctx) {
J.MethodInvocation m = super.visitMethodInvocation(method, ctx);
if (STRING_BUILDER_APPEND.matches(m)) {
List<Expression> arguments = m.getArguments();
if (arguments.size() != 1) {
return m;
}
List<Expression> flattenExpressions = new ArrayList<>();
boolean flattenable = flatAdditiveExpressions(arguments.get(0).unwrap(), flattenExpressions);
if (!flattenable) {
return m;
}
if (flattenExpressions.stream().allMatch(J.Literal.class::isInstance)) {
return m;
}
// group expressions
List<Expression> groups = new ArrayList<>();
List<Expression> group = new ArrayList<>();
boolean appendToString = false;
for (Expression exp : flattenExpressions) {
if (appendToString) {
if (exp instanceof J.Literal &&
(((J.Literal) exp).getType() == JavaType.Primitive.String)
) {
group.add(exp);
} else {
addToGroups(group, groups);
groups.add(exp);
}
} else {
if (exp instanceof J.Literal &&
(((J.Literal) exp).getType() == JavaType.Primitive.String)) {
addToGroups(group, groups);
appendToString = true;
} else if ((exp instanceof J.Identifier || exp instanceof J.MethodInvocation) && exp.getType() != null) {
JavaType.FullyQualified fullyQualified = TypeUtils.asFullyQualified(exp.getType());
if (fullyQualified != null && fullyQualified.getFullyQualifiedName().equals("java.lang.String")) {
addToGroups(group, groups);
appendToString = true;
}
}
group.add(exp);
}
}
addToGroups(group, groups);
J.MethodInvocation chainedMethods = m.withArguments(singletonList(groups.get(0)));
for (int i = 1; i < groups.size(); i++) {
chainedMethods = chainedMethods.withSelect(chainedMethods)
.withArguments(singletonList(groups.get(i).unwrap()))
.withPrefix(Space.EMPTY);
}
return chainedMethods;
}
return m;
}
}));
}
/**
* Concat two literals to an expression with '+' and surrounded with single space.
*/
public static J.Binary concatAdditionBinary(Expression left, Expression right) {
J.Binary b = getAdditiveBinaryTemplate();
return b.withPrefix(b.getLeft().getPrefix())
.withLeft(left)
.withRight(right.withPrefix(Space.build(" " + right.getPrefix().getWhitespace(), emptyList())));
}
/**
* Concat expressions to an expression with '+' connected.
*/
public static @Nullable Expression additiveExpression(Expression... expressions) {
Expression expression = null;
for (Expression element : expressions) {
if (element != null) {
expression = (expression == null) ? element : concatAdditionBinary(expression, element);
}
}
return expression;
}
public static @Nullable Expression additiveExpression(List<Expression> expressions) {
return additiveExpression(expressions.toArray(new Expression[0]));
}
public static J.Binary getAdditiveBinaryTemplate() {
if (additiveBinaryTemplate == null) {
//noinspection OptionalGetWithoutIsPresent
J.CompilationUnit cu = JavaParser.fromJavaVersion()
.build()
.parse("class A { String s = \"A\" + \"B\";}")
.map(J.CompilationUnit.class::cast)
.findFirst()
.get();
additiveBinaryTemplate = (J.Binary) ((J.VariableDeclarations) cu.getClasses().get(0)
.getBody()
.getStatements().get(0))
.getVariables().get(0)
.getInitializer();
assert additiveBinaryTemplate != null;
}
return additiveBinaryTemplate;
}
/**
* Concat an additive expression in a group and add to groups
*/
private static void addToGroups(List<Expression> group, List<Expression> groups) {
if (!group.isEmpty()) {
groups.add(additiveExpression(group));
group.clear();
}
}
public static boolean flatAdditiveExpressions(Expression expression, List<Expression> expressionList) {
if (expression instanceof J.Binary) {
J.Binary b = (J.Binary) expression;
if (b.getOperator() != J.Binary.Type.Addition) {
return false;
}
return flatAdditiveExpressions(b.getLeft(), expressionList) &&
flatAdditiveExpressions(b.getRight(), expressionList);
} else if (expression instanceof J.Literal ||
expression instanceof J.Identifier ||
expression instanceof J.MethodInvocation ||
expression instanceof J.Parentheses) {
expressionList.add(expression.withPrefix(Space.EMPTY));
return true;
}
return false;
}
}

View File

@@ -0,0 +1,527 @@
/*
* Copyright 2021 the original author or authors.
* <p>
* Licensed under the Apache License, Version 2.0 (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://www.apache.org/licenses/LICENSE-2.0
* <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.staticanalysis;
import org.jspecify.annotations.Nullable;
import org.openrewrite.*;
import org.openrewrite.java.JavaVisitor;
import org.openrewrite.java.VariableNameUtils;
import org.openrewrite.java.search.SemanticallyEqual;
import org.openrewrite.java.search.UsesJavaVersion;
import org.openrewrite.java.tree.*;
import org.openrewrite.java.tree.J.VariableDeclarations.NamedVariable;
import org.openrewrite.marker.Markers;
import org.openrewrite.staticanalysis.groovy.GroovyFileChecker;
import org.openrewrite.staticanalysis.kotlin.KotlinFileChecker;
import java.time.Duration;
import java.util.*;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import static java.util.Collections.emptyList;
import static java.util.Objects.requireNonNull;
import static org.openrewrite.Tree.randomId;
import static org.openrewrite.java.VariableNameUtils.GenerationStrategy.INCREMENT_NUMBER;
public class InstanceOfPatternMatch extends Recipe {
@Override
public String getDisplayName() {
return "Changes code to use Java 17's `instanceof` pattern matching";
}
@Override
public String getDescription() {
return "Adds pattern variables to `instanceof` expressions wherever the same (side effect free) expression is referenced in a corresponding type cast expression within the flow scope of the `instanceof`." +
" Currently, this recipe supports `if` statements and ternary operator expressions.";
}
@Override
public Duration getEstimatedEffortPerOccurrence() {
return Duration.ofMinutes(1);
}
@Override
public TreeVisitor<?, ExecutionContext> getVisitor() {
TreeVisitor<?, ExecutionContext> preconditions = Preconditions.and(
new UsesJavaVersion<>(17),
Preconditions.not(new KotlinFileChecker<>()),
Preconditions.not(new GroovyFileChecker<>())
);
return Preconditions.check(preconditions, new JavaVisitor<ExecutionContext>() {
@Override
public @Nullable J postVisit(J tree, ExecutionContext ctx) {
J result = super.postVisit(tree, ctx);
InstanceOfPatternReplacements original = getCursor().getMessage("flowTypeScope");
if (original != null && !original.isEmpty()) {
return UseInstanceOfPatternMatching.refactor(result, original, getCursor().getParentOrThrow());
}
return result;
}
@Override
public J.InstanceOf visitInstanceOf(J.InstanceOf instanceOf, ExecutionContext ctx) {
instanceOf = (J.InstanceOf) super.visitInstanceOf(instanceOf, ctx);
if (instanceOf.getPattern() != null || !instanceOf.getSideEffects().isEmpty()) {
return instanceOf;
}
Cursor maybeReplacementRoot = null;
J additionalContext = null;
boolean flowScopeBreakEncountered = false;
for (Iterator<Cursor> it = getCursor().getPathAsCursors(); it.hasNext(); ) {
Cursor next = it.next();
Object value = next.getValue();
if (value instanceof J.Binary) {
J.Binary binary = (J.Binary) value;
if (!flowScopeBreakEncountered && binary.getOperator() == J.Binary.Type.And) {
additionalContext = binary;
} else {
flowScopeBreakEncountered = true;
}
} else if (value instanceof J.Unary && ((J.Unary) value).getOperator() == J.Unary.Type.Not) {
// TODO this could be improved (the pattern variable may be applicable in the else case
// or even in subsequent statements (due to the flow scope semantics)
flowScopeBreakEncountered = true;
} else if (value instanceof Statement) {
maybeReplacementRoot = next;
break;
}
}
if (maybeReplacementRoot != null) {
J root = maybeReplacementRoot.getValue();
Set<J> contexts = new HashSet<>();
if (!flowScopeBreakEncountered) {
if (root instanceof J.If) {
contexts.add(((J.If) root).getThenPart());
} else if (root instanceof J.Ternary) {
contexts.add(((J.Ternary) root).getTruePart());
}
}
if (additionalContext != null) {
contexts.add(additionalContext);
}
if (!contexts.isEmpty()) {
InstanceOfPatternReplacements replacements = maybeReplacementRoot
.computeMessageIfAbsent("flowTypeScope", k -> new InstanceOfPatternReplacements(root));
replacements.registerInstanceOf(instanceOf, contexts);
}
}
return instanceOf;
}
@Override
public J visitTypeCast(J.TypeCast typeCast, ExecutionContext ctx) {
J result = super.visitTypeCast(typeCast, ctx);
if (result instanceof J.TypeCast) {
InstanceOfPatternReplacements replacements = getCursor().getNearestMessage("flowTypeScope");
if (replacements != null) {
replacements.registerTypeCast((J.TypeCast) result, getCursor());
}
}
return result;
}
});
}
private static class ExpressionAndType {
private final Expression expression;
private final JavaType type;
public ExpressionAndType(Expression expression, JavaType type) {
this.expression = expression;
this.type = type;
}
public Expression getExpression() {
return expression;
}
public JavaType getType() {
return type;
}
}
private static class VariableAndTypeTree {
private final J.VariableDeclarations.NamedVariable variable;
private final TypeTree type;
public VariableAndTypeTree(NamedVariable variable, TypeTree type) {
this.variable = variable;
this.type = type;
}
public J.VariableDeclarations.NamedVariable getVariable() {
return variable;
}
public TypeTree getType() {
return type;
}
}
private static class InstanceOfPatternReplacements {
private final J root;
private final Map<ExpressionAndType, J.InstanceOf> instanceOfs = new HashMap<>();
private final Map<J.InstanceOf, Set<J>> contexts = new HashMap<>();
private final Map<J.InstanceOf, Set<Cursor>> contextScopes = new HashMap<>();
private final Map<J.TypeCast, J.InstanceOf> replacements = new HashMap<>();
private final Map<J.InstanceOf, VariableAndTypeTree> variablesToDelete = new HashMap<>();
public InstanceOfPatternReplacements(J root) {
this.root = root;
}
public void registerInstanceOf(J.InstanceOf instanceOf, Set<J> contexts) {
Expression expression = instanceOf.getExpression();
JavaType type = ((TypedTree) instanceOf.getClazz()).getType();
if (type == null) {
return;
}
Optional<ExpressionAndType> existing = instanceOfs.keySet().stream()
.filter(k -> TypeUtils.isAssignableTo(type, k.getType()) &&
SemanticallyEqual.areEqual(k.getExpression(), expression))
.findAny();
if (!existing.isPresent()) {
instanceOfs.put(new ExpressionAndType(expression, type), instanceOf);
this.contexts.put(instanceOf, contexts);
}
}
public void registerTypeCast(J.TypeCast typeCast, Cursor cursor) {
Expression expression = typeCast.getExpression();
JavaType type = typeCast.getClazz().getTree().getType();
Optional<ExpressionAndType> match = instanceOfs.keySet().stream()
.filter(k -> TypeUtils.isAssignableTo(type, k.getType()) &&
SemanticallyEqual.areEqual(k.getExpression(), expression))
.findAny();
if (match.isPresent()) {
Cursor parent = cursor.getParentTreeCursor();
J.InstanceOf instanceOf = instanceOfs.get(match.get());
Set<J> validContexts = contexts.get(instanceOf);
for (Iterator<?> it = cursor.getPath(); it.hasNext(); ) {
Object next = it.next();
if (validContexts.contains(next)) {
if (isAcceptableTypeCast(typeCast) && isTheSameAsOtherTypeCasts(typeCast, instanceOf)) {
if (parent.getValue() instanceof J.VariableDeclarations.NamedVariable &&
!variablesToDelete.containsKey(instanceOf)) {
variablesToDelete.put(instanceOf, new VariableAndTypeTree(parent.getValue(),
requireNonNull(parent.firstEnclosing(J.VariableDeclarations.class).getTypeExpression())));
} else {
replacements.put(typeCast, instanceOf);
}
contextScopes.computeIfAbsent(instanceOf, k -> new HashSet<>()).add(cursor);
} else {
replacements.entrySet().removeIf(e -> e.getValue() == instanceOf);
variablesToDelete.remove(instanceOf);
contextScopes.remove(instanceOf);
contexts.remove(instanceOf);
instanceOfs.entrySet().removeIf(e -> e.getValue() == instanceOf);
}
break;
} else if (root == next) {
break;
}
}
}
}
private boolean isAcceptableTypeCast(J.TypeCast typeCast) {
TypeTree typeTree = typeCast.getClazz().getTree();
if (typeTree instanceof J.ParameterizedType) {
return requireNonNull(((J.ParameterizedType) typeTree).getTypeParameters()).stream().allMatch(J.Wildcard.class::isInstance);
}
return true;
}
private boolean isTheSameAsOtherTypeCasts(J.TypeCast typeCast, J.InstanceOf instanceOf) {
return replacements
.entrySet()
.stream()
.filter(e -> e.getValue() == instanceOf)
.findFirst()
.map(e -> e.getKey().getType().equals(typeCast.getType()))
.orElse(true);
}
public boolean isEmpty() {
return replacements.isEmpty() && variablesToDelete.isEmpty();
}
public J.InstanceOf processInstanceOf(J.InstanceOf instanceOf, Cursor cursor) {
if (!contextScopes.containsKey(instanceOf)) {
return instanceOf;
}
JavaType type = ((TypedTree) instanceOf.getClazz()).getType();
String name = patternVariableName(instanceOf, cursor);
J.InstanceOf result = instanceOf.withPattern(new J.Identifier(
randomId(),
Space.build(" ", emptyList()),
Markers.EMPTY,
emptyList(),
name,
type,
null));
J currentTypeTree = instanceOf.getClazz();
TypeTree typeCastTypeTree = computeTypeTreeFromTypeCasts(instanceOf);
// If type tree from type cast is not parameterized then NVM. Instance of should already have proper type
if (typeCastTypeTree instanceof J.ParameterizedType) {
J.ParameterizedType parameterizedType = (J.ParameterizedType) typeCastTypeTree;
result = result.withClazz(parameterizedType.withId(Tree.randomId()).withPrefix(currentTypeTree.getPrefix()));
}
// update entry in replacements to share the pattern variable name
for (Map.Entry<J.TypeCast, J.InstanceOf> entry : replacements.entrySet()) {
if (entry.getValue() == instanceOf) {
entry.setValue(result);
}
}
return result;
}
private TypeTree computeTypeTreeFromTypeCasts(J.InstanceOf instanceOf) {
TypeTree typeCastTypeTree = replacements
.entrySet()
.stream()
.filter(e -> e.getValue() == instanceOf)
.findFirst()
.map(e -> e.getKey().getClazz().getTree())
.orElse(null);
if (typeCastTypeTree == null) {
VariableAndTypeTree variable = variablesToDelete.get(instanceOf);
if (variable != null) {
typeCastTypeTree = variable.getType();
}
}
return typeCastTypeTree;
}
private String patternVariableName(J.InstanceOf instanceOf, Cursor cursor) {
VariableNameStrategy strategy;
if (root instanceof J.If) {
VariableAndTypeTree variableData = variablesToDelete.get(instanceOf);
strategy = variableData != null ?
VariableNameStrategy.exact(variableData.getVariable().getSimpleName()) :
VariableNameStrategy.normal(contextScopes.get(instanceOf));
} else {
strategy = VariableNameStrategy.short_();
}
String baseName = strategy.variableName(((TypeTree) instanceOf.getClazz()).getType());
return VariableNameUtils.generateVariableName(baseName, cursor, INCREMENT_NUMBER);
}
public @Nullable J processTypeCast(J.TypeCast typeCast, Cursor cursor) {
J.InstanceOf instanceOf = replacements.get(typeCast);
if (instanceOf != null && instanceOf.getPattern() != null) {
String name = ((J.Identifier) instanceOf.getPattern()).getSimpleName();
TypedTree owner = cursor.firstEnclosing(J.MethodDeclaration.class);
owner = owner != null ? owner : cursor.firstEnclosingOrThrow(J.ClassDeclaration.class);
JavaType.Variable fieldType = new JavaType.Variable(null, Flag.Default.getBitMask(), name, owner.getType(), typeCast.getType(), emptyList());
return new J.Identifier(
randomId(),
typeCast.getPrefix(),
Markers.EMPTY,
emptyList(),
name,
typeCast.getType(),
fieldType);
}
return null;
}
public @Nullable J processVariableDeclarations(J.VariableDeclarations multiVariable) {
return multiVariable.getVariables().stream().anyMatch(v -> variablesToDelete.values().stream().anyMatch(vd -> vd.getVariable() == v)) ? null : multiVariable;
}
}
private static class UseInstanceOfPatternMatching extends JavaVisitor<Integer> {
private final InstanceOfPatternReplacements replacements;
public UseInstanceOfPatternMatching(InstanceOfPatternReplacements replacements) {
this.replacements = replacements;
}
static @Nullable J refactor(@Nullable J tree, InstanceOfPatternReplacements replacements, Cursor cursor) {
return new UseInstanceOfPatternMatching(replacements).visit(tree, 0, cursor);
}
@Override
public J visitBinary(J.Binary original, Integer integer) {
Expression newLeft = (Expression) super.visitNonNull(original.getLeft(), integer);
if (newLeft != original.getLeft()) {
// The left side changed, so the right side should see any introduced variable names
J.Binary replacement = original.withLeft(newLeft);
Cursor widenedCursor = updateCursor(replacement);
Expression newRight;
if (original.getRight() instanceof J.InstanceOf) {
newRight = replacements.processInstanceOf((J.InstanceOf) original.getRight(), widenedCursor);
} else if (original.getRight() instanceof J.Parentheses &&
((J.Parentheses<?>) original.getRight()).getTree() instanceof J.InstanceOf) {
@SuppressWarnings("unchecked")
J.Parentheses<J.InstanceOf> originalRight = (J.Parentheses<J.InstanceOf>) original.getRight();
newRight = originalRight.withTree(replacements.processInstanceOf(originalRight.getTree(), widenedCursor));
} else {
newRight = (Expression) super.visitNonNull(original.getRight(), integer, widenedCursor);
}
return replacement.withRight(newRight);
}
// The left side didn't change, so the right side doesn't need to see any introduced variable names
return super.visitBinary(original, integer);
}
@Override
public J.InstanceOf visitInstanceOf(J.InstanceOf instanceOf, Integer executionContext) {
instanceOf = (J.InstanceOf) super.visitInstanceOf(instanceOf, executionContext);
instanceOf = replacements.processInstanceOf(instanceOf, getCursor());
return instanceOf;
}
@Override
public <T extends J> J visitParentheses(J.Parentheses<T> parens, Integer executionContext) {
if (parens.getTree() instanceof J.TypeCast) {
J replacement = replacements.processTypeCast((J.TypeCast) parens.getTree(), getCursor());
if (replacement != null) {
return replacement.withPrefix(parens.getPrefix());
}
}
return super.visitParentheses(parens, executionContext);
}
@Override
public J visitTypeCast(J.TypeCast typeCast, Integer executionContext) {
typeCast = (J.TypeCast) super.visitTypeCast(typeCast, executionContext);
J replacement = replacements.processTypeCast(typeCast, getCursor());
if (replacement != null) {
return replacement;
}
return typeCast;
}
@Override
public @Nullable J visitVariableDeclarations(J.VariableDeclarations multiVariable, Integer integer) {
multiVariable = (J.VariableDeclarations) super.visitVariableDeclarations(multiVariable, integer);
return replacements.processVariableDeclarations(multiVariable);
}
}
private static class VariableNameStrategy {
public static final Pattern NAME_SPLIT_PATTERN = Pattern.compile("[$._]*(?=\\p{Upper}+[\\p{Lower}\\p{Digit}]*)");
private final Style style;
@Nullable
private final String name;
private final Set<Cursor> contextScopes;
enum Style {
SHORT, NORMAL, EXACT
}
private VariableNameStrategy(Style style, @Nullable String exactName, Set<Cursor> contextScopes) {
this.style = style;
this.name = exactName;
this.contextScopes = contextScopes;
}
static VariableNameStrategy short_() {
return new VariableNameStrategy(Style.SHORT, null, Collections.emptySet());
}
static VariableNameStrategy normal(Set<Cursor> contextScopes) {
return new VariableNameStrategy(Style.NORMAL, null, contextScopes);
}
static VariableNameStrategy exact(String name) {
return new VariableNameStrategy(Style.EXACT, name, Collections.emptySet());
}
public String variableName(@Nullable JavaType type) {
// the instanceof operator only accepts classes (without generics) and arrays
if (style == Style.EXACT) {
//noinspection DataFlowIssue
return name;
} else if (type instanceof JavaType.FullyQualified) {
String className = ((JavaType.FullyQualified) type).getClassName();
className = className.substring(className.lastIndexOf('.') + 1);
String baseName = null;
switch (style) {
case SHORT:
StringBuilder builder = new StringBuilder();
for (int i = 0; i < className.length(); i++) {
char c = className.charAt(i);
if (Character.isUpperCase(c)) {
builder.append(Character.toLowerCase(c));
}
}
baseName = builder.length() > 0 ? builder.toString() : "o";
break;
case NORMAL:
Set<String> namesInScope = contextScopes.stream()
.flatMap(c -> VariableNameUtils.findNamesInScope(c).stream())
.collect(Collectors.toSet());
List<String> nameSegments = Stream.of(NAME_SPLIT_PATTERN.split(className))
.filter(s -> !s.isEmpty()).collect(Collectors.toList());
for (int i = nameSegments.size() - 1; i >= 0; i--) {
String name = String.join("", nameSegments.subList(i, nameSegments.size()));
if (name.length() < 2) {
continue;
}
name = Character.toLowerCase(name.charAt(0)) + name.substring(1);
if (!namesInScope.contains(name)) {
baseName = name;
break;
}
}
if (baseName == null) {
baseName = Character.toLowerCase(className.charAt(0)) + className.substring(1);
}
break;
default:
baseName = "obj";
}
String candidate = baseName;
OUTER:
while (true) {
for (Cursor scope : contextScopes) {
String newCandidate = VariableNameUtils.generateVariableName(candidate, scope, INCREMENT_NUMBER);
if (!newCandidate.equals(candidate)) {
candidate = newCandidate;
continue OUTER;
}
}
break;
}
return candidate;
} else if (type instanceof JavaType.Primitive) {
String keyword = ((JavaType.Primitive) type).getKeyword();
return style == Style.SHORT ? keyword.substring(0, 1) : keyword;
} else if (type instanceof JavaType.Array) {
JavaType elemType = ((JavaType.Array) type).getElemType();
while (elemType instanceof JavaType.Array) {
elemType = ((JavaType.Array) elemType).getElemType();
}
return variableName(elemType) + 's';
}
return style == Style.SHORT ? "o" : "obj";
}
}
}

View File

@@ -0,0 +1,100 @@
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//
package org.openrewrite.staticanalysis;
import java.util.List;
import java.util.Objects;
import java.util.stream.Stream;
import org.openrewrite.ExecutionContext;
import org.openrewrite.Incubating;
import org.openrewrite.Recipe;
import org.openrewrite.TreeVisitor;
import org.openrewrite.internal.ListUtils;
import org.openrewrite.java.JavaVisitor;
import org.openrewrite.java.tree.J;
import org.openrewrite.java.tree.Statement;
@Incubating(
since = "7.21.0"
)
public class RemoveUnneededBlock extends Recipe {
public RemoveUnneededBlock() {
}
public String getDisplayName() {
return "Remove unneeded block";
}
public String getDescription() {
return "Flatten blocks into inline statements when possible.";
}
public TreeVisitor<?, ExecutionContext> getVisitor() {
return new RemoveUnneededBlockStatementVisitor();
}
static class RemoveUnneededBlockStatementVisitor extends JavaVisitor<ExecutionContext> {
RemoveUnneededBlockStatementVisitor() {
}
public J.Block visitBlock(J.Block block, ExecutionContext ctx) {
J.Block bl = (J.Block)super.visitBlock(block, ctx);
J directParent = (J)this.getCursor().getParentTreeCursor().getValue();
return !(directParent instanceof J.NewClass) && !(directParent instanceof J.ClassDeclaration) ? this.maybeInlineBlock(bl, ctx) : bl;
}
private J.Block maybeInlineBlock(J.Block block, ExecutionContext ctx) {
List<Statement> statements = block.getStatements();
if (statements.isEmpty()) {
return block;
} else {
Statement lastStatement = (Statement)statements.get(statements.size() - 1);
J.Block flattened = block.withStatements(ListUtils.flatMap(statements, (i, stmt) -> {
J.Block nested;
if (stmt instanceof J.Try) {
J.Try _try = (J.Try)stmt;
if (_try.getResources() != null || !_try.getCatches().isEmpty() || _try.getFinally() == null || !_try.getFinally().getStatements().isEmpty()) {
return stmt;
}
nested = _try.getBody();
} else {
if (!(stmt instanceof J.Block)) {
return stmt;
}
nested = (J.Block)stmt;
}
if (i < statements.size() - 1) {
Stream var10000 = nested.getStatements().stream();
Objects.requireNonNull(J.VariableDeclarations.class);
if (var10000.anyMatch(J.VariableDeclarations.class::isInstance)) {
return stmt;
}
}
return ListUtils.map(nested.getStatements(), (j, inlinedStmt) -> {
if (j == 0) {
inlinedStmt = (Statement)inlinedStmt.withPrefix(inlinedStmt.getPrefix().withComments(ListUtils.concatAll(nested.getComments(), inlinedStmt.getComments())));
}
return (Statement)this.autoFormat(inlinedStmt, ctx, this.getCursor());
});
}));
if (flattened == block) {
return block;
} else {
if (lastStatement instanceof J.Block) {
flattened = flattened.withEnd(flattened.getEnd().withComments(ListUtils.concatAll(((J.Block)lastStatement).getEnd().getComments(), flattened.getEnd().getComments())));
}
return flattened;
}
}
}
}
}

View File

@@ -0,0 +1,138 @@
/*
* Copyright 2023 the original author or authors.
* <p>
* Licensed under the Apache License, Version 2.0 (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://www.apache.org/licenses/LICENSE-2.0
* <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.staticanalysis;
import org.openrewrite.*;
import org.openrewrite.java.JavaIsoVisitor;
import org.openrewrite.java.JavaTemplate;
import org.openrewrite.java.MethodMatcher;
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 java.time.Duration;
import java.util.ArrayList;
import java.util.List;
public class ReplaceDeprecatedRuntimeExecMethods extends Recipe {
private static final MethodMatcher RUNTIME_EXEC_CMD = new MethodMatcher("java.lang.Runtime exec(String)");
private static final MethodMatcher RUNTIME_EXEC_CMD_ENVP = new MethodMatcher("java.lang.Runtime exec(String, String[])");
private static final MethodMatcher RUNTIME_EXEC_CMD_ENVP_FILE = new MethodMatcher("java.lang.Runtime exec(String, String[], java.io.File)");
@Override
public String getDisplayName() {
return "Replace deprecated `Runtime#exec()` methods";
}
@Override
public String getDescription() {
return "Replace `Runtime#exec(String)` methods to use `exec(String[])` instead because the former is deprecated " +
"after Java 18 and is no longer recommended for use by the Java documentation.";
}
@Override
public Duration getEstimatedEffortPerOccurrence() {
return Duration.ofMinutes(3);
}
@Override
public TreeVisitor<?, ExecutionContext> getVisitor() {
return Preconditions.check(new UsesJavaVersion<>(18), new JavaIsoVisitor<ExecutionContext>() {
@Override
public J.MethodInvocation visitMethodInvocation(J.MethodInvocation method, ExecutionContext ctx) {
J.MethodInvocation m = super.visitMethodInvocation(method, ctx);
if (RUNTIME_EXEC_CMD.matches(m) || RUNTIME_EXEC_CMD_ENVP.matches(m) || RUNTIME_EXEC_CMD_ENVP_FILE.matches(m)) {
Expression command = m.getArguments().get(0);
List<Expression> commands = new ArrayList<>();
boolean flattenAble = ChainStringBuilderAppendCalls.flatAdditiveExpressions(command, commands);
StringBuilder sb = new StringBuilder();
if (flattenAble) {
for (Expression e : commands) {
if (e instanceof J.Literal && ((J.Literal) e).getType() == JavaType.Primitive.String) {
sb.append(((J.Literal) e).getValue());
} else {
flattenAble = false;
break;
}
}
}
updateCursor(m);
if (flattenAble) {
String[] cmds = sb.toString().split(" ");
String templateCode = String.format("new String[] {%s}", toStringArguments(cmds));
JavaTemplate template = JavaTemplate.builder(templateCode).build();
List<Expression> args = m.getArguments();
Cursor cursor = new Cursor(getCursor(), args.get(0));
args.set(0, template.apply(cursor, args.get(0).getCoordinates().replace()));
if (m.getMethodType() != null) {
List<JavaType> parameterTypes = m.getMethodType().getParameterTypes();
parameterTypes.set(0, JavaType.ShallowClass.build("java.lang.String[]"));
return m.withArguments(args)
.withMethodType(m.getMethodType().withParameterTypes(parameterTypes));
}
} else {
// replace argument to 'command.split(" ")'
List<Expression> args = m.getArguments();
boolean needWrap = false;
Expression arg0 = args.get(0);
if (!(arg0 instanceof J.Identifier) &&
!(arg0 instanceof J.Literal) &&
!(arg0 instanceof J.MethodInvocation)) {
needWrap = true;
}
String code = needWrap ? "(#{any()}).split(\" \")" : "#{any()}.split(\" \")";
JavaTemplate template = JavaTemplate.builder(code).contextSensitive().build();
Cursor cursor = new Cursor(getCursor(), args.get(0));
arg0 = template.apply(cursor, args.get(0).getCoordinates().replace(), args.get(0));
args.set(0, arg0);
if (m.getMethodType() != null) {
List<JavaType> parameterTypes = m.getMethodType().getParameterTypes();
parameterTypes.set(0, JavaType.ShallowClass.build("java.lang.String[]"));
return m.withArguments(args).withMethodType(m.getMethodType().withParameterTypes(parameterTypes));
}
return m;
}
}
return m;
}
});
}
private static String toStringArguments(String[] cmds) {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < cmds.length; i++) {
String token = cmds[i];
if (i != 0) {
sb.append(", ");
}
sb.append("\"")
.append(token)
.append("\"");
}
return sb.toString();
}
}

View File

@@ -0,0 +1,35 @@
/*
* Copyright 2023 the original author or authors.
* <p>
* Licensed under the Apache License, Version 2.0 (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://www.apache.org/licenses/LICENSE-2.0
* <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.staticanalysis.groovy;
import org.jspecify.annotations.Nullable;
import org.openrewrite.Tree;
import org.openrewrite.TreeVisitor;
import org.openrewrite.groovy.tree.G;
import org.openrewrite.marker.SearchResult;
/**
* Add a search marker if vising a Groovy file
*/
public class GroovyFileChecker<P> extends TreeVisitor<Tree, P> {
@Override
public @Nullable Tree visit(@Nullable Tree tree, P p) {
if (tree instanceof G.CompilationUnit) {
return SearchResult.found(tree);
}
return tree;
}
}

View File

@@ -0,0 +1,40 @@
/*
* Copyright 2023 the original author or authors.
* <p>
* Licensed under the Apache License, Version 2.0 (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://www.apache.org/licenses/LICENSE-2.0
* <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.staticanalysis.kotlin;
import org.jspecify.annotations.Nullable;
import org.openrewrite.Tree;
import org.openrewrite.TreeVisitor;
import org.openrewrite.java.tree.J;
import org.openrewrite.marker.SearchResult;
/**
* Add a search marker if vising a Kotlin file
*/
public class KotlinFileChecker<P> extends TreeVisitor<Tree, P> {
@Override
public @Nullable Tree visit(@Nullable Tree tree, P p) {
if (tree instanceof J.CompilationUnit cu) {
if (cu.getSourcePath() != null ) {
String fileName = cu.getSourcePath().getFileName().toString();
if (fileName.endsWith(".kt") || fileName.endsWith(".kts")) {
return SearchResult.found(cu);
}
}
}
return tree;
}
}

View File

@@ -0,0 +1,34 @@
package org.springframework.ide.vscode.commons.rewrite.java;
import org.openrewrite.ExecutionContext;
import org.openrewrite.NlsRewrite;
import org.openrewrite.Recipe;
import org.openrewrite.TreeVisitor;
import org.openrewrite.java.JavaIsoVisitor;
import org.openrewrite.java.tree.J;
import org.openrewrite.marker.SearchResult;
public class JavaFileChecker extends Recipe {
@Override
public @NlsRewrite.DisplayName String getDisplayName() {
return "Checks if source is a Java file";
}
@Override
public @NlsRewrite.Description String getDescription() {
return "Checks if source is a Java file.";
}
@Override
public TreeVisitor<?, ExecutionContext> getVisitor() {
return new JavaIsoVisitor<ExecutionContext>() {
@Override
public J.CompilationUnit visitCompilationUnit(J.CompilationUnit cu, ExecutionContext executionContext) {
if (cu.getSourcePath() != null && cu.getSourcePath().getFileName().toString().endsWith(".java")) {
return SearchResult.found(cu);
}
return cu;
}
};
}
}

View File

@@ -0,0 +1,192 @@
/*
* Copyright 2024 the original author or authors.
* <p>
* Licensed under the Apache License, Version 2.0 (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://www.apache.org/licenses/LICENSE-2.0
* <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.staticanalysis;
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 AddSerialAnnotationToSerialVersionUIDTest implements RewriteTest {
@Override
public void defaults(RecipeSpec spec) {
spec.recipe(new AddSerialAnnotationToSerialVersionUID())
.allSources(sourceSpec -> sourceSpec.markers(javaVersion(17)));
}
@DocumentExample
@Test
void addSerialAnnotation() {
rewriteRun(
//language=java
java(
"""
import java.io.Serializable;
class Example implements Serializable {
private static final long serialVersionUID = 1L;
}
""",
"""
import java.io.Serial;
import java.io.Serializable;
class Example implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
}
"""
)
);
}
@Test
void shouldAddToNewFieldWhenChained() {
rewriteRun(
spec -> spec.recipes(
new AddSerialVersionUidToSerializable(),
new AddSerialAnnotationToSerialVersionUID()),
//language=java
java(
"""
import java.io.Serializable;
class Example implements Serializable {
}
""",
"""
import java.io.Serial;
import java.io.Serializable;
class Example implements Serializable {
@Serial
private static final long serialVersionUID = 1;
}
"""
)
);
}
@Test
void shouldNoopIfAlreadyPresent() {
rewriteRun(
//language=java
java(
"""
import java.io.Serializable;
import java.io.Serial;
class Example implements Serializable {
String var1 = "first variable";
@Serial
private static final long serialVersionUID = 1L;
int var3 = 666;
}
"""
)
);
}
@Test
void shouldNotAnnotateNonSerializableClass() {
rewriteRun(
//language=java
java(
"""
class Example {
private static final long serialVersionUID = 1L;
}
"""
)
);
}
@Test
void shouldNotAnnotateOnJava11() {
rewriteRun(
//language=java
java(
"""
import java.io.Serializable;
class Example implements Serializable {
private static final long serialVersionUID = 1L;
}
""",
spec -> spec.markers(javaVersion(11))
)
);
}
@Test
void shouldNotAnnotateOtherFields() {
rewriteRun(
//language=java
java(
"""
import java.io.Serializable;
class Example implements Serializable {
static final long serialVersionUID = 1L;
private final long serialVersionUID = 1L;
private static long serialVersionUID = 1L;
private static final int serialVersionUID = 1L;
private static final long foo = 1L;
void doSomething() {
long serialVersionUID = 1L;
}
}
"""
)
);
}
@Test
void shouldAnnotatedFieldsInInnerClasses() {
rewriteRun(
//language=java
java(
"""
import java.io.Serializable;
class Outer implements Serializable {
private static final long serialVersionUID = 1;
static class Inner implements Serializable {
private static final long serialVersionUID = 1;
}
}
""",
"""
import java.io.Serial;
import java.io.Serializable;
class Outer implements Serializable {
@Serial
private static final long serialVersionUID = 1;
static class Inner implements Serializable {
@Serial
private static final long serialVersionUID = 1;
}
}
"""
)
);
}
}

View File

@@ -0,0 +1,275 @@
/*
* Copyright 2021 the original author or authors.
* <p>
* Licensed under the Apache License, Version 2.0 (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://www.apache.org/licenses/LICENSE-2.0
* <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.staticanalysis;
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;
@SuppressWarnings("MissingSerialAnnotation")
class AddSerialVersionUidToSerializableTest implements RewriteTest {
@Override
public void defaults(RecipeSpec spec) {
spec.recipe(new AddSerialVersionUidToSerializable());
}
@Test
void doNothingNotSerializable() {
rewriteRun(
//language=java
java(
"""
public class Example {
private String fred;
private int numberOfFreds;
}
"""
)
);
}
@DocumentExample
@Test
void addSerialVersionUID() {
rewriteRun(
//language=java
java(
"""
import java.io.Serializable;
public class Example implements Serializable {
private String fred;
private int numberOfFreds;
}
""",
"""
import java.io.Serializable;
public class Example implements Serializable {
private static final long serialVersionUID = 1;
private String fred;
private int numberOfFreds;
}
"""
)
);
}
@Test
void fixSerialVersionUIDModifiers() {
rewriteRun(
//language=java
java(
"""
import java.io.Serializable;
public class Example implements Serializable {
private final long serialVersionUID = 1;
private String fred;
private int numberOfFreds;
}
""",
"""
import java.io.Serializable;
public class Example implements Serializable {
private static final long serialVersionUID = 1;
private String fred;
private int numberOfFreds;
}
"""
)
);
}
@Test
void fixSerialVersionUIDNoModifiers() {
rewriteRun(
//language=java
java(
"""
import java.io.Serializable;
public class Example implements Serializable {
long serialVersionUID = 1;
private String fred;
private int numberOfFreds;
}
""",
"""
import java.io.Serializable;
public class Example implements Serializable {
private static final long serialVersionUID = 1;
private String fred;
private int numberOfFreds;
}
"""
)
);
}
@Test
void fixSerialVersionUIDNoModifiersWrongType() {
rewriteRun(
//language=java
java(
"""
import java.io.Serializable;
public class Example implements Serializable {
Long serialVersionUID = 1L;
private String fred;
private int numberOfFreds;
}
""",
"""
import java.io.Serializable;
public class Example implements Serializable {
private static final long serialVersionUID = 1L;
private String fred;
private int numberOfFreds;
}
"""
)
);
}
@Test
void uidAlreadyPresent() {
rewriteRun(
//language=java
java(
"""
import java.io.Serializable;
public class Example implements Serializable {
private static final long serialVersionUID = 1;
private String fred;
private int numberOfFreds;
}
"""
)
);
}
@Test
void methodDeclarationsAreNotVisited() {
rewriteRun(
//language=java
java(
"""
import java.io.Serializable;
public class Example implements Serializable {
private String fred;
private int numberOfFreds;
void doSomething() {
int serialVersionUID = 1;
}
}
""",
"""
import java.io.Serializable;
public class Example implements Serializable {
private static final long serialVersionUID = 1;
private String fred;
private int numberOfFreds;
void doSomething() {
int serialVersionUID = 1;
}
}
"""
)
);
}
@Test
void doNotAlterAnInterface() {
rewriteRun(
//language=java
java(
"""
import java.io.Serializable;
public interface Example extends Serializable {
}
"""
)
);
}
@Test
void doNotAlterAnException() {
rewriteRun(
//language=java
java(
"""
import java.io.Serializable;
public class MyException extends Exception implements Serializable {
}
"""
)
);
}
@Test
void doNotAlterARuntimeException() {
rewriteRun(
//language=java
java(
"""
import java.io.Serializable;
public class MyException extends RuntimeException implements Serializable {
}
"""
)
);
}
@Test
void serializableInnerClass() {
rewriteRun(
//language=java
java(
"""
import java.io.Serializable;
public class Outer implements Serializable {
public static class Inner implements Serializable {
}
}
""",
"""
import java.io.Serializable;
public class Outer implements Serializable {
private static final long serialVersionUID = 1;
public static class Inner implements Serializable {
private static final long serialVersionUID = 1;
}
}
"""
)
);
}
}

View File

@@ -0,0 +1,313 @@
/*
* Copyright 2023 the original author or authors.
* <p>
* Licensed under the Apache License, Version 2.0 (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://www.apache.org/licenses/LICENSE-2.0
* <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.staticanalysis;
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;
@SuppressWarnings({"StringConcatenationInsideStringBufferAppend", "StringBufferReplaceableByString"})
class ChainStringBuilderAppendCallsTest implements RewriteTest {
@Override
public void defaults(RecipeSpec spec) {
spec.recipe(new ChainStringBuilderAppendCalls());
}
@DocumentExample(value = "Chain `StringBuilder.append()` calls instead of the '+' operator to efficiently concatenate strings and numbers.")
@Test
void objectsConcatenation() {
rewriteRun(
//language=java
java(
"""
class A {
void method1() {
StringBuilder sb = new StringBuilder();
String op = "+";
sb.append("A" + op + "B");
sb.append(1 + op + 2);
}
}
""",
"""
class A {
void method1() {
StringBuilder sb = new StringBuilder();
String op = "+";
sb.append("A").append(op).append("B");
sb.append(1).append(op).append(2);
}
}
"""
)
);
}
@Test
void literalConcatenationIgnored() {
rewriteRun(
//language=java
java(
"""
class A {
void method1() {
StringBuilder sb = new StringBuilder();
sb.append("A" + "B" + "C");
}
}
"""
)
);
}
@DocumentExample("Grouping concatenation.")
@Test
void groupedStringsConcatenation() {
rewriteRun(
//language=java
java(
"""
class A {
void method1() {
StringBuilder sb = new StringBuilder();
String op = "+";
sb.append("A" + "B" + "C" + op + "D" + "E");
}
}
""",
"""
class A {
void method1() {
StringBuilder sb = new StringBuilder();
String op = "+";
sb.append("A" + "B" + "C").append(op).append("D" + "E");
}
}
"""
)
);
}
@Test
void unWrap() {
rewriteRun(
//language=java
java(
"""
class A {
void method1() {
StringBuilder sb = new StringBuilder();
String op = "+";
sb.append(("A" + op + "B"));
}
}
""",
"""
class A {
void method1() {
StringBuilder sb = new StringBuilder();
String op = "+";
sb.append("A").append(op).append("B");
}
}
"""
)
);
}
@Test
void chainedAppend() {
rewriteRun(
//language=java
java(
"""
class A {
void method1() {
StringBuilder sb = new StringBuilder();
String op = "+";
sb.append(("A" + op)).append("B");
}
}
""",
"""
class A {
void method1() {
StringBuilder sb = new StringBuilder();
String op = "+";
sb.append("A").append(op).append("B");
}
}
"""
)
);
}
@Test
void runMultipleTimes() {
rewriteRun(
//language=java
java(
"""
class A {
void method1() {
StringBuilder sb = new StringBuilder();
String op = "+";
sb.append(("A" + op) + "B");
}
}
""",
"""
class A {
void method1() {
StringBuilder sb = new StringBuilder();
String op = "+";
sb.append("A").append(op).append("B");
}
}
"""
)
);
}
@Test
void correctlyGroupConcatenations() {
rewriteRun(
//language=java
java(
"""
class A {
void method1() {
StringBuilder sb = new StringBuilder();
String op = "+";
sb.append(op + 1 + 2 + "A" + "B" + 'x');
sb.append(1 + 2 + op + 3 + 4);
sb.append(1 + 2 + name() + 3 + 4);
sb.append(op + (1 + 2));
}
String name() { return "name"; }
}
""",
"""
class A {
void method1() {
StringBuilder sb = new StringBuilder();
String op = "+";
sb.append(op).append(1).append(2).append("A" + "B").append('x');
sb.append(1 + 2).append(op).append(3).append(4);
sb.append(1 + 2).append(name()).append(3).append(4);
sb.append(op).append(1 + 2);
}
String name() { return "name"; }
}
"""
)
);
}
@Test
void appendMethods() {
rewriteRun(
//language=java
java(
"""
class A {
void method1() {
StringBuilder sb = new StringBuilder();
sb.append(str1() + str2() + str3());
}
String str1() { return "A"; }
String str2() { return "B"; }
String str3() { return "C"; }
}
""",
"""
class A {
void method1() {
StringBuilder sb = new StringBuilder();
sb.append(str1()).append(str2()).append(str3());
}
String str1() { return "A"; }
String str2() { return "B"; }
String str3() { return "C"; }
}
"""
)
);
}
@Test
void ChainedAppendWithConstructor() {
rewriteRun(
//language=java
java(
"""
class A {
void method1() {
StringBuilder sb = new StringBuilder().append("A" + operator() + "B");
}
String operator() { return "+"; }
}
""",
"""
class A {
void method1() {
StringBuilder sb = new StringBuilder().append("A").append(operator()).append("B");
}
String operator() { return "+"; }
}
"""
)
);
}
@Test
void methodArgument() {
rewriteRun(
//language=java
java(
"""
class A {
void method1() {
String op = "+";
print(new StringBuilder().append("A" + op + "C").toString());
}
void print(String str) {
}
}
""",
"""
class A {
void method1() {
String op = "+";
print(new StringBuilder().append("A").append(op).append("C").toString());
}
void print(String str) {
}
}
"""
)
);
}
}

View File

@@ -0,0 +1,568 @@
/*
* Copyright 2022 the original author or authors.
* <p>
* Licensed under the Apache License, Version 2.0 (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://www.apache.org/licenses/LICENSE-2.0
* <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.staticanalysis;
import org.junit.jupiter.api.Test;
import org.openrewrite.DocumentExample;
import org.openrewrite.Issue;
import org.openrewrite.test.RecipeSpec;
import org.openrewrite.test.RewriteTest;
import static org.openrewrite.java.Assertions.java;
@SuppressWarnings({"UnusedLabel", "StatementWithEmptyBody", "Convert2Diamond", "ConstantConditions", "ClassInitializerMayBeStatic"})
class RemoveUnneededBlockTest implements RewriteTest {
@Override
public void defaults(RecipeSpec spec) {
spec.recipe(new RemoveUnneededBlock());
}
@Test
void doNotChangeMethod() {
rewriteRun(
//language=java
java(
"""
class A {
void test() {
}
}
"""
)
);
}
@Test
void doNotChangeLabeledBlock() {
rewriteRun(
//language=java
java(
"""
class A {
void test() {
testLabel: {
System.out.println("hello!");
}
}
}
"""
)
);
}
@Test
void doNotChangeEmptyIfBlock() {
rewriteRun(
//language=java
java(
"""
class A {
void test() {
if(true) { }
}
}
"""
)
);
}
@Test
void doNotRemoveDoubleBraceInitBlocksInMethod() {
rewriteRun(
//language=java
java(
"""
import java.util.HashSet;
import java.util.Set;
public class T {
public void whenInitializeSetWithDoubleBraces_containsElements() {
Set<String> countries = new HashSet<String>() {
{
add("a");
add("b");
}
};
}
}
"""
)
);
}
@Test
void doNotRemoveDoubleBraceInitBlocks() {
rewriteRun(
//language=java
java(
"""
import java.util.HashSet;
import java.util.Set;
public class T {
final Set<String> countries = new HashSet<String>() {
{
add("a");
add("b");
}
};
}
"""
)
);
}
@Test
void doNotRemoveObjectArrayInitializer() {
rewriteRun(
//language=java
java(
"""
public class A {
Object[] a = new Object[] {
"a",
"b"
};
}
"""
)
);
}
@Test
void doNotRemoveObjectArrayArrayInitializer() {
rewriteRun(
//language=java
java(
"""
public class A {
Object[][] a = new Object[][] {
{ "a", "b" },
{ "c", "d" }
};
}
"""
)
);
}
@DocumentExample
@Test
void simplifyNestedBlock() {
rewriteRun(
//language=java
java(
"""
public class A {
void test() {
{
System.out.println("hello!");
}
}
}
""",
"""
public class A {
void test() {
System.out.println("hello!");
}
}
"""
)
);
}
@Test
void simplifyDoublyNestedBlock() {
rewriteRun(
//language=java
java(
"""
public class A {
void test() {
{
{ System.out.println("hello!"); }
}
}
}
""",
"""
public class A {
void test() {
System.out.println("hello!");
}
}
"""
)
);
}
@Test
void simplifyBlockNestedInIfBlock() {
rewriteRun(
//language=java
java(
"""
public class A {
void test() {
if (true) {
{ System.out.println("hello!"); }
}
}
}
""",
"""
public class A {
void test() {
if (true) {
System.out.println("hello!");
}
}
}
"""
)
);
}
@Test
void simplifyBlockInStaticInitializerIfBlock() {
rewriteRun(
//language=java
java(
"""
public class A {
static {
{
{
System.out.println("hello static!");
System.out.println("goodbye static!");
}
}
}
{
{
System.out.println("hello init!");
System.out.println("goodbye init!");
}
}
}
""",
"""
public class A {
static {
System.out.println("hello static!");
System.out.println("goodbye static!");
}
{
System.out.println("hello init!");
System.out.println("goodbye init!");
}
}
"""
)
);
}
@Test
void simplifyCraziness() {
rewriteRun(
//language=java
java(
"""
import java.util.HashSet;
import java.util.Set;
public class A {
static {
{
new HashSet<String>() {
{
add("a");
add("b");
{
System.out.println("hello static!");
System.out.println("goodbye static!");
}
}
};
}
}
{
{
new HashSet<String>() {
{
add("a");
add("b");
{
System.out.println("hello init!");
System.out.println("goodbye init!");
}
}
};
}
}
}
""",
"""
import java.util.HashSet;
import java.util.Set;
public class A {
static {
new HashSet<String>() {
{
add("a");
add("b");
System.out.println("hello static!");
System.out.println("goodbye static!");
}
};
}
{
new HashSet<String>() {
{
add("a");
add("b");
System.out.println("hello init!");
System.out.println("goodbye init!");
}
};
}
}
"""
)
);
}
@Test
void simplifyDoesNotFormatSurroundingCode() {
rewriteRun(
//language=java
java(
"""
public class A {
static int[] a;
static int[] b;
static {
a = new int[] { 1, 2, 3 };
b = new int[] {4,5,6};
{
System.out.println("hello static!");
}
}
}
""",
"""
public class A {
static int[] a;
static int[] b;
static {
a = new int[] { 1, 2, 3 };
b = new int[] {4,5,6};
System.out.println("hello static!");
}
}
"""
)
);
}
@Test
void simplifyDoesNotFormatInternalCode() {
rewriteRun(
//language=java
java(
"""
public class A {
int[] a;
int[] b;
static {
a = new int[] { 1, 2, 3 };
b = new int[] {4,5,6};
{
System.out.println("hello!");
System.out.println( "world!" );
}
}
}
""",
"""
public class A {
int[] a;
int[] b;
static {
a = new int[] { 1, 2, 3 };
b = new int[] {4,5,6};
System.out.println("hello!");
System.out.println("world!");
}
}
"""
)
);
}
@Test
@Issue("https://github.com/openrewrite/rewrite/issues/3073")
void preserveComments() {
rewriteRun(
//language=java
java(
"""
public class A {
static {
// comment before (outside)
{
// comment before (inside)
System.out.println("hello world!");
// comment after (inside)
}
// comment after (outside)
}
}
""",
"""
public class A {
static {
// comment before (outside)
// comment before (inside)
System.out.println("hello world!");
// comment after (inside)
// comment after (outside)
}
}
"""
)
);
}
@Test
void preserveBlocksContainingVariableDeclarations() {
rewriteRun(
//language=java
java(
"""
public class A {
static {
{
int i = 0;
}
System.out.println("hello world!");
}
}
"""
)
);
}
@Test
void inlineLastBlockContainingVariableDeclarations() {
rewriteRun(
//language=java
java(
"""
public class A {
static {
{
System.out.println("hello world!");
}
{
int i = 0;
}
}
}
""",
"""
public class A {
static {
System.out.println("hello world!");
int i = 0;
}
}
"""
)
);
}
@Test
@SuppressWarnings("EmptyFinallyBlock")
void removeEmptyTryFinallyBlock() {
rewriteRun(
//language=java
java(
"""
public class A {
public int foo() {
try {
int i = 1;
} finally {
}
}
}
""",
"""
public class A {
public int foo() {
int i = 1;
}
}
"""
)
);
}
@Test
void keepNonEmptyTryFinallyBlock() {
rewriteRun(
//language=java
java(
"""
public class A {
public int foo() {
try {
int i = 1;
} finally {
System.out.println("hello world!");
}
}
}
"""
)
);
}
@Test
@SuppressWarnings("EmptyFinallyBlock")
void keepNonEmptyTryFinallyBlock2() {
rewriteRun(
//language=java
java(
"""
public class A {
public int foo() {
try {
int i = 1;
} finally {
}
int i = 1;
}
}
"""
)
);
}
}

View File

@@ -0,0 +1,297 @@
/*
* Copyright 2023 the original author or authors.
* <p>
* Licensed under the Apache License, Version 2.0 (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://www.apache.org/licenses/LICENSE-2.0
* <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.staticanalysis;
import org.junit.jupiter.api.Test;
import org.openrewrite.test.RecipeSpec;
import org.openrewrite.test.RewriteTest;
import static org.openrewrite.java.Assertions.java;
import static org.openrewrite.java.Assertions.version;
class ReplaceDeprecatedRuntimeExecMethodsTest implements RewriteTest {
@Override
public void defaults(RecipeSpec spec) {
spec.recipe(new ReplaceDeprecatedRuntimeExecMethods());
}
@Test
void rawString() {
rewriteRun(
version(
//language=java
java(
"""
import java.io.File;
import java.io.IOException;
class A {
void method() throws IOException {
Runtime runtime = Runtime.getRuntime();
String[] envp = { "E1=1", "E2=2"};
File dir = new File("/tmp");
Process process1 = runtime.exec("ls -a -l");
Process process2 = runtime.exec("ls -a -l", envp);
Process process3 = runtime.exec("ls -a -l", envp, dir);
}
}
""",
"""
import java.io.File;
import java.io.IOException;
class A {
void method() throws IOException {
Runtime runtime = Runtime.getRuntime();
String[] envp = { "E1=1", "E2=2"};
File dir = new File("/tmp");
Process process1 = runtime.exec(new String[]{"ls", "-a", "-l"});
Process process2 = runtime.exec(new String[]{"ls", "-a", "-l"}, envp);
Process process3 = runtime.exec(new String[]{"ls", "-a", "-l"}, envp, dir);
}
}
"""
), 18)
);
}
@Test
void stringVariableAsInput() {
rewriteRun(
version(
//language=java
java(
"""
import java.io.File;
import java.io.IOException;
class B {
void method() throws IOException {
Runtime runtime = Runtime.getRuntime();
String command = "ls -al";
String[] envp = { "E1=1", "E2=2"};
File dir = new File("/tmp");
Process process1 = runtime.exec(command);
Process process2 = runtime.exec(command, envp);
Process process3 = runtime.exec(command, envp, dir);
}
}
""",
"""
import java.io.File;
import java.io.IOException;
class B {
void method() throws IOException {
Runtime runtime = Runtime.getRuntime();
String command = "ls -al";
String[] envp = { "E1=1", "E2=2"};
File dir = new File("/tmp");
Process process1 = runtime.exec(command.split(" "));
Process process2 = runtime.exec(command.split(" "), envp);
Process process3 = runtime.exec(command.split(" "), envp, dir);
}
}
"""
), 18)
);
}
@Test
void methodInvocationAsInput() {
rewriteRun(
version(
//language=java
java(
"""
import java.io.IOException;
class B {
String command() {
return "ls -al";
}
void method() throws IOException {
Runtime runtime = Runtime.getRuntime();
Process process = runtime.exec(command());
}
}
""",
"""
import java.io.IOException;
class B {
String command() {
return "ls -al";
}
void method() throws IOException {
Runtime runtime = Runtime.getRuntime();
Process process = runtime.exec(command().split(" "));
}
}
"""
), 18)
);
}
@Test
void concatenatedRawStringsAsInput() {
rewriteRun(
version(
//language=java
java(
"""
import java.io.IOException;
class A {
void method() throws IOException {
Runtime runtime = Runtime.getRuntime();
Process process = runtime.exec("ls" + " " + "-a" + " " + "-l");
}
}
""",
"""
import java.io.IOException;
class A {
void method() throws IOException {
Runtime runtime = Runtime.getRuntime();
Process process = runtime.exec(new String[]{"ls", "-a", "-l"});
}
}
"""
), 18)
);
}
@Test
void concatenatedObjectsAsInput() {
rewriteRun(
version(
//language=java
java(
"""
import java.io.IOException;
class B {
String options = "-a -l";
void method() throws IOException {
Runtime runtime = Runtime.getRuntime();
Process process = runtime.exec("ls" + " " + options);
}
}
""",
"""
import java.io.IOException;
class B {
String options = "-a -l";
void method() throws IOException {
Runtime runtime = Runtime.getRuntime();
Process process = runtime.exec(("ls" + " " + options).split(" "));
}
}
"""
), 18)
);
}
@Test
void deprecatedMethod2() {
rewriteRun(
version(
//language=java
java(
"""
import java.io.IOException;
class A {
void method() throws IOException {
Runtime runtime = Runtime.getRuntime();
String[] envp = { "E1=1", "E2=2"};
Process process = runtime.exec("ls -a -l", envp);
}
}
""",
"""
import java.io.IOException;
class A {
void method() throws IOException {
Runtime runtime = Runtime.getRuntime();
String[] envp = { "E1=1", "E2=2"};
Process process = runtime.exec(new String[]{"ls", "-a", "-l"}, envp);
}
}
"""
), 18)
);
}
@Test
void doNotChangeIfUsingNewMethods() {
rewriteRun(
version(
//language=java
java(
"""
import java.io.File;
import java.io.IOException;
class A {
void method() throws IOException {
Runtime runtime = Runtime.getRuntime();
String[] envp = { "E1=1", "E2=2"};
File dir = new File("/tmp");
Process process1 = runtime.exec(new String[]{"ls", "-al"});
Process process2 = runtime.exec(new String[]{"ls", "-al"}, envp);
Process process3 = runtime.exec(new String[]{"ls", "-a", "-l"}, envp, dir);
}
}
"""
), 18)
);
}
@Test
void doNotChangeIfUnderJava18() {
rewriteRun(
version(
//language=java
java(
"""
import java.io.File;
import java.io.IOException;
class A {
void method() throws IOException {
Runtime runtime = Runtime.getRuntime();
String[] envp = { "E1=1", "E2=2"};
File dir = new File("/tmp");
Process process1 = runtime.exec("ls -al");
Process process2 = runtime.exec("ls -al", envp);
Process process3 = runtime.exec("ls -al", envp, dir);
}
}
"""
), 17)
);
}
}

View File

@@ -118,7 +118,7 @@
<commons-codec-version>1.13</commons-codec-version>
<!-- Rewrite specific properties -->
<rewrite-recipe-bom.version>2.22.0</rewrite-recipe-bom.version>
<rewrite-recipe-bom.version>2.23.1</rewrite-recipe-bom.version>
<gradle.version>8.11</gradle.version>
<rewrite-gradle-tooling-model.version>2.7.0</rewrite-gradle-tooling-model.version>
<jgit-version>6.9.0.202403050737-r</jgit-version>

View File

@@ -38,7 +38,7 @@ public class RewriteRecipeRepositoryTest {
assertEquals(11, recipes.size());
recipes = recipeRepo.getSubRecipes("org.openrewrite.java.spring.boot3.UpgradeSpringBoot_3_1", List.of(0, 2)).get();
assertEquals(20, recipes.size());
assertEquals(22, recipes.size());
recipes = recipeRepo.getSubRecipes("org.openrewrite.java.spring.boot3.UpgradeSpringBoot_3_1",List.of(0, 345)).get();
assertEquals(0, recipes.size());