No rewrite-static-analysis dependency. Adopt latest Apache license OR
This commit is contained in:
@@ -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>
|
||||
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
"""
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
"""
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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) {
|
||||
}
|
||||
}
|
||||
"""
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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;
|
||||
}
|
||||
}
|
||||
"""
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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)
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
@@ -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());
|
||||
|
||||
Reference in New Issue
Block a user