From 71d5c3e333fbdc315951d2354a0b80f1ca0791db Mon Sep 17 00:00:00 2001 From: Kun Li Date: Wed, 16 Aug 2023 11:55:08 -0700 Subject: [PATCH 01/92] Update UnnecessaryExplicitTypeArguments to not work to Kotlin to avoid harmful changes --- .../UnnecessaryExplicitTypeArguments.java | 25 +++---------------- .../UnnecessaryExplicitTypeArgumentsTest.java | 18 +++++++++++++ 2 files changed, 21 insertions(+), 22 deletions(-) diff --git a/src/main/java/org/openrewrite/staticanalysis/UnnecessaryExplicitTypeArguments.java b/src/main/java/org/openrewrite/staticanalysis/UnnecessaryExplicitTypeArguments.java index 5c1ffbf3e..bf4c7c79f 100644 --- a/src/main/java/org/openrewrite/staticanalysis/UnnecessaryExplicitTypeArguments.java +++ b/src/main/java/org/openrewrite/staticanalysis/UnnecessaryExplicitTypeArguments.java @@ -18,7 +18,7 @@ import org.openrewrite.*; import org.openrewrite.java.JavaIsoVisitor; import org.openrewrite.java.tree.*; -import org.openrewrite.kotlin.tree.K; +import org.openrewrite.staticanalysis.kotlin.KotlinFileChecker; public class UnnecessaryExplicitTypeArguments extends Recipe { @@ -34,7 +34,7 @@ public String getDescription() { @Override public TreeVisitor getVisitor() { - return new JavaIsoVisitor() { + return Preconditions.check(Preconditions.not(new KotlinFileChecker<>()), new JavaIsoVisitor() { @Override public J.MethodInvocation visitMethodInvocation(J.MethodInvocation method, ExecutionContext ctx) { J.MethodInvocation m = super.visitMethodInvocation(method, ctx); @@ -88,31 +88,12 @@ public J.MethodInvocation visitMethodInvocation(J.MethodInvocation method, Execu } if (enclosingType != null && TypeUtils.isOfType(enclosingType, m.getMethodType().getReturnType())) { - boolean isKotlinFile = getCursor().dropParentUntil(it -> it instanceof K.CompilationUnit || - it == Cursor.ROOT_VALUE) - .getValue() instanceof K.CompilationUnit; - - if (isKotlinFile) { - // For Kotlin, avoid omitting explicit type arguments only when the method invocation includes - // arguments, as the compiler cannot perform type inference without the presence of arguments. - boolean hasArguments = false; - for (Expression arg : m.getArguments()) { - if (!(arg instanceof J.Empty)) { - hasArguments = true; - break; - } - } - - if (!hasArguments) { - return m; - } - } m = m.withTypeParameters(null); } } return m; } - }; + }); } } diff --git a/src/test/java/org/openrewrite/staticanalysis/UnnecessaryExplicitTypeArgumentsTest.java b/src/test/java/org/openrewrite/staticanalysis/UnnecessaryExplicitTypeArgumentsTest.java index 6ba1d3388..3f57da1fb 100644 --- a/src/test/java/org/openrewrite/staticanalysis/UnnecessaryExplicitTypeArgumentsTest.java +++ b/src/test/java/org/openrewrite/staticanalysis/UnnecessaryExplicitTypeArgumentsTest.java @@ -201,6 +201,7 @@ void doNotChangeIfHasNotTypeInference() { ); } + @Disabled @Test void changeIfHasTypeInference() { rewriteRun( @@ -216,5 +217,22 @@ void changeIfHasTypeInference() { ) ); } + + @Test + void doNotChangeSinceCompilerHasNoEnoughInformationToInferType() { + rewriteRun( + kotlin( + """ + fun default(arg: String): TValue? { + return null + } + + fun method() { + val email = default("email") + } + """ + ) + ); + } } } From ac029b9720ae9369bbd5250ca5564007d291d7a3 Mon Sep 17 00:00:00 2001 From: Tim te Beek Date: Mon, 21 Aug 2023 09:03:43 +0200 Subject: [PATCH 02/92] Extend SimplifyBooleanExpressionVisitor (#155) * Extend SimplifyBooleanExpressionVisitor To reduce duplication. Depends on https://github.com/openrewrite/rewrite/pull/3490 * Add `@Nullable` annotation again Not sure why it was added in the first place. When can the left or right of a binary be `null` in Kotlin? * Add `@Nullable` annotation again Not sure why it was added in the first place. When can the left or right of a binary be `null` in Kotlin? --------- Co-authored-by: Knut Wannheden --- .../SimplifyBooleanExpression.java | 147 ++---------------- 1 file changed, 10 insertions(+), 137 deletions(-) diff --git a/src/main/java/org/openrewrite/staticanalysis/SimplifyBooleanExpression.java b/src/main/java/org/openrewrite/staticanalysis/SimplifyBooleanExpression.java index 26b040411..c8b0d73a2 100644 --- a/src/main/java/org/openrewrite/staticanalysis/SimplifyBooleanExpression.java +++ b/src/main/java/org/openrewrite/staticanalysis/SimplifyBooleanExpression.java @@ -15,16 +15,15 @@ */ package org.openrewrite.staticanalysis; -import org.openrewrite.*; +import org.openrewrite.ExecutionContext; +import org.openrewrite.Recipe; +import org.openrewrite.Tree; +import org.openrewrite.TreeVisitor; import org.openrewrite.internal.lang.Nullable; -import org.openrewrite.java.JavaIsoVisitor; -import org.openrewrite.java.JavaVisitor; -import org.openrewrite.java.UnwrapParentheses; -import org.openrewrite.java.format.AutoFormatVisitor; -import org.openrewrite.java.tree.Expression; +import org.openrewrite.java.cleanup.SimplifyBooleanExpressionVisitor; import org.openrewrite.java.tree.J; import org.openrewrite.java.tree.JavaSourceFile; -import org.openrewrite.java.tree.Space; +import org.openrewrite.kotlin.marker.IsNullSafe; import java.time.Duration; import java.util.Collections; @@ -56,9 +55,7 @@ public Duration getEstimatedEffortPerOccurrence() { @Override public TreeVisitor getVisitor() { - return new JavaVisitor() { - private static final String MAYBE_AUTO_FORMAT_ME = "MAYBE_AUTO_FORMAT_ME"; - + return new SimplifyBooleanExpressionVisitor() { @Override public J visit(@Nullable Tree tree, ExecutionContext ctx) { if (tree instanceof JavaSourceFile) { @@ -72,141 +69,17 @@ public J visit(@Nullable Tree tree, ExecutionContext ctx) { return super.visit(tree, ctx); } - @Override - public J visitBinary(J.Binary binary, ExecutionContext ctx) { - J j = super.visitBinary(binary, ctx); - J.Binary asBinary = (J.Binary) j; - - if (asBinary.getOperator() == J.Binary.Type.And) { - if (isLiteralFalse(asBinary.getLeft())) { - maybeUnwrapParentheses(); - j = asBinary.getLeft(); - } else if (isLiteralFalse(asBinary.getRight())) { - maybeUnwrapParentheses(); - j = asBinary.getRight().withPrefix(asBinary.getRight().getPrefix().withWhitespace("")); - } else if (removeAllSpace(asBinary.getLeft()).printTrimmed(getCursor()) - .equals(removeAllSpace(asBinary.getRight()).printTrimmed(getCursor()))) { - maybeUnwrapParentheses(); - j = asBinary.getLeft(); - } - } else if (asBinary.getOperator() == J.Binary.Type.Or) { - if (isLiteralTrue(asBinary.getLeft())) { - maybeUnwrapParentheses(); - j = asBinary.getLeft(); - } else if (isLiteralTrue(asBinary.getRight())) { - maybeUnwrapParentheses(); - j = asBinary.getRight().withPrefix(asBinary.getRight().getPrefix().withWhitespace("")); - } else if (removeAllSpace(asBinary.getLeft()).printTrimmed(getCursor()) - .equals(removeAllSpace(asBinary.getRight()).printTrimmed(getCursor()))) { - maybeUnwrapParentheses(); - j = asBinary.getLeft(); - } - } else if (asBinary.getOperator() == J.Binary.Type.Equal) { - if (isLiteralTrue(asBinary.getLeft())) { - if (isNotKotlinNullableType(asBinary.getRight())) { - maybeUnwrapParentheses(); - j = asBinary.getRight().withPrefix(asBinary.getRight().getPrefix().withWhitespace("")); - } - } else if (isLiteralTrue(asBinary.getRight())) { - if (isNotKotlinNullableType(asBinary.getLeft())) { - maybeUnwrapParentheses(); - j = asBinary.getLeft().withPrefix(asBinary.getLeft().getPrefix().withWhitespace(" ")); - } - } - } else if (asBinary.getOperator() == J.Binary.Type.NotEqual) { - if (isLiteralFalse(asBinary.getLeft())) { - if (isNotKotlinNullableType(asBinary.getRight())) { - maybeUnwrapParentheses(); - j = asBinary.getRight().withPrefix(asBinary.getRight().getPrefix().withWhitespace("")); - } - } else if (isLiteralFalse(asBinary.getRight())) { - if (isNotKotlinNullableType(asBinary.getLeft())) { - maybeUnwrapParentheses(); - j = asBinary.getLeft().withPrefix(asBinary.getLeft().getPrefix().withWhitespace(" ")); - } - } - } - if (asBinary != j) { - getCursor().getParentTreeCursor().putMessage(MAYBE_AUTO_FORMAT_ME, ""); - } - return j; - } - - @Override - public J postVisit(J tree, ExecutionContext ctx) { - J j = super.postVisit(tree, ctx); - if (getCursor().pollMessage(MAYBE_AUTO_FORMAT_ME) != null) { - j = new AutoFormatVisitor<>().visit(j, ctx, getCursor().getParentOrThrow()); - } - return j; - } - - @Override - public J visitUnary(J.Unary unary, ExecutionContext ctx) { - J j = super.visitUnary(unary, ctx); - J.Unary asUnary = (J.Unary) j; - - if (asUnary.getOperator() == J.Unary.Type.Not) { - if (isLiteralTrue(asUnary.getExpression())) { - maybeUnwrapParentheses(); - j = ((J.Literal) asUnary.getExpression()).withValue(false).withValueSource("false"); - } else if (isLiteralFalse(asUnary.getExpression())) { - maybeUnwrapParentheses(); - j = ((J.Literal) asUnary.getExpression()).withValue(true).withValueSource("true"); - } else if (asUnary.getExpression() instanceof J.Unary && ((J.Unary) asUnary.getExpression()).getOperator() == J.Unary.Type.Not) { - maybeUnwrapParentheses(); - j = ((J.Unary) asUnary.getExpression()).getExpression(); - } - } - if (asUnary != j) { - getCursor().getParentTreeCursor().putMessage(MAYBE_AUTO_FORMAT_ME, ""); - } - return j; - } - - /** - * Specifically for removing immediately-enclosing parentheses on Identifiers and Literals. - * This queues a potential unwrap operation for the next visit. After unwrapping something, it's possible - * there are more Simplifications this recipe can identify and perform, which is why visitCompilationUnit - * checks for any changes to the entire Compilation Unit, and if so, queues up another SimplifyBooleanExpression - * recipe call. This convergence loop eventually reconciles any remaining Boolean Expression Simplifications - * the recipe can perform. - */ - private void maybeUnwrapParentheses() { - Cursor c = getCursor().getParentOrThrow().getParentTreeCursor(); - if (c.getValue() instanceof J.Parentheses) { - doAfterVisit(new UnwrapParentheses<>(c.getValue())); - } - } - - private boolean isLiteralTrue(@Nullable Expression expression) { - return expression instanceof J.Literal && ((J.Literal) expression).getValue() == Boolean.valueOf(true); - } - - private boolean isLiteralFalse(@Nullable Expression expression) { - return expression instanceof J.Literal && ((J.Literal) expression).getValue() == Boolean.valueOf(false); - } - - private J removeAllSpace(J j) { - //noinspection ConstantConditions - return new JavaIsoVisitor() { - @Override - public Space visitSpace(Space space, Space.Location loc, Integer integer) { - return Space.EMPTY; - } - }.visit(j, 0); - } - // Comparing Kotlin nullable type `?` with tree/false can not be simplified, // e.g. `X?.fun() == true` is not equivalent to `X?.fun()` - private boolean isNotKotlinNullableType(@Nullable J j) { + @Override + protected boolean shouldSimplifyEqualsOn(@Nullable J j) { if (j == null) { return true; } if (j instanceof J.MethodInvocation) { J.MethodInvocation m = (J.MethodInvocation) j; - return m.getSelect() != null && !m.getSelect().getMarkers().findFirst(org.openrewrite.kotlin.marker.IsNullSafe.class).isPresent(); + return m.getSelect() != null && !m.getSelect().getMarkers().findFirst(IsNullSafe.class).isPresent(); } return true; From a097e94357ae838b14b6a1240f4366055c8da6a8 Mon Sep 17 00:00:00 2001 From: Tim te Beek Date: Tue, 22 Aug 2023 16:55:23 +0200 Subject: [PATCH 03/92] Detect Kotlin IsNullSafe marker on method invocation (#156) After https://github.com/openrewrite/rewrite-kotlin/commit/938c89875ee4f7a3b9b87e9029979f9a1531a89f --- .../openrewrite/staticanalysis/SimplifyBooleanExpression.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/openrewrite/staticanalysis/SimplifyBooleanExpression.java b/src/main/java/org/openrewrite/staticanalysis/SimplifyBooleanExpression.java index c8b0d73a2..d84814492 100644 --- a/src/main/java/org/openrewrite/staticanalysis/SimplifyBooleanExpression.java +++ b/src/main/java/org/openrewrite/staticanalysis/SimplifyBooleanExpression.java @@ -79,7 +79,7 @@ protected boolean shouldSimplifyEqualsOn(@Nullable J j) { if (j instanceof J.MethodInvocation) { J.MethodInvocation m = (J.MethodInvocation) j; - return m.getSelect() != null && !m.getSelect().getMarkers().findFirst(IsNullSafe.class).isPresent(); + return !m.getMarkers().findFirst(IsNullSafe.class).isPresent(); } return true; From b527df36aeaa47b2eceeeea87a126bb6acbae081 Mon Sep 17 00:00:00 2001 From: Peter Streef Date: Tue, 22 Aug 2023 21:10:41 +0200 Subject: [PATCH 04/92] Ternary operator should not be nested (#93) * feat: create new recipe Ternary operators should not be nested https://github.com/openrewrite/rewrite-static-analysis/issues/51 add recipe and test * cleanup imports * update todo comment to complete solution * add another todo true part * Add more tests * try something different * reorder tests * revert to return extra block * add todo * helpers and dont reuse old return (maybe ids will conflict?) * add truePart * and remove todo * add todo * override tags * Fix tests * remove todo * remove todo & rename method * add more tests * add license * add test with whitespace * Handle ternary in lambda * cleanup * cleanup * only when nested ternary * add @Issue link * recursive approach for multi level ternaries. Add failing test for comment issue * getting there! * getting there! * fix another test * add Objects.equals handling * add binary conditions * add more tests * remove todo * end visiting of subtree at CompilationUnit Co-authored-by: Knut Wannheden * use javaVersion in tests * fixes * add constant handling for binaries * other side of equals must be constant * use helper * fix build with latest rewrite:main * remove default duration move comment * Ignore Objects.equals when finding switch expression candidates as it breaks null safety * Add more test nesting * Add two DocumentExamples * add links to issues --------- Co-authored-by: Peter Streef Co-authored-by: Knut Wannheden Co-authored-by: Tim te Beek --- .../TernaryOperatorsShouldNotBeNested.java | 388 +++++ ...TernaryOperatorsShouldNotBeNestedTest.java | 1364 +++++++++++++++++ 2 files changed, 1752 insertions(+) create mode 100644 src/main/java/org/openrewrite/staticanalysis/TernaryOperatorsShouldNotBeNested.java create mode 100644 src/test/java/org/openrewrite/staticanalysis/TernaryOperatorsShouldNotBeNestedTest.java diff --git a/src/main/java/org/openrewrite/staticanalysis/TernaryOperatorsShouldNotBeNested.java b/src/main/java/org/openrewrite/staticanalysis/TernaryOperatorsShouldNotBeNested.java new file mode 100644 index 000000000..8ffefb526 --- /dev/null +++ b/src/main/java/org/openrewrite/staticanalysis/TernaryOperatorsShouldNotBeNested.java @@ -0,0 +1,388 @@ +/* + * Copyright 2022 the original author or authors. + *

+ * 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 + *

+ * https://www.apache.org/licenses/LICENSE-2.0 + *

+ * 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 static org.openrewrite.Tree.randomId; +import static org.openrewrite.java.tree.J.Binary.Type.Equal; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Objects; +import java.util.Optional; +import java.util.Set; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import org.openrewrite.ExecutionContext; +import org.openrewrite.Recipe; +import org.openrewrite.Tree; +import org.openrewrite.TreeVisitor; +import org.openrewrite.internal.lang.Nullable; +import org.openrewrite.java.JavaIsoVisitor; +import org.openrewrite.java.JavaVisitor; +import org.openrewrite.java.marker.JavaVersion; +import org.openrewrite.java.tree.Expression; +import org.openrewrite.java.tree.Flag; +import org.openrewrite.java.tree.J; +import org.openrewrite.java.tree.JContainer; +import org.openrewrite.java.tree.JRightPadded; +import org.openrewrite.java.tree.Space; +import org.openrewrite.java.tree.Statement; +import org.openrewrite.java.tree.TypeUtils; +import org.openrewrite.marker.Markers; + + +public class TernaryOperatorsShouldNotBeNested extends Recipe { + + @Override + public String getDisplayName() { + return "Ternary operators should not be nested"; + } + + @Override + public String getDescription() { + return "Nested ternary operators can be hard to read quickly. Prefer simpler constructs for improved readability. " + + "If supported, this recipe will try to replace nested ternaries with switch expressions."; + } + @Override + public Set getTags() { + return Collections.singleton("RSPEC-3358"); + } + + @Override + public TreeVisitor getVisitor() { + return new JavaIsoVisitor() { + @Override + public J.CompilationUnit visitCompilationUnit( + final J.CompilationUnit cu, + final ExecutionContext executionContext + ) { + if (cu.getMarkers() + .findFirst(JavaVersion.class) + .filter(javaVersion -> javaVersion.getMajorVersion() >= 14) + .isPresent()) { + doAfterVisit(new UseSwitchExpressionVisitor()); + } + doAfterVisit(new UseIfVisitor()); + return cu; + } + }; + } + + private static class UseIfVisitor extends JavaVisitor { + + @Override + public J visitLambda(final J.Lambda lambda, final ExecutionContext executionContext) { + J result = rewriteNestedTernary(lambda); + if (result == lambda) { + return super.visitLambda(lambda, executionContext); + } + doAfterVisit(new RemoveUnneededBlock().getVisitor()); + return autoFormat(lambda.withBody(result.withPrefix(Space.SINGLE_SPACE)), executionContext); + } + + @Override + public J visitReturn(final J.Return retrn, final ExecutionContext executionContext) { + J result = rewriteNestedTernary(retrn); + if (result == retrn) { + return super.visitReturn(retrn, executionContext); + } + doAfterVisit(new RemoveUnneededBlock().getVisitor()); + return autoFormat(result, executionContext); + } + + private Statement rewriteNestedTernary(final Statement parent) { + return findTernary(parent).map(ternary -> { + if (!isNestedTernary(ternary)) { + return parent; + } + J.If iff = ifOf(ternary); + J.Return otherwise = returnOf(ternary.getFalsePart()); + return blockOf(iff, rewriteNestedTernary(otherwise)).withPrefix(parent.getPrefix()); + }).orElse(parent); + } + + + private J.If ifOf(final J.Ternary ternary) { + return new J.If( + Tree.randomId(), + ternary.getPrefix(), + Markers.EMPTY, + new J.ControlParentheses<>(Tree.randomId(), Space.EMPTY, Markers.EMPTY, + JRightPadded.build(ternary.getCondition()) + ).withComments(ternary.getCondition().getComments()), + JRightPadded.build(blockOf(rewriteNestedTernary(returnOf(ternary.getTruePart() + .withComments(ternary.getTruePart().getComments()))))), + null + ); + } + + private static boolean isNestedTernary(final J possibleTernary) { + int result = determineNestingLevels(possibleTernary, 0); + return result > 1; + } + + private static int determineNestingLevels(final J possibleTernary, final int level) { + if (!(possibleTernary instanceof J.Ternary)) { + return level; + } + J.Ternary ternary = (J.Ternary) possibleTernary; + int truePath = determineNestingLevels(ternary.getTruePart(), level + 1); + int falsePath = determineNestingLevels(ternary.getFalsePart(), level + 1); + return Math.max(falsePath, truePath); + } + + private static Optional findTernary(Statement parent) { + J possibleTernary = parent; + if (parent instanceof J.Return) { + possibleTernary = ((J.Return) parent).getExpression(); + } else if (parent instanceof J.Lambda) { + possibleTernary = ((J.Lambda) parent).getBody(); + } + if (possibleTernary instanceof J.Ternary) { + return Optional.of(possibleTernary).map(J.Ternary.class::cast); + } + return Optional.empty(); + } + + } + + + static class UseSwitchExpressionVisitor extends JavaVisitor { + + @Override + public J visitTernary(final J.Ternary ternary, final ExecutionContext executionContext) { + return findConditionIdentifier(ternary).map(switchVar -> { + List nestList = findNestedTernaries(ternary, switchVar); + if (nestList.size() < 2) { + return null; + } + return autoFormat(toSwitch(switchVar, nestList), executionContext); + }).map(J.class::cast) + .orElseGet(() -> super.visitTernary(ternary, executionContext)); + } + + private List findNestedTernaries(final J.Ternary ternary, final J.Identifier switchVar) { + List nestList = new ArrayList<>(); + J.Ternary next = ternary; + while (next.getFalsePart() instanceof J.Ternary) { + if (next.getTruePart() instanceof J.Ternary) { + //as long as we do not use pattern matching, an "and" nested ternary will never work for a switch: + // Example: a equals a and a equals b will never be true + return Collections.emptyList(); + } + J.Ternary nested = (J.Ternary) next.getFalsePart(); + if (!findConditionIdentifier(nested) + .filter(found -> isEqualVariable(switchVar, found)) + .isPresent()) { + return Collections.emptyList(); + } + nestList.add(next); + next = nested; + } + nestList.add(next); + return nestList; + } + + private static boolean isEqualVariable(final J.Identifier switchVar, @Nullable final J found) { + if (!(found instanceof J.Identifier)) { + return false; + } + J.Identifier foundVar = (J.Identifier) found; + return Objects.equals(foundVar.getFieldType(), switchVar.getFieldType()); + } + + private J.SwitchExpression toSwitch(final J.Identifier switchVar, final List nestList) { + J.Ternary last = nestList.get(nestList.size() - 1); + return new J.SwitchExpression( + Tree.randomId(), + Space.SINGLE_SPACE, + Markers.EMPTY, + new J.ControlParentheses<>( + Tree.randomId(), + switchVar.getPrefix().withWhitespace(" "), + switchVar.getMarkers(), + JRightPadded.build(switchVar.withPrefix(Space.EMPTY)) + ), + blockOf(Stream.concat( + nestList.stream().map(ternary -> toCase(switchVar, ternary)), + Stream.of(toDefault(last)) + ).collect(Collectors.toList())) + .withPrefix(Space.SINGLE_SPACE) + ); + } + + private J.Case toCase(final J.Identifier switchVar, final J.Ternary ternary) { + Expression compare; + if (ternary.getCondition() instanceof J.MethodInvocation) { + J.MethodInvocation inv = ((J.MethodInvocation) ternary.getCondition()); + if (isObjectsEquals(inv)) { + maybeRemoveImport("java.util.Objects"); + compare = isVariable(inv.getArguments().get(0)) + ? inv.getArguments().get(1) + : inv.getArguments().get(0); + } else { + compare = isEqualVariable(switchVar, inv.getSelect()) + ? inv.getArguments().get(0) + : inv.getSelect(); + } + } else if (isEqualsBinary(ternary.getCondition())) { + J.Binary bin = ((J.Binary) ternary.getCondition()); + compare = isEqualVariable(switchVar, bin.getLeft()) + ? bin.getRight() + : bin.getLeft(); + } else { + throw new IllegalArgumentException( + "Only J.Binary or J.MethodInvocation are expected as ternary conditions when creating a switch case"); + } + return new J.Case( + Tree.randomId(), + ternary.getPrefix().withWhitespace(" "), + ternary.getMarkers(), + J.Case.Type.Rule, + JContainer.build( + Collections.singletonList(JRightPadded.build(compare.withPrefix(Space.SINGLE_SPACE)) + .withAfter(Space.SINGLE_SPACE)) + ), + JContainer.build(Collections.emptyList()), + JRightPadded.build(ternary.getTruePart()) + ); + } + + private J.Case toDefault(final J.Ternary ternary) { + return new J.Case( + Tree.randomId(), + Space.EMPTY, + ternary.getMarkers(), + J.Case.Type.Rule, + JContainer.build(Collections.singletonList(JRightPadded.build(new J.Identifier( + randomId(), + Space.EMPTY, + Markers.EMPTY, + "default", + null, + null + )).withAfter(Space.SINGLE_SPACE))), + JContainer.build(Collections.emptyList()), + JRightPadded.build(ternary.getFalsePart()) + ); + } + + private Optional findConditionIdentifier(final J.Ternary ternary) { + J.Identifier result = null; + if (ternary.getCondition() instanceof J.MethodInvocation) { + J.MethodInvocation inv = (J.MethodInvocation) ternary.getCondition(); + if (!inv.getSimpleName().equals("equals")) { + return Optional.empty(); + } + if (inv.getArguments().size() == 1) { + J other = null; + if (isVariable(inv.getSelect())) { + result = (J.Identifier) inv.getSelect(); + other = inv.getArguments().get(0); + } + if (inv.getArguments().get(0) instanceof J.Identifier) { + result = (J.Identifier) inv.getArguments().get(0); + other = inv.getSelect(); + } + if (!isConstant(other)) { + return Optional.empty(); + } + } + } else if (isEqualsBinary(ternary.getCondition())) { + J.Binary bin = (J.Binary) ternary.getCondition(); + result = xorVariable(bin.getLeft(), bin.getRight()); + } + return Optional.ofNullable(result); + + } + + @Nullable + private static J.Identifier xorVariable(J first, J second) { + J.Identifier result = null; + if (isVariable(first) && isVariable(second)) { + return null; + } + if (isVariable(first)) { + result = (J.Identifier) first; + } + if (isVariable(second)) { + result = (J.Identifier) second; + } + return result; + } + + private static boolean isVariable(@Nullable J maybeVariable) { + if (maybeVariable == null) { + return false; + } + if (!(maybeVariable instanceof J.Identifier)) { + return false; + } + J.Identifier identifier = (J.Identifier) maybeVariable; + if (identifier.getFieldType() == null) { + return false; + } + return !identifier.getFieldType().hasFlags(Flag.Final) || !identifier.getFieldType().hasFlags(Flag.Static); + } + + private static boolean isConstant(@Nullable J maybeConstant) { + if (maybeConstant == null) { + return false; + } + if (maybeConstant instanceof J.Literal) { + return true; + } + if (!(maybeConstant instanceof J.Identifier)) { + return false; + } + J.Identifier identifier = (J.Identifier) maybeConstant; + if (identifier.getFieldType() == null) { + return false; + } + return !identifier.getFieldType().hasFlags(Flag.Final) || !identifier.getFieldType().hasFlags(Flag.Static); + } + + private static boolean isObjectsEquals(J.MethodInvocation inv) { + if (inv.getSelect() instanceof J.Identifier) { + J.Identifier maybeObjects = (J.Identifier) inv.getSelect(); + boolean isObjects = TypeUtils.isOfClassType(maybeObjects.getType(), "java.util.Objects"); + return isObjects && "equals".equals(inv.getSimpleName()); + } + return false; + } + + private static boolean isEqualsBinary(J maybeEqualsBinary) { + return maybeEqualsBinary instanceof J.Binary && ((J.Binary) maybeEqualsBinary).getOperator().equals(Equal); + } + } + + private static J.Return returnOf(Expression expression) { + return new J.Return(Tree.randomId(), Space.EMPTY, Markers.EMPTY, expression.withPrefix(Space.EMPTY)) + .withComments(expression.getComments()); + } + + private static J.Block blockOf(Statement... statements) { + return blockOf(Arrays.asList(statements)); + } + + private static J.Block blockOf(List statements) { + return J.Block.createEmptyBlock().withStatements(statements); + } + +} diff --git a/src/test/java/org/openrewrite/staticanalysis/TernaryOperatorsShouldNotBeNestedTest.java b/src/test/java/org/openrewrite/staticanalysis/TernaryOperatorsShouldNotBeNestedTest.java new file mode 100644 index 000000000..4192c9732 --- /dev/null +++ b/src/test/java/org/openrewrite/staticanalysis/TernaryOperatorsShouldNotBeNestedTest.java @@ -0,0 +1,1364 @@ +/* + * Copyright 2022 the original author or authors. + *

+ * 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 + *

+ * https://www.apache.org/licenses/LICENSE-2.0 + *

+ * 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 static org.openrewrite.java.Assertions.java; +import static org.openrewrite.java.Assertions.javaVersion; + +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junitpioneer.jupiter.ExpectedToFail; +import org.openrewrite.DocumentExample; +import org.openrewrite.Issue; +import org.openrewrite.test.RecipeSpec; +import org.openrewrite.test.RewriteTest; + +class TernaryOperatorsShouldNotBeNestedTest { + + @Nested + class SwitchExpressionNotSupported implements RewriteTest { + + @Override + public void defaults(RecipeSpec spec) { + spec.recipe(new TernaryOperatorsShouldNotBeNested()).allSources(s -> s.markers(javaVersion(11))); + } + + @Test + @DocumentExample + void doReplaceNestedOrTernaryWithIfFollowedByTernary() { + rewriteRun( + //language=java + java( + """ + class Test { + public String determineSomething(String a, String b) { + return "a".equals(a) ? "a" : "b".equals(b) ? "b" : "nope"; + } + } + """, + """ + class Test { + public String determineSomething(String a, String b) { + if ("a".equals(a)) { + return "a"; + } + return "b".equals(b) ? "b" : "nope"; + } + } + """ + ) + ); + } + + @Test + void doReplaceNestedOrTernaryPrimitiveCondition() { + rewriteRun( + //language=java + java( + """ + class Test { + public String determineSomething(int a, int b) { + return a == 1 ? "one" : b == 2 ? "two" : "other"; + } + } + """, + """ + class Test { + public String determineSomething(int a, int b) { + if (a == 1) { + return "one"; + } + return b == 2 ? "two" : "other"; + } + } + """ + ) + ); + } + + @Test + void doReplaceNestedOrTernaryPrimitiveConditionInverted() { + rewriteRun( + //language=java + java( + """ + class Test { + public String determineSomething(int a, int b) { + return 1 == a ? "one" : 2 == b ? "two" : "other"; + } + } + """, + """ + class Test { + public String determineSomething(int a, int b) { + if (1 == a) { + return "one"; + } + return 2 == b ? "two" : "other"; + } + } + """ + ) + ); + } + + @Test + void doReplaceNestedOrTernaryRecursive() { + rewriteRun( + //language=java + java( + """ + class Test { + public String determineSomething(String a, String b, String c) { + return "a".equals(a) ? "a" : "b".equals(b) ? "b" : "c".equals(b) ? "c" :"nope"; + } + } + """, + """ + class Test { + public String determineSomething(String a, String b, String c) { + if ("a".equals(a)) { + return "a"; + } + if ("b".equals(b)) { + return "b"; + } + return "c".equals(b) ? "c" : "nope"; + } + } + """ + ) + ); + } + + @Test + void doReplaceNestedAndTernaryWithIfThenTernary() { + rewriteRun( + //language=java + java( + """ + class Test { + public String determineSomething(String a, String b) { + return "a".equals(a) ? "b".equals(b) ? "b" : "a" : "nope"; + } + } + """, + """ + class Test { + public String determineSomething(String a, String b) { + if ("a".equals(a)) { + return "b".equals(b) ? "b" : "a"; + } + return "nope"; + } + } + """ + ) + ); + } + + @Test + void doReplaceNestedAndOrTernaryWithIfThenTernaryElseTernary() { + rewriteRun( + //language=java + java( + """ + class Test { + public String determineSomething(String a, String b, String c) { + return "a".equals(a) ? "b".equals(b) ? "b" : "a" : "c".equals(c) ? "c" : "nope"; + } + } + """, + """ + class Test { + public String determineSomething(String a, String b, String c) { + if ("a".equals(a)) { + return "b".equals(b) ? "b" : "a"; + } + return "c".equals(c) ? "c" : "nope"; + } + } + """ + ) + ); + } + + @Test + void doReplaceMultiLevelTernaries() { + rewriteRun( + //language=java + java( + """ + class Test { + public String determineSomething(String letter) { + return "a".equals(letter) ? "a" : + "b".equals(letter) ? "b" : + "c".equals(letter) ? "c" : + letter.contains("d") ? letter.startsWith("d") ? letter.equals("d") ? "equals" : "startsWith" : "contains" : + "e".equals(letter) ? "e" : + "f".equals(letter) ? "f" : + "g".equals(letter) ? "g" : "nope"; + } + } + """, + """ + class Test { + public String determineSomething(String letter) { + if ("a".equals(letter)) { + return "a"; + } + if ("b".equals(letter)) { + return "b"; + } + if ("c".equals(letter)) { + return "c"; + } + if (letter.contains("d")) { + if (letter.startsWith("d")) { + return letter.equals("d") ? "equals" : "startsWith"; + } + return "contains"; + } + if ("e".equals(letter)) { + return "e"; + } + if ("f".equals(letter)) { + return "f"; + } + return "g".equals(letter) ? "g" : "nope"; + } + } + """ + ) + ); + } + + @ExpectedToFail("Comment `dont forget about c` falls off. It is part of a `before` that is dropped when falsePart is extracted") + @Test + void doReplaceMultiLevelTernariesWithComments() { + rewriteRun( + //language=java + java( + """ + class Test { + public String determineSomething(String letter) { + return "a".equals(letter) ? "a" : //look its a + "b".equals(letter) ? "b" : //b is also here + "c".equals(letter) ? "c" /* dont forget about c */ : + // d is important too + letter.contains("d") ? letter.startsWith("d") ? letter.equals("d") ? "equals" : "startsWith" : "contains" : + "e".equals(letter) ? "e" : //e + "f".equals(letter) ? "f" : //f + "g".equals(letter) ? "g" : "nope"; //and nope if nope + } + } + """, + """ + class Test { + public String determineSomething(String letter) { + if ("a".equals(letter)) { + return "a"; + } + //look its a + if ("b".equals(letter)) { + return "b"; + } + //b is also here + if ("c".equals(letter)) { + return "c"; /* dont forget about c */ + } + // d is important too + if (letter.contains("d")) { + if (letter.startsWith("d")) { + return letter.equals("d") ? "equals" : "startsWith"; + } + return "contains"; + } + if ("e".equals(letter)) { + return "e"; + } + //e + if ("f".equals(letter)) { + return "f"; + }//f + return "g".equals(letter) ? "g" : "nope"; //and nope if nope + } + } + """ + ) + ); + } + + @Issue("https://github.com/openrewrite/rewrite-static-analysis/issues/112") + @ExpectedToFail("only directly returned ternaries are taken into account") + @Test + void doReplaceNestedOrAssignmentTernaryWithIfElse() { + rewriteRun( + //language=java + java( + """ + class Test { + public void doThing(String a, String b) { + String result = "a".equals(a) ? "a" : "b".equals(b) ? "b" : "nope"; + System.out.println(result); + } + } + """, + """ + class Test { + public void doThing(String a, String b) { + String result; + if ("a".equals(a)) { + result = "a"; + } + else { + result = "b".equals(b) ? "b" : "nope"; + } + System.out.println(result); + } + } + """ + ) + ); + } + + @Test + void doNotReplaceNonNestedOrTernaryInStream() { + rewriteRun( + //language=java + java( + """ + import java.util.Set; + import java.util.Arrays; + import java.util.List; + import java.util.stream.Collectors; + class Test { + public Set makeASet() { + List s = Arrays.asList("a","b","c","nope"); + return s.stream().map(item -> item.startsWith("a") ? "a" : "nope").collect(Collectors.toSet()); + } + } + """ + ) + ); + } + + @Test + void doReplaceNestedOrTernaryInStreamWithIfInBlock() { + rewriteRun( + //language=java + java( + """ + import java.util.Set; + import java.util.Arrays; + import java.util.List; + import java.util.stream.Collectors; + class Test { + public Set makeASet() { + List s = Arrays.asList("a","b","c","nope"); + return s.stream().map(item -> item.startsWith("a") ? "a" : item.startsWith("b") ? "b" : "nope").collect(Collectors.toSet()); + } + } + """, + """ + import java.util.Set; + import java.util.Arrays; + import java.util.List; + import java.util.stream.Collectors; + class Test { + public Set makeASet() { + List s = Arrays.asList("a","b","c","nope"); + return s.stream().map(item -> { + if (item.startsWith("a")) { + return "a"; + } + return item.startsWith("b") ? "b" : "nope"; + }).collect(Collectors.toSet()); + } + } + """ + ) + ); + } + + @Test + void doReplaceNestedOrTernaryInStreamContainingComments() { + rewriteRun( + //language=java + java( + """ + import java.util.Set; + import java.util.Arrays; + import java.util.List; + import java.util.stream.Collectors; + class Test { + public Set makeASet() { + List s = Arrays.asList("a","b","c","nope"); + return s.stream().map( /* look a lambda */ item -> + //look a ternary + item.startsWith("a") ? "a" : item.startsWith("b") ? "b" : "nope" + ).collect(Collectors.toSet()); + } + } + """, + """ + import java.util.Set; + import java.util.Arrays; + import java.util.List; + import java.util.stream.Collectors; + class Test { + public Set makeASet() { + List s = Arrays.asList("a","b","c","nope"); + return s.stream().map( /* look a lambda */ item -> { + //look a ternary + if (item.startsWith("a")) { + return "a"; + } + return item.startsWith("b") ? "b" : "nope"; + } + ).collect(Collectors.toSet()); + } + } + """ + ) + ); + } + + + @Test + void doReplaceNestedOrTernaryContainingNull() { + rewriteRun( + //language=java + java( + """ + class Test { + public String determineSomething(String a, String b) { + return "a".equals(a) ? null : "b".equals(b) ? "b" : null; + } + } + """, + """ + class Test { + public String determineSomething(String a, String b) { + if ("a".equals(a)) { + return null; + } + return "b".equals(b) ? "b" : null; + } + } + """ + ) + ); + } + + @Test + void doReplaceNestedOrTernaryContainingExpression() { + rewriteRun( + //language=java + java( + """ + class Test { + public String determineSomething(String a, String b) { + return "a".equals(a) ? "foo" + "bar" : "b".equals(b) ? a + b : b + a; + } + } + """, + """ + class Test { + public String determineSomething(String a, String b) { + if ("a".equals(a)) { + return "foo" + "bar"; + } + return "b".equals(b) ? a + b : b + a; + } + } + """ + ) + ); + } + + @Test + void doReplaceNestedOrTernaryContainingComments() { + rewriteRun( + //language=java + java( + """ + class Test { + public String determineSomething(String a, String b) { + //this should be before the if and followed by a new line + + return "a".equals(a) ? "a" : "b".equals(b) ? "b" : "nope"; //this should be behind the ternary + } + } + """, + """ + class Test { + public String determineSomething(String a, String b) { + //this should be before the if and followed by a new line + + if ("a".equals(a)) { + return "a"; + } + return "b".equals(b) ? "b" : "nope"; //this should be behind the ternary + } + } + """ + ) + ); + } + + @Test + void doReplaceNestedOrTernaryContainingMethodCall() { + //language=java + rewriteRun( + java(""" + class M{ + static String a(){return "a";} + static String b(){return "b";} + static String c(){return "c";} + static String nope(){return "nope";} + } + """), + java( + """ + class Test { + public String determineSomething(String a, String b) { + return "a".equals(a) ? M.a() : "b".equals(b) ? M.b() : M.nope(); + } + } + """, + """ + class Test { + public String determineSomething(String a, String b) { + if ("a".equals(a)) { + return M.a(); + } + return "b".equals(b) ? M.b() : M.nope(); + } + } + """ + ) + ); + } + + @Test + void doReplaceNestedOrTernaryContainingNewlines() { + rewriteRun( + //language=java + java( + """ + class Test { + public String determineSomething(String a, String b) { + return "a".equals(a) + ? null + : "b".equals(b) + ? "b" + : null; + } + } + """, + """ + class Test { + public String determineSomething(String a, String b) { + if ("a".equals(a)) { + return null; + } + return "b".equals(b) + ? "b" + : null; + } + } + """ + ) + ); + } + } + + @Nested + class SwitchExpressionSupported implements RewriteTest { + + + @Override + public void defaults(RecipeSpec spec) { + spec.recipe(new TernaryOperatorsShouldNotBeNested()).allSources(s -> s.markers(javaVersion(14))); + } + + @Nested + class ReplaceWithSwitchExpression { + @Test + @DocumentExample + void doReplaceNestedOrTernaryWithSwitchExpression() { + rewriteRun( + //language=java + java( + """ + class Test { + public String determineSomething(String a, String b) { + return "a".equals(a) ? "a" : "b".equals(a) ? "b" : "nope"; + } + } + """, + """ + class Test { + public String determineSomething(String a, String b) { + return switch (a) { + case "a" -> "a"; + case "b" -> "b"; + default -> "nope"; + }; + } + } + """ + ) + ); + } + + @Test + void doReplaceNestedOrTernaryPrimitiveCondition() { + rewriteRun( + //language=java + java( + """ + class Test { + public String determineSomething(int a) { + return a == 1 ? "one" : a == 2 ? "two" : "other"; + } + } + """, + """ + class Test { + public String determineSomething(int a) { + return switch (a) { + case 1 -> "one"; + case 2 -> "two"; + default -> "other"; + }; + } + } + """ + ) + ); + } + + @Test + void doReplaceNestedOrTernaryPrimitiveConditionWithConstants() { + rewriteRun( + //language=java + java( + """ + class Test { + private static final int ONE = 1; + private static final int TWO = 2; + public String determineSomething(int a) { + return a == ONE ? "one" : a == TWO ? "two" : "other"; + } + } + """, + """ + class Test { + private static final int ONE = 1; + private static final int TWO = 2; + public String determineSomething(int a) { + return switch (a) { + case ONE -> "one"; + case TWO -> "two"; + default -> "other"; + }; + } + } + """ + ) + ); + } + + + @Test + void doReplaceNestedOrTernaryPrimitiveNotEqualsCondition() { + rewriteRun( + //language=java + java( + """ + class Test { + public String determineSomething(int a) { + return a >= 3 ? "a lot" : a == 2 ? "two" : "other"; + } + } + """, + """ + class Test { + public String determineSomething(int a) { + if (a >= 3) { + return "a lot"; + } + return a == 2 ? "two" : "other"; + } + } + """ + ) + ); + } + + @Test + void doReplaceNestedOrTernaryPrimitiveConditionInverted() { + rewriteRun( + //language=java + java( + """ + class Test { + public String determineSomething(int a) { + return 1 == a ? "one" : 2 == a ? "two" : "other"; + } + } + """, + """ + class Test { + public String determineSomething(int a) { + return switch (a) { + case 1 -> "one"; + case 2 -> "two"; + default -> "other"; + }; + } + } + """ + ) + ); + } + + @Test + void doReplaceNestedOrTernaryWithSwitchExpressionInvertedEquals() { + rewriteRun( + //language=java + java( + """ + class Test { + public String determineSomething(String a, String b) { + return a.equals("a") ? "a" : a.equals("b") ? b : "nope"; + } + } + """, + """ + class Test { + public String determineSomething(String a, String b) { + return switch (a) { + case "a" -> "a"; + case "b" -> b; + default -> "nope"; + }; + } + } + """ + ) + ); + } + + @ExpectedToFail("switch(null) is not supported before Java 18. This would break null safety.") + @Issue("https://github.com/openrewrite/rewrite-static-analysis/issues/157") + @Test + void doReplaceNestedOrTernaryWithSwitchExpressionNullSafeEquals() { + rewriteRun( + //language=java + java( + """ + import java.util.Objects; + class Test { + public String determineSomething(String a, String b) { + return Objects.equals(a, "a") ? "a" : Objects.equals(a, "b") ? b : "nope"; + } + } + """, + """ + class Test { + public String determineSomething(String a, String b) { + return switch (a) { + case "a" -> "a"; + case "b" -> b; + default -> "nope"; + }; + } + } + """ + ) + ); + } + + @ExpectedToFail("switch(null) is not supported before Java 18. This would break null safety.") + @Issue("https://github.com/openrewrite/rewrite-static-analysis/issues/157") + @Test + void doReplaceNestedOrTernaryWithSwitchExpressionNullSafeEqualsInverted() { + rewriteRun( + //language=java + java( + """ + import java.util.Objects; + class Test { + public String determineSomething(String a, String b) { + return Objects.equals("a", a) ? "a" : Objects.equals("b", a) ? b : "nope"; + } + } + """, + """ + class Test { + public String determineSomething(String a, String b) { + return switch (a) { + case "a" -> "a"; + case "b" -> b; + default -> "nope"; + }; + } + } + """ + ) + ); + } + + @Test + void doReplaceNestedOrTernaryRecursive() { + rewriteRun( + //language=java + java( + """ + class Test { + public String determineSomething(String a) { + return "a".equals(a) ? "a" : "b".equals(a) ? "b" : "c".equals(a) ? "c" : "nope"; + } + } + """, + """ + class Test { + public String determineSomething(String a) { + return switch (a) { + case "a" -> "a"; + case "b" -> "b"; + case "c" -> "c"; + default -> "nope"; + }; + } + } + """ + ) + ); + } + + + @Test + void doReplaceMultiLevelTernariesWithSwitchExpression() { + rewriteRun( + //language=java + java( + """ + class Test { + public String determineSomething(String letter) { + return "a".equals(letter) ? "a" : //look its a + "b".equals(letter) ? "b" : //b is also here + "c".equals(letter) ? "c" : /* dont forget about c */ + // d is important too + "d".equals(letter) ? "d" : + "e".equals(letter) ? "e" : //e + "f".equals(letter) ? "f" : //f + "g".equals(letter) ? "g" : "nope"; //and nope if nope + } + } + """, + """ + class Test { + public String determineSomething(String letter) { + return switch (letter) { + case "a" -> "a"; //look its a + case "b" -> "b"; //b is also here + case "c" -> "c"; /* dont forget about c */ + // d is important too + case "d" -> "d"; + case "e" -> "e"; //e + case "f" -> "f"; //f + case "g" -> "g"; + default -> "nope"; + }; //and nope if nope + } + } + """ + ) + ); + } + + @Test + void doReplaceNestedOrAssignmentTernaryWithSwitch() { + rewriteRun( + //language=java + java( + """ + class Test { + public void doThing(String a, String b) { + String result = "a".equals(a) ? "a" : "b".equals(a) ? b : "nope"; + System.out.println(result); + } + } + """, + """ + class Test { + public void doThing(String a, String b) { + String result = switch (a) { + case "a" -> "a"; + case "b" -> b; + default -> "nope"; + }; + System.out.println(result); + } + } + """ + ) + ); + } + + @Test + void doReplaceNestedOrTernaryInStreamWithSwitch() { + rewriteRun( + //language=java + java( + """ + import java.util.Set; + import java.util.Arrays; + import java.util.List; + import java.util.stream.Collectors; + class Test { + public Set makeASet() { + List s = Arrays.asList("a","b","c","nope"); + return s.stream().map(item -> item.equals("a") ? "a" : item.equals("b") ? "b" : "nope").collect(Collectors.toSet()); + } + } + """, + """ + import java.util.Set; + import java.util.Arrays; + import java.util.List; + import java.util.stream.Collectors; + class Test { + public Set makeASet() { + List s = Arrays.asList("a","b","c","nope"); + return s.stream().map(item -> switch (item) { + case "a" -> "a"; + case "b" -> "b"; + default -> "nope"; + }).collect(Collectors.toSet()); + } + } + """ + ) + ); + } + + @Test + void doReplaceNestedOrTernaryInStreamContainingComments() { + rewriteRun( + //language=java + java( + """ + import java.util.Set; + import java.util.Arrays; + import java.util.List; + import java.util.stream.Collectors; + class Test { + public Set makeASet() { + List s = Arrays.asList("a","b","c","nope"); + return s.stream().map( /* look a lambda */ item -> + //look a ternary + item.equals("a") ? "a" : item.equals("b") ? "b" : "nope" + ).collect(Collectors.toSet()); + } + } + """, + """ + import java.util.Set; + import java.util.Arrays; + import java.util.List; + import java.util.stream.Collectors; + class Test { + public Set makeASet() { + List s = Arrays.asList("a","b","c","nope"); + return s.stream().map( /* look a lambda */ item -> switch (item) { //look a ternary + case "a" -> "a"; + case "b" -> "b"; + default -> "nope"; + } + ).collect(Collectors.toSet()); + } + } + """ + ) + ); + } + + @Test + void doReplaceNestedOrTernaryContainingNull() { + rewriteRun( + //language=java + java( + """ + class Test { + public String determineSomething(String a) { + return "a".equals(a) ? null : "b".equals(a) ? "b" : null; + } + } + """, + """ + class Test { + public String determineSomething(String a) { + return switch (a) { + case "a" -> null; + case "b" -> "b"; + default -> null; + }; + } + } + """ + ) + ); + } + + @Test + void doReplaceNestedOrTernaryContainingExpression() { + rewriteRun( + //language=java + java( + """ + class Test { + public String determineSomething(String a, String b) { + return "a".equals(a) ? "foo" + "bar" : "b".equals(a) ? a + b : b + a; + } + } + """, + """ + class Test { + public String determineSomething(String a, String b) { + return switch (a) { + case "a" -> "foo" + "bar"; + case "b" -> a + b; + default -> b + a; + }; + } + } + """ + ) + ); + } + + @Test + void doReplaceNestedOrTernaryContainingComments() { + rewriteRun( + //language=java + java( + """ + class Test { + public String determineSomething(String a, String b) { + //this should be before the if and followed by a new line + + return "a".equals(a) ? "a" : "b".equals(a) ? "b" : "nope"; //this should be behind the ternary + } + } + """, + """ + class Test { + public String determineSomething(String a, String b) { + //this should be before the if and followed by a new line + + return switch (a) { + case "a" -> "a"; + case "b" -> "b"; + default -> "nope"; + }; //this should be behind the ternary + } + } + """ + ) + ); + } + + @Test + void doReplaceNestedOrTernaryContainingMethodCall() { + //language=java + rewriteRun( + java(""" + class M{ + static String a(){return "a";} + static String b(){return "b";} + static String c(){return "c";} + static String nope(){return "nope";} + } + """), + java( + """ + class Test { + public String determineSomething(String a, String b) { + return "a".equals(a) ? M.a() : "b".equals(a) ? M.b() : M.nope(); + } + } + """, + """ + class Test { + public String determineSomething(String a, String b) { + return switch (a) { + case "a" -> M.a(); + case "b" -> M.b(); + default -> M.nope(); + }; + } + } + """ + ) + ); + } + + @ExpectedToFail("Pattern matching not yet implemented") + @Issue("https://github.com/openrewrite/rewrite-static-analysis/issues/158") + @Test + void doReplaceNestedOrTernaryInStreamWithPatternMatchingSwitch() { + rewriteRun( + //language=java + java( + """ + import java.util.Set; + import java.util.Arrays; + import java.util.List; + import java.util.stream.Collectors; + class Test { + public Set makeASet() { + List s = Arrays.asList("a","b","c","nope"); + return s.stream().map(item -> item.startsWith("a") ? "a" : item.startsWith("b") ? "b" : "nope").collect(Collectors.toSet()); + } + } + """, + """ + import java.util.Set; + import java.util.Arrays; + import java.util.List; + import java.util.stream.Collectors; + class Test { + public Set makeASet() { + List s = Arrays.asList("a","b","c","nope"); + return s.stream().map(item -> + switch (item) { + case String st && st.startsWith("a") -> "a"; + case String st && st.startsWith("b") -> "b"; + default -> "nope"; + } + ).collect(Collectors.toSet()); + } + } + """ + ) + ); + } + + @ExpectedToFail("not yet implemented collapsing cases") + @Issue("https://github.com/openrewrite/rewrite-static-analysis/issues/159") + @Test + void doReplaceNestedOrTernaryContainingNullCollapsingSameCases() { + rewriteRun( + //language=java + java( + """ + class Test { + public String determineSomething(String a) { + return "a".equals(a) ? null : "b".equals(a) ? "b" : "c".equals(a) ? "c" : null; + } + } + """, + """ + class Test { + public String determineSomething(String a, String b) { + return switch (a) { + case "b" -> "b"; + case "c" -> "c"; + default -> null; + }; + } + } + """ + ) + ); + } + } + + @Nested + class ReplaceWithIf { + + @Test + void doReplaceNestedOrTernaryRecursiveWithIfsWhenMultipleVariablesAreUsed() { + rewriteRun( + //language=java + java( + """ + class Test { + public String determineSomething(String a, String b, String c) { + return "a".equals(a) ? "a" : "b".equals(b) ? "b" : "c".equals(c) ? "c" :"nope"; + } + } + """, + """ + class Test { + public String determineSomething(String a, String b, String c) { + if ("a".equals(a)) { + return "a"; + } + if ("b".equals(b)) { + return "b"; + } + return "c".equals(c) ? "c" : "nope"; + } + } + """ + ) + ); + } + + @Test + void doReplaceNestedAndTernaryWithIfThenTernary() { + rewriteRun( + //language=java + java( + """ + class Test { + public String determineSomething(String a, String b) { + return "a".equals(a) ? "b".equals(b) ? "b" : "a" : "nope"; + } + } + """, + """ + class Test { + public String determineSomething(String a, String b) { + if ("a".equals(a)) { + return "b".equals(b) ? "b" : "a"; + } + return "nope"; + } + } + """ + ) + ); + } + + @Test + void doReplaceNestedAndOrTernaryWithIfThenTernaryElseTernary() { + rewriteRun( + //language=java + java( + """ + class Test { + public String determineSomething(String a, String b, String c) { + return "a".equals(a) ? "b".equals(b) ? "b" : "a" : "c".equals(c) ? "c" : "nope"; + } + } + """, + """ + class Test { + public String determineSomething(String a, String b, String c) { + if ("a".equals(a)) { + return "b".equals(b) ? "b" : "a"; + } + return "c".equals(c) ? "c" : "nope"; + } + } + """ + ) + ); + } + + @Test + void doNotReplaceNestedOrTernaryWithIfWhenUsingNullSafeEquals() { + rewriteRun( + //language=java + java( + """ + import java.util.Objects; + class Test { + public String determineSomething(String a, String b) { + return Objects.equals(a, "a") ? "a" : Objects.equals(a, "b") ? b : "nope"; + } + } + """, + """ + import java.util.Objects; + class Test { + public String determineSomething(String a, String b) { + if (Objects.equals(a, "a")) { + return "a"; + } + return Objects.equals(a, "b") ? b : "nope"; + } + } + """ + ) + ); + } + + @Test + void doReplaceNestedOrTernaryWithNonConstantEqualsWithIf() { + rewriteRun( + //language=java + java( + """ + class Test { + public String determineSomething(String a, String b) { + return a.equals("a".toString()) ? "a" : "b".equals(a) ? "b" : "nope"; + } + } + """, + """ + class Test { + public String determineSomething(String a, String b) { + if (a.equals("a".toString())) { + return "a"; + } + return "b".equals(a) ? "b" : "nope"; + } + } + """ + ) + ); + } + + } + + @Nested + class DoNotReplace { + + @Test + void doNotReplaceNonNestedOrTernaryInStreamWithSwitch() { + rewriteRun( + //language=java + java( + """ + import java.util.Set; + import java.util.Arrays; + import java.util.List; + import java.util.stream.Collectors; + class Test { + public Set makeASet() { + List s = Arrays.asList("a","b","c","nope"); + return s.stream().map(item -> item.startsWith("a") ? "a" : "nope").collect(Collectors.toSet()); + } + } + """ + ) + ); + } + + } + + } + + +} \ No newline at end of file From 869b0d67cb2b017ee0dc414c13de7844b61368b7 Mon Sep 17 00:00:00 2001 From: Mike Sol Date: Wed, 23 Aug 2023 10:24:14 -0700 Subject: [PATCH 05/92] Clean up some recipe descriptions To improve clarity, grammar, and style. --- .../staticanalysis/BooleanChecksNotInverted.java | 3 +-- .../RemoveToStringCallsFromArrayInstances.java | 2 +- .../openrewrite/staticanalysis/ReplaceWeekYearWithYear.java | 2 +- .../staticanalysis/SimplifyBooleanExpression.java | 2 +- .../openrewrite/staticanalysis/SimplifyBooleanReturn.java | 2 +- .../staticanalysis/SortedSetStreamToLinkedHashSet.java | 2 +- .../staticanalysis/UnnecessaryExplicitTypeArguments.java | 2 +- .../org/openrewrite/staticanalysis/UnnecessaryThrows.java | 6 +++--- 8 files changed, 10 insertions(+), 11 deletions(-) diff --git a/src/main/java/org/openrewrite/staticanalysis/BooleanChecksNotInverted.java b/src/main/java/org/openrewrite/staticanalysis/BooleanChecksNotInverted.java index d152bd354..a88e6f562 100644 --- a/src/main/java/org/openrewrite/staticanalysis/BooleanChecksNotInverted.java +++ b/src/main/java/org/openrewrite/staticanalysis/BooleanChecksNotInverted.java @@ -34,8 +34,7 @@ public String getDisplayName() { @Override public String getDescription() { - return "It is needlessly complex to invert the result of a boolean comparison. The opposite comparison should be made instead. " - + "Also double negation of boolean expressions should be avoided."; + return "Ensures that boolean checks are not unnecessarily inverted. Also fixes double negative boolean expressions."; } @Override diff --git a/src/main/java/org/openrewrite/staticanalysis/RemoveToStringCallsFromArrayInstances.java b/src/main/java/org/openrewrite/staticanalysis/RemoveToStringCallsFromArrayInstances.java index dd13461e6..2810f7ca8 100644 --- a/src/main/java/org/openrewrite/staticanalysis/RemoveToStringCallsFromArrayInstances.java +++ b/src/main/java/org/openrewrite/staticanalysis/RemoveToStringCallsFromArrayInstances.java @@ -56,7 +56,7 @@ public String getDisplayName() { @Override public String getDescription() { return "The result from `toString()` calls on arrays is largely useless. The output does not actually reflect" + - " the contents of the array. `Arrays.toString(array)` give the contents of the array."; + " the contents of the array. `Arrays.toString(array)` should be used instead as it gives the contents of the array."; } public TreeVisitor getVisitor() { diff --git a/src/main/java/org/openrewrite/staticanalysis/ReplaceWeekYearWithYear.java b/src/main/java/org/openrewrite/staticanalysis/ReplaceWeekYearWithYear.java index 6262fc2c9..ee13f6b11 100644 --- a/src/main/java/org/openrewrite/staticanalysis/ReplaceWeekYearWithYear.java +++ b/src/main/java/org/openrewrite/staticanalysis/ReplaceWeekYearWithYear.java @@ -35,7 +35,7 @@ public String getDisplayName() { @Override public String getDescription() { return "For most dates Week Year (YYYY) and Year (yyyy) yield the same results. However, on the last week of" + - " December and first week of January Week Year could produce unexpected results."; + " December and the first week of January, Week Year could produce unexpected results."; } @Override diff --git a/src/main/java/org/openrewrite/staticanalysis/SimplifyBooleanExpression.java b/src/main/java/org/openrewrite/staticanalysis/SimplifyBooleanExpression.java index 26b040411..1025b48dd 100644 --- a/src/main/java/org/openrewrite/staticanalysis/SimplifyBooleanExpression.java +++ b/src/main/java/org/openrewrite/staticanalysis/SimplifyBooleanExpression.java @@ -41,7 +41,7 @@ public String getDisplayName() { @Override public String getDescription() { - return "Checks for over-complicated boolean expressions. Finds code like `if (b == true)`, `b || true`, `!false`, etc."; + return "Checks for overly complicated boolean expressions, such as `if (b == true)`, `b || true`, `!false`, etc."; } @Override diff --git a/src/main/java/org/openrewrite/staticanalysis/SimplifyBooleanReturn.java b/src/main/java/org/openrewrite/staticanalysis/SimplifyBooleanReturn.java index e72762c58..118455179 100644 --- a/src/main/java/org/openrewrite/staticanalysis/SimplifyBooleanReturn.java +++ b/src/main/java/org/openrewrite/staticanalysis/SimplifyBooleanReturn.java @@ -44,7 +44,7 @@ public String getDisplayName() { @Override public String getDescription() { - return "Simplifies Boolean expressions by removing redundancies, e.g.: `a && true` simplifies to `a`."; + return "Simplifies Boolean expressions by removing redundancies. For example, `a && true` simplifies to `a`."; } @Override diff --git a/src/main/java/org/openrewrite/staticanalysis/SortedSetStreamToLinkedHashSet.java b/src/main/java/org/openrewrite/staticanalysis/SortedSetStreamToLinkedHashSet.java index 66ac67b07..c62a837c9 100644 --- a/src/main/java/org/openrewrite/staticanalysis/SortedSetStreamToLinkedHashSet.java +++ b/src/main/java/org/openrewrite/staticanalysis/SortedSetStreamToLinkedHashSet.java @@ -38,7 +38,7 @@ public String getDisplayName() { @Override public String getDescription() { - return "Correct 'set.stream().sorted().collect(Collectors.toSet())' to 'set.stream().sorted().collect(LinkedHashSet::new)'."; + return "Converts `set.stream().sorted().collect(Collectors.toSet())` to `set.stream().sorted().collect(LinkedHashSet::new)`."; } private static final MethodMatcher STREAM_COLLECT_METHOD_MATCHER = new MethodMatcher("java.util.stream.Stream collect(java.util.stream.Collector)"); diff --git a/src/main/java/org/openrewrite/staticanalysis/UnnecessaryExplicitTypeArguments.java b/src/main/java/org/openrewrite/staticanalysis/UnnecessaryExplicitTypeArguments.java index de7e805cc..66dde6a1a 100644 --- a/src/main/java/org/openrewrite/staticanalysis/UnnecessaryExplicitTypeArguments.java +++ b/src/main/java/org/openrewrite/staticanalysis/UnnecessaryExplicitTypeArguments.java @@ -31,7 +31,7 @@ public String getDisplayName() { @Override public String getDescription() { - return "When explicit type arguments are inferrable by the compiler, they may be removed."; + return "When explicit type arguments are inferable by the compiler, they may be removed."; } @Override diff --git a/src/main/java/org/openrewrite/staticanalysis/UnnecessaryThrows.java b/src/main/java/org/openrewrite/staticanalysis/UnnecessaryThrows.java index 9543be13e..eb600b7a4 100644 --- a/src/main/java/org/openrewrite/staticanalysis/UnnecessaryThrows.java +++ b/src/main/java/org/openrewrite/staticanalysis/UnnecessaryThrows.java @@ -38,12 +38,12 @@ public String getDisplayName() { @Override public String getDescription() { - return "Remove unnecessary `throws` declarations. This recipe will only remove unused, checked exception if:\n" + + return "Remove unnecessary `throws` declarations. This recipe will only remove unused, checked exceptions if:\n" + "\n" + "- The declaring class or the method declaration is `final`.\n" + "- The method declaration is `static` or `private`.\n" + - "- If the method overriding a method declaration in a super class and the super does not throw the exception.\n" + - "- If the method is `public` or `protected` and the exception is not documented via a JavaDoc as a `@throws` tag."; + "- The method overrides a method declaration in a super class and the super class does not throw the exception.\n" + + "- The method is `public` or `protected` and the exception is not documented via a JavaDoc as a `@throws` tag."; } @Override From 3fb1a08101747ee0be1d1ace2accebbdd096711a Mon Sep 17 00:00:00 2001 From: joanvr Date: Thu, 24 Aug 2023 15:42:02 +0200 Subject: [PATCH 06/92] do not remove try block if there are resources --- .../staticanalysis/EmptyBlockVisitor.java | 8 ++++--- .../staticanalysis/EmptyBlockTest.java | 23 +++++++++++++++++++ 2 files changed, 28 insertions(+), 3 deletions(-) diff --git a/src/main/java/org/openrewrite/staticanalysis/EmptyBlockVisitor.java b/src/main/java/org/openrewrite/staticanalysis/EmptyBlockVisitor.java index 5e68b7426..ede357c4f 100644 --- a/src/main/java/org/openrewrite/staticanalysis/EmptyBlockVisitor.java +++ b/src/main/java/org/openrewrite/staticanalysis/EmptyBlockVisitor.java @@ -82,7 +82,7 @@ public J.Block visitBlock(J.Block block, P p) { J.Block nestedBlock = (J.Block) s; if (isEmptyBlock(nestedBlock) && ((Boolean.TRUE.equals(emptyBlockStyle.getStaticInit()) && nestedBlock.isStatic()) || - (Boolean.TRUE.equals(emptyBlockStyle.getInstanceInit()) && !nestedBlock.isStatic()))) { + (Boolean.TRUE.equals(emptyBlockStyle.getInstanceInit()) && !nestedBlock.isStatic()))) { filtered.set(true); return null; } @@ -97,7 +97,9 @@ public J.Block visitBlock(J.Block block, P p) { public J.Try visitTry(J.Try tryable, P p) { J.Try t = super.visitTry(tryable, p); - if (Boolean.TRUE.equals(emptyBlockStyle.getLiteralTry()) && isEmptyBlock(t.getBody())) { + if (Boolean.TRUE.equals(emptyBlockStyle.getLiteralTry()) && + isEmptyBlock(t.getBody()) && + (t.getResources() == null || t.getResources().isEmpty())) { doAfterVisit(new DeleteStatement<>(tryable)); } else if (Boolean.TRUE.equals(emptyBlockStyle.getLiteralFinally()) && t.getFinally() != null && !t.getCatches().isEmpty() && isEmptyBlock(t.getFinally())) { @@ -211,7 +213,7 @@ public J.Switch visitSwitch(J.Switch switch_, P p) { private boolean isEmptyBlock(Statement blockNode) { if (blockNode instanceof J.Block) { - J.Block block = (J.Block)blockNode; + J.Block block = (J.Block) blockNode; if (EmptyBlockStyle.BlockPolicy.STATEMENT.equals(emptyBlockStyle.getBlockPolicy())) { return block.getStatements().isEmpty(); } else if (EmptyBlockStyle.BlockPolicy.TEXT.equals(emptyBlockStyle.getBlockPolicy())) { diff --git a/src/test/java/org/openrewrite/staticanalysis/EmptyBlockTest.java b/src/test/java/org/openrewrite/staticanalysis/EmptyBlockTest.java index 8e5b1f619..a7c1aa2cc 100644 --- a/src/test/java/org/openrewrite/staticanalysis/EmptyBlockTest.java +++ b/src/test/java/org/openrewrite/staticanalysis/EmptyBlockTest.java @@ -414,4 +414,27 @@ public class A { ) ); } + + @Test + void emptyTryWithResources() { + rewriteRun( + //language=java + java( + """ + import java.io.FileInputStream;import java.io.IOException;import java.io.UncheckedIOException;public class A { + private final InputStream stdin = new FileInputStream("test.in"); + private final InputStream stdout = new FileInputStream("test.out"); + + public void destroy() { + // close all streams in a try-with-resources + try (stdin; stdout) { + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + } + """ + ) + ); + } } From 764045ed588762a9478169302fb70263845e8081 Mon Sep 17 00:00:00 2001 From: joanvr Date: Thu, 24 Aug 2023 15:50:11 +0200 Subject: [PATCH 07/92] undo styling --- .../java/org/openrewrite/staticanalysis/EmptyBlockVisitor.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/openrewrite/staticanalysis/EmptyBlockVisitor.java b/src/main/java/org/openrewrite/staticanalysis/EmptyBlockVisitor.java index ede357c4f..4d59eca8d 100644 --- a/src/main/java/org/openrewrite/staticanalysis/EmptyBlockVisitor.java +++ b/src/main/java/org/openrewrite/staticanalysis/EmptyBlockVisitor.java @@ -82,7 +82,7 @@ public J.Block visitBlock(J.Block block, P p) { J.Block nestedBlock = (J.Block) s; if (isEmptyBlock(nestedBlock) && ((Boolean.TRUE.equals(emptyBlockStyle.getStaticInit()) && nestedBlock.isStatic()) || - (Boolean.TRUE.equals(emptyBlockStyle.getInstanceInit()) && !nestedBlock.isStatic()))) { + (Boolean.TRUE.equals(emptyBlockStyle.getInstanceInit()) && !nestedBlock.isStatic()))) { filtered.set(true); return null; } From 05ebad240e6764bc30fa92134b44a468c19177ac Mon Sep 17 00:00:00 2001 From: joanvr Date: Thu, 24 Aug 2023 16:44:20 +0200 Subject: [PATCH 08/92] improved empty resources detection --- .../staticanalysis/EmptyBlockVisitor.java | 31 +++++++++-- .../staticanalysis/EmptyBlockTest.java | 51 ++++++++++++++++++- 2 files changed, 76 insertions(+), 6 deletions(-) diff --git a/src/main/java/org/openrewrite/staticanalysis/EmptyBlockVisitor.java b/src/main/java/org/openrewrite/staticanalysis/EmptyBlockVisitor.java index 4d59eca8d..d904881be 100644 --- a/src/main/java/org/openrewrite/staticanalysis/EmptyBlockVisitor.java +++ b/src/main/java/org/openrewrite/staticanalysis/EmptyBlockVisitor.java @@ -18,16 +18,14 @@ import lombok.EqualsAndHashCode; import lombok.Value; import org.openrewrite.internal.ListUtils; +import org.openrewrite.internal.lang.Nullable; import org.openrewrite.java.DeleteStatement; import org.openrewrite.java.JavaIsoVisitor; import org.openrewrite.java.JavaTemplate; import org.openrewrite.java.JavaVisitor; import org.openrewrite.java.format.ShiftFormat; import org.openrewrite.java.style.EmptyBlockStyle; -import org.openrewrite.java.tree.Expression; -import org.openrewrite.java.tree.J; -import org.openrewrite.java.tree.Space; -import org.openrewrite.java.tree.Statement; +import org.openrewrite.java.tree.*; import org.openrewrite.marker.Markers; import java.util.ArrayList; @@ -99,7 +97,7 @@ public J.Try visitTry(J.Try tryable, P p) { if (Boolean.TRUE.equals(emptyBlockStyle.getLiteralTry()) && isEmptyBlock(t.getBody()) && - (t.getResources() == null || t.getResources().isEmpty())) { + isEmptyResources(t.getResources())) { doAfterVisit(new DeleteStatement<>(tryable)); } else if (Boolean.TRUE.equals(emptyBlockStyle.getLiteralFinally()) && t.getFinally() != null && !t.getCatches().isEmpty() && isEmptyBlock(t.getFinally())) { @@ -223,6 +221,29 @@ private boolean isEmptyBlock(Statement blockNode) { return false; } + private boolean isEmptyResources(@Nullable List resources) { + if (resources == null || resources.isEmpty()) { + return true; + } + // Searching for access to instances from outside the scope to detect potential side effects. + // If that's the case, we cannot remove this resources block. + for (J.Try.Resource resource : resources) { + // Any reference to an identifier used here comes from outside the scope. + if (resource.getVariableDeclarations() instanceof J.Identifier) { + return false; + } else if (resource.getVariableDeclarations() instanceof J.VariableDeclarations) { + J.VariableDeclarations variableDeclarations = (J.VariableDeclarations) resource.getVariableDeclarations(); + for (J.VariableDeclarations.NamedVariable variable : variableDeclarations.getVariables()) { + // If the variable is not initialized with a new instance, it means it can come from outside the scope. + if (!(variable.getInitializer() instanceof J.NewClass)) { + return false; + } + } + } + } + return true; + } + private static class ExtractSideEffectsOfIfCondition

extends JavaVisitor

{ private final J.Block enclosingBlock; private final J.If toExtract; diff --git a/src/test/java/org/openrewrite/staticanalysis/EmptyBlockTest.java b/src/test/java/org/openrewrite/staticanalysis/EmptyBlockTest.java index a7c1aa2cc..630c04f71 100644 --- a/src/test/java/org/openrewrite/staticanalysis/EmptyBlockTest.java +++ b/src/test/java/org/openrewrite/staticanalysis/EmptyBlockTest.java @@ -416,7 +416,7 @@ public class A { } @Test - void emptyTryWithResources() { + void emptyTryWithResourcesWithExternalResources() { rewriteRun( //language=java java( @@ -437,4 +437,53 @@ public void destroy() { ) ); } + + @Test + void emptyTryWithResourcesWithVariableInitializationOfExternalResource() { + rewriteRun( + //language=java + java( + """ + import java.io.FileInputStream;import java.io.IOException;import java.io.InputStream;import java.io.UncheckedIOException;public class A { + private final InputStream stdin = new FileInputStream("test.in"); + + public void destroy() { + // close stream in a try-with-resources + try (InputStream s = stdin) { + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + } + """ + ) + ); + } + + @Test + void emptyTryWithResourcesWithVariableInitializationMethodInvocation() { + rewriteRun( + //language=java + java( + """ + import java.io.FileInputStream;import java.io.IOException;import java.io.InputStream;import java.io.UncheckedIOException;public class A { + private final InputStream stdin = new FileInputStream("test.in"); + + private InputStream getStdin() { + return stdin; + } + + public void destroy() { + // close stream in a try-with-resources + try (InputStream s = getStdin()) { + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + } + """ + ) + ); + } + } From abb01e0146d6f2cd234bb3f67168f71507eee872 Mon Sep 17 00:00:00 2001 From: joanvr Date: Thu, 24 Aug 2023 16:48:41 +0200 Subject: [PATCH 09/92] fixed imports --- .../org/openrewrite/staticanalysis/EmptyBlockVisitor.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/openrewrite/staticanalysis/EmptyBlockVisitor.java b/src/main/java/org/openrewrite/staticanalysis/EmptyBlockVisitor.java index d904881be..53d2d1275 100644 --- a/src/main/java/org/openrewrite/staticanalysis/EmptyBlockVisitor.java +++ b/src/main/java/org/openrewrite/staticanalysis/EmptyBlockVisitor.java @@ -25,7 +25,10 @@ import org.openrewrite.java.JavaVisitor; import org.openrewrite.java.format.ShiftFormat; import org.openrewrite.java.style.EmptyBlockStyle; -import org.openrewrite.java.tree.*; +import org.openrewrite.java.tree.Expression; +import org.openrewrite.java.tree.J; +import org.openrewrite.java.tree.Space; +import org.openrewrite.java.tree.Statement; import org.openrewrite.marker.Markers; import java.util.ArrayList; From 8d16fdd3da18d06ba072d17821c194b704dce8ef Mon Sep 17 00:00:00 2001 From: joanvr Date: Thu, 24 Aug 2023 17:33:17 +0200 Subject: [PATCH 10/92] simplified use case --- .../staticanalysis/EmptyBlockVisitor.java | 21 +----- .../staticanalysis/EmptyBlockTest.java | 65 ++----------------- 2 files changed, 8 insertions(+), 78 deletions(-) diff --git a/src/main/java/org/openrewrite/staticanalysis/EmptyBlockVisitor.java b/src/main/java/org/openrewrite/staticanalysis/EmptyBlockVisitor.java index 53d2d1275..c9218dc7d 100644 --- a/src/main/java/org/openrewrite/staticanalysis/EmptyBlockVisitor.java +++ b/src/main/java/org/openrewrite/staticanalysis/EmptyBlockVisitor.java @@ -225,26 +225,7 @@ private boolean isEmptyBlock(Statement blockNode) { } private boolean isEmptyResources(@Nullable List resources) { - if (resources == null || resources.isEmpty()) { - return true; - } - // Searching for access to instances from outside the scope to detect potential side effects. - // If that's the case, we cannot remove this resources block. - for (J.Try.Resource resource : resources) { - // Any reference to an identifier used here comes from outside the scope. - if (resource.getVariableDeclarations() instanceof J.Identifier) { - return false; - } else if (resource.getVariableDeclarations() instanceof J.VariableDeclarations) { - J.VariableDeclarations variableDeclarations = (J.VariableDeclarations) resource.getVariableDeclarations(); - for (J.VariableDeclarations.NamedVariable variable : variableDeclarations.getVariables()) { - // If the variable is not initialized with a new instance, it means it can come from outside the scope. - if (!(variable.getInitializer() instanceof J.NewClass)) { - return false; - } - } - } - } - return true; + return resources == null || resources.isEmpty(); } private static class ExtractSideEffectsOfIfCondition

extends JavaVisitor

{ diff --git a/src/test/java/org/openrewrite/staticanalysis/EmptyBlockTest.java b/src/test/java/org/openrewrite/staticanalysis/EmptyBlockTest.java index 630c04f71..4d28214aa 100644 --- a/src/test/java/org/openrewrite/staticanalysis/EmptyBlockTest.java +++ b/src/test/java/org/openrewrite/staticanalysis/EmptyBlockTest.java @@ -116,7 +116,7 @@ void emptyTry() { public class A { { final String fileName = "fileName"; - try(FileInputStream fis = new FileInputStream(fileName)) { + try { } catch (IOException e) { } } @@ -416,68 +416,18 @@ public class A { } @Test - void emptyTryWithResourcesWithExternalResources() { + void emptyTryWithResources() { rewriteRun( //language=java java( """ - import java.io.FileInputStream;import java.io.IOException;import java.io.UncheckedIOException;public class A { - private final InputStream stdin = new FileInputStream("test.in"); - private final InputStream stdout = new FileInputStream("test.out"); - - public void destroy() { - // close all streams in a try-with-resources - try (stdin; stdout) { - } catch (IOException e) { - throw new UncheckedIOException(e); - } - } - } - """ - ) - ); - } - - @Test - void emptyTryWithResourcesWithVariableInitializationOfExternalResource() { - rewriteRun( - //language=java - java( - """ - import java.io.FileInputStream;import java.io.IOException;import java.io.InputStream;import java.io.UncheckedIOException;public class A { - private final InputStream stdin = new FileInputStream("test.in"); - - public void destroy() { - // close stream in a try-with-resources - try (InputStream s = stdin) { - } catch (IOException e) { - throw new UncheckedIOException(e); - } - } - } - """ - ) - ); - } + import java.io.*; - @Test - void emptyTryWithResourcesWithVariableInitializationMethodInvocation() { - rewriteRun( - //language=java - java( - """ - import java.io.FileInputStream;import java.io.IOException;import java.io.InputStream;import java.io.UncheckedIOException;public class A { - private final InputStream stdin = new FileInputStream("test.in"); - - private InputStream getStdin() { - return stdin; - } - - public void destroy() { - // close stream in a try-with-resources - try (InputStream s = getStdin()) { + public class A { + { + final String fileName = "fileName"; + try (FileInputStream fis = new FileInputStream(fileName)) { } catch (IOException e) { - throw new UncheckedIOException(e); } } } @@ -485,5 +435,4 @@ public void destroy() { ) ); } - } From 35a96b65539c1f5ccb36d39fc0fc417f6a44ede9 Mon Sep 17 00:00:00 2001 From: pstreef Date: Thu, 24 Aug 2023 19:52:34 +0200 Subject: [PATCH 11/92] Simplify lambda blocks to expressions replaces return that is used to infer which overloaded method to pick --- .../LambdaBlockToExpressionTest.java | 49 +++++++++++++++++-- 1 file changed, 46 insertions(+), 3 deletions(-) diff --git a/src/test/java/org/openrewrite/staticanalysis/LambdaBlockToExpressionTest.java b/src/test/java/org/openrewrite/staticanalysis/LambdaBlockToExpressionTest.java index 2e7a6c282..04f5a52fc 100644 --- a/src/test/java/org/openrewrite/staticanalysis/LambdaBlockToExpressionTest.java +++ b/src/test/java/org/openrewrite/staticanalysis/LambdaBlockToExpressionTest.java @@ -15,22 +15,26 @@ */ package org.openrewrite.staticanalysis; -import org.junit.jupiter.api.Disabled; 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; class LambdaBlockToExpressionTest implements RewriteTest { + @Override + public void defaults(RecipeSpec spec) { + spec.recipe(new LambdaBlockToExpression()); + } + @DocumentExample @SuppressWarnings("CodeBlock2Expr") @Test void simplifyLambdaBlockToExpression() { rewriteRun( - spec -> spec.recipe(new LambdaBlockToExpression()), //language=java java( """ @@ -55,7 +59,6 @@ class Test { @Issue("https://github.com/openrewrite/rewrite-static-analysis/issues/1") void simplifyLambdaBlockToExpressionWithComments() { rewriteRun( - spec -> spec.recipe(new LambdaBlockToExpression()), //language=java java( """ @@ -78,4 +81,44 @@ class Test { ) ); } + + @Test + @Issue("https://github.com/openrewrite/rewrite-static-analysis/issues/162") + void simplifyLambdaBlockWithAmbiguousMethod() { + //language=java + rewriteRun( + java(""" + import java.util.function.Function; + import java.util.function.Consumer; + class A { + void aMethod(Consumer consumer){ + } + + void aMethod(Function function){ + } + } + """), + java( + """ + class Test { + void doTest() { + A a = new A(); + a.aMethod(value -> { + return value.toString(); + }); + } + } + """, + """ + class Test { + void doTest() { + A a = new A(); + a.aMethod(value -> value.toString()); + } + } + """ + ) + ); + } + } From 9d69ae3704bf9f2c7af3d996a4041bd3df276b7f Mon Sep 17 00:00:00 2001 From: Kun Li Date: Thu, 24 Aug 2023 10:56:55 -0700 Subject: [PATCH 12/92] Fix a false positive of UseDiamondOperator recipe to not rewrite if no infer type --- .../staticanalysis/UseDiamondOperator.java | 41 +++++++++++- .../UseDiamondOperatorTest.java | 63 +++++++++++++++++++ 2 files changed, 103 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/openrewrite/staticanalysis/UseDiamondOperator.java b/src/main/java/org/openrewrite/staticanalysis/UseDiamondOperator.java index 5f89f0866..f33c9bd39 100644 --- a/src/main/java/org/openrewrite/staticanalysis/UseDiamondOperator.java +++ b/src/main/java/org/openrewrite/staticanalysis/UseDiamondOperator.java @@ -116,9 +116,27 @@ public J.MethodInvocation visitMethodInvocation(J.MethodInvocation method, Execu J.MethodInvocation mi = super.visitMethodInvocation(method, executionContext); JavaType.Method methodType = mi.getMethodType(); - if (methodType != null) { + + if (methodType != null && + !mi.getArguments().isEmpty() && + methodType.getParameterTypes().size() <= mi.getArguments().size()) { mi = mi.withArguments(ListUtils.map(mi.getArguments(), (i, arg) -> { if (arg instanceof J.NewClass) { + boolean isGenericType = false; + boolean isVarArg = methodType.getParameterTypes().size() == 1 && + methodType.getParameterTypes().get(0) instanceof JavaType.Array; + + if (isVarArg) { + isGenericType = isGenericType(((JavaType.Array) methodType.getParameterTypes().get(0)).getElemType()); + } else if (i < methodType.getParameterTypes().size()) { + JavaType parameterType = methodType.getParameterTypes().get(i); + isGenericType = isGenericType(parameterType); + } + + if (isGenericType) { + return arg; + } + J.NewClass nc = (J.NewClass) arg; if ((java9 || nc.getBody() == null) && !methodType.getParameterTypes().isEmpty()) { JavaType.Parameterized paramType = TypeUtils.asParameterized(getMethodParamType(methodType, i)); @@ -133,6 +151,27 @@ public J.MethodInvocation visitMethodInvocation(J.MethodInvocation method, Execu return mi; } + private boolean isGenericType(@Nullable JavaType type) { + if (type == null) { + return false; + } + + boolean isGeneric = false; + JavaType.Parameterized parameterized = TypeUtils.asParameterized(type); + if (parameterized != null) { + List types = parameterized.getTypeParameters(); + + for (JavaType tp : types) { + if (tp instanceof JavaType.GenericTypeVariable) { + isGeneric = true; + break; + } + } + } + + return isGeneric; + } + private JavaType getMethodParamType(JavaType.Method methodType, int paramIndex) { if (methodType.hasFlags(Flag.Varargs) && paramIndex >= methodType.getParameterTypes().size() - 1) { return ((JavaType.Array) methodType.getParameterTypes().get(methodType.getParameterTypes().size() - 1)).getElemType(); diff --git a/src/test/java/org/openrewrite/staticanalysis/UseDiamondOperatorTest.java b/src/test/java/org/openrewrite/staticanalysis/UseDiamondOperatorTest.java index 34fb4342e..8fde09d52 100644 --- a/src/test/java/org/openrewrite/staticanalysis/UseDiamondOperatorTest.java +++ b/src/test/java/org/openrewrite/staticanalysis/UseDiamondOperatorTest.java @@ -404,6 +404,69 @@ class Foo { ); } + @Test + void anonymousNewClassInferTypesJava9Plus() { + rewriteRun( + spec -> spec.allSources(s -> s.markers(javaVersion(11))), + java( + """ + interface Serializer { + byte[] serialize(T t); + } + + public class Printer { + public static void setSerializerGenericType(Serializer serializer) {} + public static void setSerializerConcreteType(Serializer serializer) {} + } + """ + ), + java( + """ + class Test { + void method() { + // Generic type, no infer type, can NOT use diamond operator + Printer.setSerializerGenericType(new Serializer() { + @Override + public byte[] serialize(Integer integer) { + return new byte[0]; + } + }); + + // Concrete type, OK to use diamond operator + Printer.setSerializerConcreteType(new Serializer() { + @Override + public byte[] serialize(Integer integer) { + return new byte[0]; + } + }); + } + } + """, + """ + class Test { + void method() { + // Generic type, no infer type, can NOT use diamond operator + Printer.setSerializerGenericType(new Serializer() { + @Override + public byte[] serialize(Integer integer) { + return new byte[0]; + } + }); + + // Concrete type, OK to use diamond operator + Printer.setSerializerConcreteType(new Serializer<>() { + @Override + public byte[] serialize(Integer integer) { + return new byte[0]; + } + }); + } + } + """ + ) + ); + } + @Nested class kotlinTest { @Test From 5c8d58efd6fa3087f906331f1269db2ade25bfd6 Mon Sep 17 00:00:00 2001 From: pstreef Date: Thu, 24 Aug 2023 19:57:12 +0200 Subject: [PATCH 13/92] add logCompilationWarningsAndErrors --- .../staticanalysis/LambdaBlockToExpressionTest.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/test/java/org/openrewrite/staticanalysis/LambdaBlockToExpressionTest.java b/src/test/java/org/openrewrite/staticanalysis/LambdaBlockToExpressionTest.java index 04f5a52fc..06de1aef5 100644 --- a/src/test/java/org/openrewrite/staticanalysis/LambdaBlockToExpressionTest.java +++ b/src/test/java/org/openrewrite/staticanalysis/LambdaBlockToExpressionTest.java @@ -18,6 +18,7 @@ import org.junit.jupiter.api.Test; import org.openrewrite.DocumentExample; import org.openrewrite.Issue; +import org.openrewrite.java.JavaParser; import org.openrewrite.test.RecipeSpec; import org.openrewrite.test.RewriteTest; @@ -27,7 +28,8 @@ class LambdaBlockToExpressionTest implements RewriteTest { @Override public void defaults(RecipeSpec spec) { - spec.recipe(new LambdaBlockToExpression()); + spec.recipe(new LambdaBlockToExpression()) + .parser(JavaParser.fromJavaVersion().logCompilationWarningsAndErrors(true)); } @DocumentExample From ba080f5e7fb3698cc196e6199fbed22822e720f2 Mon Sep 17 00:00:00 2001 From: Tim te Beek Date: Thu, 24 Aug 2023 19:09:51 +0100 Subject: [PATCH 14/92] Make SimplifyDurationCreationUnits.getConstantIntegralValue public For reuse in https://github.com/openrewrite/rewrite-testing-frameworks/pull/401 --- .../staticanalysis/SimplifyDurationCreationUnits.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/openrewrite/staticanalysis/SimplifyDurationCreationUnits.java b/src/main/java/org/openrewrite/staticanalysis/SimplifyDurationCreationUnits.java index 3a1d251c6..89421d564 100644 --- a/src/main/java/org/openrewrite/staticanalysis/SimplifyDurationCreationUnits.java +++ b/src/main/java/org/openrewrite/staticanalysis/SimplifyDurationCreationUnits.java @@ -113,7 +113,7 @@ public J.MethodInvocation visitMethodInvocation(J.MethodInvocation method, Execu } @Nullable - private static Long getConstantIntegralValue(Expression expression) { + public static Long getConstantIntegralValue(Expression expression) { if (expression instanceof J.Literal) { J.Literal literal = (J.Literal) expression; if (literal.getType() != JavaType.Primitive.Int && literal.getType() != JavaType.Primitive.Long) { From 61143f5905c24bb94fb4b127d6a8a6da1155af99 Mon Sep 17 00:00:00 2001 From: Kun Li Date: Thu, 24 Aug 2023 11:35:22 -0700 Subject: [PATCH 15/92] Fix a typo --- .../openrewrite/staticanalysis/SimplifyBooleanExpression.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/openrewrite/staticanalysis/SimplifyBooleanExpression.java b/src/main/java/org/openrewrite/staticanalysis/SimplifyBooleanExpression.java index e5c0907e4..bb7b3ae0b 100644 --- a/src/main/java/org/openrewrite/staticanalysis/SimplifyBooleanExpression.java +++ b/src/main/java/org/openrewrite/staticanalysis/SimplifyBooleanExpression.java @@ -69,7 +69,7 @@ public J visit(@Nullable Tree tree, ExecutionContext ctx) { return super.visit(tree, ctx); } - // Comparing Kotlin nullable type `?` with tree/false can not be simplified, + // Comparing Kotlin nullable type `?` with true/false can not be simplified, // e.g. `X?.fun() == true` is not equivalent to `X?.fun()` @Override protected boolean shouldSimplifyEqualsOn(@Nullable J j) { From 373068803b41e4a47de4cf92c4f9a73d3601cf15 Mon Sep 17 00:00:00 2001 From: Kun Li Date: Thu, 24 Aug 2023 11:52:46 -0700 Subject: [PATCH 16/92] Add JavaFileChecker to simplify code and also can be shared by code --- .../staticanalysis/UseDiamondOperator.java | 15 ++------- .../staticanalysis/java/JavaFileChecker.java | 32 +++++++++++++++++++ 2 files changed, 35 insertions(+), 12 deletions(-) create mode 100644 src/main/java/org/openrewrite/staticanalysis/java/JavaFileChecker.java diff --git a/src/main/java/org/openrewrite/staticanalysis/UseDiamondOperator.java b/src/main/java/org/openrewrite/staticanalysis/UseDiamondOperator.java index f33c9bd39..607ade525 100644 --- a/src/main/java/org/openrewrite/staticanalysis/UseDiamondOperator.java +++ b/src/main/java/org/openrewrite/staticanalysis/UseDiamondOperator.java @@ -22,6 +22,7 @@ import org.openrewrite.java.search.UsesJavaVersion; import org.openrewrite.java.tree.*; import org.openrewrite.marker.Markers; +import org.openrewrite.staticanalysis.java.JavaFileChecker; import java.time.Duration; import java.util.ArrayList; @@ -30,7 +31,6 @@ import java.util.Set; import static java.util.Collections.singletonList; -import static java.util.Objects.requireNonNull; import static org.openrewrite.Tree.randomId; public class UseDiamondOperator extends Recipe { @@ -57,22 +57,13 @@ public Duration getEstimatedEffortPerOccurrence() { @Override public TreeVisitor getVisitor() { - return new UseDiamondOperatorVisitor(); + // don't try to do this for Groovy or Kotlin sources + return Preconditions.check(new JavaFileChecker<>(), new UseDiamondOperatorVisitor()); } private static class UseDiamondOperatorVisitor extends JavaIsoVisitor { private boolean java9; - @Override - public J visit(@Nullable Tree tree, ExecutionContext ctx) { - if (tree instanceof JavaSourceFile) { - JavaSourceFile cu = (JavaSourceFile) requireNonNull(tree); - // don't try to do this for Groovy or Kotlin sources - return cu instanceof J.CompilationUnit ? visitCompilationUnit((J.CompilationUnit) cu, ctx) : cu; - } - return super.visit(tree, ctx); - } - @Override public J.CompilationUnit visitCompilationUnit(J.CompilationUnit cu, ExecutionContext ctx) { java9 = new UsesJavaVersion(9).visit(cu, 0) != cu; diff --git a/src/main/java/org/openrewrite/staticanalysis/java/JavaFileChecker.java b/src/main/java/org/openrewrite/staticanalysis/java/JavaFileChecker.java new file mode 100644 index 000000000..bf5edaf68 --- /dev/null +++ b/src/main/java/org/openrewrite/staticanalysis/java/JavaFileChecker.java @@ -0,0 +1,32 @@ +/* + * Copyright 2023 the original author or authors. + *

+ * 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 + *

+ * https://www.apache.org/licenses/LICENSE-2.0 + *

+ * 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.java; + +import org.openrewrite.Tree; +import org.openrewrite.TreeVisitor; +import org.openrewrite.internal.lang.Nullable; +import org.openrewrite.java.tree.J; +import org.openrewrite.marker.SearchResult; + +public class JavaFileChecker

extends TreeVisitor { + @Override + public @Nullable Tree visit(@Nullable Tree tree, P p) { + if (tree instanceof J.CompilationUnit) { + return SearchResult.found(tree); + } + return tree; + } +} From 3c3fdedf86e0c60782c591ac78e28346f3e4fcd5 Mon Sep 17 00:00:00 2001 From: Kun Li Date: Thu, 24 Aug 2023 12:56:19 -0700 Subject: [PATCH 17/92] Fix a false positive of LambdaBlockToExpression recipe to not rewrite when there are ambiguous method overloading --- .../LambdaBlockToExpression.java | 58 +++++++++++++++---- .../LambdaBlockToExpressionTest.java | 33 ++++------- 2 files changed, 59 insertions(+), 32 deletions(-) diff --git a/src/main/java/org/openrewrite/staticanalysis/LambdaBlockToExpression.java b/src/main/java/org/openrewrite/staticanalysis/LambdaBlockToExpression.java index 7d007f412..2e7c3456c 100644 --- a/src/main/java/org/openrewrite/staticanalysis/LambdaBlockToExpression.java +++ b/src/main/java/org/openrewrite/staticanalysis/LambdaBlockToExpression.java @@ -20,12 +20,11 @@ import org.openrewrite.Recipe; import org.openrewrite.TreeVisitor; import org.openrewrite.java.JavaIsoVisitor; -import org.openrewrite.java.tree.J; -import org.openrewrite.java.tree.Space; -import org.openrewrite.java.tree.Statement; -import org.openrewrite.marker.SearchResult; +import org.openrewrite.java.tree.*; +import org.openrewrite.staticanalysis.java.JavaFileChecker; import java.util.List; +import java.util.Optional; public class LambdaBlockToExpression extends Recipe { @Override @@ -40,13 +39,7 @@ public String getDescription() { @Override public TreeVisitor getVisitor() { - return Preconditions.check( - new JavaIsoVisitor() { - @Override - public J.CompilationUnit visitCompilationUnit(J.CompilationUnit cu, ExecutionContext executionContext) { - return SearchResult.found(cu); - } - }, + return Preconditions.check(new JavaFileChecker<>(), new JavaIsoVisitor() { @Override public J.Lambda visitLambda(J.Lambda lambda, ExecutionContext executionContext) { @@ -64,7 +57,50 @@ public J.Lambda visitLambda(J.Lambda lambda, ExecutionContext executionContext) } return l; } + + @Override + public J.MethodInvocation visitMethodInvocation(J.MethodInvocation method, ExecutionContext executionContext) { + if (hasLambdaArgument(method) && hasMethodOverloading(method)) { + return method; + } + return super.visitMethodInvocation(method, executionContext); + } } ); } + + // Check whether a method has overloading methods in the declaring class + private static boolean hasMethodOverloading(J.MethodInvocation method) { + String methodName = method.getSimpleName(); + return Optional.ofNullable(method.getMethodType()) + .map(JavaType.Method::getDeclaringType) + .filter(JavaType.Class.class::isInstance) + .map(JavaType.Class.class::cast) + .map(JavaType.Class::getMethods) + .map(methods -> { + int overloadingCount = 0; + for (JavaType.Method dm : methods) { + if (dm.getName().equals(methodName)) { + overloadingCount++; + if (overloadingCount > 1) { + + return true; + } + } + } + return false; + }) + .orElse(false); + } + + private static boolean hasLambdaArgument(J.MethodInvocation method) { + boolean hasLambdaArgument = false; + for (Expression arg : method.getArguments()) { + if (arg instanceof J.Lambda) { + hasLambdaArgument = true; + break; + } + } + return hasLambdaArgument; + } } diff --git a/src/test/java/org/openrewrite/staticanalysis/LambdaBlockToExpressionTest.java b/src/test/java/org/openrewrite/staticanalysis/LambdaBlockToExpressionTest.java index 06de1aef5..276eb8d84 100644 --- a/src/test/java/org/openrewrite/staticanalysis/LambdaBlockToExpressionTest.java +++ b/src/test/java/org/openrewrite/staticanalysis/LambdaBlockToExpressionTest.java @@ -84,22 +84,21 @@ class Test { ); } - @Test @Issue("https://github.com/openrewrite/rewrite-static-analysis/issues/162") - void simplifyLambdaBlockWithAmbiguousMethod() { + @Test + void noChangeIfLambdaBlockWithAmbiguousMethod() { //language=java rewriteRun( - java(""" - import java.util.function.Function; - import java.util.function.Consumer; - class A { - void aMethod(Consumer consumer){ - } - - void aMethod(Function function){ - } - } - """), + java( + """ + import java.util.function.Function; + import java.util.function.Consumer; + class A { + void aMethod(Consumer consumer) {} + void aMethod(Function function) {} + } + """ + ), java( """ class Test { @@ -110,14 +109,6 @@ void doTest() { }); } } - """, - """ - class Test { - void doTest() { - A a = new A(); - a.aMethod(value -> value.toString()); - } - } """ ) ); From aa87e6127c92eba60d5ed07536a1c6199e653aa3 Mon Sep 17 00:00:00 2001 From: Sam Snyder Date: Tue, 29 Aug 2023 10:56:39 -0700 Subject: [PATCH 18/92] Prepare for getParentTreeCursor() to possibly return a JavaDoc --- .../openrewrite/staticanalysis/EqualsAvoidsNullVisitor.java | 5 +++-- .../staticanalysis/RemoveUnusedLocalVariables.java | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/main/java/org/openrewrite/staticanalysis/EqualsAvoidsNullVisitor.java b/src/main/java/org/openrewrite/staticanalysis/EqualsAvoidsNullVisitor.java index f7ab0fdde..e351aa1d8 100644 --- a/src/main/java/org/openrewrite/staticanalysis/EqualsAvoidsNullVisitor.java +++ b/src/main/java/org/openrewrite/staticanalysis/EqualsAvoidsNullVisitor.java @@ -17,6 +17,7 @@ import lombok.EqualsAndHashCode; import lombok.Value; +import org.openrewrite.Tree; import org.openrewrite.java.JavaIsoVisitor; import org.openrewrite.java.JavaVisitor; import org.openrewrite.java.MethodMatcher; @@ -48,7 +49,7 @@ public J.MethodInvocation visitMethodInvocation(J.MethodInvocation method, P p) if ((STRING_EQUALS.matches(m) || (!Boolean.TRUE.equals(style.getIgnoreEqualsIgnoreCase()) && STRING_EQUALS_IGNORE_CASE.matches(m))) && m.getArguments().get(0) instanceof J.Literal && !(m.getSelect() instanceof J.Literal)) { - J parent = getCursor().getParentTreeCursor().getValue(); + Tree parent = getCursor().getParentTreeCursor().getValue(); if (parent instanceof J.Binary) { J.Binary binary = (J.Binary) parent; if (binary.getOperator() == J.Binary.Type.And && binary.getLeft() instanceof J.Binary) { @@ -84,7 +85,7 @@ public RemoveUnnecessaryNullCheck(J.Binary scope) { @Override public J visitBinary(J.Binary binary, P p) { - J parens = getCursor().getParentTreeCursor().getValue(); + Tree parens = getCursor().getParentTreeCursor().getValue(); if(parens instanceof J.Parentheses) { doAfterVisit(new UnwrapParentheses<>((J.Parentheses) parens)); } diff --git a/src/main/java/org/openrewrite/staticanalysis/RemoveUnusedLocalVariables.java b/src/main/java/org/openrewrite/staticanalysis/RemoveUnusedLocalVariables.java index d590d0f30..6e5c3528a 100644 --- a/src/main/java/org/openrewrite/staticanalysis/RemoveUnusedLocalVariables.java +++ b/src/main/java/org/openrewrite/staticanalysis/RemoveUnusedLocalVariables.java @@ -279,7 +279,7 @@ private static boolean isRhsValue(Cursor tree) { if (parent.getValue() instanceof J.AssignmentOperation) { J.AssignmentOperation assignmentOperation = parent.getValue(); if (assignmentOperation.getVariable() == tree.getValue()) { - J grandParent = parent.getParentTreeCursor().getValue(); + Tree grandParent = parent.getParentTreeCursor().getValue(); return (grandParent instanceof Expression || grandParent instanceof J.Return); } } From 2fb2bfb0faf43e9f12bd1ce149e5e42ee14337a2 Mon Sep 17 00:00:00 2001 From: Sam Snyder Date: Thu, 31 Aug 2023 19:24:22 +0000 Subject: [PATCH 19/92] refactor: Update Gradle wrapper Use this link to re-run the recipe: https://app.moderne.io/recipes/org.openrewrite.gradle.UpdateGradleWrapper?defaults=W3sidmFsdWUiOiI4LjMiLCJuYW1lIjoidmVyc2lvbiJ9LHsidmFsdWUiOiJiaW4iLCJuYW1lIjoiZGlzdHJpYnV0aW9uIn1d&organizationId=T3BlblJld3JpdGU%3D Co-authored-by: Moderne --- gradle/wrapper/gradle-wrapper.jar | Bin 61574 -> 63721 bytes gradle/wrapper/gradle-wrapper.properties | 4 +- gradlew | 19 ++- gradlew.bat | 184 +++++++++++------------ 4 files changed, 106 insertions(+), 101 deletions(-) diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 943f0cbfa754578e88a3dae77fce6e3dea56edbf..7f93135c49b765f8051ef9d0a6055ff8e46073d8 100644 GIT binary patch delta 41154 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%0DEqyP8OADCy^l*0 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 diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 700740e26..d222ac902 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,7 +1,7 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.6-bin.zip -distributionSha256Sum=7ba68c54029790ab444b39d7e293d3236b2632631fb5f2e012bb28b4ff669e4b +distributionUrl=https\://services.gradle.org/distributions/gradle-8.3-bin.zip +distributionSha256Sum=591855b517fc635b9e04de1d05d5e76ada3f89f5fc76f87978d1b245b4f69225 networkTimeout=10000 zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew index 65dcd68d6..0adc8e1a5 100755 --- a/gradlew +++ b/gradlew @@ -83,10 +83,8 @@ done # This is normally unused # shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} -APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit - -# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum @@ -133,10 +131,13 @@ location of your Java installation." fi else JAVACMD=java - which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. Please set the JAVA_HOME variable in your environment to match the location of your Java installation." + fi fi # Increase the maximum file descriptors if we can. @@ -144,7 +145,7 @@ if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then case $MAX_FD in #( max*) # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. - # shellcheck disable=SC3045 + # shellcheck disable=SC3045 MAX_FD=$( ulimit -H -n ) || warn "Could not query maximum file descriptor limit" esac @@ -152,7 +153,7 @@ if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then '' | soft) :;; #( *) # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. - # shellcheck disable=SC3045 + # shellcheck disable=SC3045 ulimit -n "$MAX_FD" || warn "Could not set maximum file descriptor limit to $MAX_FD" esac @@ -197,6 +198,10 @@ if "$cygwin" || "$msys" ; then done fi + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + # Collect all arguments for the java command; # * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of # shell script including quotes and variable substitutions, so put them in diff --git a/gradlew.bat b/gradlew.bat index 93e3f59f1..6689b85be 100755 --- a/gradlew.bat +++ b/gradlew.bat @@ -1,92 +1,92 @@ -@rem -@rem Copyright 2015 the original author or authors. -@rem -@rem Licensed under the Apache License, Version 2.0 (the "License"); -@rem you may not use this file except in compliance with the License. -@rem You may obtain a copy of the License at -@rem -@rem https://www.apache.org/licenses/LICENSE-2.0 -@rem -@rem Unless required by applicable law or agreed to in writing, software -@rem distributed under the License is distributed on an "AS IS" BASIS, -@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -@rem See the License for the specific language governing permissions and -@rem limitations under the License. -@rem - -@if "%DEBUG%"=="" @echo off -@rem ########################################################################## -@rem -@rem Gradle startup script for Windows -@rem -@rem ########################################################################## - -@rem Set local scope for the variables with windows NT shell -if "%OS%"=="Windows_NT" setlocal - -set DIRNAME=%~dp0 -if "%DIRNAME%"=="" set DIRNAME=. -@rem This is normally unused -set APP_BASE_NAME=%~n0 -set APP_HOME=%DIRNAME% - -@rem Resolve any "." and ".." in APP_HOME to make it shorter. -for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi - -@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" - -@rem Find java.exe -if defined JAVA_HOME goto findJavaFromJavaHome - -set JAVA_EXE=java.exe -%JAVA_EXE% -version >NUL 2>&1 -if %ERRORLEVEL% equ 0 goto execute - -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. - -goto fail - -:findJavaFromJavaHome -set JAVA_HOME=%JAVA_HOME:"=% -set JAVA_EXE=%JAVA_HOME%/bin/java.exe - -if exist "%JAVA_EXE%" goto execute - -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. - -goto fail - -:execute -@rem Setup the command line - -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar - - -@rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* - -:end -@rem End local scope for the variables with windows NT shell -if %ERRORLEVEL% equ 0 goto mainEnd - -:fail -rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of -rem the _cmd.exe /c_ return code! -set EXIT_CODE=%ERRORLEVEL% -if %EXIT_CODE% equ 0 set EXIT_CODE=1 -if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% -exit /b %EXIT_CODE% - -:mainEnd -if "%OS%"=="Windows_NT" endlocal - -:omega +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%"=="" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if %ERRORLEVEL% equ 0 goto execute + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if %ERRORLEVEL% equ 0 goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega From 451eef891203adca363141971edcc5df3782aa84 Mon Sep 17 00:00:00 2001 From: Peter Streef Date: Thu, 14 Sep 2023 11:57:39 +0200 Subject: [PATCH 20/92] Unnecessary explicit type arguments removes necessary type arguments (#165) * Unnecessary explicit type arguments removes necessary type arguments * formatting --- .../UnnecessaryExplicitTypeArgumentsTest.java | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/src/test/java/org/openrewrite/staticanalysis/UnnecessaryExplicitTypeArgumentsTest.java b/src/test/java/org/openrewrite/staticanalysis/UnnecessaryExplicitTypeArgumentsTest.java index 3f57da1fb..bdcbae3e4 100644 --- a/src/test/java/org/openrewrite/staticanalysis/UnnecessaryExplicitTypeArgumentsTest.java +++ b/src/test/java/org/openrewrite/staticanalysis/UnnecessaryExplicitTypeArgumentsTest.java @@ -18,6 +18,7 @@ import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; +import org.junitpioneer.jupiter.ExpectedToFail; import org.openrewrite.DocumentExample; import org.openrewrite.Issue; import org.openrewrite.test.RecipeSpec; @@ -137,6 +138,35 @@ void test() { ); } + @ExpectedToFail + @Issue("https://github.com/openrewrite/rewrite-static-analysis/issues/164") + @Test + void doesNotRemoveNecessaryTypeArguments() { + rewriteRun( + //language=java + java( + """ + import java.util.Optional; + import java.util.stream.Stream; + public class Test { + void test() { + Stream.of("hi") + .map(it -> it == null ? Optional.empty() : Optional.of(it)) + .flatMap(Optional::stream) + .map(this::mapper); //this requires the type information + } + Optional mapper(String value) { + return Optional.ofNullable(value) + .filter("hi"::equals); + } + } + """ + ) + ); + } + + + @SuppressWarnings("UnnecessaryLocalVariable") @Issue("https://github.com/openrewrite/rewrite/issues/2818") @Test From 2bb47d0f91ec3efdef3ff439730b85b6f52223aa Mon Sep 17 00:00:00 2001 From: Sam Snyder Date: Wed, 20 Sep 2023 18:25:56 -0700 Subject: [PATCH 21/92] Avoid removing unnecessary parenthesis that aren't actually unnecessary --- .../EqualsAvoidsNullVisitor.java | 17 ++++-- .../staticanalysis/EqualsAvoidsNullTest.java | 53 ++----------------- 2 files changed, 16 insertions(+), 54 deletions(-) diff --git a/src/main/java/org/openrewrite/staticanalysis/EqualsAvoidsNullVisitor.java b/src/main/java/org/openrewrite/staticanalysis/EqualsAvoidsNullVisitor.java index e351aa1d8..b2754d95b 100644 --- a/src/main/java/org/openrewrite/staticanalysis/EqualsAvoidsNullVisitor.java +++ b/src/main/java/org/openrewrite/staticanalysis/EqualsAvoidsNullVisitor.java @@ -17,11 +17,13 @@ import lombok.EqualsAndHashCode; import lombok.Value; +import org.openrewrite.Cursor; import org.openrewrite.Tree; +import org.openrewrite.internal.lang.Nullable; import org.openrewrite.java.JavaIsoVisitor; import org.openrewrite.java.JavaVisitor; import org.openrewrite.java.MethodMatcher; -import org.openrewrite.java.UnwrapParentheses; +import org.openrewrite.java.cleanup.UnnecessaryParenthesesVisitor; import org.openrewrite.java.style.EqualsAvoidsNullStyle; import org.openrewrite.java.tree.Expression; import org.openrewrite.java.tree.J; @@ -84,13 +86,18 @@ public RemoveUnnecessaryNullCheck(J.Binary scope) { } @Override - public J visitBinary(J.Binary binary, P p) { - Tree parens = getCursor().getParentTreeCursor().getValue(); - if(parens instanceof J.Parentheses) { - doAfterVisit(new UnwrapParentheses<>((J.Parentheses) parens)); + public @Nullable J postVisit(J j, P p) { + if(getCursor().pollMessage("simplify") != null) { + j = new UnnecessaryParenthesesVisitor

().visit(j, p, getCursor()); } + return j; + } + @Override + public J visitBinary(J.Binary binary, P p) { if (scope.isScope(binary)) { + Cursor parent = getCursor().dropParentUntil(it -> it instanceof J && !(it instanceof J.Parentheses) && !(it instanceof J.ControlParentheses)); + parent.putMessage("simplify", true); return binary.getRight().withPrefix(Space.EMPTY); } diff --git a/src/test/java/org/openrewrite/staticanalysis/EqualsAvoidsNullTest.java b/src/test/java/org/openrewrite/staticanalysis/EqualsAvoidsNullTest.java index eae9db052..762175c4f 100644 --- a/src/test/java/org/openrewrite/staticanalysis/EqualsAvoidsNullTest.java +++ b/src/test/java/org/openrewrite/staticanalysis/EqualsAvoidsNullTest.java @@ -17,15 +17,9 @@ import org.junit.jupiter.api.Test; import org.openrewrite.DocumentExample; -import org.openrewrite.java.JavaParser; -import org.openrewrite.java.style.EqualsAvoidsNullStyle; -import org.openrewrite.style.NamedStyles; import org.openrewrite.test.RecipeSpec; import org.openrewrite.test.RewriteTest; -import static java.util.Collections.emptySet; -import static java.util.Collections.singletonList; -import static org.openrewrite.Tree.randomId; import static org.openrewrite.java.Assertions.java; @SuppressWarnings({"ClassInitializerMayBeStatic", "StatementWithEmptyBody", "ConstantConditions"}) @@ -64,37 +58,6 @@ public class A { ); } - @Test - void ignoreEqualsIgnoreCase() { - rewriteRun( - spec -> spec.parser(JavaParser.fromJavaVersion().styles(singletonList( - new NamedStyles(randomId(), "test", "", "", emptySet(), singletonList( - new EqualsAvoidsNullStyle(true))))) - ), - //language=java - java( - """ - public class A { - { - String s = null; - if(s.equals("test")) {} - if(s.equalsIgnoreCase("test")) {} - } - } - """, - """ - public class A { - { - String s = null; - if("test".equals(s)) {} - if(s.equalsIgnoreCase("test")) {} - } - } - """ - ) - ); - } - @Test void removeUnnecessaryNullCheckAndParens() { rewriteRun( @@ -131,16 +94,12 @@ void removeUnnecessaryNullCheckAndKeepNecessaryParens() { java( """ import java.util.Collection; - + public class A { public void triggersRecipe(String toCheck) { if (toCheck != null && toCheck.equals("stringLiteral")) { } } - - public boolean triggersParenthesesRemoval() { - return (System.getProperties() != null) ? System.getProperties().keySet().isEmpty() : false; - } - + public boolean needsToKeepParentheses() { Collection set = System.getProperties().keySet(); return !(set == null || set.isEmpty()); @@ -149,16 +108,12 @@ public boolean needsToKeepParentheses() { """, """ import java.util.Collection; - + public class A { public void triggersRecipe(String toCheck) { if ("stringLiteral".equals(toCheck)) { } } - - public boolean triggersParenthesesRemoval() { - return System.getProperties() != null ? System.getProperties().keySet().isEmpty() : false; - } - + public boolean needsToKeepParentheses() { Collection set = System.getProperties().keySet(); return !(set == null || set.isEmpty()); From a330fe97f5f04019d8cc5c87d77c351a6e88ca3d Mon Sep 17 00:00:00 2001 From: Sam Snyder Date: Wed, 20 Sep 2023 18:55:01 -0700 Subject: [PATCH 22/92] Simplify by removing error-prone attempt to remove unnecessary parenthesis. It is hard to know exactly how high up in the tree to go to begin a properly-scoped parenthesis removal, leading to a variety of wrong changes. Even the UnnecessaryParenthesis recipe specialized for this task makes incorrect removals if you start its visiting from the wrong point. UnnecessaryParenthesis recipe comes after this in common static analysis anyway, so the complications are not worth it. --- .../EqualsAvoidsNullVisitor.java | 22 +++++----- .../staticanalysis/EqualsAvoidsNullTest.java | 41 +------------------ 2 files changed, 11 insertions(+), 52 deletions(-) diff --git a/src/main/java/org/openrewrite/staticanalysis/EqualsAvoidsNullVisitor.java b/src/main/java/org/openrewrite/staticanalysis/EqualsAvoidsNullVisitor.java index b2754d95b..58894b5cb 100644 --- a/src/main/java/org/openrewrite/staticanalysis/EqualsAvoidsNullVisitor.java +++ b/src/main/java/org/openrewrite/staticanalysis/EqualsAvoidsNullVisitor.java @@ -17,13 +17,11 @@ import lombok.EqualsAndHashCode; import lombok.Value; -import org.openrewrite.Cursor; import org.openrewrite.Tree; import org.openrewrite.internal.lang.Nullable; import org.openrewrite.java.JavaIsoVisitor; import org.openrewrite.java.JavaVisitor; import org.openrewrite.java.MethodMatcher; -import org.openrewrite.java.cleanup.UnnecessaryParenthesesVisitor; import org.openrewrite.java.style.EqualsAvoidsNullStyle; import org.openrewrite.java.tree.Expression; import org.openrewrite.java.tree.J; @@ -80,24 +78,24 @@ private boolean matchesSelect(Expression expression, Expression select) { private static class RemoveUnnecessaryNullCheck

extends JavaVisitor

{ private final J.Binary scope; - - public RemoveUnnecessaryNullCheck(J.Binary scope) { - this.scope = scope; - } + boolean done; @Override - public @Nullable J postVisit(J j, P p) { - if(getCursor().pollMessage("simplify") != null) { - j = new UnnecessaryParenthesesVisitor

().visit(j, p, getCursor()); + public @Nullable J visit(@Nullable Tree tree, P p) { + if(done) { + return (J) tree; } - return j; + return super.visit(tree, p); + } + + public RemoveUnnecessaryNullCheck(J.Binary scope) { + this.scope = scope; } @Override public J visitBinary(J.Binary binary, P p) { if (scope.isScope(binary)) { - Cursor parent = getCursor().dropParentUntil(it -> it instanceof J && !(it instanceof J.Parentheses) && !(it instanceof J.ControlParentheses)); - parent.putMessage("simplify", true); + done = true; return binary.getRight().withPrefix(Space.EMPTY); } diff --git a/src/test/java/org/openrewrite/staticanalysis/EqualsAvoidsNullTest.java b/src/test/java/org/openrewrite/staticanalysis/EqualsAvoidsNullTest.java index 762175c4f..5f87cbe42 100644 --- a/src/test/java/org/openrewrite/staticanalysis/EqualsAvoidsNullTest.java +++ b/src/test/java/org/openrewrite/staticanalysis/EqualsAvoidsNullTest.java @@ -59,7 +59,7 @@ public class A { } @Test - void removeUnnecessaryNullCheckAndParens() { + void removeUnnecessaryNullCheck() { rewriteRun( //language=java java( @@ -67,7 +67,6 @@ void removeUnnecessaryNullCheckAndParens() { public class A { { String s = null; - if((s != null && s.equals("test"))) {} if(s != null && s.equals("test")) {} if(null != s && s.equals("test")) {} } @@ -79,48 +78,10 @@ public class A { String s = null; if("test".equals(s)) {} if("test".equals(s)) {} - if("test".equals(s)) {} } } """ ) ); } - - @Test - void removeUnnecessaryNullCheckAndKeepNecessaryParens() { - rewriteRun( - //language=java - java( - """ - import java.util.Collection; - - public class A { - public void triggersRecipe(String toCheck) { - if (toCheck != null && toCheck.equals("stringLiteral")) { } - } - - public boolean needsToKeepParentheses() { - Collection set = System.getProperties().keySet(); - return !(set == null || set.isEmpty()); - } - } - """, - """ - import java.util.Collection; - - public class A { - public void triggersRecipe(String toCheck) { - if ("stringLiteral".equals(toCheck)) { } - } - - public boolean needsToKeepParentheses() { - Collection set = System.getProperties().keySet(); - return !(set == null || set.isEmpty()); - } - } - """ - ) - ); - } } From 37f870e69a198f97c34194cc35eba05d0b43eed6 Mon Sep 17 00:00:00 2001 From: Sam Snyder Date: Wed, 20 Sep 2023 19:43:51 -0700 Subject: [PATCH 23/92] Fix indentation of statements modified by switch FallThrough recipe --- .../staticanalysis/FallThroughVisitor.java | 4 +- .../staticanalysis/FallThroughTest.java | 48 +++++++++++++++++++ 2 files changed, 50 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/openrewrite/staticanalysis/FallThroughVisitor.java b/src/main/java/org/openrewrite/staticanalysis/FallThroughVisitor.java index 4f69e63d5..60bf1010e 100644 --- a/src/main/java/org/openrewrite/staticanalysis/FallThroughVisitor.java +++ b/src/main/java/org/openrewrite/staticanalysis/FallThroughVisitor.java @@ -84,7 +84,7 @@ public J.Case visitCase(J.Case case_, P p) { p ); statements.add(breakToAdd); - c = c.withStatements(ListUtils.map(statements, stmt -> autoFormat(stmt, p))); + c = c.withStatements(ListUtils.map(statements, stmt -> autoFormat(stmt, p, getCursor()))); } return c; } @@ -104,7 +104,7 @@ public J.Block visitBlock(J.Block block, P p) { p ); statements.add(breakToAdd); - b = b.withStatements(ListUtils.map(statements, stmt -> autoFormat(stmt, p))); + b = b.withStatements(ListUtils.map(statements, stmt -> autoFormat(stmt, p, getCursor()))); } return b; } diff --git a/src/test/java/org/openrewrite/staticanalysis/FallThroughTest.java b/src/test/java/org/openrewrite/staticanalysis/FallThroughTest.java index bba315d48..d59d8227d 100644 --- a/src/test/java/org/openrewrite/staticanalysis/FallThroughTest.java +++ b/src/test/java/org/openrewrite/staticanalysis/FallThroughTest.java @@ -319,4 +319,52 @@ public class A { ) ); } + + @Test + void nestedSwitch() { + rewriteRun( + //language=java + java( + """ + enum Enum { + A, B + } + public class Test { + void foo(Enum a) { + switch(a) { + case A: + default: + switch(a) { + case B: + System.out.println("B"); + default: + System.out.print("other"); + } + } + } + } + """, + """ + enum Enum { + A, B + } + public class Test { + void foo(Enum a) { + switch(a) { + case A: + default: + switch(a) { + case B: + System.out.println("B"); + break; + default: + System.out.print("other"); + } + } + } + } + """ + ) + ); + } } From 194e5071296b077a4920cbecd83bae895b1dece0 Mon Sep 17 00:00:00 2001 From: Tim te Beek Date: Fri, 22 Sep 2023 19:27:25 +0200 Subject: [PATCH 24/92] Do not simplify boolean return with comments --- .../staticanalysis/SimplifyBooleanReturn.java | 31 +++++- .../SimplifyBooleanReturnTest.java | 96 +++++++++++++++++++ 2 files changed, 123 insertions(+), 4 deletions(-) diff --git a/src/main/java/org/openrewrite/staticanalysis/SimplifyBooleanReturn.java b/src/main/java/org/openrewrite/staticanalysis/SimplifyBooleanReturn.java index 118455179..45bb714b8 100644 --- a/src/main/java/org/openrewrite/staticanalysis/SimplifyBooleanReturn.java +++ b/src/main/java/org/openrewrite/staticanalysis/SimplifyBooleanReturn.java @@ -70,18 +70,24 @@ public J visitIf(J.If iff, ExecutionContext ctx) { Cursor parent = getCursor().getParentTreeCursor(); if (parent.getValue() instanceof J.Block && - parent.getParentOrThrow().getValue() instanceof J.MethodDeclaration && - thenHasOnlyReturnStatement(iff) && - elseWithOnlyReturn(i)) { + parent.getParentOrThrow().getValue() instanceof J.MethodDeclaration && + thenHasOnlyReturnStatement(iff) && + elseWithOnlyReturn(i)) { List followingStatements = followingStatements(); Optional singleFollowingStatement = Optional.ofNullable(followingStatements.isEmpty() ? null : followingStatements.get(0)) .flatMap(stat -> Optional.ofNullable(stat instanceof J.Return ? (J.Return) stat : null)) + .filter(r -> r.getComments().isEmpty()) .map(J.Return::getExpression); if (followingStatements.isEmpty() || singleFollowingStatement.map(r -> isLiteralFalse(r) || isLiteralTrue(r)).orElse(false)) { J.Return return_ = getReturnIfOnlyStatementInThen(iff).orElse(null); assert return_ != null; + // Do not remove comments that are attached to the return statement + if (!return_.getComments().isEmpty() || hasElseWithComment(i.getElsePart())) { + return i; + } + Expression ifCondition = i.getIfCondition().getTree(); if (isLiteralTrue(return_.getExpression())) { @@ -89,7 +95,7 @@ public J visitIf(J.If iff, ExecutionContext ctx) { doAfterVisit(new DeleteStatement<>(followingStatements().get(0))); return maybeAutoFormat(return_, return_.withExpression(ifCondition), ctx, parent); } else if (!singleFollowingStatement.isPresent() && - getReturnExprIfOnlyStatementInElseThen(i).map(this::isLiteralFalse).orElse(false)) { + getReturnExprIfOnlyStatementInElseThen(i).map(this::isLiteralFalse).orElse(false)) { if (i.getElsePart() != null) { doAfterVisit(new DeleteStatement<>(i.getElsePart().getBody())); } @@ -185,6 +191,23 @@ private Optional getReturnExprIfOnlyStatementInElseThen(J.If iff2) { return Optional.empty(); } + + private boolean hasElseWithComment(J.If.Else else_) { + if (else_ == null || else_.getBody() == null) { + return false; + } + if (!else_.getComments().isEmpty()) { + return true; + } + if (!else_.getBody().getComments().isEmpty()) { + return true; + } + if (else_.getBody() instanceof J.Block + && !((J.Block) else_.getBody()).getStatements().get(0).getComments().isEmpty()) { + return true; + } + return false; + } }; } diff --git a/src/test/java/org/openrewrite/staticanalysis/SimplifyBooleanReturnTest.java b/src/test/java/org/openrewrite/staticanalysis/SimplifyBooleanReturnTest.java index 02072ecdc..81418513c 100644 --- a/src/test/java/org/openrewrite/staticanalysis/SimplifyBooleanReturnTest.java +++ b/src/test/java/org/openrewrite/staticanalysis/SimplifyBooleanReturnTest.java @@ -15,6 +15,7 @@ */ package org.openrewrite.staticanalysis; +import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.openrewrite.DocumentExample; import org.openrewrite.test.RecipeSpec; @@ -34,6 +35,7 @@ public void defaults(RecipeSpec spec) { @Test void simplifyBooleanReturn() { rewriteRun( + //language=java java( """ public class A { @@ -74,6 +76,7 @@ static boolean isOddMillis() { @Test void dontSimplifyToReturnUnlessLastStatement() { rewriteRun( + //language=java java( """ public class A { @@ -105,6 +108,7 @@ public boolean absurdEquals(Object o) { @Test void nestedIfsWithNoBlock() { rewriteRun( + //language=java java( """ public class A { @@ -123,6 +127,7 @@ public boolean absurdEquals(Object o) { @Test void dontAlterWhenElseIfPresent() { rewriteRun( + //language=java java( """ public class A { @@ -146,6 +151,7 @@ else if (n == 2) { @Test void dontAlterWhenElseContainsSomethingOtherThanReturn() { rewriteRun( + //language=java java( """ public class A { @@ -167,6 +173,7 @@ public boolean foo(int n) { @Test void onlySimplifyToReturnWhenLastStatement() { rewriteRun( + //language=java java( """ import java.util.*; @@ -188,6 +195,7 @@ public static boolean deepEquals(List l, List r) { @Test void wrapNotReturnsOfTernaryIfConditionsInParentheses() { rewriteRun( + //language=java java( """ public class A { @@ -211,4 +219,92 @@ public boolean equals(Object o) { ) ); } + + @Nested + class RetainComments { + @Test + void onIfReturn() { + rewriteRun( + //language=java + java( + """ + class A { + boolean foo(int n) { + if (n == 1) { + // A comment that provides important context + return true; + } + else { + return false; + } + } + } + """ + ) + ); + } + + @Test + void onElseBlockReturn() { + rewriteRun( + //language=java + java( + """ + class A { + boolean foo(int n) { + if (n == 1) { + return true; + } + else { + // A comment that provides important context + return false; + } + } + } + """ + ) + ); + } + + @Test + void onElseReturn() { + rewriteRun( + //language=java + java( + """ + class A { + boolean foo(int n) { + if (n == 1) { + return true; + } + else + // A comment that provides important context + return false; + } + } + """ + ) + ); + } + + @Test + void onImpliedElse() { + rewriteRun( + //language=java + java( + """ + class A { + boolean foo(int n) { + if (n == 1) { + return true; + } + // A comment that provides important context + return false; + } + } + """ + ) + ); + } + } } From 51478fffcb33f060d18d9d02a12d4317a7beb58a Mon Sep 17 00:00:00 2001 From: Tim te Beek Date: Sat, 23 Sep 2023 18:45:38 +0200 Subject: [PATCH 25/92] Preserve arrays when inlining variables (#170) Before this PR we get a compiler error. After this PR we retain the array for now. A future PR might move the type declaration. --- .../staticanalysis/InlineVariable.java | 8 ++++++-- .../staticanalysis/InlineVariableTest.java | 17 +++++++++++++++++ 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/openrewrite/staticanalysis/InlineVariable.java b/src/main/java/org/openrewrite/staticanalysis/InlineVariable.java index 2e6cb7159..aca8dbdd2 100644 --- a/src/main/java/org/openrewrite/staticanalysis/InlineVariable.java +++ b/src/main/java/org/openrewrite/staticanalysis/InlineVariable.java @@ -21,7 +21,9 @@ import org.openrewrite.internal.ListUtils; import org.openrewrite.internal.lang.Nullable; import org.openrewrite.java.JavaIsoVisitor; +import org.openrewrite.java.tree.Expression; import org.openrewrite.java.tree.J; +import org.openrewrite.java.tree.JavaType; import org.openrewrite.java.tree.Statement; import java.time.Duration; @@ -98,8 +100,10 @@ private String identReturned(List stats) { Statement lastStatement = stats.get(stats.size() - 1); if (lastStatement instanceof J.Return) { J.Return return_ = (J.Return) lastStatement; - if (return_.getExpression() instanceof J.Identifier) { - return ((J.Identifier) return_.getExpression()).getSimpleName(); + Expression expression = return_.getExpression(); + if (expression instanceof J.Identifier && + !(expression.getType() instanceof JavaType.Array)) { + return ((J.Identifier) expression).getSimpleName(); } } else if (lastStatement instanceof J.Throw) { J.Throw thr = (J.Throw) lastStatement; diff --git a/src/test/java/org/openrewrite/staticanalysis/InlineVariableTest.java b/src/test/java/org/openrewrite/staticanalysis/InlineVariableTest.java index f3b34655d..cbc71e330 100644 --- a/src/test/java/org/openrewrite/staticanalysis/InlineVariableTest.java +++ b/src/test/java/org/openrewrite/staticanalysis/InlineVariableTest.java @@ -195,4 +195,21 @@ int test(int i1, int i2) { ) ); } + + @Test + void preserveArray() { + rewriteRun( + //language=java + java( + """ + class Test { + int[] test() { + int[] arr = {1, 2, 3}; + return arr; + } + } + """ + ) + ); + } } From 44257d8034aace49ce620a8c44cb3c9564bbee55 Mon Sep 17 00:00:00 2001 From: Tim te Beek Date: Tue, 26 Sep 2023 15:49:23 +0200 Subject: [PATCH 26/92] Do not throw NPE in `DeclarationSiteTypeVariance.validate()` Fixes https://github.com/openrewrite/rewrite-gradle-plugin/issues/198 --- .../DeclarationSiteTypeVariance.java | 21 +++++++++++-------- .../DeclarationSiteTypeVarianceTest.java | 9 ++++++++ 2 files changed, 21 insertions(+), 9 deletions(-) diff --git a/src/main/java/org/openrewrite/staticanalysis/DeclarationSiteTypeVariance.java b/src/main/java/org/openrewrite/staticanalysis/DeclarationSiteTypeVariance.java index 57a022062..6520a4f9f 100644 --- a/src/main/java/org/openrewrite/staticanalysis/DeclarationSiteTypeVariance.java +++ b/src/main/java/org/openrewrite/staticanalysis/DeclarationSiteTypeVariance.java @@ -69,15 +69,18 @@ public String getDescription() { @Override public Validated validate() { Validated v = super.validate(); - for (String variantType : variantTypes) { - v = v.and(Validated.test("variantTypes", "Must be a valid variant type", variantType, vt -> { - try { - VariantTypeSpec.build(vt); - return true; - } catch (Throwable ignored) { - return false; - } - })); + v = v.and(Validated.required("variantTypes", variantTypes)); + if (v.isValid()) { + for (String variantType : variantTypes) { + v = v.and(Validated.test("variantTypes", "Must be a valid variant type", variantType, vt -> { + try { + VariantTypeSpec.build(vt); + return true; + } catch (Throwable ignored) { + return false; + } + })); + } } return v; } diff --git a/src/test/java/org/openrewrite/staticanalysis/DeclarationSiteTypeVarianceTest.java b/src/test/java/org/openrewrite/staticanalysis/DeclarationSiteTypeVarianceTest.java index 8d0811c2c..398e52f69 100644 --- a/src/test/java/org/openrewrite/staticanalysis/DeclarationSiteTypeVarianceTest.java +++ b/src/test/java/org/openrewrite/staticanalysis/DeclarationSiteTypeVarianceTest.java @@ -47,6 +47,15 @@ void validation() { ).validate().isInvalid()).isTrue(); } + @Test + void validationWhenNull() { + assertThat(new DeclarationSiteTypeVariance( + null, + null, + null + ).validate().isInvalid()).isTrue(); + } + @DocumentExample @Test void inOutVariance() { From 4277f55e886af623e09397161a51fb375c59c2cf Mon Sep 17 00:00:00 2001 From: Jonathan Schneider Date: Tue, 26 Sep 2023 10:41:41 -0500 Subject: [PATCH 27/92] RenamePrivateFieldsToCamelCase: when a field looks like a constant, make it static --- .../RenamePrivateFieldsToCamelCase.java | 36 +++++++++++++++---- .../RenamePrivateFieldsToCamelCaseTest.java | 19 ++++++++++ 2 files changed, 49 insertions(+), 6 deletions(-) diff --git a/src/main/java/org/openrewrite/staticanalysis/RenamePrivateFieldsToCamelCase.java b/src/main/java/org/openrewrite/staticanalysis/RenamePrivateFieldsToCamelCase.java index 7236cbff9..0de3a7755 100644 --- a/src/main/java/org/openrewrite/staticanalysis/RenamePrivateFieldsToCamelCase.java +++ b/src/main/java/org/openrewrite/staticanalysis/RenamePrivateFieldsToCamelCase.java @@ -16,14 +16,17 @@ package org.openrewrite.staticanalysis; import org.openrewrite.*; +import org.openrewrite.internal.ListUtils; import org.openrewrite.java.tree.Flag; import org.openrewrite.java.tree.J; +import org.openrewrite.java.tree.JavaType; +import org.openrewrite.java.tree.Space; +import org.openrewrite.marker.Markers; import java.time.Duration; -import java.util.Arrays; -import java.util.LinkedHashSet; -import java.util.Set; +import java.util.*; +import static java.util.Collections.emptyList; import static org.openrewrite.internal.NameCaseConvention.LOWER_CAMEL; /** @@ -85,15 +88,25 @@ public J.VariableDeclarations.NamedVariable visitVariable(J.VariableDeclarations // Only make changes to private fields that are not constants. // Does not apply for instance variables of inner classes // Only make a change if the variable does not conform to lower camelcase format. + JavaType.Variable type = variable.getVariableType(); if (parentScope.getParent() != null && parentScope.getParent().getValue() instanceof J.ClassDeclaration && !(parentScope.getValue() instanceof J.ClassDeclaration) && - variable.getVariableType() != null && - variable.getVariableType().hasFlags(Flag.Private) && - !(variable.getVariableType().hasFlags(Flag.Static, Flag.Final)) && + type != null && + type.hasFlags(Flag.Private) && + !(type.hasFlags(Flag.Static, Flag.Final)) && !((J.ClassDeclaration) parentScope.getParent().getValue()).getType().getFullyQualifiedName().contains("$") && !LOWER_CAMEL.matches(variable.getSimpleName())) { + if (variable.getSimpleName().toUpperCase(Locale.getDefault()).equals(variable.getSimpleName()) && + type.hasFlags(Flag.Private, Flag.Final) && !type.hasFlags(Flag.Static)) { + // instead, add a static modifier + Set flags = new HashSet<>(type.getFlags()); + flags.add(Flag.Static); + getCursor().getParentTreeCursor().putMessage("ADD_STATIC", true); + return variable.withVariableType(type.withFlags(flags)); + } + String toName = LOWER_CAMEL.format(variable.getSimpleName()); renameVariable(variable, toName); } else { @@ -103,6 +116,17 @@ public J.VariableDeclarations.NamedVariable visitVariable(J.VariableDeclarations return super.visitVariable(variable, ctx); } + @Override + public J.VariableDeclarations visitVariableDeclarations(J.VariableDeclarations multiVariable, ExecutionContext ctx) { + J.VariableDeclarations vds = super.visitVariableDeclarations(multiVariable, ctx); + if (getCursor().getMessage("ADD_STATIC", false)) { + return vds.withModifiers(ListUtils.insert(vds.getModifiers(), + new J.Modifier(Tree.randomId(), Space.format(" "), Markers.EMPTY, + "static", J.Modifier.Type.Static, emptyList()), 1)); + } + return vds; + } + /** * Returns either the current block or a J.Type that may create a reference to a variable. * I.E. for(int target = 0; target < N; target++) creates a new name scope for `target`. diff --git a/src/test/java/org/openrewrite/staticanalysis/RenamePrivateFieldsToCamelCaseTest.java b/src/test/java/org/openrewrite/staticanalysis/RenamePrivateFieldsToCamelCaseTest.java index 4650358b0..37d0e005a 100644 --- a/src/test/java/org/openrewrite/staticanalysis/RenamePrivateFieldsToCamelCaseTest.java +++ b/src/test/java/org/openrewrite/staticanalysis/RenamePrivateFieldsToCamelCaseTest.java @@ -495,6 +495,25 @@ public boolean method() { ); } + @Test + void markConstantsStatic() { + rewriteRun( + //language=java + java( + """ + class A { + private final int CONSTANT = 42; + } + """, + """ + class A { + private static final int CONSTANT = 42; + } + """ + ) + ); + } + @Issue("https://github.com/openrewrite/rewrite-static-analysis/issues/103") @Test void doNotRenameUnderscoreOnly() { From 30b963b9c36f3f1a4c580a0fcc1b36405b9a7e29 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonathan=20Schn=C3=A9ider?= Date: Tue, 26 Sep 2023 18:47:13 +0000 Subject: [PATCH 28/92] refactor: Spaces Use this link to re-run the recipe: https://app.moderne.io/recipes/org.openrewrite.java.format.Spaces?organizationId=T3BlblJld3JpdGU%3D Co-authored-by: Moderne --- .../AddSerialVersionUidToSerializable.java | 10 +++++----- .../ControlFlowIndentation.java | 6 +++--- .../EqualsAvoidsNullVisitor.java | 4 ++-- .../ExplicitLambdaArgumentTypes.java | 8 ++++---- .../FinalizeLocalVariables.java | 2 +- .../MultipleVariableDeclarationsVisitor.java | 4 ++-- .../ReferentialEqualityToObjectEquals.java | 20 +++++++++---------- ...RemoveToStringCallsFromArrayInstances.java | 2 +- .../ReplaceWeekYearWithYear.java | 2 +- .../staticanalysis/UseDiamondOperator.java | 2 +- .../ChainStringBuilderAppendCallsTest.java | 2 +- .../FinalizePrivateFieldsTest.java | 2 +- 12 files changed, 32 insertions(+), 32 deletions(-) diff --git a/src/main/java/org/openrewrite/staticanalysis/AddSerialVersionUidToSerializable.java b/src/main/java/org/openrewrite/staticanalysis/AddSerialVersionUidToSerializable.java index 1a6acca44..96fc3608c 100644 --- a/src/main/java/org/openrewrite/staticanalysis/AddSerialVersionUidToSerializable.java +++ b/src/main/java/org/openrewrite/staticanalysis/AddSerialVersionUidToSerializable.java @@ -71,18 +71,18 @@ public J.VariableDeclarations visitVariableDeclarations(J.VariableDeclarations m @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())) { + 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)) { + 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())) { + 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)); } @@ -104,7 +104,7 @@ private J.VariableDeclarations maybeFixVariableDeclarations(J.VariableDeclaratio 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(), singleSpace, Markers.EMPTY, null, J.Modifier.Type.Static, Collections.emptyList()), - new J.Modifier(Tree.randomId(), singleSpace, Markers.EMPTY, null, J.Modifier.Type.Final, Collections.emptyList()) + new J.Modifier(Tree.randomId(), singleSpace, Markers.EMPTY, null, J.Modifier.Type.Final, Collections.emptyList()) )); } if (TypeUtils.asPrimitive(varDecls.getType()) != JavaType.Primitive.Long) { diff --git a/src/main/java/org/openrewrite/staticanalysis/ControlFlowIndentation.java b/src/main/java/org/openrewrite/staticanalysis/ControlFlowIndentation.java index 09e0e4ce1..75dea4653 100755 --- a/src/main/java/org/openrewrite/staticanalysis/ControlFlowIndentation.java +++ b/src/main/java/org/openrewrite/staticanalysis/ControlFlowIndentation.java @@ -80,7 +80,7 @@ public J.Block visitBlock(J.Block block, ExecutionContext executionContext) { J.Block b = super.visitBlock(block, executionContext); AtomicBoolean foundControlFlowRequiringReformatting = new AtomicBoolean(false); return b.withStatements(ListUtils.map(b.getStatements(), (i, statement) -> { - if(foundControlFlowRequiringReformatting.get() || shouldReformat(statement)) { + if (foundControlFlowRequiringReformatting.get() || shouldReformat(statement)) { foundControlFlowRequiringReformatting.set(true); return (Statement) new TabsAndIndentsVisitor<>(tabsAndIndentsStyle).visit(statement, executionContext, getCursor()); } @@ -89,9 +89,9 @@ public J.Block visitBlock(J.Block block, ExecutionContext executionContext) { } boolean shouldReformat(Statement s) { - if(s instanceof J.If) { + if (s instanceof J.If) { return shouldReformat((J.If) s); - } else if(s instanceof Loop) { + } else if (s instanceof Loop) { Statement body = ((Loop) s).getBody(); return !(body instanceof J.Block); } diff --git a/src/main/java/org/openrewrite/staticanalysis/EqualsAvoidsNullVisitor.java b/src/main/java/org/openrewrite/staticanalysis/EqualsAvoidsNullVisitor.java index 58894b5cb..104c96555 100644 --- a/src/main/java/org/openrewrite/staticanalysis/EqualsAvoidsNullVisitor.java +++ b/src/main/java/org/openrewrite/staticanalysis/EqualsAvoidsNullVisitor.java @@ -42,7 +42,7 @@ public class EqualsAvoidsNullVisitor

extends JavaIsoVisitor

{ public J.MethodInvocation visitMethodInvocation(J.MethodInvocation method, P p) { J.MethodInvocation m = super.visitMethodInvocation(method, p); - if(m.getSelect() == null) { + if (m.getSelect() == null) { return m; } @@ -82,7 +82,7 @@ private static class RemoveUnnecessaryNullCheck

extends JavaVisitor

{ @Override public @Nullable J visit(@Nullable Tree tree, P p) { - if(done) { + if (done) { return (J) tree; } return super.visit(tree, p); diff --git a/src/main/java/org/openrewrite/staticanalysis/ExplicitLambdaArgumentTypes.java b/src/main/java/org/openrewrite/staticanalysis/ExplicitLambdaArgumentTypes.java index a5174555f..473f0c017 100755 --- a/src/main/java/org/openrewrite/staticanalysis/ExplicitLambdaArgumentTypes.java +++ b/src/main/java/org/openrewrite/staticanalysis/ExplicitLambdaArgumentTypes.java @@ -95,9 +95,9 @@ private J.VariableDeclarations maybeAddTypeExpression(J.VariableDeclarations mul TypeTree typeExpression = buildTypeTree(nv.getType(), Space.EMPTY); if (typeExpression != null) { // "? extends Foo" is not a valid type definition on its own. Unwrap wildcard and replace with its bound - if(typeExpression instanceof J.Wildcard) { - J.Wildcard wildcard = (J.Wildcard)typeExpression; - if(wildcard.getBoundedType() == null) { + if (typeExpression instanceof J.Wildcard) { + J.Wildcard wildcard = (J.Wildcard) typeExpression; + if (wildcard.getBoundedType() == null) { return multiVariable; } typeExpression = buildTypeTree(wildcard.getBoundedType().getType(), Space.EMPTY); @@ -168,7 +168,7 @@ private TypeTree buildTypeTree(@Nullable JavaType type, Space space) { } } else if (type instanceof JavaType.Array) { return (buildTypeTree(((JavaType.Array) type).getElemType(), space)); - } else if(type instanceof JavaType.Variable) { + } else if (type instanceof JavaType.Variable) { return buildTypeTree(((JavaType.Variable) type).getType(), space); } else if (type instanceof JavaType.GenericTypeVariable) { JavaType.GenericTypeVariable genericType = (JavaType.GenericTypeVariable) type; diff --git a/src/main/java/org/openrewrite/staticanalysis/FinalizeLocalVariables.java b/src/main/java/org/openrewrite/staticanalysis/FinalizeLocalVariables.java index 22017c8eb..1eb96a7c0 100644 --- a/src/main/java/org/openrewrite/staticanalysis/FinalizeLocalVariables.java +++ b/src/main/java/org/openrewrite/staticanalysis/FinalizeLocalVariables.java @@ -58,7 +58,7 @@ public J.VariableDeclarations visitVariableDeclarations(J.VariableDeclarations m return mv; } - if(isDeclaredInForLoopControl(getCursor())) { + if (isDeclaredInForLoopControl(getCursor())) { return mv; } diff --git a/src/main/java/org/openrewrite/staticanalysis/MultipleVariableDeclarationsVisitor.java b/src/main/java/org/openrewrite/staticanalysis/MultipleVariableDeclarationsVisitor.java index d59764692..04f9b5d90 100644 --- a/src/main/java/org/openrewrite/staticanalysis/MultipleVariableDeclarationsVisitor.java +++ b/src/main/java/org/openrewrite/staticanalysis/MultipleVariableDeclarationsVisitor.java @@ -37,11 +37,11 @@ public J.Block visitBlock(J.Block block, ExecutionContext ctx) { J.Block b = super.visitBlock(block, ctx); return b.withStatements(ListUtils.flatMap(b.getStatements(), statement -> { - if(!(statement instanceof J.VariableDeclarations)) { + if (!(statement instanceof J.VariableDeclarations)) { return statement; } J.VariableDeclarations mv = (J.VariableDeclarations) statement; - if(mv.getVariables().size() <= 1) { + if (mv.getVariables().size() <= 1) { return mv; } diff --git a/src/main/java/org/openrewrite/staticanalysis/ReferentialEqualityToObjectEquals.java b/src/main/java/org/openrewrite/staticanalysis/ReferentialEqualityToObjectEquals.java index a850a846d..a0602fb38 100644 --- a/src/main/java/org/openrewrite/staticanalysis/ReferentialEqualityToObjectEquals.java +++ b/src/main/java/org/openrewrite/staticanalysis/ReferentialEqualityToObjectEquals.java @@ -143,8 +143,8 @@ private boolean isPrimitiveNull(Expression expression) { } private boolean hasThisIdentifier(J.Binary binary) { - return ((binary.getRight() instanceof J.Identifier && "this".equals(((J.Identifier)binary.getRight()).getSimpleName())) - || ((binary.getLeft() instanceof J.Identifier && "this".equals(((J.Identifier)binary.getLeft()).getSimpleName())))); + return ((binary.getRight() instanceof J.Identifier && "this".equals(((J.Identifier) binary.getRight()).getSimpleName())) + || ((binary.getLeft() instanceof J.Identifier && "this".equals(((J.Identifier) binary.getLeft()).getSimpleName())))); } private boolean isBoxedTypeComparison(J.Binary binary) { @@ -157,14 +157,14 @@ private boolean isBoxedTypeComparison(J.Binary binary) { } private boolean isBoxed(JavaType type) { return type instanceof JavaType.Primitive - || TypeUtils.isOfClassType(type,"java.lang.Byte") - || TypeUtils.isOfClassType(type,"java.lang.Character") - || TypeUtils.isOfClassType(type,"java.lang.Short") - || TypeUtils.isOfClassType(type,"java.lang.Integer") - || TypeUtils.isOfClassType(type,"java.lang.Long") - || TypeUtils.isOfClassType(type,"java.lang.Float") - || TypeUtils.isOfClassType(type,"java.lang.Double") - || TypeUtils.isOfClassType(type,"java.lang.Boolean"); + || TypeUtils.isOfClassType(type, "java.lang.Byte") + || TypeUtils.isOfClassType(type, "java.lang.Character") + || TypeUtils.isOfClassType(type, "java.lang.Short") + || TypeUtils.isOfClassType(type, "java.lang.Integer") + || TypeUtils.isOfClassType(type, "java.lang.Long") + || TypeUtils.isOfClassType(type, "java.lang.Float") + || TypeUtils.isOfClassType(type, "java.lang.Double") + || TypeUtils.isOfClassType(type, "java.lang.Boolean"); } } diff --git a/src/main/java/org/openrewrite/staticanalysis/RemoveToStringCallsFromArrayInstances.java b/src/main/java/org/openrewrite/staticanalysis/RemoveToStringCallsFromArrayInstances.java index 2810f7ca8..30537759b 100644 --- a/src/main/java/org/openrewrite/staticanalysis/RemoveToStringCallsFromArrayInstances.java +++ b/src/main/java/org/openrewrite/staticanalysis/RemoveToStringCallsFromArrayInstances.java @@ -82,7 +82,7 @@ public J visitMethodInvocation(J.MethodInvocation mi, ExecutionContext ctx) { break; } } - }else if (OBJECTS_TOSTRING_MATCHER.matches(mi) || VALUEOF_MATCHER.matches(mi)) { + } else if (OBJECTS_TOSTRING_MATCHER.matches(mi) || VALUEOF_MATCHER.matches(mi)) { // method is static Expression select = mi.getArguments().get(0); maybeRemoveImport("java.util.Objects"); diff --git a/src/main/java/org/openrewrite/staticanalysis/ReplaceWeekYearWithYear.java b/src/main/java/org/openrewrite/staticanalysis/ReplaceWeekYearWithYear.java index ee13f6b11..83300347e 100644 --- a/src/main/java/org/openrewrite/staticanalysis/ReplaceWeekYearWithYear.java +++ b/src/main/java/org/openrewrite/staticanalysis/ReplaceWeekYearWithYear.java @@ -91,7 +91,7 @@ public J.Literal visitLiteral(J.Literal li, ExecutionContext ctx) { return li; } - return li.withValueSource("\""+newValue+"\"").withValue(newValue); + return li.withValueSource("\"" + newValue + "\"").withValue(newValue); } } diff --git a/src/main/java/org/openrewrite/staticanalysis/UseDiamondOperator.java b/src/main/java/org/openrewrite/staticanalysis/UseDiamondOperator.java index 607ade525..2712f1ee9 100644 --- a/src/main/java/org/openrewrite/staticanalysis/UseDiamondOperator.java +++ b/src/main/java/org/openrewrite/staticanalysis/UseDiamondOperator.java @@ -174,7 +174,7 @@ private JavaType getMethodParamType(JavaType.Method methodType, int paramIndex) @Override public J.Return visitReturn(J.Return _return, ExecutionContext executionContext) { J.Return return_ = super.visitReturn(_return, executionContext); - J.NewClass nc = return_.getExpression() instanceof J.NewClass ? (J.NewClass)return_.getExpression() : null; + J.NewClass nc = return_.getExpression() instanceof J.NewClass ? (J.NewClass) return_.getExpression() : null; if (nc != null && (java9 || nc.getBody() == null) && nc.getClazz() instanceof J.ParameterizedType) { J parentBlock = getCursor().dropParentUntil(v -> v instanceof J.MethodDeclaration || v instanceof J.Lambda).getValue(); if (parentBlock instanceof J.MethodDeclaration) { diff --git a/src/test/java/org/openrewrite/staticanalysis/ChainStringBuilderAppendCallsTest.java b/src/test/java/org/openrewrite/staticanalysis/ChainStringBuilderAppendCallsTest.java index 24638b5c4..f48c0dafa 100644 --- a/src/test/java/org/openrewrite/staticanalysis/ChainStringBuilderAppendCallsTest.java +++ b/src/test/java/org/openrewrite/staticanalysis/ChainStringBuilderAppendCallsTest.java @@ -29,7 +29,7 @@ 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.") + @DocumentExample(value = "Chain `StringBuilder.append()` calls instead of the '+' operator to efficiently concatenate strings and numbers.") @Test void objectsConcatenation() { rewriteRun( diff --git a/src/test/java/org/openrewrite/staticanalysis/FinalizePrivateFieldsTest.java b/src/test/java/org/openrewrite/staticanalysis/FinalizePrivateFieldsTest.java index 1aa1b3355..0614be6e9 100644 --- a/src/test/java/org/openrewrite/staticanalysis/FinalizePrivateFieldsTest.java +++ b/src/test/java/org/openrewrite/staticanalysis/FinalizePrivateFieldsTest.java @@ -396,7 +396,7 @@ class A { ); } - @Disabled ("Doesn't support multiple constructors, to be enhanced") + @Disabled("Doesn't support multiple constructors, to be enhanced") @Test void fieldAssignedInAllAlternateConstructors() { rewriteRun( From a311d1d885a93d41caf5d0eb957547cd122dec14 Mon Sep 17 00:00:00 2001 From: Knut Wannheden Date: Wed, 27 Sep 2023 06:44:04 +0200 Subject: [PATCH 29/92] Fix a few type validation errors in test cases --- .../UnnecessaryParentheses.java | 9 ++++++++ .../staticanalysis/EmptyBlockTest.java | 1 + .../ExplicitLambdaArgumentTypesTest.java | 2 ++ .../FinalizeMethodArgumentsTest.java | 2 ++ .../FinalizePrivateFieldsTest.java | 9 +++++--- .../RemoveRedundantTypeCastTest.java | 18 --------------- ...veToStringCallsFromArrayInstancesTest.java | 4 ++-- .../RemoveUnusedLocalVariablesTest.java | 4 ++-- .../RenameLocalVariablesToCamelCaseTest.java | 2 +- .../ReplaceLambdaWithMethodReferenceTest.java | 5 ++++- .../SortedSetStreamToLinkedHashSetTest.java | 1 + .../UnnecessaryParenthesesTest.java | 22 +++++++++++++++++++ .../staticanalysis/UnnecessaryThrowsTest.java | 4 +++- 13 files changed, 55 insertions(+), 28 deletions(-) diff --git a/src/main/java/org/openrewrite/staticanalysis/UnnecessaryParentheses.java b/src/main/java/org/openrewrite/staticanalysis/UnnecessaryParentheses.java index efc5e6215..4b44d01fd 100644 --- a/src/main/java/org/openrewrite/staticanalysis/UnnecessaryParentheses.java +++ b/src/main/java/org/openrewrite/staticanalysis/UnnecessaryParentheses.java @@ -217,6 +217,15 @@ public J visitForControl(J.ForLoop.Control control, ExecutionContext ctx) { } return fc; } + + @Override + public J visitTernary(J.Ternary ternary, ExecutionContext ctx) { + J.Ternary te = (J.Ternary) super.visitTernary(ternary, ctx); + if (te.getCondition() instanceof J.Parentheses) { + te = (J.Ternary) new UnwrapParentheses<>((J.Parentheses) te.getCondition()).visitNonNull(te, ctx, getCursor().getParentOrThrow()); + } + return te; + } }; } diff --git a/src/test/java/org/openrewrite/staticanalysis/EmptyBlockTest.java b/src/test/java/org/openrewrite/staticanalysis/EmptyBlockTest.java index 4d28214aa..df55421f0 100644 --- a/src/test/java/org/openrewrite/staticanalysis/EmptyBlockTest.java +++ b/src/test/java/org/openrewrite/staticanalysis/EmptyBlockTest.java @@ -139,6 +139,7 @@ void emptyCatchBlockWithIOException() { //language=java java( """ + import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.nio.file.*; diff --git a/src/test/java/org/openrewrite/staticanalysis/ExplicitLambdaArgumentTypesTest.java b/src/test/java/org/openrewrite/staticanalysis/ExplicitLambdaArgumentTypesTest.java index 9433b6553..5623e8cd1 100644 --- a/src/test/java/org/openrewrite/staticanalysis/ExplicitLambdaArgumentTypesTest.java +++ b/src/test/java/org/openrewrite/staticanalysis/ExplicitLambdaArgumentTypesTest.java @@ -20,6 +20,7 @@ import org.openrewrite.Issue; import org.openrewrite.test.RecipeSpec; import org.openrewrite.test.RewriteTest; +import org.openrewrite.test.TypeValidation; import static org.openrewrite.java.Assertions.java; @@ -39,6 +40,7 @@ public void defaults(RecipeSpec spec) { @Test void unknownArgumentType() { rewriteRun( + spec -> spec.typeValidationOptions(TypeValidation.builder().identifiers(false).build()), //language=java java( """ diff --git a/src/test/java/org/openrewrite/staticanalysis/FinalizeMethodArgumentsTest.java b/src/test/java/org/openrewrite/staticanalysis/FinalizeMethodArgumentsTest.java index 041c8a3b6..b790bf752 100644 --- a/src/test/java/org/openrewrite/staticanalysis/FinalizeMethodArgumentsTest.java +++ b/src/test/java/org/openrewrite/staticanalysis/FinalizeMethodArgumentsTest.java @@ -20,6 +20,7 @@ import org.openrewrite.Issue; import org.openrewrite.test.RecipeSpec; import org.openrewrite.test.RewriteTest; +import org.openrewrite.test.TypeValidation; import static org.openrewrite.java.Assertions.java; @@ -173,6 +174,7 @@ private void getAccaCouponData() { @Test void doNotReplaceWithFinalModifier() { rewriteRun( + spec -> spec.typeValidationOptions(TypeValidation.none()), //language=java java( """ diff --git a/src/test/java/org/openrewrite/staticanalysis/FinalizePrivateFieldsTest.java b/src/test/java/org/openrewrite/staticanalysis/FinalizePrivateFieldsTest.java index 0614be6e9..d1f866dad 100644 --- a/src/test/java/org/openrewrite/staticanalysis/FinalizePrivateFieldsTest.java +++ b/src/test/java/org/openrewrite/staticanalysis/FinalizePrivateFieldsTest.java @@ -19,6 +19,7 @@ import org.junit.jupiter.api.Test; import org.openrewrite.DocumentExample; import org.openrewrite.Issue; +import org.openrewrite.java.JavaParser; import org.openrewrite.java.tree.Flag; import org.openrewrite.java.tree.J; import org.openrewrite.test.RecipeSpec; @@ -772,6 +773,7 @@ class A { @Test void anyFieldAnnotationAppliedIgnored() { rewriteRun( + spec -> spec.parser(JavaParser.fromJavaVersion().classpath("lombok")), //language=java java( """ @@ -784,8 +786,8 @@ public class A { static void test() { A a = new A(); - a.setNum(2); - a.setName("XYZ"); + // a.setNum(2); + // a.setName("XYZ"); } } """ @@ -796,6 +798,7 @@ static void test() { @Test void anyAnnotationAppliedClassIgnored() { rewriteRun( + spec -> spec.parser(JavaParser.fromJavaVersion().classpath("lombok")), //language=java java( """ @@ -807,7 +810,7 @@ public class B { void func() { B b = new B(); - b.setNum(1); + // b.setNum(1); } } """ diff --git a/src/test/java/org/openrewrite/staticanalysis/RemoveRedundantTypeCastTest.java b/src/test/java/org/openrewrite/staticanalysis/RemoveRedundantTypeCastTest.java index 4fbdecb6a..c700d6af9 100644 --- a/src/test/java/org/openrewrite/staticanalysis/RemoveRedundantTypeCastTest.java +++ b/src/test/java/org/openrewrite/staticanalysis/RemoveRedundantTypeCastTest.java @@ -30,24 +30,6 @@ public void defaults(RecipeSpec spec) { spec.recipe(new RemoveRedundantTypeCast()); } - @Test - void doNotChangeUpCast() { - rewriteRun( - //language=java - java( - """ - import java.util.List; - class Test { - Object o = ""; - String s = (String) o; - List l; - String[] sArray = (String[]) l.toArray(new String.get(0)); - } - """ - ) - ); - } - @Issue("https://github.com/openrewrite/rewrite/issues/1784") @Test void objectToObjectArray() { diff --git a/src/test/java/org/openrewrite/staticanalysis/RemoveToStringCallsFromArrayInstancesTest.java b/src/test/java/org/openrewrite/staticanalysis/RemoveToStringCallsFromArrayInstancesTest.java index 4b246d548..9eeb732f5 100644 --- a/src/test/java/org/openrewrite/staticanalysis/RemoveToStringCallsFromArrayInstancesTest.java +++ b/src/test/java/org/openrewrite/staticanalysis/RemoveToStringCallsFromArrayInstancesTest.java @@ -66,7 +66,7 @@ void doesNotRunOnNonArrayInstances() { """ class SomeClass { public static void main(String[] args) { - int number = 5; + Integer number = 5; System.out.println(number.toString()); } } @@ -204,7 +204,7 @@ void doesNotRunOnNormalStringConcat() { java( """ class SomeClass { - public static void main(Stringp[] args) { + public static void main(String[] args) { String strOne = "hello, "; String strTwo = "world!"; System.out.print(strOne + strTwo); diff --git a/src/test/java/org/openrewrite/staticanalysis/RemoveUnusedLocalVariablesTest.java b/src/test/java/org/openrewrite/staticanalysis/RemoveUnusedLocalVariablesTest.java index ffcc1c056..54c3616ff 100644 --- a/src/test/java/org/openrewrite/staticanalysis/RemoveUnusedLocalVariablesTest.java +++ b/src/test/java/org/openrewrite/staticanalysis/RemoveUnusedLocalVariablesTest.java @@ -139,9 +139,9 @@ void keepStatementWhenSideEffectInAccess() { java( """ class Test { - void method(Object someData) { + void method(java.util.Scanner reader, Object someData) { String a = ""; - while((a = reader.nexLine()) != null) { + while((a = reader.nextLine()) != null) { System.out.println(a); } } diff --git a/src/test/java/org/openrewrite/staticanalysis/RenameLocalVariablesToCamelCaseTest.java b/src/test/java/org/openrewrite/staticanalysis/RenameLocalVariablesToCamelCaseTest.java index 8dd0c3ba7..ffa96572c 100644 --- a/src/test/java/org/openrewrite/staticanalysis/RenameLocalVariablesToCamelCaseTest.java +++ b/src/test/java/org/openrewrite/staticanalysis/RenameLocalVariablesToCamelCaseTest.java @@ -228,7 +228,7 @@ public int addTen(String value) { Integer.valueOf(value); // Rule does not apply to catch variables with 1 character. } catch (Exception E){ - throw new NumberFormatException("Test", E); + throw new IllegalArgumentException("Test", E); } return DoNoTChange + 10; } diff --git a/src/test/java/org/openrewrite/staticanalysis/ReplaceLambdaWithMethodReferenceTest.java b/src/test/java/org/openrewrite/staticanalysis/ReplaceLambdaWithMethodReferenceTest.java index 5dbd5ec89..580a28004 100644 --- a/src/test/java/org/openrewrite/staticanalysis/ReplaceLambdaWithMethodReferenceTest.java +++ b/src/test/java/org/openrewrite/staticanalysis/ReplaceLambdaWithMethodReferenceTest.java @@ -24,6 +24,7 @@ import org.openrewrite.java.tree.TypeUtils; import org.openrewrite.test.RecipeSpec; import org.openrewrite.test.RewriteTest; +import org.openrewrite.test.TypeValidation; import static org.assertj.core.api.Assertions.assertThat; import static org.openrewrite.java.Assertions.java; @@ -301,6 +302,7 @@ public class CheckType { java( """ import java.util.List; + import java.util.Optional; import java.util.stream.Collectors; import org.test.CheckType; @@ -846,6 +848,7 @@ String execute(Integer i) { @Test void returnExpressionIsNotAMethodInvocation() { rewriteRun( + spec -> spec.typeValidationOptions(TypeValidation.builder().methodInvocations(false).build()), //language=java java( """ @@ -981,7 +984,7 @@ void foo() { f = i -> new ArrayList(i) {}; Object o; - o = i -> new ArrayList(i); + o = i -> new ArrayList(1); } } """ diff --git a/src/test/java/org/openrewrite/staticanalysis/SortedSetStreamToLinkedHashSetTest.java b/src/test/java/org/openrewrite/staticanalysis/SortedSetStreamToLinkedHashSetTest.java index 52859ac2c..444846432 100644 --- a/src/test/java/org/openrewrite/staticanalysis/SortedSetStreamToLinkedHashSetTest.java +++ b/src/test/java/org/openrewrite/staticanalysis/SortedSetStreamToLinkedHashSetTest.java @@ -104,6 +104,7 @@ void ignoreCollectToList() { //language=java java(""" import java.util.List; + import java.util.Set; import java.util.stream.Collectors; class A { diff --git a/src/test/java/org/openrewrite/staticanalysis/UnnecessaryParenthesesTest.java b/src/test/java/org/openrewrite/staticanalysis/UnnecessaryParenthesesTest.java index 071b5c97a..c2970cf62 100644 --- a/src/test/java/org/openrewrite/staticanalysis/UnnecessaryParenthesesTest.java +++ b/src/test/java/org/openrewrite/staticanalysis/UnnecessaryParenthesesTest.java @@ -909,5 +909,27 @@ void test(String s) { ) ); } + + @Test + void ternaryCondition() { + rewriteRun( + java( + """ + class Test { + String test(String s) { + return (s.isEmpty()) ? "true" : "false"; + } + } + """, + """ + class Test { + String test(String s) { + return s.isEmpty() ? "true" : "false"; + } + } + """ + ) + ); + } } diff --git a/src/test/java/org/openrewrite/staticanalysis/UnnecessaryThrowsTest.java b/src/test/java/org/openrewrite/staticanalysis/UnnecessaryThrowsTest.java index 02a4c07e3..62125b91b 100644 --- a/src/test/java/org/openrewrite/staticanalysis/UnnecessaryThrowsTest.java +++ b/src/test/java/org/openrewrite/staticanalysis/UnnecessaryThrowsTest.java @@ -34,6 +34,7 @@ import org.openrewrite.Issue; import org.openrewrite.test.RecipeSpec; import org.openrewrite.test.RewriteTest; +import org.openrewrite.test.TypeValidation; import static org.openrewrite.java.Assertions.java; @@ -112,7 +113,7 @@ void necessaryThrowsFromCloseable() { class Test { public void closeable() throws IOException { // URLClassLoader implements Closeable and throws IOException from its close() method - try (URLClassLoader cl = new URLClassLoader(new URL.get(0))) { + try (URLClassLoader cl = new URLClassLoader(new URL[0])) { } } } @@ -293,6 +294,7 @@ void foo() throws Exception { @Test void preventTransformationIfAnyThrownExceptionHasNullOrUnknownType() { rewriteRun( + spec -> spec.typeValidationOptions(TypeValidation.none()), //language=java java( """ From 80bd00128456a323ddec9aa9c814cdca809e494b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonathan=20Schn=C3=A9ider?= Date: Wed, 27 Sep 2023 11:38:29 -0500 Subject: [PATCH 30/92] Remove LowercasePackage from CSA --- src/main/resources/META-INF/rewrite/common-static-analysis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/resources/META-INF/rewrite/common-static-analysis.yml b/src/main/resources/META-INF/rewrite/common-static-analysis.yml index 6976f2672..2d31ecf0e 100644 --- a/src/main/resources/META-INF/rewrite/common-static-analysis.yml +++ b/src/main/resources/META-INF/rewrite/common-static-analysis.yml @@ -44,7 +44,7 @@ recipeList: - org.openrewrite.staticanalysis.InlineVariable - org.openrewrite.staticanalysis.IsEmptyCallOnCollections - org.openrewrite.staticanalysis.LambdaBlockToExpression - - org.openrewrite.staticanalysis.LowercasePackage +# - org.openrewrite.staticanalysis.LowercasePackage - org.openrewrite.staticanalysis.MethodNameCasing - org.openrewrite.staticanalysis.MinimumSwitchCases - org.openrewrite.staticanalysis.ModifierOrder From 0ce5108a9a2438718f14286f7004568e495a9051 Mon Sep 17 00:00:00 2001 From: Knut Wannheden Date: Thu, 28 Sep 2023 21:19:00 +0200 Subject: [PATCH 31/92] Polish `ReplaceDeprecatedRuntimeExecMethods` documentation --- .../ReplaceDeprecatedRuntimeExecMethods.java | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/main/java/org/openrewrite/staticanalysis/ReplaceDeprecatedRuntimeExecMethods.java b/src/main/java/org/openrewrite/staticanalysis/ReplaceDeprecatedRuntimeExecMethods.java index 9a90e2fd4..22b8c6251 100644 --- a/src/main/java/org/openrewrite/staticanalysis/ReplaceDeprecatedRuntimeExecMethods.java +++ b/src/main/java/org/openrewrite/staticanalysis/ReplaceDeprecatedRuntimeExecMethods.java @@ -16,7 +16,6 @@ package org.openrewrite.staticanalysis; import org.openrewrite.*; -import org.openrewrite.internal.lang.Nullable; import org.openrewrite.java.JavaIsoVisitor; import org.openrewrite.java.JavaTemplate; import org.openrewrite.java.MethodMatcher; @@ -36,17 +35,17 @@ public class ReplaceDeprecatedRuntimeExecMethods extends Recipe { @Override public String getDisplayName() { - return "Replace deprecated Runtime.Exec() methods"; + 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 " + + 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 @Nullable Duration getEstimatedEffortPerOccurrence() { + public Duration getEstimatedEffortPerOccurrence() { return Duration.ofMinutes(3); } From 2e3a96174d1e84df891ee422e22e8d41d8ebf4c7 Mon Sep 17 00:00:00 2001 From: Knut Wannheden Date: Mon, 2 Oct 2023 11:58:20 +0200 Subject: [PATCH 32/92] Allow `ReplaceRedundantFormatWithPrintf` to work with generics --- .../ReplaceRedundantFormatWithPrintf.java | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/openrewrite/staticanalysis/ReplaceRedundantFormatWithPrintf.java b/src/main/java/org/openrewrite/staticanalysis/ReplaceRedundantFormatWithPrintf.java index d6a041192..a00dc1804 100644 --- a/src/main/java/org/openrewrite/staticanalysis/ReplaceRedundantFormatWithPrintf.java +++ b/src/main/java/org/openrewrite/staticanalysis/ReplaceRedundantFormatWithPrintf.java @@ -133,7 +133,20 @@ public J.MethodInvocation visitMethodInvocation(J.MethodInvocation method, Execu if (i != 0) { code.append(", "); } - code.append("#{any(").append(argType).append(")}"); + code.append("#{any("); + if (argType instanceof JavaType.GenericTypeVariable) { + List bounds = ((JavaType.GenericTypeVariable) argType).getBounds(); + if (bounds.isEmpty()) { + code.append("Object"); + } else { + code.append(bounds.get(0)); + } + } else if (argType instanceof JavaType.Parameterized) { + code.append(((JavaType.Parameterized) argType).getType()); + } else { + code.append(argType); + } + code.append(")}"); } code.append(")"); From 9e9b5dce41dae1f5fb6dcf4e97288f17e966af1b Mon Sep 17 00:00:00 2001 From: Michael Keppler Date: Mon, 2 Oct 2023 13:12:55 +0200 Subject: [PATCH 33/92] Don't redeclare EnumSet as Set (#183) * Don't redeclare EnumSet as Set That changes the type of the generic, potentially breaking iterators. Fixes #179 * Update src/test/java/org/openrewrite/staticanalysis/UseCollectionInterfacesTest.java --------- Co-authored-by: Tim te Beek --- .../UseCollectionInterfaces.java | 1 - .../UseCollectionInterfacesTest.java | 149 +++++++++--------- 2 files changed, 74 insertions(+), 76 deletions(-) diff --git a/src/main/java/org/openrewrite/staticanalysis/UseCollectionInterfaces.java b/src/main/java/org/openrewrite/staticanalysis/UseCollectionInterfaces.java index 0b85e90b5..a87227eb1 100644 --- a/src/main/java/org/openrewrite/staticanalysis/UseCollectionInterfaces.java +++ b/src/main/java/org/openrewrite/staticanalysis/UseCollectionInterfaces.java @@ -84,7 +84,6 @@ public Duration getEstimatedEffortPerOccurrence() { rspecRulesReplaceTypeMap.put("java.util.concurrent.ConcurrentLinkedQueue", "java.util.Queue"); // Set rspecRulesReplaceTypeMap.put("java.util.AbstractSet", "java.util.Set"); - rspecRulesReplaceTypeMap.put("java.util.EnumSet", "java.util.Set"); rspecRulesReplaceTypeMap.put("java.util.HashSet", "java.util.Set"); rspecRulesReplaceTypeMap.put("java.util.LinkedHashSet", "java.util.Set"); rspecRulesReplaceTypeMap.put("java.util.concurrent.CopyOnWriteArraySet", "java.util.Set"); diff --git a/src/test/java/org/openrewrite/staticanalysis/UseCollectionInterfacesTest.java b/src/test/java/org/openrewrite/staticanalysis/UseCollectionInterfacesTest.java index 0117bcd4d..ade6075de 100644 --- a/src/test/java/org/openrewrite/staticanalysis/UseCollectionInterfacesTest.java +++ b/src/test/java/org/openrewrite/staticanalysis/UseCollectionInterfacesTest.java @@ -25,6 +25,7 @@ import java.util.UUID; +import static org.assertj.core.api.Assertions.assertThat; import static org.openrewrite.java.Assertions.java; @SuppressWarnings("rawtypes") @@ -42,7 +43,7 @@ void noTargetInUse() { """ import java.util.Collections; import java.util.Set; - + class Test { Set method() { return Collections.emptySet(); @@ -61,7 +62,7 @@ void returnIsAlreadyInterface() { """ import java.util.HashSet; import java.util.Set; - + class Test { public Set method() { return new HashSet<>(); @@ -80,7 +81,7 @@ void rawReturnType() { java( """ import java.util.HashSet; - + class Test { public HashSet method() { return new HashSet<>(); @@ -90,7 +91,7 @@ public HashSet method() { """ import java.util.HashSet; import java.util.Set; - + class Test { public Set method() { return new HashSet<>(); @@ -108,7 +109,7 @@ void parameterizedReturnType() { java( """ import java.util.HashSet; - + class Test { public HashSet method() { return new HashSet<>(); @@ -118,7 +119,7 @@ public HashSet method() { """ import java.util.HashSet; import java.util.Set; - + class Test { public Set method() { return new HashSet<>(); @@ -136,7 +137,7 @@ void preserveParameters() { java( """ import java.util.HashSet; - + class Test { public HashSet method(int primitive, Integer integer) { return new HashSet<>(); @@ -146,7 +147,7 @@ public HashSet method(int primitive, Integer integer) { """ import java.util.HashSet; import java.util.Set; - + class Test { public Set method(int primitive, Integer integer) { return new HashSet<>(); @@ -165,7 +166,7 @@ void fieldIsAlreadyInterface() { """ import java.util.HashSet; import java.util.Set; - + class Test { public Set values = new HashSet<>(); } @@ -181,7 +182,7 @@ void rawFieldType() { java( """ import java.util.HashSet; - + class Test { public HashSet values = new HashSet(); } @@ -189,7 +190,7 @@ class Test { """ import java.util.HashSet; import java.util.Set; - + class Test { public Set values = new HashSet(); } @@ -205,7 +206,7 @@ void parameterizedFieldType() { java( """ import java.util.HashSet; - + class Test { public HashSet values = new HashSet<>(); } @@ -213,7 +214,7 @@ class Test { """ import java.util.HashSet; import java.util.Set; - + class Test { public Set values = new HashSet<>(); } @@ -229,7 +230,7 @@ void arrayDeque() { java( """ import java.util.ArrayDeque; - + class Test { public ArrayDeque values = new ArrayDeque(); } @@ -237,7 +238,7 @@ class Test { """ import java.util.ArrayDeque; import java.util.Deque; - + class Test { public Deque values = new ArrayDeque(); } @@ -253,7 +254,7 @@ void concurrentLinkedDeque() { java( """ import java.util.concurrent.ConcurrentLinkedDeque; - + class Test { public ConcurrentLinkedDeque values = new ConcurrentLinkedDeque(); } @@ -261,7 +262,7 @@ class Test { """ import java.util.Deque; import java.util.concurrent.ConcurrentLinkedDeque; - + class Test { public Deque values = new ConcurrentLinkedDeque(); } @@ -278,7 +279,7 @@ void abstractList() { """ import java.util.ArrayList; import java.util.AbstractList; - + class Test { public AbstractList values = new ArrayList(); } @@ -286,7 +287,7 @@ class Test { """ import java.util.ArrayList; import java.util.List; - + class Test { public List values = new ArrayList(); } @@ -303,7 +304,7 @@ void abstractSequentialList() { """ import java.util.AbstractSequentialList; import java.util.LinkedList; - + class Test { public AbstractSequentialList values = new LinkedList(); } @@ -311,7 +312,7 @@ class Test { """ import java.util.LinkedList; import java.util.List; - + class Test { public List values = new LinkedList(); } @@ -327,7 +328,7 @@ void arrayList() { java( """ import java.util.ArrayList; - + class Test { public ArrayList values = new ArrayList(); } @@ -335,7 +336,7 @@ class Test { """ import java.util.ArrayList; import java.util.List; - + class Test { public List values = new ArrayList(); } @@ -351,7 +352,7 @@ void copyOnWriteArrayList() { java( """ import java.util.concurrent.CopyOnWriteArrayList; - + class Test { public CopyOnWriteArrayList values = new CopyOnWriteArrayList(); } @@ -359,7 +360,7 @@ class Test { """ import java.util.List; import java.util.concurrent.CopyOnWriteArrayList; - + class Test { public List values = new CopyOnWriteArrayList(); } @@ -376,7 +377,7 @@ void abstractMap() { """ import java.util.AbstractMap; import java.util.HashMap; - + class Test { public AbstractMap values = new HashMap(); } @@ -384,7 +385,7 @@ class Test { """ import java.util.HashMap; import java.util.Map; - + class Test { public Map values = new HashMap(); } @@ -403,7 +404,7 @@ void enumMap() { java( """ import java.util.EnumMap; - + class Test { public EnumMap values = new EnumMap(A.class); } @@ -411,7 +412,7 @@ class Test { """ import java.util.EnumMap; import java.util.Map; - + class Test { public Map values = new EnumMap(A.class); } @@ -427,7 +428,7 @@ void hashMap() { java( """ import java.util.HashMap; - + class Test { public HashMap values = new HashMap(); } @@ -435,7 +436,7 @@ class Test { """ import java.util.HashMap; import java.util.Map; - + class Test { public Map values = new HashMap(); } @@ -451,7 +452,7 @@ void hashtable() { java( """ import java.util.Hashtable; - + class Test { public Hashtable values = new Hashtable(); } @@ -459,7 +460,7 @@ class Test { """ import java.util.Hashtable; import java.util.Map; - + class Test { public Map values = new Hashtable(); } @@ -475,7 +476,7 @@ void identityHashMap() { java( """ import java.util.IdentityHashMap; - + class Test { public IdentityHashMap values = new IdentityHashMap(); } @@ -483,7 +484,7 @@ class Test { """ import java.util.IdentityHashMap; import java.util.Map; - + class Test { public Map values = new IdentityHashMap(); } @@ -499,7 +500,7 @@ void linkedHashMap() { java( """ import java.util.LinkedHashMap; - + class Test { public LinkedHashMap values = new LinkedHashMap(); } @@ -507,7 +508,7 @@ class Test { """ import java.util.LinkedHashMap; import java.util.Map; - + class Test { public Map values = new LinkedHashMap(); } @@ -523,7 +524,7 @@ void weakHashMap() { java( """ import java.util.WeakHashMap; - + class Test { public WeakHashMap values = new WeakHashMap(); } @@ -531,7 +532,7 @@ class Test { """ import java.util.Map; import java.util.WeakHashMap; - + class Test { public Map values = new WeakHashMap(); } @@ -547,7 +548,7 @@ void concurrentHashMap() { java( """ import java.util.concurrent.ConcurrentHashMap; - + class Test { public ConcurrentHashMap values = new ConcurrentHashMap(); } @@ -555,7 +556,7 @@ class Test { """ import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; - + class Test { public ConcurrentMap values = new ConcurrentHashMap(); } @@ -571,7 +572,7 @@ void concurrentSkipListMap() { java( """ import java.util.concurrent.ConcurrentSkipListMap; - + class Test { public ConcurrentSkipListMap values = new ConcurrentSkipListMap(); } @@ -579,7 +580,7 @@ class Test { """ import java.util.concurrent.ConcurrentMap; import java.util.concurrent.ConcurrentSkipListMap; - + class Test { public ConcurrentMap values = new ConcurrentSkipListMap(); } @@ -596,7 +597,7 @@ void abstractQueue() { """ import java.util.AbstractQueue; import java.util.PriorityQueue; - + class Test { public AbstractQueue values = new PriorityQueue(); } @@ -604,7 +605,7 @@ class Test { """ import java.util.PriorityQueue; import java.util.Queue; - + class Test { public Queue values = new PriorityQueue(); } @@ -620,7 +621,7 @@ void concurrentLinkedQueue() { java( """ import java.util.concurrent.ConcurrentLinkedQueue; - + class Test { public ConcurrentLinkedQueue values = new ConcurrentLinkedQueue(); } @@ -628,7 +629,7 @@ class Test { """ import java.util.Queue; import java.util.concurrent.ConcurrentLinkedQueue; - + class Test { public Queue values = new ConcurrentLinkedQueue(); } @@ -645,7 +646,7 @@ void abstractSet() { """ import java.util.AbstractSet; import java.util.HashSet; - + class Test { public AbstractSet values = new HashSet(); } @@ -653,7 +654,7 @@ class Test { """ import java.util.HashSet; import java.util.Set; - + class Test { public Set values = new HashSet(); } @@ -663,7 +664,8 @@ class Test { } @Test - void enumSet() { + @Issue("https://github.com/openrewrite/rewrite-static-analysis/issues/179") + void enumSetHasDifferentGenericTypeThanSet() { rewriteRun( //language=java java("public enum A {}"), @@ -671,17 +673,14 @@ void enumSet() { java( """ import java.util.EnumSet; - + class Test { public EnumSet values = EnumSet.allOf(A.class); - } - """, - """ - import java.util.EnumSet; - import java.util.Set; - - class Test { - public Set values = EnumSet.allOf(A.class); + void iterate() { + for (Enum e : values) { + // the for loop wouldn't compile anymore if the EnumSet declaration were changed + } + } } """ ) @@ -695,7 +694,7 @@ void hashSet() { java( """ import java.util.HashSet; - + class Test { public HashSet values = new HashSet(); } @@ -703,7 +702,7 @@ class Test { """ import java.util.HashSet; import java.util.Set; - + class Test { public Set values = new HashSet(); } @@ -719,7 +718,7 @@ void linkedHashSet() { java( """ import java.util.LinkedHashSet; - + class Test { public LinkedHashSet values = new LinkedHashSet(); } @@ -727,7 +726,7 @@ class Test { """ import java.util.LinkedHashSet; import java.util.Set; - + class Test { public Set values = new LinkedHashSet(); } @@ -743,7 +742,7 @@ void copyOnWriteArraySet() { java( """ import java.util.concurrent.CopyOnWriteArraySet; - + class Test { public CopyOnWriteArraySet values = new CopyOnWriteArraySet(); } @@ -751,7 +750,7 @@ class Test { """ import java.util.Set; import java.util.concurrent.CopyOnWriteArraySet; - + class Test { public Set values = new CopyOnWriteArraySet(); } @@ -768,7 +767,7 @@ void privateVariable() { java( """ import java.util.ArrayList; - + class Test { private ArrayList values = new ArrayList<>(); } @@ -776,7 +775,7 @@ class Test { """ import java.util.ArrayList; import java.util.List; - + class Test { private List values = new ArrayList<>(); } @@ -793,7 +792,7 @@ void noModifierOnVariable() { java( """ import java.util.ArrayList; - + class Test { ArrayList values = new ArrayList<>(); } @@ -801,7 +800,7 @@ class Test { """ import java.util.ArrayList; import java.util.List; - + class Test { List values = new ArrayList<>(); } @@ -818,7 +817,7 @@ void privateMethod() { java( """ import java.util.HashSet; - + class Test { private HashSet method() { return new HashSet<>(); @@ -828,7 +827,7 @@ private HashSet method() { """ import java.util.HashSet; import java.util.Set; - + class Test { private Set method() { return new HashSet<>(); @@ -847,7 +846,7 @@ void noModifierOnMethod() { java( """ import java.util.HashSet; - + class Test { HashSet method() { return new HashSet<>(); @@ -857,7 +856,7 @@ HashSet method() { """ import java.util.HashSet; import java.util.Set; - + class Test { Set method() { return new HashSet<>(); @@ -878,7 +877,7 @@ void testExplicitImplementationClassInApi() { """ import java.util.ArrayList; import java.util.List; - + class Test { List m() { List result = new ArrayList<>(); @@ -907,7 +906,7 @@ void variableWithVar() { java( """ import java.util.ArrayList; - + class Test { public void method() { var list = new ArrayList<>(); From c41a24afe41e4f745ca411399fd16e0bab9567c8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alejandro=20Bertolo=20Fafi=C3=A1n?= <60966552+AlejandroBertolo@users.noreply.github.com> Date: Mon, 2 Oct 2023 13:36:11 +0200 Subject: [PATCH 34/92] fix: FinalizeMethodArguments considers unary and += expressions (#180) * fix: FinalizeMethodArguments considers unary and += expressions * chore: add language tag * chore: mark contributed code * Update src/test/java/org/openrewrite/staticanalysis/FinalizeMethodArgumentsTest.java Co-authored-by: Tim te Beek * Polish --------- Co-authored-by: Tim te Beek Co-authored-by: Tim te Beek --- .../FinalizeMethodArguments.java | 32 +++ .../FinalizeMethodArgumentsTest.java | 258 ++++++++++-------- 2 files changed, 181 insertions(+), 109 deletions(-) diff --git a/src/main/java/org/openrewrite/staticanalysis/FinalizeMethodArguments.java b/src/main/java/org/openrewrite/staticanalysis/FinalizeMethodArguments.java index b6070fb51..d7191cdb2 100644 --- a/src/main/java/org/openrewrite/staticanalysis/FinalizeMethodArguments.java +++ b/src/main/java/org/openrewrite/staticanalysis/FinalizeMethodArguments.java @@ -145,6 +145,38 @@ public J.Assignment visitAssignment(J.Assignment a, AtomicBoolean hasAssignment) return assignment; } + + @Override + public J.Unary visitUnary(final J.Unary unary, final AtomicBoolean hasAssignment) { + if (hasAssignment.get()) { + return unary; + } + + final J.Unary u = super.visitUnary(unary, hasAssignment); + if (u.getOperator().isModifying() && u.getExpression() instanceof J.Identifier) { + final J.Identifier i = (J.Identifier) u.getExpression(); + if (i.getSimpleName().equals(this.variable.getSimpleName())) { + hasAssignment.set(true); + } + } + return u; + } + + @Override + public J.AssignmentOperation visitAssignmentOperation(final J.AssignmentOperation assignOp, final AtomicBoolean hasAssignment) { + if (hasAssignment.get()) { + return assignOp; + } + + final J.AssignmentOperation a = super.visitAssignmentOperation(assignOp, hasAssignment); + if (a.getVariable() instanceof J.Identifier) { + final J.Identifier i = (J.Identifier) a.getVariable(); + if (i.getSimpleName().equals(this.variable.getSimpleName())) { + hasAssignment.set(true); + } + } + return a; + } } private static Statement updateParam(final Statement p) { diff --git a/src/test/java/org/openrewrite/staticanalysis/FinalizeMethodArgumentsTest.java b/src/test/java/org/openrewrite/staticanalysis/FinalizeMethodArgumentsTest.java index b790bf752..33982ae33 100644 --- a/src/test/java/org/openrewrite/staticanalysis/FinalizeMethodArgumentsTest.java +++ b/src/test/java/org/openrewrite/staticanalysis/FinalizeMethodArgumentsTest.java @@ -76,13 +76,12 @@ void doNotAddFinalIfAssigned() { //language=java java( """ - package a; - class A { - void SubeventUtils(String a) { - a = "abc"; - } - } - """ + class A { + void SubeventUtils(String a) { + a = "abc"; + } + } + """ ) ); } @@ -93,12 +92,10 @@ void doNotAddFinalIfInterface() { //language=java java( """ - package a; - public interface MarketDeleteService { - - void deleteMarket(Long marketId, String deletionTimestamp); - } - """ + public interface MarketDeleteService { + void deleteMarket(Long marketId, String deletionTimestamp); + } + """ ) ); } @@ -110,25 +107,17 @@ void replaceWithFinalModifier() { //language=java java( """ - package com.test; - - class TestClass { - - private void getAccaCouponData(String responsiveRequestConfig, String card) { - - } - } - """, + class TestClass { + private void getAccaCouponData(String responsiveRequestConfig, String card) { + } + } + """, """ - package com.test; - - class TestClass { - - private void getAccaCouponData(final String responsiveRequestConfig, final String card) { - - } - } - """ + class TestClass { + private void getAccaCouponData(final String responsiveRequestConfig, final String card) { + } + } + """ ) ); } @@ -139,103 +128,154 @@ void replaceWithFinalModifierWhenAnnotated() { //language=java java( """ - public class Test { - public void test(@Override Integer test) {} - } - """, + class Test { + public void test(@Override Integer test) {} + } + """, """ - public class Test { - public void test(@Override final Integer test) {} - } - """ + class Test { + public void test(@Override final Integer test) {} + } + """ ) ); } @Test - void replaceWithFinalModifierNoArguments() { + void doNotReplaceWithFinalModifier() { rewriteRun( + spec -> spec.typeValidationOptions(TypeValidation.none()), //language=java java( """ - package com.test; - - class TestClass { - - private void getAccaCouponData() { - - } - } - """ + package responsive.utils.subevent; + + import responsive.enums.subevent.SubeventTypes; + import responsive.model.dto.card.SubEvent; + import java.util.List; + import org.springframework.beans.factory.annotation.Value; + import org.springframework.stereotype.Component; + + import static responsive.enums.matchdata.MatchDataTitleSeparator.AT; + import static responsive.enums.matchdata.MatchDataTitleSeparator.VS; + import static java.lang.String.format; + import static org.apache.commons.lang3.StringUtils.splitByWholeSeparator; + + /** + * Created by mza05 on 13/10/2017. + */ + @Component + class SubeventUtils { + + private static final String SUBEVENT_FORMAT = "%s%s%s"; + private final List categoryGroupIdForChangeSubeventName; + + public SubeventUtils( + @Value("#{'${responsive.category.group.id.change.subevent.name}'.split(',')}") final List categoryGroupIdForChangeSubeventName) { + this.categoryGroupIdForChangeSubeventName = categoryGroupIdForChangeSubeventName; + } + + public static boolean isSubeventOfSpecifiedType(final SubEvent subEvent, final List requiredTypes) { + + if (subEvent.getType() == null) { + return false; + } + return requiredTypes.stream() + .anyMatch(requiredType -> requiredType.getType().equalsIgnoreCase(subEvent.getType())); + + } + + /** + * Change SubeventName by CategoryGroupId and rebub + * @param subeventName + * @param categoryGroupId + * @return + */ + public String getSubeventNameForCategoryGroupId(final String subeventName, final Integer categoryGroupId) { + + if (subeventName != null && categoryGroupId != null + && subeventName.contains(AT.getSeparator()) + && categoryGroupIdForChangeSubeventName.contains(categoryGroupId)) { + final var subeventTeamSplit = splitByWholeSeparator(subeventName, AT.getSeparator()); + if (subeventTeamSplit.length > 0) { + return format(SUBEVENT_FORMAT, subeventTeamSplit[0], VS.getSeparator(), subeventTeamSplit[1]); + } + } + + return subeventName; + } + } + """ ) ); } @Test - void doNotReplaceWithFinalModifier() { + @Issue("https://github.com/openrewrite/rewrite-static-analysis/issues/176") + void doNotReplaceIfAssignedThroughUnaryOrAccumulator() { rewriteRun( - spec -> spec.typeValidationOptions(TypeValidation.none()), //language=java java( """ - package responsive.utils.subevent; - - import responsive.enums.subevent.SubeventTypes; - import responsive.model.dto.card.SubEvent; - import java.util.List; - import org.springframework.beans.factory.annotation.Value; - import org.springframework.stereotype.Component; - - import static responsive.enums.matchdata.MatchDataTitleSeparator.AT; - import static responsive.enums.matchdata.MatchDataTitleSeparator.VS; - import static java.lang.String.format; - import static org.apache.commons.lang3.StringUtils.splitByWholeSeparator; - - /** - * Created by mza05 on 13/10/2017. - */ - @Component - public class SubeventUtils { - - private static final String SUBEVENT_FORMAT = "%s%s%s"; - private final List categoryGroupIdForChangeSubeventName; - - public SubeventUtils( - @Value("#{'${responsive.category.group.id.change.subevent.name}'.split(',')}") final List categoryGroupIdForChangeSubeventName) { - this.categoryGroupIdForChangeSubeventName = categoryGroupIdForChangeSubeventName; - } - - public static boolean isSubeventOfSpecifiedType(final SubEvent subEvent, final List requiredTypes) { - - if (subEvent.getType() == null) { - return false; - } - return requiredTypes.stream() - .anyMatch(requiredType -> requiredType.getType().equalsIgnoreCase(subEvent.getType())); - - } - - /** - * Change SubeventName by CategoryGroupId and rebub - * @param subeventName - * @param categoryGroupId - * @return - */ - public String getSubeventNameForCategoryGroupId(final String subeventName, final Integer categoryGroupId) { - - if (subeventName != null && categoryGroupId != null - && subeventName.contains(AT.getSeparator()) - && categoryGroupIdForChangeSubeventName.contains(categoryGroupId)) { - final var subeventTeamSplit = splitByWholeSeparator(subeventName, AT.getSeparator()); - if (subeventTeamSplit.length > 0) { - return format(SUBEVENT_FORMAT, subeventTeamSplit[0], VS.getSeparator(), subeventTeamSplit[1]); - } - } - - return subeventName; - } - } - """ + class Test { + protected int addFinalToThisVar(int unalteredVariable) { + return unalteredVariable; + } + + protected int increment(int variableToIncrement) { + variableToIncrement++; + return variableToIncrement; + } + + protected int preIncrement(int variableToPreIncrement) { + return ++variableToPreIncrement; + } + + protected int decrement(int variableToDecrement) { + variableToDecrement--; + return variableToDecrement; + } + + protected int preDecrement(int variableToPreDecrement) { + return --variableToPreDecrement; + } + + protected int accumulate(int add, int accumulator) { + accumulator += add; + return accumulator; + } + } + """, + """ + class Test { + protected int addFinalToThisVar(final int unalteredVariable) { + return unalteredVariable; + } + + protected int increment(int variableToIncrement) { + variableToIncrement++; + return variableToIncrement; + } + + protected int preIncrement(int variableToPreIncrement) { + return ++variableToPreIncrement; + } + + protected int decrement(int variableToDecrement) { + variableToDecrement--; + return variableToDecrement; + } + + protected int preDecrement(int variableToPreDecrement) { + return --variableToPreDecrement; + } + + protected int accumulate(int add, int accumulator) { + accumulator += add; + return accumulator; + } + } + """ ) ); } From ad96b86d41e3beae3f38ffda20de0bdc092a106d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alejandro=20Bertolo=20Fafi=C3=A1n?= <60966552+AlejandroBertolo@users.noreply.github.com> Date: Mon, 2 Oct 2023 13:44:49 +0200 Subject: [PATCH 35/92] fix: skip finalizing anonymous class fields in FinalizeLocalVariables (#182) * fix: skip finalizing anonymous class fields * chore: add issue to test * chore: mark contributed code * chore: update anonymous class identifying logic * Apply formatter * chore: remove javafx types from test * Polish * Apply suggestions from code review --------- Co-authored-by: Tim te Beek Co-authored-by: Tim te Beek --- .../FinalizeLocalVariables.java | 13 +- .../FinalizeLocalVariablesTest.java | 151 ++++++++++-------- 2 files changed, 99 insertions(+), 65 deletions(-) diff --git a/src/main/java/org/openrewrite/staticanalysis/FinalizeLocalVariables.java b/src/main/java/org/openrewrite/staticanalysis/FinalizeLocalVariables.java index 1eb96a7c0..aca7846ba 100644 --- a/src/main/java/org/openrewrite/staticanalysis/FinalizeLocalVariables.java +++ b/src/main/java/org/openrewrite/staticanalysis/FinalizeLocalVariables.java @@ -44,8 +44,8 @@ public String getDescription() { public TreeVisitor getVisitor() { return new JavaIsoVisitor() { - @Override - public J.VariableDeclarations visitVariableDeclarations(J.VariableDeclarations multiVariable, ExecutionContext p) { + @Override + public J.VariableDeclarations visitVariableDeclarations(J.VariableDeclarations multiVariable, ExecutionContext p) { J.VariableDeclarations mv = super.visitVariableDeclarations(multiVariable, p); // if this already has "final", we don't need to bother going any further; we're done @@ -67,6 +67,11 @@ public J.VariableDeclarations visitVariableDeclarations(J.VariableDeclarations m return mv; } + // ignores anonymous class fields, contributed code for issue #181 + if (this.getCursorToParentScope(this.getCursor()).getValue() instanceof J.NewClass) { + return mv; + } + if (mv.getVariables().stream() .noneMatch(v -> { Cursor declaringCursor = v.getDeclaringScope(getCursor()); @@ -80,6 +85,10 @@ public J.VariableDeclarations visitVariableDeclarations(J.VariableDeclarations m return mv; } + + private Cursor getCursorToParentScope(final Cursor cursor) { + return cursor.dropParentUntil(is -> is instanceof J.NewClass || is instanceof J.ClassDeclaration); + } }; } diff --git a/src/test/java/org/openrewrite/staticanalysis/FinalizeLocalVariablesTest.java b/src/test/java/org/openrewrite/staticanalysis/FinalizeLocalVariablesTest.java index 876e27cd5..fc396d2c3 100644 --- a/src/test/java/org/openrewrite/staticanalysis/FinalizeLocalVariablesTest.java +++ b/src/test/java/org/openrewrite/staticanalysis/FinalizeLocalVariablesTest.java @@ -225,14 +225,14 @@ class Test { } """, """ - class Test { - static { - final int n = 1; - for(int i = 0; i < n; i++) { - } - } - } - """ + class Test { + static { + final int n = 1; + for(int i = 0; i < n; i++) { + } + } + } + """ ) ); } @@ -282,7 +282,7 @@ void forLoopVariablesIgnored() { java( """ import java.util.concurrent.FutureTask; - + class A { void f() { for(FutureTask future; (future = new FutureTask<>(() -> "hello world")) != null;) { } @@ -296,34 +296,36 @@ void f() { @Test void shouldNotFinalizeForCounterWhichIsReassignedWithinForHeader() { rewriteRun( - //language=java - java(""" - class A { - static { - for (int i = 0; i < 10; i++) { - // no-op - } - } - } - """ - ) + //language=java + java( + """ + class A { + static { + for (int i = 0; i < 10; i++) { + // no-op + } + } + } + """ + ) ); } @Test void shouldNotFinalizeForCounterWhichIsReassignedWithinForBody() { rewriteRun( - //language=java - java(""" - class A { - static { - for (int i = 0; i < 10;) { - i = 11; - } - } - } - """ - ) + //language=java + java( + """ + class A { + static { + for (int i = 0; i < 10;) { + i = 11; + } + } + } + """ + ) ); } @@ -394,48 +396,71 @@ class Person { void recordShouldNotIntroduceExtraClosingParenthesis() { rewriteRun( version( - //language=java - java( - """ - public class Main { - public static void test() { - var myVar = ""; - } - public record EmptyRecord() { - } - } - """, - """ - public class Main { - public static void test() { - final var myVar = ""; - } - public record EmptyRecord() { + //language=java + java( + """ + public class Main { + public static void test() { + var myVar = ""; + } + public record EmptyRecord() { + } } - } + """, """ - ), 17) + public class Main { + public static void test() { + final var myVar = ""; + } + public record EmptyRecord() { + } + } + """ + ), 17) ); } @Test void shouldNotFinalizeVariableWhichIsReassignedInAnotherSwitchBranch() { rewriteRun( - //language=java - java(""" - class A { - static int variable = 0; - static { - switch (variable) { - case 0: - int notFinalized = 0; - default: - notFinalized = 1; - } + //language=java + java( + """ + class A { + static int variable = 0; + static { + switch (variable) { + case 0: + int notFinalized = 0; + default: + notFinalized = 1; } } - """ - ) + } + """ + ) + ); + } + + @Test + @Issue("https://github.com/openrewrite/rewrite-static-analysis/issues/181") + void shouldNotFinalizeVariablesWhichAreAssignedInAnonymousClasses() { + this.rewriteRun( + // language=java + java( + """ + class Test { + private final Object objectWithInnerField = new Object() { + private int count = 0; + @Override + public String toString() { + this.count++; + return super.toString() + this.count; + } + }; + } + """ + ) ); } } From bd227d5e3c9ca5e0852ce70cfbc559cd8c4b91b6 Mon Sep 17 00:00:00 2001 From: Michael Keppler Date: Tue, 3 Oct 2023 13:55:04 +0200 Subject: [PATCH 36/92] Transform collection.size() < 1 to isEmpty() (#184) Not only size comparisons with zero can be optimized to isEmpty() calls. Additionally we can do (plus order changes): * size() < 1 to isEmpty() * size() >= 1 to !isEmpty() --- .../IsEmptyCallOnCollections.java | 29 +++++++++++- .../IsEmptyCallOnCollectionsTest.java | 47 ++++++++++++++++++- 2 files changed, 73 insertions(+), 3 deletions(-) diff --git a/src/main/java/org/openrewrite/staticanalysis/IsEmptyCallOnCollections.java b/src/main/java/org/openrewrite/staticanalysis/IsEmptyCallOnCollections.java index bbb0d1f23..fb6259293 100755 --- a/src/main/java/org/openrewrite/staticanalysis/IsEmptyCallOnCollections.java +++ b/src/main/java/org/openrewrite/staticanalysis/IsEmptyCallOnCollections.java @@ -68,10 +68,10 @@ public TreeVisitor getVisitor() { public J visitBinary(J.Binary binary, ExecutionContext ctx) { if (isZero(binary.getLeft()) || isZero(binary.getRight())) { boolean zeroRight = isZero(binary.getRight()); - J maybeSizeCall = zeroRight ? binary.getLeft() : binary.getRight(); if (binary.getOperator() == J.Binary.Type.Equal || binary.getOperator() == J.Binary.Type.NotEqual || zeroRight && binary.getOperator() == J.Binary.Type.GreaterThan || !zeroRight && binary.getOperator() == J.Binary.Type.LessThan) { + J maybeSizeCall = zeroRight ? binary.getLeft() : binary.getRight(); if (maybeSizeCall instanceof J.MethodInvocation) { J.MethodInvocation maybeSizeCallMethod = (J.MethodInvocation) maybeSizeCall; if (COLLECTION_SIZE.matches(maybeSizeCallMethod)) { @@ -84,6 +84,28 @@ public J visitBinary(J.Binary binary, ExecutionContext ctx) { } } } + if (isOne(binary.getLeft()) || isOne(binary.getRight())) { + boolean oneRight = isOne(binary.getRight()); + if ((oneRight && binary.getOperator() == J.Binary.Type.LessThan) + || (!oneRight && binary.getOperator() == J.Binary.Type.GreaterThan) + || (oneRight && binary.getOperator() == J.Binary.Type.GreaterThanOrEqual) + || (!oneRight && binary.getOperator() == J.Binary.Type.LessThanOrEqual)) { + J maybeSizeCall = oneRight ? binary.getLeft() : binary.getRight(); + if (maybeSizeCall instanceof J.MethodInvocation) { + J.MethodInvocation maybeSizeCallMethod = (J.MethodInvocation) maybeSizeCall; + if (COLLECTION_SIZE.matches(maybeSizeCallMethod)) { + String op = ""; + if (binary.getOperator() == J.Binary.Type.GreaterThanOrEqual || binary.getOperator() == J.Binary.Type.LessThanOrEqual) { + op = "!"; + } + return (maybeSizeCallMethod.getSelect() == null ? + isEmptyNoReceiver.apply(getCursor(), binary.getCoordinates().replace(), op) : + isEmpty.apply(getCursor(), binary.getCoordinates().replace(), op, maybeSizeCallMethod.getSelect()) + ).withPrefix(binary.getPrefix()); + } + } + } + } return super.visitBinary(binary, ctx); } }); @@ -92,4 +114,9 @@ public J visitBinary(J.Binary binary, ExecutionContext ctx) { private static boolean isZero(Expression expression) { return expression instanceof J.Literal && Integer.valueOf(0).equals(((J.Literal) expression).getValue()); } + + private static boolean isOne(Expression expression) { + return expression instanceof J.Literal && Integer.valueOf(1).equals(((J.Literal) expression).getValue()); + } + } diff --git a/src/test/java/org/openrewrite/staticanalysis/IsEmptyCallOnCollectionsTest.java b/src/test/java/org/openrewrite/staticanalysis/IsEmptyCallOnCollectionsTest.java index 416d44374..2f952cb56 100644 --- a/src/test/java/org/openrewrite/staticanalysis/IsEmptyCallOnCollectionsTest.java +++ b/src/test/java/org/openrewrite/staticanalysis/IsEmptyCallOnCollectionsTest.java @@ -75,7 +75,7 @@ boolean test() { } @Test - void isEmptyCallOnCollections() { + void comparisonWithZero() { rewriteRun( //language=java java( @@ -84,7 +84,7 @@ void isEmptyCallOnCollections() { class Test { static void method(List l) { - if (l.isEmpty() || 0 == l.size()) { + if (l.size() == 0 || 0 == l.size()) { // empty body } else if (l.size() != 0 || 0 != l.size()) { // empty body @@ -117,6 +117,49 @@ static void method(List l) { ); } + @Test + void comparisonWithOne() { + rewriteRun( + //language=java + java( + """ + import java.util.List; + + class Test { + static void method(List l) { + if (l.size() < 1 || 1 > l.size()) { + // empty body + } else if (l.size() > 1 || 1 < l.size()) { + // empty body + } else if (l.size() >= 1 || 1 <= l.size()) { + // empty body + } else if (l.size() <= 1 || 1 >= l.size()) { + // empty body + } + } + } + """, + """ + import java.util.List; + + class Test { + static void method(List l) { + if (l.isEmpty() || l.isEmpty()) { + // empty body + } else if (l.size() > 1 || 1 < l.size()) { + // empty body + } else if (!l.isEmpty() || !l.isEmpty()) { + // empty body + } else if (l.size() <= 1 || 1 >= l.size()) { + // empty body + } + } + } + """ + ) + ); + } + @Issue("https://github.com/openrewrite/rewrite/issues/1112") @Test void formatting() { From f30987fa5ce693b4d644d99de1970f70cedef228 Mon Sep 17 00:00:00 2001 From: Knut Wannheden Date: Thu, 5 Oct 2023 14:43:32 +0200 Subject: [PATCH 37/92] Use Java 17 as default for test cases in `InstanceOfPatternMatchTest` --- .../InstanceOfPatternMatchTest.java | 1061 ++++++++--------- 1 file changed, 505 insertions(+), 556 deletions(-) diff --git a/src/test/java/org/openrewrite/staticanalysis/InstanceOfPatternMatchTest.java b/src/test/java/org/openrewrite/staticanalysis/InstanceOfPatternMatchTest.java index 5d0797e64..0d29e191f 100644 --- a/src/test/java/org/openrewrite/staticanalysis/InstanceOfPatternMatchTest.java +++ b/src/test/java/org/openrewrite/staticanalysis/InstanceOfPatternMatchTest.java @@ -30,7 +30,8 @@ class InstanceOfPatternMatchTest implements RewriteTest { @Override public void defaults(RecipeSpec spec) { - spec.recipe(new InstanceOfPatternMatch()); + spec.recipe(new InstanceOfPatternMatch()) + .allSources(sourceSpec -> version(sourceSpec, 17)); } @SuppressWarnings({"ImplicitArrayToString", "PatternVariableCanBeUsed", "UnnecessaryLocalVariable"}) @@ -40,34 +41,32 @@ class If { @Test void ifConditionWithoutPattern() { rewriteRun( - version( - //language=java - java( + //language=java + java( + """ + public class A { + void test(Object o) { + Object s = 1; + if (o instanceof String && ((String) (o)).length() > 0) { + if (((String) o).length() > 1) { + System.out.println(o); + } + } + } + } + """, + """ + public class A { + void test(Object o) { + Object s = 1; + if (o instanceof String string && string.length() > 0) { + if (string.length() > 1) { + System.out.println(o); + } + } + } + } """ - public class A { - void test(Object o) { - Object s = 1; - if (o instanceof String && ((String) (o)).length() > 0) { - if (((String) o).length() > 1) { - System.out.println(o); - } - } - } - } - """, - """ - public class A { - void test(Object o) { - Object s = 1; - if (o instanceof String string && string.length() > 0) { - if (string.length() > 1) { - System.out.println(o); - } - } - } - } - """ - ), 17 ) ); } @@ -75,32 +74,30 @@ void test(Object o) { @Test void multipleCasts() { rewriteRun( - version( - //language=java - java( - """ - public class A { - void test(Object o, Object o2) { - Object string = 1; - if (o instanceof String && o2 instanceof Integer) { - System.out.println((String) o); - System.out.println((Integer) o2); - } - } - } - """, + //language=java + java( + """ + public class A { + void test(Object o, Object o2) { + Object string = 1; + if (o instanceof String && o2 instanceof Integer) { + System.out.println((String) o); + System.out.println((Integer) o2); + } + } + } + """, + """ + public class A { + void test(Object o, Object o2) { + Object string = 1; + if (o instanceof String string1 && o2 instanceof Integer integer) { + System.out.println(string1); + System.out.println(integer); + } + } + } """ - public class A { - void test(Object o, Object o2) { - Object string = 1; - if (o instanceof String string1 && o2 instanceof Integer integer) { - System.out.println(string1); - System.out.println(integer); - } - } - } - """ - ), 17 ) ); } @@ -108,32 +105,30 @@ void test(Object o, Object o2) { @Test void longNames() { rewriteRun( - version( - //language=java - java( + //language=java + java( + """ + import java.util.ArrayList; + public class A { + void test(Object o) { + Object list = 1; + if (o instanceof ArrayList) { + System.out.println((ArrayList) o); + } + } + } + """, + """ + import java.util.ArrayList; + public class A { + void test(Object o) { + Object list = 1; + if (o instanceof ArrayList arrayList) { + System.out.println(arrayList); + } + } + } """ - import java.util.ArrayList; - public class A { - void test(Object o) { - Object list = 1; - if (o instanceof ArrayList) { - System.out.println((ArrayList) o); - } - } - } - """, - """ - import java.util.ArrayList; - public class A { - void test(Object o) { - Object list = 1; - if (o instanceof ArrayList arrayList) { - System.out.println(arrayList); - } - } - } - """ - ), 17 ) ); } @@ -141,28 +136,26 @@ void test(Object o) { @Test void primitiveArray() { rewriteRun( - version( - //language=java - java( - """ - public class A { - void test(Object o) { - if (o instanceof int[]) { - System.out.println((int[]) o); - } - } - } - """, + //language=java + java( + """ + public class A { + void test(Object o) { + if (o instanceof int[]) { + System.out.println((int[]) o); + } + } + } + """, + """ + public class A { + void test(Object o) { + if (o instanceof int[] ints) { + System.out.println(ints); + } + } + } """ - public class A { - void test(Object o) { - if (o instanceof int[] ints) { - System.out.println(ints); - } - } - } - """ - ), 17 ) ); } @@ -170,31 +163,29 @@ void test(Object o) { @Test void matchingVariableInBody() { rewriteRun( - version( - //language=java - java( + //language=java + java( + """ + public class A { + void test(Object o) { + if (o instanceof String) { + String str = (String) o; + String str2 = (String) o; + System.out.println(str + str2); + } + } + } + """, + """ + public class A { + void test(Object o) { + if (o instanceof String str) { + String str2 = str; + System.out.println(str + str2); + } + } + } """ - public class A { - void test(Object o) { - if (o instanceof String) { - String str = (String) o; - String str2 = (String) o; - System.out.println(str + str2); - } - } - } - """, - """ - public class A { - void test(Object o) { - if (o instanceof String str) { - String str2 = str; - System.out.println(str + str2); - } - } - } - """ - ), 17 ) ); } @@ -202,32 +193,30 @@ void test(Object o) { @Test void conflictingVariableInBody() { rewriteRun( - version( - //language=java - java( - """ - public class A { - void test(Object o) { - if (o instanceof String) { - String string = 'x'; - System.out.println((String) o); - // String string1 = "y"; - } - } - } - """, + //language=java + java( + """ + public class A { + void test(Object o) { + if (o instanceof String) { + String string = 'x'; + System.out.println((String) o); + // String string1 = "y"; + } + } + } + """, + """ + public class A { + void test(Object o) { + if (o instanceof String string1) { + String string = 'x'; + System.out.println(string1); + // String string1 = "y"; + } + } + } """ - public class A { - void test(Object o) { - if (o instanceof String string1) { - String string = 'x'; - System.out.println(string1); - // String string1 = "y"; - } - } - } - """ - ), 17 ) ); } @@ -237,34 +226,32 @@ void test(Object o) { @Test void nestedPotentiallyConflictingIfs() { rewriteRun( - version( - //language=java - java( + //language=java + java( + """ + public class A { + void test(Object o) { + if (o instanceof String) { + if (o instanceof String) { + System.out.println((String) o); + } + System.out.println((String) o); + } + } + } + """, + """ + public class A { + void test(Object o) { + if (o instanceof String string) { + if (o instanceof String string1) { + System.out.println(string1); + } + System.out.println(string); + } + } + } """ - public class A { - void test(Object o) { - if (o instanceof String) { - if (o instanceof String) { - System.out.println((String) o); - } - System.out.println((String) o); - } - } - } - """, - """ - public class A { - void test(Object o) { - if (o instanceof String string) { - if (o instanceof String string1) { - System.out.println(string1); - } - System.out.println(string); - } - } - } - """ - ), 17 ) ); } @@ -272,25 +259,23 @@ void test(Object o) { @Test void expressionWithSideEffects() { rewriteRun( - version( - //language=java - java( + //language=java + java( + """ + public class A { + void test(Object o) { + Object s = 1; + if (convert(o) instanceof String && ((String) convert(o)).length() > 0) { + if (((String) convert(o)).length() > 1) { + System.out.println(o); + } + } + } + Object convert(Object o) { + return o; + } + } """ - public class A { - void test(Object o) { - Object s = 1; - if (convert(o) instanceof String && ((String) convert(o)).length() > 0) { - if (((String) convert(o)).length() > 1) { - System.out.println(o); - } - } - } - Object convert(Object o) { - return o; - } - } - """ - ), 17 ) ); } @@ -298,80 +283,75 @@ Object convert(Object o) { @Test void noTypeCast() { rewriteRun( - version( - //language=java - java( - """ - public class A { - void test(Object o) { - if (o instanceof String) { - System.out.println(o); - } - } - } - """ - ), 17) + //language=java + java( + """ + public class A { + void test(Object o) { + if (o instanceof String) { + System.out.println(o); + } + } + } + """ + ) ); } @Test void typeCastInElse() { rewriteRun( - version( - //language=java - java( - """ - public class A { - void test(Object o) { - if (o instanceof String) { - System.out.println(o); - } else { - System.out.println((String) o); - } - } - } - """ - ), 17) + //language=java + java( + """ + public class A { + void test(Object o) { + if (o instanceof String) { + System.out.println(o); + } else { + System.out.println((String) o); + } + } + } + """ + ) ); } @Test void ifConditionWithPattern() { rewriteRun( - version( - //language=java - java( - """ - public class A { - void test(Object o) { - if (o instanceof String s && s.length() > 0) { - System.out.println(s); - } - } - } - """ - ), 17) + //language=java + java( + """ + public class A { + void test(Object o) { + if (o instanceof String s && s.length() > 0) { + System.out.println(s); + } + } + } + """ + ) ); } @Test void orOperationInIfCondition() { rewriteRun( - version( - //language=java - java( + //language=java + java( + """ + public class A { + void test(Object o) { + if (o instanceof String || ((String) o).length() > 0) { + if (((String) o).length() > 1) { + System.out.println(o); + } + } + } + } """ - public class A { - void test(Object o) { - if (o instanceof String || ((String) o).length() > 0) { - if (((String) o).length() > 1) { - System.out.println(o); - } - } - } - } - """ - ), 17 ) ); } @@ -379,21 +359,19 @@ void test(Object o) { @Test void negatedInstanceOfMatchedInElse() { rewriteRun( - version( - //language=java - java( + //language=java + java( + """ + public class A { + void test(Object o) { + if (!(o instanceof String)) { + System.out.println(((String) o).length()); + } else { + System.out.println(((String) o).length()); + } + } + } """ - public class A { - void test(Object o) { - if (!(o instanceof String)) { - System.out.println(((String) o).length()); - } else { - System.out.println(((String) o).length()); - } - } - } - """ - ), 17 ) ); } @@ -406,24 +384,22 @@ class Ternary { @Test void typeCastInTrue() { rewriteRun( - version( - //language=java - java( - """ - public class A { - String test(Object o) { - return o instanceof String ? ((String) o).substring(1) : o.toString(); - } - } - """, + //language=java + java( + """ + public class A { + String test(Object o) { + return o instanceof String ? ((String) o).substring(1) : o.toString(); + } + } + """, + """ + public class A { + String test(Object o) { + return o instanceof String s ? s.substring(1) : o.toString(); + } + } """ - public class A { - String test(Object o) { - return o instanceof String s ? s.substring(1) : o.toString(); - } - } - """ - ), 17 ) ); } @@ -431,26 +407,24 @@ String test(Object o) { @Test void multipleVariablesOnlyOneUsed() { rewriteRun( - version( - //language=java - java( - """ - public class A { - String test(Object o1, Object o2) { - return o1 instanceof String && o2 instanceof Number - ? ((String) o1).substring(1) : o1.toString(); - } - } - """, + //language=java + java( + """ + public class A { + String test(Object o1, Object o2) { + return o1 instanceof String && o2 instanceof Number + ? ((String) o1).substring(1) : o1.toString(); + } + } + """, + """ + public class A { + String test(Object o1, Object o2) { + return o1 instanceof String s && o2 instanceof Number + ? s.substring(1) : o1.toString(); + } + } """ - public class A { - String test(Object o1, Object o2) { - return o1 instanceof String s && o2 instanceof Number - ? s.substring(1) : o1.toString(); - } - } - """ - ), 17 ) ); } @@ -458,34 +432,32 @@ String test(Object o1, Object o2) { @Test void initBlocks() { rewriteRun( - version( - //language=java - java( + //language=java + java( + """ + public class A { + static { + Object o = null; + String s = o instanceof String ? ((String) o).substring(1) : String.valueOf(o); + } + { + Object o = null; + String s = o instanceof String ? ((String) o).substring(1) : String.valueOf(o); + } + } + """, + """ + public class A { + static { + Object o = null; + String s = o instanceof String s1 ? s1.substring(1) : String.valueOf(o); + } + { + Object o = null; + String s = o instanceof String s1 ? s1.substring(1) : String.valueOf(o); + } + } """ - public class A { - static { - Object o = null; - String s = o instanceof String ? ((String) o).substring(1) : String.valueOf(o); - } - { - Object o = null; - String s = o instanceof String ? ((String) o).substring(1) : String.valueOf(o); - } - } - """, - """ - public class A { - static { - Object o = null; - String s = o instanceof String s1 ? s1.substring(1) : String.valueOf(o); - } - { - Object o = null; - String s = o instanceof String s1 ? s1.substring(1) : String.valueOf(o); - } - } - """ - ), 17 ) ); } @@ -493,17 +465,15 @@ public class A { @Test void typeCastInFalse() { rewriteRun( - version( - //language=java - java( + //language=java + java( + """ + public class A { + String test(Object o) { + return o instanceof String ? o.toString() : ((String) o).substring(1); + } + } """ - public class A { - String test(Object o) { - return o instanceof String ? o.toString() : ((String) o).substring(1); - } - } - """ - ), 17 ) ); } @@ -515,24 +485,22 @@ class Binary { @Test void onlyReplacementsBeforeOrOperator() { rewriteRun( - version( - //language=java - java( - """ - public class A { - boolean test(Object o) { - return o instanceof String && ((String) o).length() > 1 || ((String) o).length() > 2; - } - } - """, + //language=java + java( + """ + public class A { + boolean test(Object o) { + return o instanceof String && ((String) o).length() > 1 || ((String) o).length() > 2; + } + } + """, + """ + public class A { + boolean test(Object o) { + return o instanceof String s && s.length() > 1 || ((String) o).length() > 2; + } + } """ - public class A { - boolean test(Object o) { - return o instanceof String s && s.length() > 1 || ((String) o).length() > 2; - } - } - """ - ), 17 ) ); } @@ -540,20 +508,18 @@ boolean test(Object o) { @Test void methodCallBreaksFlowScope() { rewriteRun( - version( - //language=java - java( + //language=java + java( + """ + public class A { + boolean m(Object o) { + return test(o instanceof String) && ((String) o).length() > 1; + } + boolean test(boolean b) { + return b; + } + } """ - public class A { - boolean m(Object o) { - return test(o instanceof String) && ((String) o).length() > 1; - } - boolean test(boolean b) { - return b; - } - } - """ - ), 17 ) ); } @@ -565,24 +531,22 @@ class Arrays { @Test void string() { rewriteRun( - version( - //language=java - java( + //language=java + java( + """ + public class A { + boolean test(Object o) { + return o instanceof String[] && ((java.lang.String[]) o).length > 1 || ((String[]) o).length > 2; + } + } + """, + """ + public class A { + boolean test(Object o) { + return o instanceof String[] ss && ss.length > 1 || ((String[]) o).length > 2; + } + } """ - public class A { - boolean test(Object o) { - return o instanceof String[] && ((java.lang.String[]) o).length > 1 || ((String[]) o).length > 2; - } - } - """, - """ - public class A { - boolean test(Object o) { - return o instanceof String[] ss && ss.length > 1 || ((String[]) o).length > 2; - } - } - """ - ), 17 ) ); } @@ -590,24 +554,22 @@ boolean test(Object o) { @Test void primitive() { rewriteRun( - version( - //language=java - java( - """ - public class A { - boolean test(Object o) { - return o instanceof int[] && ((int[]) o).length > 1 || ((int[]) o).length > 2; - } - } - """, + //language=java + java( + """ + public class A { + boolean test(Object o) { + return o instanceof int[] && ((int[]) o).length > 1 || ((int[]) o).length > 2; + } + } + """, + """ + public class A { + boolean test(Object o) { + return o instanceof int[] is && is.length > 1 || ((int[]) o).length > 2; + } + } """ - public class A { - boolean test(Object o) { - return o instanceof int[] is && is.length > 1 || ((int[]) o).length > 2; - } - } - """ - ), 17 ) ); } @@ -615,24 +577,22 @@ boolean test(Object o) { @Test void multiDimensional() { rewriteRun( - version( - //language=java - java( + //language=java + java( + """ + public class A { + boolean test(Object o) { + return o instanceof int[][] && ((int[][]) o).length > 1 || ((int[][]) o).length > 2; + } + } + """, + """ + public class A { + boolean test(Object o) { + return o instanceof int[][] is && is.length > 1 || ((int[][]) o).length > 2; + } + } """ - public class A { - boolean test(Object o) { - return o instanceof int[][] && ((int[][]) o).length > 1 || ((int[][]) o).length > 2; - } - } - """, - """ - public class A { - boolean test(Object o) { - return o instanceof int[][] is && is.length > 1 || ((int[][]) o).length > 2; - } - } - """ - ), 17 ) ); } @@ -640,17 +600,15 @@ boolean test(Object o) { @Test void dimensionalMismatch() { rewriteRun( - version( - //language=java - java( + //language=java + java( + """ + public class A { + boolean test(Object o) { + return o instanceof int[][] && ((int[]) o).length > 1; + } + } """ - public class A { - boolean test(Object o) { - return o instanceof int[][] && ((int[]) o).length > 1; - } - } - """ - ), 17 ) ); } @@ -662,102 +620,97 @@ class Generics { @Test void wildcardInstanceOf() { rewriteRun( - version( - //language=java - java( - """ - import java.util.List; - public class A { - Object test(Object o) { - if (o instanceof List) { - return ((List) o).get(0); - } - return o.toString(); - } - } - """, + //language=java + java( + """ + import java.util.List; + public class A { + Object test(Object o) { + if (o instanceof List) { + return ((List) o).get(0); + } + return o.toString(); + } + } + """, + """ + import java.util.List; + public class A { + Object test(Object o) { + if (o instanceof List list) { + return list.get(0); + } + return o.toString(); + } + } """ - import java.util.List; - public class A { - Object test(Object o) { - if (o instanceof List list) { - return list.get(0); - } - return o.toString(); - } - } - """ - ), 17) + ) ); } @Test void rawInstanceOfAndWildcardParameterizedCast() { rewriteRun( - version( - //language=java - java( - """ - import java.util.List; - public class A { - Object test(Object o) { - return o instanceof List ? ((List) o).get(0) : o.toString(); - } - } - """, + //language=java + java( + """ + import java.util.List; + public class A { + Object test(Object o) { + return o instanceof List ? ((List) o).get(0) : o.toString(); + } + } + """, + """ + import java.util.List; + public class A { + Object test(Object o) { + return o instanceof List l ? l.get(0) : o.toString(); + } + } """ - import java.util.List; - public class A { - Object test(Object o) { - return o instanceof List l ? l.get(0) : o.toString(); - } - } - """ - ), 17) + ) ); } @Test void rawInstanceOfAndObjectParameterizedCast() { rewriteRun( - version( - //language=java - java( - """ - import java.util.List; - public class A { - Object test(Object o) { - return o instanceof List ? ((List) o).get(0) : o.toString(); - } - } - """, + //language=java + java( + """ + import java.util.List; + public class A { + Object test(Object o) { + return o instanceof List ? ((List) o).get(0) : o.toString(); + } + } + """, + """ + import java.util.List; + public class A { + Object test(Object o) { + return o instanceof List l ? l.get(0) : o.toString(); + } + } """ - import java.util.List; - public class A { - Object test(Object o) { - return o instanceof List l ? l.get(0) : o.toString(); - } - } - """ - ), 17) + ) ); } @Test void rawInstanceOfAndParameterizedCast() { rewriteRun( - version( - //language=java - java( + //language=java + java( + """ + import java.util.List; + public class A { + String test(Object o) { + return o instanceof List ? ((List) o).get(0) : o.toString(); + } + } """ - import java.util.List; - public class A { - String test(Object o) { - return o instanceof List ? ((List) o).get(0) : o.toString(); - } - } - """ - ), 17 ) ); } @@ -765,21 +718,20 @@ String test(Object o) { @Test void unboundGenericTypeVariable() { rewriteRun( - version( - //language=java - java( + //language=java + java( + """ + import java.util.List; + public class A { + void test(Object t) { + if (t instanceof List) { + List l = (List) t; + System.out.println(l.size()); + } + } + } """ - import java.util.List; - public class A { - void test(Object t) { - if (t instanceof List) { - List l = (List) t; - System.out.println(l.size()); - } - } - } - """ - ), 17) + ) ); } } @@ -789,56 +741,53 @@ class Various { @Test void unaryWithoutSideEffects() { rewriteRun( - version( - //language=java - java( + //language=java + java( + """ + public class A { + String test(Object o) { + return ((Object) ("1" + ~1)) instanceof String ? ((String) ((Object) ("1" + ~1))).substring(1) : o.toString(); + } + } + """, + """ + public class A { + String test(Object o) { + return ((Object) ("1" + ~1)) instanceof String s ? s.substring(1) : o.toString(); + } + } """ - public class A { - String test(Object o) { - return ((Object) ("1" + ~1)) instanceof String ? ((String) ((Object) ("1" + ~1))).substring(1) : o.toString(); - } - } - """, - """ - public class A { - String test(Object o) { - return ((Object) ("1" + ~1)) instanceof String s ? s.substring(1) : o.toString(); - } - } - """ - ), 17 ) ); } + @Test void nestedClasses() { rewriteRun( - version( - //language=java - java( - """ - public class A { - public static class Else {} - String test(Object o) { - if (o instanceof Else) { - return ((Else) o).toString(); - } - return o.toString(); - } - } - """, + //language=java + java( + """ + public class A { + public static class Else {} + String test(Object o) { + if (o instanceof Else) { + return ((Else) o).toString(); + } + return o.toString(); + } + } + """, + """ + public class A { + public static class Else {} + String test(Object o) { + if (o instanceof Else else1) { + return else1.toString(); + } + return o.toString(); + } + } """ - public class A { - public static class Else {} - String test(Object o) { - if (o instanceof Else else1) { - return else1.toString(); - } - return o.toString(); - } - } - """ - ), 17 ) ); } From 012666b3302cb64441b909da7c6a245d1d0ae42b Mon Sep 17 00:00:00 2001 From: Jorge Otero Date: Tue, 10 Oct 2023 22:34:58 +0200 Subject: [PATCH 38/92] Fix `default` modifier type position to comply with Java Language Specification (#188) * fix: put default modifier at right position on modifiers sorting * test: add specific unit test for default position issue --- .../staticanalysis/ModifierOrder.java | 18 ++++++++- .../staticanalysis/ModifierOrderTest.java | 40 +++++++++++++++++++ 2 files changed, 57 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/openrewrite/staticanalysis/ModifierOrder.java b/src/main/java/org/openrewrite/staticanalysis/ModifierOrder.java index 61fce5016..8f37af3a0 100644 --- a/src/main/java/org/openrewrite/staticanalysis/ModifierOrder.java +++ b/src/main/java/org/openrewrite/staticanalysis/ModifierOrder.java @@ -21,12 +21,14 @@ import org.openrewrite.internal.ListUtils; import org.openrewrite.java.JavaIsoVisitor; import org.openrewrite.java.tree.J; +import org.openrewrite.java.tree.J.Modifier.Type; import java.time.Duration; import java.util.Collections; import java.util.Comparator; import java.util.List; import java.util.Set; +import java.util.function.ToIntFunction; import static java.util.stream.Collectors.toList; @@ -84,10 +86,24 @@ public static List sortModifiers(List modifiers) { List sortedTypes = modifiers.stream() .map(J.Modifier::getType) - .sorted(Comparator.comparingInt(J.Modifier.Type::ordinal)) + .sorted(Comparator.comparingInt(createModifierTypeToPositionFunction())) .collect(toList()); return ListUtils.map(modifiers, (i, mod) -> mod.getType() == sortedTypes.get(i) ? mod : mod.withType(sortedTypes.get(i))); } + + private static ToIntFunction createModifierTypeToPositionFunction() { + final int DEFAULT_MOD_POSITION = 4; + return type -> { + if (type == Type.Default) { + return DEFAULT_MOD_POSITION; + } + int ordinal = type.ordinal(); + if (ordinal <= DEFAULT_MOD_POSITION) { + return ordinal - 1; + } + return ordinal; + }; + } } diff --git a/src/test/java/org/openrewrite/staticanalysis/ModifierOrderTest.java b/src/test/java/org/openrewrite/staticanalysis/ModifierOrderTest.java index d1f79497a..edd76a057 100644 --- a/src/test/java/org/openrewrite/staticanalysis/ModifierOrderTest.java +++ b/src/test/java/org/openrewrite/staticanalysis/ModifierOrderTest.java @@ -73,6 +73,46 @@ public static void main(String[] args) { ); } + @Test + @Issue("https://github.com/openrewrite/rewrite-static-analysis/issues/187") + void putDefaultModifierAtJLSRightPosition() { + // default modifier must be placed between abstract and static modifiers + rewriteRun( + //language=java + java( + """ + interface Foo { + public static default void bar() { + int i = 5; + } + + default private static void baz() { + int i = 5; + } + + static default protected void qux() { + int i = 5; + } + } + """ + , """ + interface Foo { + public default static void bar() { + int i = 5; + } + + private default static void baz() { + int i = 5; + } + + protected default static void qux() { + int i = 5; + } + } + """) + ); + } + @Nested class KotlinTest { @Test From 1950fb4dcc9540f7f29db4818682307216253d56 Mon Sep 17 00:00:00 2001 From: Jonathan Schneider Date: Tue, 26 Sep 2023 13:37:22 -0500 Subject: [PATCH 39/92] Polish --- .../UnnecessaryParenthesesTest.java | 31 ++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/src/test/java/org/openrewrite/staticanalysis/UnnecessaryParenthesesTest.java b/src/test/java/org/openrewrite/staticanalysis/UnnecessaryParenthesesTest.java index c2970cf62..d46c1e32b 100644 --- a/src/test/java/org/openrewrite/staticanalysis/UnnecessaryParenthesesTest.java +++ b/src/test/java/org/openrewrite/staticanalysis/UnnecessaryParenthesesTest.java @@ -49,7 +49,7 @@ import static java.util.Collections.singletonList; import static org.openrewrite.java.Assertions.java; -@SuppressWarnings({"ConstantConditions"}) +@SuppressWarnings({"ConstantConditions", "UnusedAssignment", "IdempotentLoopBody", "ParameterCanBeLocal", "UnnecessaryLocalVariable"}) class UnnecessaryParenthesesTest implements RewriteTest { @Override public void defaults(RecipeSpec spec) { @@ -70,6 +70,7 @@ private static Consumer unnecessaryParentheses(UnaryOperator style.withExpr(true)), java( @@ -176,6 +179,7 @@ void method(int x, int y, boolean a) { @Test void unwrapIdent() { + //language=java rewriteRun( unnecessaryParentheses(style -> style.withIdent(true)), java( @@ -201,6 +205,7 @@ class Test { @Test void unwrapNum() { + //language=java rewriteRun( unnecessaryParentheses(style -> style.withNumDouble(true).withNumFloat(true) .withNumInt(true).withNumLong(true)), @@ -235,8 +240,10 @@ void doNothing() { ); } + @SuppressWarnings({"PointlessBooleanExpression", "MismatchedStringCase"}) @Test void unwrapLiteral() { + //language=java rewriteRun( unnecessaryParentheses(style -> style.withLiteralFalse(true).withLiteralTrue(true) .withLiteralNull(true).withStringLiteral(true)), @@ -287,8 +294,10 @@ void doNothing() { ); } + @SuppressWarnings("SillyAssignment") @Test void unwrapAssignment() { + //language=java rewriteRun( unnecessaryParentheses(style -> style.withAssign(true)), java( @@ -338,6 +347,7 @@ void doNothing() { @Test void unwrapBandAssign() { + //language=java rewriteRun( unnecessaryParentheses(style -> style.withBitAndAssign(true)), java( @@ -369,6 +379,7 @@ void bitwiseAnd() { @Test void unwrapBorAssign() { + //language=java rewriteRun( unnecessaryParentheses(style -> style.withBitOrAssign(true)), java( @@ -400,6 +411,7 @@ void bitwiseOr() { @Test void unwrapBsrAssign() { + //language=java rewriteRun( unnecessaryParentheses(style -> style.withBitShiftRightAssign(true)), java( @@ -429,6 +441,7 @@ void unsignedRightShiftAssignment() { @Test void unwrapBxorAssign() { + //language=java rewriteRun( unnecessaryParentheses(style -> style.withBitXorAssign(true)), java( @@ -460,6 +473,7 @@ void bitwiseExclusiveOr() { @Test void unwrapDivAssign() { + //language=java rewriteRun( unnecessaryParentheses(style -> style.withDivAssign(true)), java( @@ -491,6 +505,7 @@ void divisionAssignmentOperator() { @Test void unwrapMinusAssign() { + //language=java rewriteRun( unnecessaryParentheses(style -> style.withMinusAssign(true)), java( @@ -523,6 +538,7 @@ void minusAssignment() { @Issue("https://github.com/openrewrite/rewrite/issues/1486") @Test void unwrapMinusReturnExpression() { + //language=java rewriteRun( unnecessaryParentheses(style -> style.withExpr(true)), java( @@ -577,6 +593,7 @@ void remainderAssignment() { @Test void unwrapPlusAssign() { + //language=java rewriteRun( unnecessaryParentheses(style -> style.withPlusAssign(true)), java( @@ -608,6 +625,7 @@ void plusAssignment() { @Test void unwrapSlAssign() { + //language=java rewriteRun( unnecessaryParentheses(style -> style.withShiftLeftAssign(true)), java( @@ -639,6 +657,7 @@ void leftShiftAssignment() { @Test void unwrapSrAssign() { + //language=java rewriteRun( unnecessaryParentheses(style -> style.withShiftRightAssign(true)), java( @@ -670,6 +689,7 @@ void signedRightShiftAssignment() { @Test void unwrapStarAssign() { + //language=java rewriteRun( unnecessaryParentheses(style -> style.withStarAssign(true)), java( @@ -701,6 +721,7 @@ void multiplicationAssignmentOperator() { @Test void unwrapLambda() { + //language=java rewriteRun( unnecessaryParentheses(style -> style.withLambda(true)), java( @@ -735,6 +756,7 @@ void doNothing() { @Issue("https://github.com/openrewrite/rewrite/issues/798") @Test void unwrapDoubleParens() { + //language=java rewriteRun( java( """ @@ -783,6 +805,7 @@ void test(String s) { @Test void doNotUnwrapIfNoParens() { + //language=java rewriteRun( java( """ @@ -800,6 +823,7 @@ void test(String s) { @Test void doNotUnwrapNegatedIfParens() { + //language=java rewriteRun( java( """ @@ -815,8 +839,10 @@ void test(String s) { ); } + @SuppressWarnings("PointlessBooleanExpression") @Test void doNotUnwrapIfParens() { + //language=java rewriteRun( java( """ @@ -834,6 +860,7 @@ void test(String s) { @Test void unwrapWhileParens() { + //language=java rewriteRun( java( """ @@ -860,6 +887,7 @@ void test(String s) { @Test void unwrapDoWhileParens() { + //language=java rewriteRun( java( """ @@ -886,6 +914,7 @@ void test(String s) { @Test void unwrapForControlParens() { + //language=java rewriteRun( java( """ From b25d63071ae4c66f3cdca653858b2fd8069638ee Mon Sep 17 00:00:00 2001 From: Jonathan Schneider Date: Tue, 17 Oct 2023 02:16:13 -0600 Subject: [PATCH 40/92] UseLambdaForFunctionalInterface casts lambda when necessary (fixes #194) --- build.gradle.kts | 2 + .../UseLambdaForFunctionalInterface.java | 46 +++++++++++++++++-- .../UseLambdaForFunctionalInterfaceTest.java | 44 ++++++++++++++++++ 3 files changed, 87 insertions(+), 5 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index f483d4a40..0e2ee2e1a 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -24,6 +24,8 @@ dependencies { testImplementation("org.junit-pioneer:junit-pioneer:2.0.1") testImplementation("junit:junit:4.13.2") + testImplementation("com.google.code.gson:gson:latest.release") + testRuntimeOnly("org.openrewrite:rewrite-java-17") testRuntimeOnly("com.google.code.findbugs:jsr305:latest.release") } diff --git a/src/main/java/org/openrewrite/staticanalysis/UseLambdaForFunctionalInterface.java b/src/main/java/org/openrewrite/staticanalysis/UseLambdaForFunctionalInterface.java index 97a56be6d..78611c0c4 100644 --- a/src/main/java/org/openrewrite/staticanalysis/UseLambdaForFunctionalInterface.java +++ b/src/main/java/org/openrewrite/staticanalysis/UseLambdaForFunctionalInterface.java @@ -23,6 +23,7 @@ import org.openrewrite.java.RemoveUnusedImports; import org.openrewrite.java.cleanup.UnnecessaryParenthesesVisitor; import org.openrewrite.java.tree.*; +import org.openrewrite.marker.Markers; import java.time.Duration; import java.util.ArrayList; @@ -91,7 +92,7 @@ public J visitNewClass(J.NewClass newClass, ExecutionContext ctx) { return n; } - //The interface may be parameterized and that is needed to maintain type attribution: + // The interface may be parameterized and that is needed to maintain type attribution: JavaType.FullyQualified typedInterface = null; JavaType.FullyQualified anonymousClass = TypeUtils.asFullyQualified(n.getType()); if (anonymousClass != null) { @@ -123,7 +124,7 @@ public J visitNewClass(J.NewClass newClass, ExecutionContext ctx) { .build() .apply(getCursor(), n.getCoordinates().replace()); lambda = lambda.withType(typedInterface); - lambda = (J.Lambda) new UnnecessaryParenthesesVisitor() + lambda = (J.Lambda) new UnnecessaryParenthesesVisitor<>() .visitNonNull(lambda, ctx, getCursor().getParentOrThrow()); J.Block lambdaBody = methodDeclaration.getBody(); @@ -132,15 +133,50 @@ public J visitNewClass(J.NewClass newClass, ExecutionContext ctx) { lambda = lambda.withBody(lambdaBody.withPrefix(Space.format(" "))); lambda = (J.Lambda) new LambdaBlockToExpression().getVisitor().visitNonNull(lambda, ctx, getCursor().getParentOrThrow()); - doAfterVisit(new RemoveUnusedImports().getVisitor()); - return autoFormat(lambda, ctx); + return autoFormat(maybeAddCast(lambda, newClass), ctx); } } return n; } + private J maybeAddCast(J.Lambda lambda, J.NewClass original) { + J parent = getCursor().getParentTreeCursor().getValue(); + + JavaType.FullyQualified lambdaType = TypeUtils.asFullyQualified(lambda.getType()); + if (lambdaType == null) { + return lambda; + } + String lambdaFqn = lambdaType.getFullyQualifiedName(); + + if (parent instanceof J.MethodInvocation) { + J.MethodInvocation method = (J.MethodInvocation) parent; + List arguments = method.getArguments(); + for (int i = 0; i < arguments.size(); i++) { + Expression argument = arguments.get(i); + if (argument == original && method.getMethodType() != null && + !TypeUtils.isOfClassType(method.getMethodType().getParameterTypes().get(i), lambdaFqn) && + original.getClazz() != null) { + return new J.TypeCast( + Tree.randomId(), + lambda.getPrefix(), + Markers.EMPTY, + new J.ControlParentheses<>( + Tree.randomId(), + Space.EMPTY, + Markers.EMPTY, + JRightPadded.build(original.getClazz()) + ), + lambda.withPrefix(Space.format(" ")) + ); + } + } + } + + return lambda; + } + private String valueOfType(@Nullable JavaType type) { JavaType.Primitive primitive = TypeUtils.asPrimitive(type); if (primitive != null) { @@ -276,7 +312,7 @@ private static boolean shadowsLocalVariable(Cursor cursor) { nameScopeBlocks.add((J.Block) p); } return p instanceof J.MethodDeclaration || p instanceof J.ClassDeclaration; - } ).getValue(); + }).getValue(); if (nameScope instanceof J.MethodDeclaration) { J.MethodDeclaration m = (J.MethodDeclaration) nameScope; localVariables.addAll(parameterNames(m)); diff --git a/src/test/java/org/openrewrite/staticanalysis/UseLambdaForFunctionalInterfaceTest.java b/src/test/java/org/openrewrite/staticanalysis/UseLambdaForFunctionalInterfaceTest.java index 3f5f66750..61b2e1ae2 100644 --- a/src/test/java/org/openrewrite/staticanalysis/UseLambdaForFunctionalInterfaceTest.java +++ b/src/test/java/org/openrewrite/staticanalysis/UseLambdaForFunctionalInterfaceTest.java @@ -20,6 +20,7 @@ import org.junit.jupiter.api.Test; import org.openrewrite.DocumentExample; import org.openrewrite.Issue; +import org.openrewrite.java.JavaParser; import org.openrewrite.test.RecipeSpec; import org.openrewrite.test.RewriteTest; @@ -32,6 +33,49 @@ public void defaults(RecipeSpec spec) { spec.recipe(new UseLambdaForFunctionalInterface()); } + @SuppressWarnings("ConstantConditions") + @Issue("https://github.com/openrewrite/rewrite-static-analysis/issues/194") + @Test + void gson() { + rewriteRun( + spec -> spec.recipe(new UseLambdaForFunctionalInterface()) + .parser(JavaParser.fromJavaVersion().classpath("gson")), + //language=java + java( + """ + import com.google.gson.GsonBuilder; + import com.google.gson.JsonPrimitive; + import com.google.gson.JsonSerializer; + import java.time.LocalDateTime; + + class Test { + void test() { + new GsonBuilder().registerTypeAdapter(LocalDateTime.class, new JsonSerializer() { + @Override + public JsonElement serialize(LocalDateTime object, Type type, JsonSerializationContext context) { + return new JsonPrimitive(object.format(null)); + } + }); + } + } + """, + """ + import com.google.gson.GsonBuilder; + import com.google.gson.JsonPrimitive; + import com.google.gson.JsonSerializer; + import java.time.LocalDateTime; + + class Test { + void test() { + new GsonBuilder().registerTypeAdapter(LocalDateTime.class, (JsonSerializer) (object, type, context) -> new JsonPrimitive(object.format(null))); + } + } + """ + ) + ); + } + + @SuppressWarnings({"Convert2Lambda", "TrivialFunctionalExpressionUsage"}) @Test void usedAsStatementWithNonInferrableType() { From 517b1d0efc1e8c08f62b8a995136f457f1011995 Mon Sep 17 00:00:00 2001 From: Jonathan Schneider Date: Tue, 17 Oct 2023 03:20:18 -0600 Subject: [PATCH 41/92] UseLambdaForFunctionalInterface handles ambiguous method definitions (fixes #10) --- .../UseLambdaForFunctionalInterface.java | 93 +++++++++++++++---- .../UseLambdaForFunctionalInterfaceTest.java | 51 +++++++++- 2 files changed, 121 insertions(+), 23 deletions(-) diff --git a/src/main/java/org/openrewrite/staticanalysis/UseLambdaForFunctionalInterface.java b/src/main/java/org/openrewrite/staticanalysis/UseLambdaForFunctionalInterface.java index 78611c0c4..c97e0bac2 100644 --- a/src/main/java/org/openrewrite/staticanalysis/UseLambdaForFunctionalInterface.java +++ b/src/main/java/org/openrewrite/staticanalysis/UseLambdaForFunctionalInterface.java @@ -69,18 +69,9 @@ public J visitNewClass(J.NewClass newClass, ExecutionContext ctx) { n.getBody().getStatements().size() == 1 && n.getBody().getStatements().get(0) instanceof J.MethodDeclaration && n.getClazz() != null) { - JavaType.@Nullable FullyQualified type = TypeUtils.asFullyQualified(n.getClazz().getType()); + JavaType.FullyQualified type = TypeUtils.asFullyQualified(n.getClazz().getType()); if (type != null && type.getKind().equals(JavaType.Class.Kind.Interface)) { - JavaType.Method sam = null; - for (JavaType.Method method : type.getMethods()) { - if (method.hasFlags(Flag.Default) || method.hasFlags(Flag.Static)) { - continue; - } - if (sam != null) { - return n; - } - sam = method; - } + JavaType.Method sam = getSamCompatible(type); if (sam == null) { return n; } @@ -141,22 +132,34 @@ public J visitNewClass(J.NewClass newClass, ExecutionContext ctx) { return n; } + @Nullable + private JavaType.Method getSamCompatible(JavaType type) { + JavaType.Method sam = null; + JavaType.FullyQualified fullyQualified = TypeUtils.asFullyQualified(type); + if (fullyQualified == null) { + return null; + } + for (JavaType.Method method : fullyQualified.getMethods()) { + if (method.hasFlags(Flag.Default) || method.hasFlags(Flag.Static)) { + continue; + } + if (sam != null) { + return null; + } + sam = method; + } + return sam; + } + private J maybeAddCast(J.Lambda lambda, J.NewClass original) { J parent = getCursor().getParentTreeCursor().getValue(); - JavaType.FullyQualified lambdaType = TypeUtils.asFullyQualified(lambda.getType()); - if (lambdaType == null) { - return lambda; - } - String lambdaFqn = lambdaType.getFullyQualifiedName(); - if (parent instanceof J.MethodInvocation) { J.MethodInvocation method = (J.MethodInvocation) parent; List arguments = method.getArguments(); for (int i = 0; i < arguments.size(); i++) { Expression argument = arguments.get(i); - if (argument == original && method.getMethodType() != null && - !TypeUtils.isOfClassType(method.getMethodType().getParameterTypes().get(i), lambdaFqn) && + if (argument == original && methodArgumentRequiresCast(lambda, method, i) && original.getClazz() != null) { return new J.TypeCast( Tree.randomId(), @@ -177,6 +180,54 @@ private J maybeAddCast(J.Lambda lambda, J.NewClass original) { return lambda; } + private boolean methodArgumentRequiresCast(J.Lambda lambda, J.MethodInvocation method, int argumentIndex) { + JavaType.FullyQualified lambdaType = TypeUtils.asFullyQualified(lambda.getType()); + if (lambdaType == null) { + return false; + } + String lambdaFqn = lambdaType.getFullyQualifiedName(); + + JavaType.Method methodType = method.getMethodType(); + if (methodType == null) { + return false; + } + if (!TypeUtils.isOfClassType(methodType.getParameterTypes().get(argumentIndex), lambdaFqn)) { + return true; + } + + // look for ambiguous methods + int count = 0; + for (JavaType.Method maybeAmbiguous : methodType.getDeclaringType().getMethods()) { + if (methodType.getName().equals(maybeAmbiguous.getName()) && + methodType.getParameterTypes().size() == maybeAmbiguous.getParameterTypes().size()) { + if (areMethodsAmbiguous( + getSamCompatible(methodType.getParameterTypes().get(argumentIndex)), + getSamCompatible(maybeAmbiguous.getParameterTypes().get(argumentIndex)))) { + count++; + } + } + } + return count >= 2; + } + + private boolean areMethodsAmbiguous(@Nullable JavaType.Method m1, @Nullable JavaType.Method m2) { + if (m1 == null || m2 == null) { + return false; + } + if (m1 == m2) { + return true; + } + for (int i = 0; i < m1.getParameterTypes().size(); i++) { + JavaType m1i = m1.getParameterTypes().get(i); + JavaType m2i = m2.getParameterTypes().get(i); + if (!TypeUtils.isAssignableTo(m1i, m2i) && + !TypeUtils.isAssignableTo(m2i, m1i)) { + return false; + } + } + return true; + } + private String valueOfType(@Nullable JavaType type) { JavaType.Primitive primitive = TypeUtils.asPrimitive(type); if (primitive != null) { @@ -203,7 +254,9 @@ private String valueOfType(@Nullable JavaType type) { return "null"; } - }; + } + + ; } private static boolean usesThis(Cursor cursor) { diff --git a/src/test/java/org/openrewrite/staticanalysis/UseLambdaForFunctionalInterfaceTest.java b/src/test/java/org/openrewrite/staticanalysis/UseLambdaForFunctionalInterfaceTest.java index 61b2e1ae2..63bc1a032 100644 --- a/src/test/java/org/openrewrite/staticanalysis/UseLambdaForFunctionalInterfaceTest.java +++ b/src/test/java/org/openrewrite/staticanalysis/UseLambdaForFunctionalInterfaceTest.java @@ -33,6 +33,52 @@ public void defaults(RecipeSpec spec) { spec.recipe(new UseLambdaForFunctionalInterface()); } + @SuppressWarnings("removal") + @Issue("https://github.com/openrewrite/rewrite-static-analysis/issues/10") + @Test + void castingAmbiguity() { + rewriteRun( + spec -> spec.recipe(new UseLambdaForFunctionalInterface()), + //language=java + java( + """ + import java.security.AccessController; + import java.security.PrivilegedAction; + import java.security.PrivilegedExceptionAction; + + class Test { + void test() { + AccessController.doPrivileged(new PrivilegedAction() { + @Override public Integer run() { + return 0; + } + }); + AccessController.doPrivileged(new PrivilegedExceptionAction() { + @Override public Integer run() throws Exception { + throw new Exception("i feel privileged to throw a checked exception"); + } + }); + } + } + """, + """ + import java.security.AccessController; + import java.security.PrivilegedAction; + import java.security.PrivilegedExceptionAction; + + class Test { + void test() { + AccessController.doPrivileged((PrivilegedAction) () -> 0); + AccessController.doPrivileged((PrivilegedExceptionAction) () -> { + throw new Exception("i feel privileged to throw a checked exception"); + }); + } + } + """ + ) + ); + } + @SuppressWarnings("ConstantConditions") @Issue("https://github.com/openrewrite/rewrite-static-analysis/issues/194") @Test @@ -47,7 +93,7 @@ void gson() { import com.google.gson.JsonPrimitive; import com.google.gson.JsonSerializer; import java.time.LocalDateTime; - + class Test { void test() { new GsonBuilder().registerTypeAdapter(LocalDateTime.class, new JsonSerializer() { @@ -64,7 +110,7 @@ public JsonElement serialize(LocalDateTime object, Type type, JsonSerializationC import com.google.gson.JsonPrimitive; import com.google.gson.JsonSerializer; import java.time.LocalDateTime; - + class Test { void test() { new GsonBuilder().registerTypeAdapter(LocalDateTime.class, (JsonSerializer) (object, type, context) -> new JsonPrimitive(object.format(null))); @@ -75,7 +121,6 @@ void test() { ); } - @SuppressWarnings({"Convert2Lambda", "TrivialFunctionalExpressionUsage"}) @Test void usedAsStatementWithNonInferrableType() { From a66d927fa0950ca127267efbc150aacd3843ccdf Mon Sep 17 00:00:00 2001 From: Jonathan Schneider Date: Tue, 17 Oct 2023 03:29:48 -0600 Subject: [PATCH 42/92] FallThrough handles switch in switch (fixes #173) --- .../staticanalysis/FallThroughVisitor.java | 1 + .../staticanalysis/FallThroughTest.java | 29 +++++++++++++++++++ 2 files changed, 30 insertions(+) diff --git a/src/main/java/org/openrewrite/staticanalysis/FallThroughVisitor.java b/src/main/java/org/openrewrite/staticanalysis/FallThroughVisitor.java index 60bf1010e..45e6e674a 100644 --- a/src/main/java/org/openrewrite/staticanalysis/FallThroughVisitor.java +++ b/src/main/java/org/openrewrite/staticanalysis/FallThroughVisitor.java @@ -146,6 +146,7 @@ private static boolean lastLineBreaksOrFallsThrough(List trees) s instanceof J.Break || s instanceof J.Continue || s instanceof J.Throw || + s instanceof J.Switch || // https://github.com/openrewrite/rewrite-static-analysis/issues/173 ((J) s).getComments().stream().anyMatch(HAS_RELIEF_PATTERN_COMMENT) ).orElse(false); } diff --git a/src/test/java/org/openrewrite/staticanalysis/FallThroughTest.java b/src/test/java/org/openrewrite/staticanalysis/FallThroughTest.java index d59d8227d..4dc0f3ffe 100644 --- a/src/test/java/org/openrewrite/staticanalysis/FallThroughTest.java +++ b/src/test/java/org/openrewrite/staticanalysis/FallThroughTest.java @@ -17,6 +17,7 @@ import org.junit.jupiter.api.Test; import org.openrewrite.DocumentExample; +import org.openrewrite.Issue; import org.openrewrite.Tree; import org.openrewrite.java.JavaParser; import org.openrewrite.java.style.FallThroughStyle; @@ -35,6 +36,34 @@ public void defaults(RecipeSpec spec) { spec.recipe(new FallThrough()); } + @Issue("https://github.com/openrewrite/rewrite-static-analysis/issues/173") + @Test + void switchInSwitch() { + //language=java + rewriteRun( + java( + """ + class Test { + void test() { + switch (day) { + case 1: + int month = 1; + switch (month) { + case 1: + return "January"; + default: + return "no valid month"; + } + default: + return "No valid day"; + } + } + } + """ + ) + ); + } + @Test void switchExpressions() { rewriteRun( From c69bba21c0e6195214ad964ebeeee00770661f78 Mon Sep 17 00:00:00 2001 From: Joan Viladrosa Date: Wed, 18 Oct 2023 22:12:10 +0200 Subject: [PATCH 43/92] rename when same id but in different scopes (#196) * rename when same id in different scopes * missing case * moved method to children class --- .../RenameLocalVariablesToCamelCase.java | 27 +++++++- .../RenamePrivateFieldsToCamelCase.java | 9 ++- .../staticanalysis/RenameToCamelCase.java | 22 ++++++- .../RenameLocalVariablesToCamelCaseTest.java | 64 +++++++++++++++++++ 4 files changed, 116 insertions(+), 6 deletions(-) diff --git a/src/main/java/org/openrewrite/staticanalysis/RenameLocalVariablesToCamelCase.java b/src/main/java/org/openrewrite/staticanalysis/RenameLocalVariablesToCamelCase.java index 0251b1819..66df86ea3 100644 --- a/src/main/java/org/openrewrite/staticanalysis/RenameLocalVariablesToCamelCase.java +++ b/src/main/java/org/openrewrite/staticanalysis/RenameLocalVariablesToCamelCase.java @@ -21,9 +21,11 @@ import org.openrewrite.TreeVisitor; import org.openrewrite.java.tree.J; import org.openrewrite.java.tree.JavaSourceFile; +import org.openrewrite.java.tree.JavaType; import java.time.Duration; import java.util.Collections; +import java.util.HashSet; import java.util.List; import java.util.Set; @@ -77,7 +79,8 @@ protected boolean shouldRename(Set hasNameKey, J.VariableDeclarations.Na if (toName.isEmpty() || !Character.isAlphabetic(toName.charAt(0))) { return false; } - return !hasNameKey.contains(toName); + Set keys = computeAllKeys(toName, variable); + return keys.stream().noneMatch(hasNameKey::contains); } @Override @@ -94,7 +97,7 @@ public J.VariableDeclarations visitVariableDeclarations(J.VariableDeclarations m if (!LOWER_CAMEL.matches(name) && name.length() > 1) { renameVariable(v, LOWER_CAMEL.format(name)); } else { - hasNameKey(name); + hasNameKey(computeKey(name, v)); } } return mv; @@ -140,7 +143,7 @@ private boolean isDeclaredInCatch() { @Override public J.Identifier visitIdentifier(J.Identifier identifier, ExecutionContext ctx) { - hasNameKey(identifier.getSimpleName()); + hasNameKey(computeKey(identifier.getSimpleName(), identifier)); return identifier; } @@ -167,6 +170,24 @@ private Cursor getCursorToParentScope(Cursor cursor) { is instanceof JavaSourceFile ); } + + private Set computeAllKeys(String identifier, J context) { + Set keys = new HashSet<>(); + keys.add(identifier); + JavaType.Variable fieldType = getFieldType(context); + if (fieldType != null && fieldType.getOwner() != null) { + keys.add(fieldType.getOwner() + " " + identifier); + if (fieldType.getOwner() instanceof JavaType.Method) { + // Add all enclosing classes + JavaType.FullyQualified declaringType = ((JavaType.Method) fieldType.getOwner()).getDeclaringType(); + while (declaringType != null) { + keys.add(declaringType + " " + identifier); + declaringType = declaringType.getOwningClass(); + } + } + } + return keys; + } }; } } diff --git a/src/main/java/org/openrewrite/staticanalysis/RenamePrivateFieldsToCamelCase.java b/src/main/java/org/openrewrite/staticanalysis/RenamePrivateFieldsToCamelCase.java index 0de3a7755..144c303b6 100644 --- a/src/main/java/org/openrewrite/staticanalysis/RenamePrivateFieldsToCamelCase.java +++ b/src/main/java/org/openrewrite/staticanalysis/RenamePrivateFieldsToCamelCase.java @@ -75,7 +75,12 @@ protected boolean shouldRename(Set hasNameKey, J.VariableDeclarations.Na if (toName.isEmpty() || !Character.isAlphabetic(toName.charAt(0))) { return false; } - return !hasNameKey.contains(toName) && !hasNameKey.contains(variable.getSimpleName()); + return hasNameKey.stream().noneMatch(key -> + key.equals(toName) || + key.equals(variable.getSimpleName()) || + key.endsWith(" " + toName) || + key.endsWith(" " + variable.getSimpleName()) + ); } @SuppressWarnings("all") @@ -110,7 +115,7 @@ public J.VariableDeclarations.NamedVariable visitVariable(J.VariableDeclarations String toName = LOWER_CAMEL.format(variable.getSimpleName()); renameVariable(variable, toName); } else { - hasNameKey(variable.getSimpleName()); + hasNameKey(computeKey(variable.getSimpleName(), variable)); } return super.visitVariable(variable, ctx); diff --git a/src/main/java/org/openrewrite/staticanalysis/RenameToCamelCase.java b/src/main/java/org/openrewrite/staticanalysis/RenameToCamelCase.java index e694fd7b2..a877aa1d0 100644 --- a/src/main/java/org/openrewrite/staticanalysis/RenameToCamelCase.java +++ b/src/main/java/org/openrewrite/staticanalysis/RenameToCamelCase.java @@ -22,6 +22,7 @@ import org.openrewrite.java.RenameVariable; import org.openrewrite.java.tree.J; import org.openrewrite.java.tree.JavaSourceFile; +import org.openrewrite.java.tree.JavaType; import java.util.HashSet; import java.util.LinkedHashMap; @@ -43,7 +44,7 @@ public abstract class RenameToCamelCase extends JavaIsoVisitor String toName = entry.getValue(); if (shouldRename(hasNameSet, variable, toName)) { cu = (JavaSourceFile) new RenameVariable<>(variable, toName).visitNonNull(cu, ctx); - hasNameSet.add(toName); + hasNameSet.add(computeKey(toName, variable)); } } return cu; @@ -65,4 +66,23 @@ protected void hasNameKey(String variableName) { .computeMessageIfAbsent("HAS_NAME_KEY", k -> new HashSet<>()) .add(variableName); } + + protected String computeKey(String identifier, J context) { + JavaType.Variable fieldType = getFieldType(context); + if (fieldType != null && fieldType.getOwner() != null) { + return fieldType.getOwner() + " " + identifier; + } + return identifier; + } + + @Nullable + protected JavaType.Variable getFieldType(J tree) { + if (tree instanceof J.Identifier) { + return ((J.Identifier) tree).getFieldType(); + } + if (tree instanceof J.VariableDeclarations.NamedVariable) { + return ((J.VariableDeclarations.NamedVariable) tree).getVariableType(); + } + return null; + } } diff --git a/src/test/java/org/openrewrite/staticanalysis/RenameLocalVariablesToCamelCaseTest.java b/src/test/java/org/openrewrite/staticanalysis/RenameLocalVariablesToCamelCaseTest.java index ffa96572c..dc40fb394 100644 --- a/src/test/java/org/openrewrite/staticanalysis/RenameLocalVariablesToCamelCaseTest.java +++ b/src/test/java/org/openrewrite/staticanalysis/RenameLocalVariablesToCamelCaseTest.java @@ -372,4 +372,68 @@ public void doSomething() { ) ); } + + @Test + @Issue("https://github.com/openrewrite/rewrite-static-analysis/issues/171") + void renameMultipleOcurrencesDifferentScope() { + rewriteRun( + java( + """ + class Test { + void test() { + String ID; + } + void test2() { + String ID; + } + } + """, + """ + class Test { + void test() { + String id; + } + void test2() { + String id; + } + } + """ + ) + ); + } + + @Test + void doNotRenameIfInParent() { + rewriteRun( + java( + """ + class Test { + String id; + void test() { + String ID; + } + } + """ + ) + ); + } + + @Test + void doNotRenameIfInParentFromInnerClass() { + rewriteRun( + java( + """ + class Test { + String id; + class InnerClass { + void test() { + String ID; + } + } + } + """ + ) + ); + } + } From 64e5490c61541abb57d1c167be90d91a2a088649 Mon Sep 17 00:00:00 2001 From: Knut Wannheden Date: Wed, 18 Oct 2023 22:29:20 +0200 Subject: [PATCH 44/92] Align `SimplifyBooleanExpression` with visitor To match https://github.com/openrewrite/rewrite/commit/a9e347e2de62f38dcc2a20d262e6880ebb59fbaa and other recent changes. --- .../SimplifyBooleanExpression.java | 15 +++--------- .../SimplifyBooleanExpressionTest.java | 2 +- .../SimplifyBooleanReturnTest.java | 24 +++++++++++++++++++ 3 files changed, 28 insertions(+), 13 deletions(-) diff --git a/src/main/java/org/openrewrite/staticanalysis/SimplifyBooleanExpression.java b/src/main/java/org/openrewrite/staticanalysis/SimplifyBooleanExpression.java index bb7b3ae0b..92e6c7eec 100644 --- a/src/main/java/org/openrewrite/staticanalysis/SimplifyBooleanExpression.java +++ b/src/main/java/org/openrewrite/staticanalysis/SimplifyBooleanExpression.java @@ -22,15 +22,12 @@ import org.openrewrite.internal.lang.Nullable; import org.openrewrite.java.cleanup.SimplifyBooleanExpressionVisitor; import org.openrewrite.java.tree.J; -import org.openrewrite.java.tree.JavaSourceFile; import org.openrewrite.kotlin.marker.IsNullSafe; import java.time.Duration; import java.util.Collections; import java.util.Set; -import static java.util.Objects.requireNonNull; - public class SimplifyBooleanExpression extends Recipe { @Override @@ -56,16 +53,10 @@ public Duration getEstimatedEffortPerOccurrence() { @Override public TreeVisitor getVisitor() { return new SimplifyBooleanExpressionVisitor() { + @Override - public J visit(@Nullable Tree tree, ExecutionContext ctx) { - if (tree instanceof JavaSourceFile) { - JavaSourceFile cu = (JavaSourceFile) requireNonNull(super.visit(tree, ctx)); - if (tree != cu) { - // recursive simplification - cu = (JavaSourceFile) getVisitor().visitNonNull(cu, ctx); - } - return cu; - } + public @Nullable J visit(@Nullable Tree tree, ExecutionContext ctx) { + // NOTE: This method is required here for the `TreeVisitorAdapter` to work return super.visit(tree, ctx); } diff --git a/src/test/java/org/openrewrite/staticanalysis/SimplifyBooleanExpressionTest.java b/src/test/java/org/openrewrite/staticanalysis/SimplifyBooleanExpressionTest.java index b91ba01c6..b45bceab3 100644 --- a/src/test/java/org/openrewrite/staticanalysis/SimplifyBooleanExpressionTest.java +++ b/src/test/java/org/openrewrite/staticanalysis/SimplifyBooleanExpressionTest.java @@ -312,7 +312,7 @@ void autoFormatIsConditionallyApplied() { public class A { { boolean a=true; - boolean i=(a!=true); + boolean i=a!=true; } } """ diff --git a/src/test/java/org/openrewrite/staticanalysis/SimplifyBooleanReturnTest.java b/src/test/java/org/openrewrite/staticanalysis/SimplifyBooleanReturnTest.java index 81418513c..93efadcf1 100644 --- a/src/test/java/org/openrewrite/staticanalysis/SimplifyBooleanReturnTest.java +++ b/src/test/java/org/openrewrite/staticanalysis/SimplifyBooleanReturnTest.java @@ -31,6 +31,30 @@ public void defaults(RecipeSpec spec) { spec.recipe(new SimplifyBooleanReturn()); } + @Test + void xxx() { + rewriteRun( + java( + """ + public class A { + boolean m(int i) { + return !!(m == 1); + } + } + """, + """ + public class A { + { + if (false) { + System.out.println(""); + } + } + } + """ + ) + ); + } + @DocumentExample @Test void simplifyBooleanReturn() { From 8c900fe042dc7c87b1d4b929355853a6c98fafb3 Mon Sep 17 00:00:00 2001 From: Knut Wannheden Date: Wed, 18 Oct 2023 22:30:56 +0200 Subject: [PATCH 45/92] Revert `SimplifyBooleanReturnTest` --- .../SimplifyBooleanReturnTest.java | 24 ------------------- 1 file changed, 24 deletions(-) diff --git a/src/test/java/org/openrewrite/staticanalysis/SimplifyBooleanReturnTest.java b/src/test/java/org/openrewrite/staticanalysis/SimplifyBooleanReturnTest.java index 93efadcf1..81418513c 100644 --- a/src/test/java/org/openrewrite/staticanalysis/SimplifyBooleanReturnTest.java +++ b/src/test/java/org/openrewrite/staticanalysis/SimplifyBooleanReturnTest.java @@ -31,30 +31,6 @@ public void defaults(RecipeSpec spec) { spec.recipe(new SimplifyBooleanReturn()); } - @Test - void xxx() { - rewriteRun( - java( - """ - public class A { - boolean m(int i) { - return !!(m == 1); - } - } - """, - """ - public class A { - { - if (false) { - System.out.println(""); - } - } - } - """ - ) - ); - } - @DocumentExample @Test void simplifyBooleanReturn() { From 9e83c2b93cf088c35814256898f4e6becb09f7a4 Mon Sep 17 00:00:00 2001 From: Knut Wannheden Date: Thu, 19 Oct 2023 08:47:59 +0200 Subject: [PATCH 46/92] Remove duplicated visitor code in `UnnecessaryParentheses` --- .../UnnecessaryParentheses.java | 187 +----------------- 1 file changed, 5 insertions(+), 182 deletions(-) diff --git a/src/main/java/org/openrewrite/staticanalysis/UnnecessaryParentheses.java b/src/main/java/org/openrewrite/staticanalysis/UnnecessaryParentheses.java index 4b44d01fd..1bdec44ba 100644 --- a/src/main/java/org/openrewrite/staticanalysis/UnnecessaryParentheses.java +++ b/src/main/java/org/openrewrite/staticanalysis/UnnecessaryParentheses.java @@ -15,12 +15,10 @@ */ package org.openrewrite.staticanalysis; -import org.openrewrite.*; -import org.openrewrite.java.JavaVisitor; -import org.openrewrite.java.UnwrapParentheses; -import org.openrewrite.java.style.Checkstyle; -import org.openrewrite.java.style.UnnecessaryParenthesesStyle; -import org.openrewrite.java.tree.*; +import org.openrewrite.ExecutionContext; +import org.openrewrite.Recipe; +import org.openrewrite.TreeVisitor; +import org.openrewrite.java.cleanup.UnnecessaryParenthesesVisitor; import java.time.Duration; import java.util.Arrays; @@ -51,182 +49,7 @@ public Duration getEstimatedEffortPerOccurrence() { @Override public TreeVisitor getVisitor() { - return new JavaVisitor() { - - @Override - public boolean isAcceptable(SourceFile sourceFile, ExecutionContext executionContext) { - // Causes problems on other languages like JavaScript - return sourceFile instanceof J.CompilationUnit; - } - - private static final String UNNECESSARY_PARENTHESES_MESSAGE = "unnecessaryParenthesesUnwrapTarget"; - - transient UnnecessaryParenthesesStyle style; - - private UnnecessaryParenthesesStyle getStyle() { - if (style == null) { - JavaSourceFile cu = getCursor().firstEnclosingOrThrow(JavaSourceFile.class); - style = ((SourceFile) cu).getStyle(UnnecessaryParenthesesStyle.class, Checkstyle.unnecessaryParentheses()); - } - return style; - } - - @Override - public J visitParentheses(J.Parentheses parens, ExecutionContext ctx) { - J par = super.visitParentheses(parens, ctx); - Cursor c = getCursor().pollNearestMessage(UNNECESSARY_PARENTHESES_MESSAGE); - if (c != null && (c.getValue() instanceof J.Literal || c.getValue() instanceof J.Identifier)) { - par = new UnwrapParentheses<>((J.Parentheses) par).visit(par, ctx, getCursor().getParentOrThrow()); - } - - assert par != null; - if (par instanceof J.Parentheses) { - if (getCursor().getParentTreeCursor().getValue() instanceof J.Parentheses) { - return ((J.Parentheses) par).getTree().withPrefix(Space.EMPTY); - } - } - return par; - } - - @Override - public J visitIdentifier(J.Identifier ident, ExecutionContext ctx) { - J.Identifier i = (J.Identifier) super.visitIdentifier(ident, ctx); - if (getStyle().getIdent() && getCursor().getParentTreeCursor().getValue() instanceof J.Parentheses) { - getCursor().putMessageOnFirstEnclosing(J.Parentheses.class, UNNECESSARY_PARENTHESES_MESSAGE, getCursor()); - } - return i; - } - - @Override - public J visitLiteral(J.Literal literal, ExecutionContext ctx) { - J.Literal l = (J.Literal) super.visitLiteral(literal, ctx); - JavaType.Primitive type = l.getType(); - if ((getStyle().getNumInt() && type == JavaType.Primitive.Int) || - (getStyle().getNumDouble() && type == JavaType.Primitive.Double) || - (getStyle().getNumLong() && type == JavaType.Primitive.Long) || - (getStyle().getNumFloat() && type == JavaType.Primitive.Float) || - (getStyle().getStringLiteral() && type == JavaType.Primitive.String) || - (getStyle().getLiteralNull() && type == JavaType.Primitive.Null) || - (getStyle().getLiteralFalse() && type == JavaType.Primitive.Boolean && l.getValue() == Boolean.valueOf(false)) || - (getStyle().getLiteralTrue() && type == JavaType.Primitive.Boolean && l.getValue() == Boolean.valueOf(true))) { - if (getCursor().getParentTreeCursor().getValue() instanceof J.Parentheses) { - getCursor().putMessageOnFirstEnclosing(J.Parentheses.class, UNNECESSARY_PARENTHESES_MESSAGE, getCursor()); - } - } - return l; - } - - @Override - public J visitAssignmentOperation(J.AssignmentOperation assignOp, ExecutionContext ctx) { - J.AssignmentOperation a = (J.AssignmentOperation) super.visitAssignmentOperation(assignOp, ctx); - J.AssignmentOperation.Type op = a.getOperator(); - if (a.getAssignment() instanceof J.Parentheses && ((getStyle().getBitAndAssign() && op == J.AssignmentOperation.Type.BitAnd) || - (getStyle().getBitOrAssign() && op == J.AssignmentOperation.Type.BitOr) || - (getStyle().getBitShiftRightAssign() && op == J.AssignmentOperation.Type.UnsignedRightShift) || - (getStyle().getBitXorAssign() && op == J.AssignmentOperation.Type.BitXor) || - (getStyle().getShiftRightAssign() && op == J.AssignmentOperation.Type.RightShift) || - (getStyle().getShiftLeftAssign() && op == J.AssignmentOperation.Type.LeftShift) || - (getStyle().getMinusAssign() && op == J.AssignmentOperation.Type.Subtraction) || - (getStyle().getDivAssign() && op == J.AssignmentOperation.Type.Division) || - (getStyle().getPlusAssign() && op == J.AssignmentOperation.Type.Addition) || - (getStyle().getStarAssign() && op == J.AssignmentOperation.Type.Multiplication) || - (getStyle().getModAssign() && op == J.AssignmentOperation.Type.Modulo))) { - a = (J.AssignmentOperation) new UnwrapParentheses<>((J.Parentheses) a.getAssignment()).visitNonNull(a, ctx, getCursor().getParentOrThrow()); - } - return a; - } - - @Override - public J visitAssignment(J.Assignment assignment, ExecutionContext ctx) { - J.Assignment a = visitAndCast(assignment, ctx, super::visitAssignment); - if (getStyle().getAssign() && a.getAssignment() instanceof J.Parentheses) { - a = (J.Assignment) new UnwrapParentheses<>((J.Parentheses) a.getAssignment()).visitNonNull(a, ctx, getCursor().getParentOrThrow()); - } - return a; - } - - @Override - public J visitReturn(J.Return return_, ExecutionContext ctx) { - J.Return rtn = (J.Return) super.visitReturn(return_, ctx); - if (getStyle().getExpr() && rtn.getExpression() instanceof J.Parentheses) { - rtn = (J.Return) new UnwrapParentheses<>((J.Parentheses) rtn.getExpression()).visitNonNull(rtn, ctx, getCursor().getParentOrThrow()); - } - return rtn; - } - - @Override - public J visitVariable(J.VariableDeclarations.NamedVariable variable, ExecutionContext ctx) { - J.VariableDeclarations.NamedVariable v = (J.VariableDeclarations.NamedVariable) super.visitVariable(variable, ctx); - if (getStyle().getAssign() && v.getInitializer() != null && v.getInitializer() instanceof J.Parentheses) { - v = (J.VariableDeclarations.NamedVariable) new UnwrapParentheses<>((J.Parentheses) v.getInitializer()).visitNonNull(v, ctx, getCursor().getParentOrThrow()); - } - return v; - } - - @Override - public J visitLambda(J.Lambda lambda, ExecutionContext ctx) { - J.Lambda l = (J.Lambda) super.visitLambda(lambda, ctx); - if (l.getParameters().getParameters().size() == 1 && - l.getParameters().isParenthesized() && - l.getParameters().getParameters().get(0) instanceof J.VariableDeclarations && - ((J.VariableDeclarations) l.getParameters().getParameters().get(0)).getTypeExpression() == null) { - l = l.withParameters(l.getParameters().withParenthesized(false)); - } - return l; - } - - @Override - public J visitIf(J.If iff, ExecutionContext ctx) { - J.If i = (J.If) super.visitIf(iff, ctx); - // Unwrap when if condition is a single parenthesized expression - Expression expression = i.getIfCondition().getTree(); - if (expression instanceof J.Parentheses) { - i = (J.If) new UnwrapParentheses<>((J.Parentheses) expression).visitNonNull(i, ctx, getCursor().getParentOrThrow()); - } - return i; - } - - @Override - public J visitWhileLoop(J.WhileLoop whileLoop, ExecutionContext ctx) { - J.WhileLoop w = (J.WhileLoop) super.visitWhileLoop(whileLoop, ctx); - // Unwrap when while condition is a single parenthesized expression - Expression expression = w.getCondition().getTree(); - if (expression instanceof J.Parentheses) { - w = (J.WhileLoop) new UnwrapParentheses<>((J.Parentheses) expression).visitNonNull(w, ctx, getCursor().getParentOrThrow()); - } - return w; - } - - @Override - public J visitDoWhileLoop(J.DoWhileLoop doWhileLoop, ExecutionContext ctx) { - J.DoWhileLoop dw = (J.DoWhileLoop) super.visitDoWhileLoop(doWhileLoop, ctx); - // Unwrap when while condition is a single parenthesized expression - Expression expression = dw.getWhileCondition().getTree(); - if (expression instanceof J.Parentheses) { - dw = (J.DoWhileLoop) new UnwrapParentheses<>((J.Parentheses) expression).visitNonNull(dw, ctx, getCursor().getParentOrThrow()); - } - return dw; - } - - @Override - public J visitForControl(J.ForLoop.Control control, ExecutionContext ctx) { - J.ForLoop.Control fc = (J.ForLoop.Control) super.visitForControl(control, ctx); - Expression condition = fc.getCondition(); - if (condition instanceof J.Parentheses) { - fc = (J.ForLoop.Control) new UnwrapParentheses<>((J.Parentheses) condition).visitNonNull(fc, ctx, getCursor().getParentOrThrow()); - } - return fc; - } - - @Override - public J visitTernary(J.Ternary ternary, ExecutionContext ctx) { - J.Ternary te = (J.Ternary) super.visitTernary(ternary, ctx); - if (te.getCondition() instanceof J.Parentheses) { - te = (J.Ternary) new UnwrapParentheses<>((J.Parentheses) te.getCondition()).visitNonNull(te, ctx, getCursor().getParentOrThrow()); - } - return te; - } - }; + return new UnnecessaryParenthesesVisitor<>(); } @Override From 3c93ea79fbd96bc290dd90c4478c0a7e03a82c54 Mon Sep 17 00:00:00 2001 From: Joan Viladrosa Date: Thu, 19 Oct 2023 12:38:15 +0200 Subject: [PATCH 47/92] remove comments from same line (#195) * remove comments from same line * added language highlighting in tests --- .../RemoveUnusedPrivateFields.java | 87 ++++++++-- .../RemoveUnusedPrivateFieldsTest.java | 148 ++++++++++++++++++ 2 files changed, 224 insertions(+), 11 deletions(-) diff --git a/src/main/java/org/openrewrite/staticanalysis/RemoveUnusedPrivateFields.java b/src/main/java/org/openrewrite/staticanalysis/RemoveUnusedPrivateFields.java index d660670cc..616dccddf 100644 --- a/src/main/java/org/openrewrite/staticanalysis/RemoveUnusedPrivateFields.java +++ b/src/main/java/org/openrewrite/staticanalysis/RemoveUnusedPrivateFields.java @@ -21,14 +21,17 @@ import org.openrewrite.ExecutionContext; import org.openrewrite.Recipe; import org.openrewrite.TreeVisitor; +import org.openrewrite.internal.lang.Nullable; import org.openrewrite.java.JavaIsoVisitor; import org.openrewrite.java.JavaVisitor; import org.openrewrite.java.tree.J; +import org.openrewrite.java.tree.Space; import org.openrewrite.java.tree.Statement; import org.openrewrite.java.tree.TypeUtils; import java.time.Duration; import java.util.*; +import java.util.concurrent.atomic.AtomicBoolean; @Value @EqualsAndHashCode(callSuper = true) @@ -57,23 +60,32 @@ public Duration getEstimatedEffortPerOccurrence() { @Override public TreeVisitor getVisitor() { return new JavaIsoVisitor() { + @Value + class CheckField { + J.VariableDeclarations declarations; + @Nullable Statement nextStatement; + } @Override public J.ClassDeclaration visitClassDeclaration(J.ClassDeclaration classDecl, ExecutionContext executionContext) { J.ClassDeclaration cd = super.visitClassDeclaration(classDecl, executionContext); - List checkFields = new ArrayList<>(); + List checkFields = new ArrayList<>(); // Do not remove fields with `serialVersionUID` name. boolean skipSerialVersionUID = cd.getType() == null || cd.getType().isAssignableTo("java.io.Serializable"); - for (Statement statement : cd.getBody().getStatements()) { + + List statements = cd.getBody().getStatements(); + for (int i = 0; i < statements.size(); i++) { + Statement statement = statements.get(i); if (statement instanceof J.VariableDeclarations) { J.VariableDeclarations vd = (J.VariableDeclarations) statement; // RSPEC-1068 does not apply serialVersionUID of Serializable classes, or fields with annotations. if (!(skipSerialVersionUID && isSerialVersionUid(vd)) && vd.getLeadingAnnotations().isEmpty() && vd.hasModifier(J.Modifier.Type.Private)) { - checkFields.add(vd); + Statement nextStatement = i < statements.size() - 1 ? statements.get(i + 1) : null; + checkFields.add(new CheckField(vd, nextStatement)); } } else if (statement instanceof J.MethodDeclaration) { // RSPEC-1068 does not apply fields from classes with native methods. @@ -94,12 +106,18 @@ public J.ClassDeclaration visitClassDeclaration(J.ClassDeclaration classDecl, Ex outer = parent.getValue(); } } - for (J.VariableDeclarations fields : checkFields) { + for (CheckField checkField : checkFields) { // Find variable uses. - Map> inUse = VariableUses.find(fields, outer); + Map> inUse = + VariableUses.find(checkField.declarations, outer); for (Map.Entry> entry : inUse.entrySet()) { if (entry.getValue().isEmpty()) { - cd = (J.ClassDeclaration) new RemoveUnusedField(entry.getKey()).visitNonNull(cd, executionContext); + AtomicBoolean declarationDeleted = new AtomicBoolean(); + cd = (J.ClassDeclaration) new RemoveUnusedField(entry.getKey()).visitNonNull(cd, declarationDeleted); + // Maybe remove next statement comment if variable declarations is removed + if (declarationDeleted.get()) { + cd = (J.ClassDeclaration) new MaybeRemoveComment(checkField.nextStatement, cd).visitNonNull(cd, executionContext); + } } } } @@ -159,7 +177,7 @@ public J.Identifier visitIdentifier(J.Identifier identifier, } } - private static class RemoveUnusedField extends JavaVisitor { + private static class RemoveUnusedField extends JavaVisitor { private final J.VariableDeclarations.NamedVariable namedVariable; public RemoveUnusedField(J.VariableDeclarations.NamedVariable namedVariable) { @@ -167,21 +185,68 @@ public RemoveUnusedField(J.VariableDeclarations.NamedVariable namedVariable) { } @Override - public J visitVariableDeclarations(J.VariableDeclarations multiVariable, ExecutionContext executionContext) { + public J visitVariableDeclarations(J.VariableDeclarations multiVariable, AtomicBoolean declarationDeleted) { if (multiVariable.getVariables().size() == 1 && multiVariable.getVariables().contains(namedVariable)) { + declarationDeleted.set(true); //noinspection ConstantConditions return null; } - return super.visitVariableDeclarations(multiVariable, executionContext); + return super.visitVariableDeclarations(multiVariable, declarationDeleted); } @Override - public J visitVariable(J.VariableDeclarations.NamedVariable variable, ExecutionContext executionContext) { + public J visitVariable(J.VariableDeclarations.NamedVariable variable, AtomicBoolean declarationDeleted) { if (variable == namedVariable) { //noinspection ConstantConditions return null; } - return super.visitVariable(variable, executionContext); + return super.visitVariable(variable, declarationDeleted); } } + + private static class MaybeRemoveComment extends JavaVisitor { + @Nullable + private final Statement statement; + private final J.ClassDeclaration classDeclaration; + + public MaybeRemoveComment(@Nullable Statement statement, J.ClassDeclaration classDeclaration) { + this.statement = statement; + this.classDeclaration = classDeclaration; + } + + @Override + public J visitStatement(Statement s, ExecutionContext executionContext) { + if (s == statement) { + Space prefix = s.getPrefix(); + // If we have at least one comment and there is no newline + if (prefix.getComments().size() > 0 && !prefix.getWhitespace().contains("\n")) { + return s.withPrefix(prefix + // Copy suffix to prefix + .withWhitespace(prefix.getComments().get(0).getSuffix()) + // Remove the first comment + .withComments(prefix.getComments().subList(1, prefix.getComments().size()) + )); + + } + } + return super.visitStatement(s, executionContext); + } + + @Override + public J visitClassDeclaration(J.ClassDeclaration c, ExecutionContext executionContext) { + // We also need to remove comments attached to end of classDeclaration if it's the last statement + if (statement == null && c == classDeclaration) { + Space end = c.getBody().getEnd(); + // If we have at least one comment and there is no newline + if (end.getComments().size() > 0 && !end.getWhitespace().contains("\n")) { + return c.withBody(c.getBody().withEnd(end + .withWhitespace(end.getComments().get(0).getSuffix()) + .withComments(end.getComments().subList(1, end.getComments().size())) + )); + } + } + return super.visitClassDeclaration(c, executionContext); + } + } + } diff --git a/src/test/java/org/openrewrite/staticanalysis/RemoveUnusedPrivateFieldsTest.java b/src/test/java/org/openrewrite/staticanalysis/RemoveUnusedPrivateFieldsTest.java index 7013f6534..be8f4e380 100644 --- a/src/test/java/org/openrewrite/staticanalysis/RemoveUnusedPrivateFieldsTest.java +++ b/src/test/java/org/openrewrite/staticanalysis/RemoveUnusedPrivateFieldsTest.java @@ -224,5 +224,153 @@ public doSomethingWithAVehicle() { ) ); } + + @Test + void removeCommentsPrefix() { + rewriteRun( + //language=java + java( + """ + public class Test { + // Some comment + private int a; + } + """, """ + public class Test { + } + """ + ) + ); + } + @Test + void removeCommentsLastExpression() { + rewriteRun( + //language=java + java( + """ + public class Test { + private int a; // Some comment + } + """, """ + public class Test { + } + """ + ) + ); + } + + @Test + void removeCommentsSameLine() { + rewriteRun( + //language=java + java( + """ + public class Test { + private int a; + private int b; // Some comment + + public void test() { + a = 42; + } + } + """, """ + public class Test { + private int a; + + public void test() { + a = 42; + } + } + """ + ) + ); + } + + @Test + void removeCommentsMultiLine() { + rewriteRun( + //language=java + java( + """ + public class Test { + private int a; + private int b; /* + Some + multiline + comment + */ + + public void test() { + a = 42; + } + } + """, """ + public class Test { + private int a; + + public void test() { + a = 42; + } + } + """ + ) + ); + + } + + @Test + void doNotRemoveCommentsIfNewline() { + rewriteRun( + //language=java + java( + """ + public class Test { + private int a; + private int b; + // Some comment + + public void test() { + a = 42; + } + } + """, """ + public class Test { + private int a; + // Some comment + + public void test() { + a = 42; + } + } + """ + ) + ); + } + + @Test + void doNotRemoveCommentsIfNotRemovedWholeVariableDeclarations() { + rewriteRun( + //language=java + java( + """ + public class Test { + private int a, b; // Some comment + + public void test() { + a = 42; + } + } + """, """ + public class Test { + private int a; // Some comment + + public void test() { + a = 42; + } + } + """ + ) + ); + } } From 3fcd93169bb3a5d2c9f54eaef6ffafe805b84735 Mon Sep 17 00:00:00 2001 From: Joan Viladrosa Date: Thu, 19 Oct 2023 15:10:19 +0200 Subject: [PATCH 48/92] RemoveEmptyJavaDocParameters: added tests + fixed bugs (#198) * fixed some bugs and tidy up + added some tests * empty javadoc --- .../RemoveEmptyJavaDocParameters.java | 81 ++- .../RemoveEmptyJavaDocParametersTest.java | 524 +++++++++++++++--- 2 files changed, 494 insertions(+), 111 deletions(-) diff --git a/src/main/java/org/openrewrite/staticanalysis/RemoveEmptyJavaDocParameters.java b/src/main/java/org/openrewrite/staticanalysis/RemoveEmptyJavaDocParameters.java index d076b3a64..1758148c2 100644 --- a/src/main/java/org/openrewrite/staticanalysis/RemoveEmptyJavaDocParameters.java +++ b/src/main/java/org/openrewrite/staticanalysis/RemoveEmptyJavaDocParameters.java @@ -78,52 +78,63 @@ public Javadoc visitDocComment(Javadoc.DocComment javadoc, ExecutionContext ctx) List newBody = new ArrayList<>(javadoc.getBody().size()); boolean useNewBody = false; - List body = javadoc.getBody(); + List body = new ArrayList<>(javadoc.getBody()); + // We add a trailing element, to fix elements on the first line without space + // We can use null since this element is never going to be read, and nulls get filtered later on. + body.add(0, null); + for (int i = 0; i < body.size(); i++) { // JavaDocs require a look ahead, because the current element may be an element that exists on the same line as a parameter. // I.E. the space that precedes `* @param` will be a `Javadoc.Text` and needs to be removed along with the empty `@param`. // A `Javadoc` will always precede a parameter even if there is empty space like `*@param`. Javadoc currentDoc = body.get(i); - boolean skipCurrentDoc = false; if (i + 1 < body.size()) { Javadoc nextDoc = body.get(i + 1); if (nextDoc instanceof Javadoc.Parameter) { - Javadoc.Parameter parameter = (Javadoc.Parameter) visitParameter((Javadoc.Parameter) nextDoc, ctx); - if (parameter == null) { + Javadoc.Parameter nextParameter = (Javadoc.Parameter) nextDoc; + if (isEmptyParameter(nextParameter)) { // The `@param` being removed is the last item in the JavaDoc body, and contains // relevant whitespace via the JavaDoc.LineBreak. if (i + 1 == body.size() - 1) { - newBody.add(((Javadoc.Parameter) body.get(i + 1)).getDescription().get(0)); + // If we have a previous LineBreak we need to remove it before adding the new one + if (!newBody.isEmpty() && newBody.get(newBody.size() - 1) instanceof Javadoc.LineBreak) { + newBody.remove(newBody.size() - 1); + } + if (!nextParameter.getDescription().isEmpty()) { + newBody.add(nextParameter.getDescription().get(0)); + } } // No need to reprocess the next element. i += 1; useNewBody = true; - skipCurrentDoc = true; + currentDoc = null; } } else if (nextDoc instanceof Javadoc.Return) { - Javadoc.Return aReturn = (Javadoc.Return) visitReturn((Javadoc.Return) nextDoc, ctx); - if (aReturn == null) { - if (!newBody.isEmpty() && newBody.get(newBody.size() - 1) instanceof Javadoc.LineBreak) { - newBody.remove(newBody.size() - 1); - } - + Javadoc.Return nextReturn = (Javadoc.Return) nextDoc; + if (isEmptyReturn(nextReturn)) { // The `@return` being removed is the last item in the JavaDoc body, and contains // relevant whitespace via the JavaDoc.LineBreak. if (i + 1 == body.size() - 1) { - newBody.add(((Javadoc.Return) body.get(i + 1)).getDescription().get(0)); + // If we have a previous LineBreak we need to remove it before adding the new one + if (!newBody.isEmpty() && newBody.get(newBody.size() - 1) instanceof Javadoc.LineBreak) { + newBody.remove(newBody.size() - 1); + } + if (!nextReturn.getDescription().isEmpty()) { + newBody.add(nextReturn.getDescription().get(0)); + } } // No need to reprocess the next element. i += 1; useNewBody = true; - skipCurrentDoc = true; + currentDoc = null; } } else if (nextDoc instanceof Javadoc.Erroneous) { - Javadoc.Erroneous erroneous = (Javadoc.Erroneous) visitErroneous((Javadoc.Erroneous) nextDoc, ctx); - if (erroneous == null) { + Javadoc.Erroneous nextErroneous = (Javadoc.Erroneous) nextDoc; + if (isEmptyErroneous(nextErroneous)) { if (!newBody.isEmpty() && newBody.get(newBody.size() - 1) instanceof Javadoc.LineBreak) { newBody.remove(newBody.size() - 1); } @@ -132,12 +143,12 @@ public Javadoc visitDocComment(Javadoc.DocComment javadoc, ExecutionContext ctx) i += 1; useNewBody = true; - skipCurrentDoc = true; + currentDoc = null; } } } - if (!skipCurrentDoc) { + if (currentDoc != null) { newBody.add(currentDoc); } } @@ -145,35 +156,23 @@ public Javadoc visitDocComment(Javadoc.DocComment javadoc, ExecutionContext ctx) if (useNewBody) { javadoc = javadoc.withBody(newBody); } - return super.visitDocComment(javadoc, ctx); + // No need to call super visitor, already covered all cases by adding an empty first element when needed. + return javadoc; } - @Override - public Javadoc visitParameter(Javadoc.Parameter parameter, ExecutionContext ctx) { - if (parameter.getDescription().stream().allMatch(it -> it instanceof Javadoc.LineBreak)) { - return null; - } - return super.visitParameter(parameter, ctx); + public boolean isEmptyParameter(Javadoc.Parameter parameter) { + return parameter.getDescription().stream().allMatch(it -> it instanceof Javadoc.LineBreak); } - @Override - public Javadoc visitReturn(Javadoc.Return aReturn, ExecutionContext ctx) { - if (aReturn.getDescription().stream().allMatch(it -> it instanceof Javadoc.LineBreak)) { - return null; - } - return super.visitReturn(aReturn, ctx); + public boolean isEmptyReturn(Javadoc.Return aReturn) { + return aReturn.getDescription().stream().allMatch(it -> it instanceof Javadoc.LineBreak); } - @Override - public Javadoc visitErroneous(Javadoc.Erroneous erroneous, ExecutionContext ctx) { - if (erroneous.getText().size() == 1 && erroneous.getText().get(0) instanceof Javadoc.Text) { - Javadoc.Text text = (Javadoc.Text) erroneous.getText().get(0); - // Empty throws result in an Erroneous type. - if ("@throws".equals(text.getText())) { - return null; - } - } - return super.visitErroneous(erroneous, ctx); + public boolean isEmptyErroneous(Javadoc.Erroneous erroneous) { + // Empty throws result in an Erroneous type. + return erroneous.getText().size() == 1 && + erroneous.getText().get(0) instanceof Javadoc.Text && + "@throws".equals(((Javadoc.Text) erroneous.getText().get(0)).getText()); } } }; diff --git a/src/test/java/org/openrewrite/staticanalysis/RemoveEmptyJavaDocParametersTest.java b/src/test/java/org/openrewrite/staticanalysis/RemoveEmptyJavaDocParametersTest.java index c10a9d559..b2b66d4d7 100644 --- a/src/test/java/org/openrewrite/staticanalysis/RemoveEmptyJavaDocParametersTest.java +++ b/src/test/java/org/openrewrite/staticanalysis/RemoveEmptyJavaDocParametersTest.java @@ -16,6 +16,7 @@ package org.openrewrite.staticanalysis; +import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.openrewrite.DocumentExample; import org.openrewrite.Issue; @@ -35,20 +36,23 @@ public void defaults(RecipeSpec spec) { @DocumentExample @Test - void singleLineParam() { + void emptyParam() { rewriteRun( //language=java java( """ class Test { - /**@param arg0*/ + /** + * @param arg0 + */ void method(int arg0) { } } """, """ class Test { - /***/ + /** + */ void method(int arg0) { } } @@ -58,28 +62,42 @@ void method(int arg0) { } @Test - @Issue("https://github.com/openrewrite/rewrite/issues/3078") - void visitingQuarkMustNotFail() { + void emptyReturn() { rewriteRun( - other( + //language=java + java( """ - foo + class Test { + /** + * @return + */ + int method() { + } + } + """, + """ + class Test { + /** + */ + int method() { + } + } """ ) ); } @Test - void removeParamWithNoPrefix() { + void emptyThrows() { rewriteRun( //language=java java( """ class Test { /** - *@param arg0 + * @throws */ - void method(int arg0) { + void method() throws IllegalStateException { } } """, @@ -87,7 +105,7 @@ void method(int arg0) { class Test { /** */ - void method(int arg0) { + void method() throws IllegalStateException { } } """ @@ -156,76 +174,442 @@ void method(int arg0, int arg1) { ); } - @Test - void emptyReturn() { - rewriteRun( - //language=java - java( - """ - class Test { - /** - * @return - */ - int method() { + @Nested + class NoSpace { + @Test + void emptyParamNoSpace() { + rewriteRun( + //language=java + java( + """ + class Test { + /** + *@param arg0 + */ + void method(int arg0) { + } } - } - """, - """ - class Test { - /** - */ - int method() { + """, + """ + class Test { + /** + */ + void method(int arg0) { + } } - } - """ - ) - ); + """ + ) + ); + } + + @Test + void emptyReturnNoSpace() { + rewriteRun( + //language=java + java( + """ + class Test { + /** + *@return + */ + int method() { + } + } + """, + """ + class Test { + /** + */ + int method() { + } + } + """ + ) + ); + } + + @Test + void emptyThrowsNoSpace() { + rewriteRun( + //language=java + java( + """ + class Test { + /** + *@throws + */ + void method() throws IllegalStateException { + } + } + """, + """ + class Test { + /** + */ + void method() throws IllegalStateException { + } + } + """ + ) + ); + } + } - @Test - void emptyThrows() { - rewriteRun( - //language=java - java( - """ - class Test { - /** - * @throws - */ - void method() throws IllegalStateException { + @Nested + class SingleLine { + @Test + void singleLineParam() { + rewriteRun( + //language=java + java( + """ + class Test { + /** @param arg0*/ + void method(int arg0) { + } } - } - """, - """ - class Test { - /** - */ - void method() throws IllegalStateException { + """, + """ + class Test { + /***/ + void method(int arg0) { + } } - } - """ - ) - ); + """ + ) + ); + } + + @Test + void singleLineReturn() { + rewriteRun( + //language=java + java( + """ + class Test { + /** @return*/ + int method() { + } + } + """, + """ + class Test { + /***/ + int method() { + } + } + """ + ) + ); + } + + @Test + void singleLineThrows() { + rewriteRun( + //language=java + java( + """ + class Test { + /** @throws*/ + void method() throws IllegalStateException { + } + } + """, + """ + class Test { + /***/ + void method() throws IllegalStateException { + } + } + """ + ) + ); + } + + @Test + void singleLineParamNoSpace() { + rewriteRun( + //language=java + java( + """ + class Test { + /**@param arg0*/ + void method(int arg0) { + } + } + """, + """ + class Test { + /***/ + void method(int arg0) { + } + } + """ + ) + ); + } + + @Test + void singleLineReturnNoSpace() { + rewriteRun( + //language=java + java( + """ + class Test { + /**@return*/ + int method() { + } + } + """, + """ + class Test { + /***/ + int method() { + } + } + """ + ) + ); + } + + @Test + void singleLineThrowsNoSpace() { + rewriteRun( + //language=java + java( + """ + class Test { + /**@throws*/ + void method() throws IllegalStateException { + } + } + """, + """ + class Test { + /***/ + void method() throws IllegalStateException { + } + } + """ + ) + ); + } } + @Nested + class FirstLine { + @Test + void firstLineParam() { + rewriteRun( + //language=java + java( + """ + class Test { + /** @param arg0 + */ + void method(int arg0) { + } + } + """, + """ + class Test { + /** + */ + void method(int arg0) { + } + } + """ + ) + ); + } + + @Test + void firstLineReturn() { + rewriteRun( + //language=java + java( + """ + class Test { + /** @return + */ + int method() { + } + } + """, + """ + class Test { + /** + */ + int method() { + } + } + """ + ) + ); + } + + @Test + void firstLineThrows() { + rewriteRun( + //language=java + java( + """ + class Test { + /** @throws + */ + int method() throws IllegalStateException { + } + } + """, + """ + class Test { + /** + */ + int method() throws IllegalStateException { + } + } + """ + ) + ); + } + + @Test + void firstLineParamNoSpace() { + rewriteRun( + //language=java + java( + """ + class Test { + /**@param arg0 + */ + void method(int arg0) { + } + } + """, + """ + class Test { + /** + */ + void method(int arg0) { + } + } + """ + ) + ); + } + + @Test + void firstLineReturnNoSpace() { + rewriteRun( + //language=java + java( + """ + class Test { + /**@return + */ + int method() { + } + } + """, + """ + class Test { + /** + */ + int method() { + } + } + """ + ) + ); + } + + @Test + void firstLineThrowsNoSpace() { + rewriteRun( + //language=java + java( + """ + class Test { + /**@throws + */ + int method() throws IllegalStateException { + } + } + """, + """ + class Test { + /** + */ + int method() throws IllegalStateException { + } + } + """ + ) + ); + } + } + + @Nested + class EmptyJavaDoc { + @Test + void emptyJavaDoc() { + rewriteRun( + //language=java + java( + """ + class Test { + /** + */ + void method(int arg0) { + } + } + """ + ) + ); + } + + @Test + void emptyJavaDocSingleLine() { + rewriteRun( + //language=java + java( + """ + class Test { + /** */ + void method(int arg0) { + } + } + """ + ) + ); + } + + @Test + void emptyJavaDocSingleLineNoSpace() { + rewriteRun( + //language=java + java( + """ + class Test { + /***/ + void method(int arg0) { + } + } + """ + ) + ); + } + } + + @Test - void emptyThrowsOnFirstLine() { + @Issue("https://github.com/openrewrite/rewrite/issues/3078") + void visitingQuarkMustNotFail() { rewriteRun( - //language=java - java( - """ - class Test { - /** @throws*/ - void method() throws IllegalStateException { - } - } - """, + other( """ - class Test { - /***/ - void method() throws IllegalStateException { - } - } + foo """ ) ); From 431a32d084713d6cdbfb9e170d18e18d6ef1b90f Mon Sep 17 00:00:00 2001 From: Joan Viladrosa Date: Thu, 19 Oct 2023 16:14:40 +0200 Subject: [PATCH 49/92] avoid doing changes to newClass as argument to generic method to avoid type inference issues. (#197) --- .../staticanalysis/UseDiamondOperator.java | 16 +++++++--- .../UseDiamondOperatorTest.java | 30 +++++++++++++++++++ 2 files changed, 42 insertions(+), 4 deletions(-) diff --git a/src/main/java/org/openrewrite/staticanalysis/UseDiamondOperator.java b/src/main/java/org/openrewrite/staticanalysis/UseDiamondOperator.java index 2712f1ee9..f7060efed 100644 --- a/src/main/java/org/openrewrite/staticanalysis/UseDiamondOperator.java +++ b/src/main/java/org/openrewrite/staticanalysis/UseDiamondOperator.java @@ -25,13 +25,11 @@ import org.openrewrite.staticanalysis.java.JavaFileChecker; import java.time.Duration; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Set; +import java.util.*; import static java.util.Collections.singletonList; import static org.openrewrite.Tree.randomId; +import static org.openrewrite.java.tree.TypeUtils.findDeclaredMethod; public class UseDiamondOperator extends Recipe { @@ -111,6 +109,16 @@ public J.MethodInvocation visitMethodInvocation(J.MethodInvocation method, Execu if (methodType != null && !mi.getArguments().isEmpty() && methodType.getParameterTypes().size() <= mi.getArguments().size()) { + + + Optional declaredMethodType = findDeclaredMethod(methodType.getDeclaringType(), methodType.getName(), methodType.getParameterTypes()); + if (!declaredMethodType.isPresent()) { + // If we cannot find the method in the declaringType is because its parameter types doesn't match + // due to generic type parameters being inferred on the invocation. We cannot safely apply the + // diamond operator on an argument of this method, because we cannot guarantee type inference. + return method; + } + mi = mi.withArguments(ListUtils.map(mi.getArguments(), (i, arg) -> { if (arg instanceof J.NewClass) { boolean isGenericType = false; diff --git a/src/test/java/org/openrewrite/staticanalysis/UseDiamondOperatorTest.java b/src/test/java/org/openrewrite/staticanalysis/UseDiamondOperatorTest.java index 8fde09d52..ac7ecb5d0 100644 --- a/src/test/java/org/openrewrite/staticanalysis/UseDiamondOperatorTest.java +++ b/src/test/java/org/openrewrite/staticanalysis/UseDiamondOperatorTest.java @@ -487,4 +487,34 @@ fun method() { } } + @Test + void doNotChangeInferredGenericTypes() { + rewriteRun( + spec -> spec.allSources(s -> s.markers(javaVersion(9))), + //language=java + java(""" + @FunctionalInterface + public interface IVisitor { + void visit(T object, R ret); + } + """ + ), + //language=java + java(""" + class Test { + public R method(IVisitor visitor) { + return null; + } + private void test(Tree t) { + String s = method(new IVisitor() { + @Override + public void visit(Integer object, String ret) { } + }); + } + } + """ + ) + ); + } + } From 5f5b7e18098d7dff0e31efd66809048bfcb21499 Mon Sep 17 00:00:00 2001 From: Joan Viladrosa Date: Fri, 20 Oct 2023 11:43:44 +0200 Subject: [PATCH 50/92] rename final variables too (#199) --- .../RenameLocalVariablesToCamelCase.java | 5 ----- .../RenameLocalVariablesToCamelCaseTest.java | 22 +++++++++++++++++++ 2 files changed, 22 insertions(+), 5 deletions(-) diff --git a/src/main/java/org/openrewrite/staticanalysis/RenameLocalVariablesToCamelCase.java b/src/main/java/org/openrewrite/staticanalysis/RenameLocalVariablesToCamelCase.java index 66df86ea3..568155a68 100644 --- a/src/main/java/org/openrewrite/staticanalysis/RenameLocalVariablesToCamelCase.java +++ b/src/main/java/org/openrewrite/staticanalysis/RenameLocalVariablesToCamelCase.java @@ -109,11 +109,6 @@ private boolean isLocalVariable(J.VariableDeclarations mv) { return false; } - // Skip constant variable - if (mv.hasModifier(J.Modifier.Type.Final)) { - return false; - } - // Ignore fields (aka "instance variable" or "class variable") for (J.VariableDeclarations.NamedVariable v : mv.getVariables()) { if (v.isField(getCursor())) { diff --git a/src/test/java/org/openrewrite/staticanalysis/RenameLocalVariablesToCamelCaseTest.java b/src/test/java/org/openrewrite/staticanalysis/RenameLocalVariablesToCamelCaseTest.java index dc40fb394..250264069 100644 --- a/src/test/java/org/openrewrite/staticanalysis/RenameLocalVariablesToCamelCaseTest.java +++ b/src/test/java/org/openrewrite/staticanalysis/RenameLocalVariablesToCamelCaseTest.java @@ -436,4 +436,26 @@ void test() { ); } + @Test + void renameFinalLocalVariables() { + rewriteRun( + java( + """ + class Test { + void test() { + final String FINAL_VARIABLE; + } + } + """, + """ + class Test { + void test() { + final String finalVariable; + } + } + """ + ) + ); + } + } From cd8a8bfbcb1f0f7976d68a4601fedbd3fbe997c4 Mon Sep 17 00:00:00 2001 From: Knut Wannheden Date: Sat, 21 Oct 2023 21:39:09 +0200 Subject: [PATCH 51/92] Correctly shorten names for nested types Fixes: #200 --- .../staticanalysis/JavaElementFactory.java | 26 ++++++++++++ .../ReplaceLambdaWithMethodReference.java | 10 +++-- .../ReplaceLambdaWithMethodReferenceTest.java | 40 +++++++++++++++++++ 3 files changed, 72 insertions(+), 4 deletions(-) diff --git a/src/main/java/org/openrewrite/staticanalysis/JavaElementFactory.java b/src/main/java/org/openrewrite/staticanalysis/JavaElementFactory.java index 718e7ffa5..ce9e4d057 100644 --- a/src/main/java/org/openrewrite/staticanalysis/JavaElementFactory.java +++ b/src/main/java/org/openrewrite/staticanalysis/JavaElementFactory.java @@ -36,6 +36,32 @@ static J.MemberReference newStaticMethodReference(JavaType.Method method, boolea static Expression className(JavaType type, boolean qualified) { Expression name = null; String qualifiedName; + if (type instanceof JavaType.Parameterized) { + type = ((JavaType.Parameterized) type).getType(); + } + if (qualified && type instanceof JavaType.FullyQualified && ((JavaType.FullyQualified) type).getOwningClass() != null) { + J.FieldAccess expression = (J.FieldAccess) className(((JavaType.FullyQualified) type).getOwningClass(), true); + String simpleName = ((JavaType.FullyQualified) type).getClassName(); + return new J.FieldAccess( + randomId(), + Space.EMPTY, + Markers.EMPTY, + expression, + new JLeftPadded<>( + Space.EMPTY, + new J.Identifier( + randomId(), + Space.EMPTY, + Markers.EMPTY, + emptyList(), + simpleName.substring(simpleName.lastIndexOf('.') + 1), + type, + null + ), + Markers.EMPTY), + type + ); + } if (type instanceof JavaType.FullyQualified) { qualifiedName = qualified ? ((JavaType.FullyQualified) type).getFullyQualifiedName() : ((JavaType.FullyQualified) type).getClassName(); } else { diff --git a/src/main/java/org/openrewrite/staticanalysis/ReplaceLambdaWithMethodReference.java b/src/main/java/org/openrewrite/staticanalysis/ReplaceLambdaWithMethodReference.java index 9637cf582..fe300f4a9 100644 --- a/src/main/java/org/openrewrite/staticanalysis/ReplaceLambdaWithMethodReference.java +++ b/src/main/java/org/openrewrite/staticanalysis/ReplaceLambdaWithMethodReference.java @@ -125,13 +125,14 @@ public J visitLambda(J.Lambda lambda, ExecutionContext executionContext) { J.Binary binary = (J.Binary) body; if (isNullCheck(binary.getLeft(), binary.getRight()) || isNullCheck(binary.getRight(), binary.getLeft())) { - doAfterVisit(new ShortenFullyQualifiedTypeReferences().getVisitor()); code = J.Binary.Type.Equal.equals(binary.getOperator()) ? "java.util.Objects::isNull" : "java.util.Objects::nonNull"; - return JavaTemplate.builder(code) + J updated = JavaTemplate.builder(code) .contextSensitive() .build() .apply(getCursor(), l.getCoordinates().replace()); + doAfterVisit(ShortenFullyQualifiedTypeReferences.modifyOnly(updated)); + return updated; } } else if (body instanceof MethodCall) { MethodCall method = (MethodCall) body; @@ -162,8 +163,9 @@ public J visitLambda(J.Lambda lambda, ExecutionContext executionContext) { if (methodType != null && !isMethodReferenceAmbiguous(methodType)) { if (methodType.hasFlags(Flag.Static) || methodSelectMatchesFirstLambdaParameter(method, lambda)) { - doAfterVisit(new ShortenFullyQualifiedTypeReferences().getVisitor()); - return newStaticMethodReference(methodType, true, lambda.getType()).withPrefix(lambda.getPrefix()); + J.MemberReference updated = newStaticMethodReference(methodType, true, lambda.getType()).withPrefix(lambda.getPrefix()); + doAfterVisit(ShortenFullyQualifiedTypeReferences.modifyOnly(updated)); + return updated; } else if (method instanceof J.NewClass) { return JavaTemplate.builder("#{}::new") .contextSensitive() diff --git a/src/test/java/org/openrewrite/staticanalysis/ReplaceLambdaWithMethodReferenceTest.java b/src/test/java/org/openrewrite/staticanalysis/ReplaceLambdaWithMethodReferenceTest.java index 580a28004..a00fcfcab 100644 --- a/src/test/java/org/openrewrite/staticanalysis/ReplaceLambdaWithMethodReferenceTest.java +++ b/src/test/java/org/openrewrite/staticanalysis/ReplaceLambdaWithMethodReferenceTest.java @@ -1199,6 +1199,46 @@ void m() { ); } + @SuppressWarnings({"ConstantValue"}) + @Test + @Issue("https://github.com/openrewrite/rewrite-static-analysis/issues/200") + void nestedType() { + rewriteRun( + //language=java + java( + """ + import java.util.HashMap; + + class A { + String m() { + return new HashMap() + .entrySet() + .stream() + .map(e -> e.getValue()) + .findFirst() + .orElse(null); + } + } + """, + """ + import java.util.HashMap; + import java.util.Map; + + class A { + String m() { + return new HashMap() + .entrySet() + .stream() + .map(Map.Entry::getValue) + .findFirst() + .orElse(null); + } + } + """ + ) + ); + } + @Test @Issue("https://github.com/openrewrite/rewrite-static-analysis/pull/132") void dontReplaceLambdaSupplierOfMethodReference() { From cf6282898510f1b6af839021d98b2f11d59722af Mon Sep 17 00:00:00 2001 From: Knut Wannheden Date: Sat, 21 Oct 2023 22:13:12 +0200 Subject: [PATCH 52/92] Fix a cast with type casts in `ReplaceLambdaWithMethodReference` Fixes: #201 --- .../ReplaceLambdaWithMethodReference.java | 9 +++++--- .../ReplaceLambdaWithMethodReferenceTest.java | 23 +++++++++++++++++++ 2 files changed, 29 insertions(+), 3 deletions(-) diff --git a/src/main/java/org/openrewrite/staticanalysis/ReplaceLambdaWithMethodReference.java b/src/main/java/org/openrewrite/staticanalysis/ReplaceLambdaWithMethodReference.java index fe300f4a9..023f7592d 100644 --- a/src/main/java/org/openrewrite/staticanalysis/ReplaceLambdaWithMethodReference.java +++ b/src/main/java/org/openrewrite/staticanalysis/ReplaceLambdaWithMethodReference.java @@ -102,9 +102,12 @@ public J visitLambda(J.Lambda lambda, ExecutionContext executionContext) { } } } - } else if (body instanceof J.TypeCast) { - if (!(((J.TypeCast) body).getExpression() instanceof J.MethodInvocation)) { - J.ControlParentheses j = ((J.TypeCast) body).getClazz(); + } else if (body instanceof J.TypeCast && l.getParameters().getParameters().size() == 1) { + J.TypeCast cast = (J.TypeCast) body; + J param = l.getParameters().getParameters().get(0); + if (cast.getExpression() instanceof J.Identifier && param instanceof J.VariableDeclarations && + ((J.Identifier) cast.getExpression()).getSimpleName().equals(((J.VariableDeclarations) param).getVariables().get(0).getSimpleName())) { + J.ControlParentheses j = cast.getClazz(); J tree = j.getTree(); if ((tree instanceof J.Identifier || tree instanceof J.FieldAccess) && !(j.getType() instanceof JavaType.GenericTypeVariable)) { diff --git a/src/test/java/org/openrewrite/staticanalysis/ReplaceLambdaWithMethodReferenceTest.java b/src/test/java/org/openrewrite/staticanalysis/ReplaceLambdaWithMethodReferenceTest.java index a00fcfcab..287f81600 100644 --- a/src/test/java/org/openrewrite/staticanalysis/ReplaceLambdaWithMethodReferenceTest.java +++ b/src/test/java/org/openrewrite/staticanalysis/ReplaceLambdaWithMethodReferenceTest.java @@ -142,6 +142,29 @@ public void foo() { ); } + @Test + @Issue("https://github.com/openrewrite/rewrite-static-analysis/issues/201") + void typeCastOnConstructorCall() { + rewriteRun( + //language=java + java( + """ + import java.util.List; + import java.util.stream.Collectors; + import java.util.stream.Stream; + + class Test { + public void foo() { + List bar = Stream.of("A", "b") + .map(s -> (Object) new String(s + ":")) + .collect(Collectors.toList()); + } + } + """ + ) + ); + } + @Test void instanceOf() { rewriteRun( From 1e6a6a50b5a8b24e500e0982ce7b6ea3f0dc850a Mon Sep 17 00:00:00 2001 From: Knut Wannheden Date: Sat, 21 Oct 2023 22:37:48 +0200 Subject: [PATCH 53/92] Another few cases where name shortening was missing Fixes: #200 --- .../ReplaceLambdaWithMethodReference.java | 8 ++++++-- .../ReplaceLambdaWithMethodReferenceTest.java | 16 ++++++++++------ 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/src/main/java/org/openrewrite/staticanalysis/ReplaceLambdaWithMethodReference.java b/src/main/java/org/openrewrite/staticanalysis/ReplaceLambdaWithMethodReference.java index 023f7592d..4fb32dab1 100644 --- a/src/main/java/org/openrewrite/staticanalysis/ReplaceLambdaWithMethodReference.java +++ b/src/main/java/org/openrewrite/staticanalysis/ReplaceLambdaWithMethodReference.java @@ -98,7 +98,9 @@ public J visitLambda(J.Lambda lambda, ExecutionContext executionContext) { JavaType.FullyQualified rawClassType = ((JavaType.Parameterized) classLiteral.getType()).getType(); Optional isInstanceMethod = rawClassType.getMethods().stream().filter(m -> m.getName().equals("isInstance")).findFirst(); if (isInstanceMethod.isPresent()) { - return newInstanceMethodReference(isInstanceMethod.get(), classLiteral, lambda.getType()).withPrefix(lambda.getPrefix()); + J.MemberReference updated = newInstanceMethodReference(isInstanceMethod.get(), classLiteral, lambda.getType()).withPrefix(lambda.getPrefix()); + doAfterVisit(ShortenFullyQualifiedTypeReferences.modifyOnly(updated)); + return updated; } } } @@ -117,7 +119,9 @@ public J visitLambda(J.Lambda lambda, ExecutionContext executionContext) { JavaType.FullyQualified classType = ((JavaType.Parameterized) classLiteral.getType()).getType(); Optional castMethod = classType.getMethods().stream().filter(m -> m.getName().equals("cast")).findFirst(); if (castMethod.isPresent()) { - return newInstanceMethodReference(castMethod.get(), classLiteral, lambda.getType()).withPrefix(lambda.getPrefix()); + J.MemberReference updated = newInstanceMethodReference(castMethod.get(), classLiteral, lambda.getType()).withPrefix(lambda.getPrefix()); + doAfterVisit(ShortenFullyQualifiedTypeReferences.modifyOnly(updated)); + return updated; } } } diff --git a/src/test/java/org/openrewrite/staticanalysis/ReplaceLambdaWithMethodReferenceTest.java b/src/test/java/org/openrewrite/staticanalysis/ReplaceLambdaWithMethodReferenceTest.java index 287f81600..276c3ec39 100644 --- a/src/test/java/org/openrewrite/staticanalysis/ReplaceLambdaWithMethodReferenceTest.java +++ b/src/test/java/org/openrewrite/staticanalysis/ReplaceLambdaWithMethodReferenceTest.java @@ -238,12 +238,14 @@ List method(List input) { } """, """ + import org.test.CheckType; + import java.util.List; import java.util.stream.Collectors; class Test { List method(List input) { - return input.stream().filter(org.test.CheckType.class::isInstance).collect(Collectors.toList()); + return input.stream().filter(CheckType.class::isInstance).collect(Collectors.toList()); } } """, @@ -594,6 +596,8 @@ List filter(List l) { } """, """ + import org.test.CheckType; + import java.util.List; import java.util.stream.Collectors; @@ -601,7 +605,7 @@ class Test { List filter(List l) { return l.stream() .filter(org.test.CheckType.class::isInstance) - .map(org.test.CheckType.class::cast) + .map(CheckType.class::cast) .collect(Collectors.toList()); } } @@ -1233,11 +1237,11 @@ void nestedType() { import java.util.HashMap; class A { - String m() { + Boolean m() { return new HashMap() .entrySet() .stream() - .map(e -> e.getValue()) + .map(e -> e instanceof java.util.Map.Entry) .findFirst() .orElse(null); } @@ -1248,11 +1252,11 @@ String m() { import java.util.Map; class A { - String m() { + Boolean m() { return new HashMap() .entrySet() .stream() - .map(Map.Entry::getValue) + .map(Map.Entry.class::isInstance) .findFirst() .orElse(null); } From 19154f00e43e01009bef8b8301915a9641343e6d Mon Sep 17 00:00:00 2001 From: Knut Wannheden Date: Sat, 21 Oct 2023 20:53:54 +0000 Subject: [PATCH 54/92] refactor: Use method references in lambda Use this link to re-run the recipe: https://app.moderne.io/recipes/org.openrewrite.staticanalysis.ReplaceLambdaWithMethodReference?organizationId=T3BlblJld3JpdGU%3D Co-authored-by: Moderne --- .../ChainStringBuilderAppendCalls.java | 2 +- .../staticanalysis/FinalizePrivateFields.java | 14 +++++++------- .../staticanalysis/HiddenFieldVisitor.java | 4 ++-- .../NoDoubleBraceInitialization.java | 6 +++--- .../RemoveEmptyJavaDocParameters.java | 6 +++--- .../staticanalysis/RemoveExtraSemicolons.java | 2 +- .../staticanalysis/RemoveUnneededBlock.java | 2 +- .../staticanalysis/RemoveUnusedLocalVariables.java | 2 +- .../ReplaceDuplicateStringLiterals.java | 4 ++-- .../ReplaceOptionalIsPresentWithIfPresent.java | 2 +- .../UseLambdaForFunctionalInterface.java | 4 ++-- 11 files changed, 24 insertions(+), 24 deletions(-) diff --git a/src/main/java/org/openrewrite/staticanalysis/ChainStringBuilderAppendCalls.java b/src/main/java/org/openrewrite/staticanalysis/ChainStringBuilderAppendCalls.java index d73552c87..823e85091 100644 --- a/src/main/java/org/openrewrite/staticanalysis/ChainStringBuilderAppendCalls.java +++ b/src/main/java/org/openrewrite/staticanalysis/ChainStringBuilderAppendCalls.java @@ -68,7 +68,7 @@ public J.MethodInvocation visitMethodInvocation(J.MethodInvocation method, Execu return m; } - if (flattenExpressions.stream().allMatch(exp -> exp instanceof J.Literal)) { + if (flattenExpressions.stream().allMatch(J.Literal.class::isInstance)) { return m; } diff --git a/src/main/java/org/openrewrite/staticanalysis/FinalizePrivateFields.java b/src/main/java/org/openrewrite/staticanalysis/FinalizePrivateFields.java index e5e526344..d43e3dcd2 100644 --- a/src/main/java/org/openrewrite/staticanalysis/FinalizePrivateFields.java +++ b/src/main/java/org/openrewrite/staticanalysis/FinalizePrivateFields.java @@ -90,7 +90,7 @@ public J.VariableDeclarations visitVariableDeclarations(J.VariableDeclarations m boolean canAllVariablesBeFinalized = mv.getVariables() .stream() .map(J.VariableDeclarations.NamedVariable::getVariableType) - .allMatch(v -> privateFieldsToBeFinalized.contains(v)); + .allMatch(privateFieldsToBeFinalized::contains); if (canAllVariablesBeFinalized) { mv = autoFormat(mv.withVariables(ListUtils.map(mv.getVariables(), v -> { @@ -119,7 +119,7 @@ private static List collectPrivateFields(J return classDecl.getBody() .getStatements() .stream() - .filter(statement -> statement instanceof J.VariableDeclarations) + .filter(J.VariableDeclarations.class::isInstance) .map(J.VariableDeclarations.class::cast) .filter(mv -> mv.hasModifier(J.Modifier.Type.Private) && !mv.hasModifier(J.Modifier.Type.Final) @@ -134,7 +134,7 @@ private static int getConstructorCount(J.ClassDeclaration classDecl) { return (int) classDecl.getBody() .getStatements() .stream() - .filter(statement -> statement instanceof J.MethodDeclaration) + .filter(J.MethodDeclaration.class::isInstance) .map(J.MethodDeclaration.class::cast) .filter(J.MethodDeclaration::isConstructor) .count(); @@ -275,25 +275,25 @@ private static boolean dropUntilMeetCondition(Cursor cursor, private static boolean isInForLoop(Cursor cursor) { return dropUntilMeetCondition(cursor, CollectPrivateFieldsAssignmentCounts::dropCursorEndCondition, - parent -> parent instanceof J.ForLoop); + J.ForLoop.class::isInstance); } private static boolean isInDoWhileLoopLoop(Cursor cursor) { return dropUntilMeetCondition(cursor, CollectPrivateFieldsAssignmentCounts::dropCursorEndCondition, - parent -> parent instanceof J.DoWhileLoop); + J.DoWhileLoop.class::isInstance); } private static boolean isInWhileLoop(Cursor cursor) { return dropUntilMeetCondition(cursor, CollectPrivateFieldsAssignmentCounts::dropCursorEndCondition, - parent -> parent instanceof J.WhileLoop); + J.WhileLoop.class::isInstance); } private static boolean isInLambda(Cursor cursor) { return dropUntilMeetCondition(cursor, CollectPrivateFieldsAssignmentCounts::dropCursorEndCondition, - parent -> parent instanceof J.Lambda); + J.Lambda.class::isInstance); } } diff --git a/src/main/java/org/openrewrite/staticanalysis/HiddenFieldVisitor.java b/src/main/java/org/openrewrite/staticanalysis/HiddenFieldVisitor.java index 144da951a..7e0b6c1c2 100644 --- a/src/main/java/org/openrewrite/staticanalysis/HiddenFieldVisitor.java +++ b/src/main/java/org/openrewrite/staticanalysis/HiddenFieldVisitor.java @@ -152,8 +152,8 @@ public J.VariableDeclarations.NamedVariable visitVariable(J.VariableDeclarations doAfterVisit(new RenameVariable<>(v, nextName)); if (parentScope.getValue() instanceof J.MethodDeclaration) { Optional variableParameter = ((J.MethodDeclaration) parentScope.getValue()).getParameters().stream() - .filter(it -> it instanceof J.VariableDeclarations) - .map(it -> (J.VariableDeclarations) it) + .filter(J.VariableDeclarations.class::isInstance) + .map(J.VariableDeclarations.class::cast) .filter(it -> it.getVariables().contains(v)) .findFirst(); if (variableParameter.isPresent()) { diff --git a/src/main/java/org/openrewrite/staticanalysis/NoDoubleBraceInitialization.java b/src/main/java/org/openrewrite/staticanalysis/NoDoubleBraceInitialization.java index 496074a55..4a24e7ff8 100644 --- a/src/main/java/org/openrewrite/staticanalysis/NoDoubleBraceInitialization.java +++ b/src/main/java/org/openrewrite/staticanalysis/NoDoubleBraceInitialization.java @@ -97,7 +97,7 @@ public J.NewClass visitNewClass(J.NewClass newClass, ExecutionContext executionC List initStatements = secondBlock.getStatements(); boolean maybeMistakenlyMissedAddingElement = !initStatements.isEmpty() - && initStatements.stream().allMatch(statement -> statement instanceof J.NewClass); + && initStatements.stream().allMatch(J.NewClass.class::isInstance); if (maybeMistakenlyMissedAddingElement) { JavaType newClassType = nc.getType(); @@ -115,7 +115,7 @@ public J.NewClass visitNewClass(J.NewClass newClass, ExecutionContext executionC if (parentBlockCursor.getParent().getValue() instanceof J.ClassDeclaration) { JavaType.FullyQualified fq = TypeUtils.asFullyQualified(nc.getType()); if (fq != null && fq.getSupertype() != null) { - Cursor varDeclsCursor = getCursor().dropParentUntil(parent -> parent instanceof J.VariableDeclarations); + Cursor varDeclsCursor = getCursor().dropParentUntil(J.VariableDeclarations.class::isInstance); Cursor namedVarCursor = getCursor().dropParentUntil(J.VariableDeclarations.NamedVariable.class::isInstance); namedVarCursor.putMessage("DROP_INITIALIZER", Boolean.TRUE); @@ -129,7 +129,7 @@ public J.NewClass visitNewClass(J.NewClass newClass, ExecutionContext executionC } } else if (parentBlockCursor.getParent().getValue() instanceof J.MethodDeclaration) { initStatements = addSelectToInitStatements(initStatements, var.getName(), executionContext); - Cursor varDeclsCursor = getCursor().dropParentUntil(parent -> parent instanceof J.VariableDeclarations); + Cursor varDeclsCursor = getCursor().dropParentUntil(J.VariableDeclarations.class::isInstance); parentBlockCursor.computeMessageIfAbsent("METHOD_DECL_STATEMENTS", v -> new HashMap>()).put(varDeclsCursor.getValue(), initStatements); nc = nc.withBody(null); } diff --git a/src/main/java/org/openrewrite/staticanalysis/RemoveEmptyJavaDocParameters.java b/src/main/java/org/openrewrite/staticanalysis/RemoveEmptyJavaDocParameters.java index 1758148c2..da5103a04 100644 --- a/src/main/java/org/openrewrite/staticanalysis/RemoveEmptyJavaDocParameters.java +++ b/src/main/java/org/openrewrite/staticanalysis/RemoveEmptyJavaDocParameters.java @@ -56,7 +56,7 @@ public TreeVisitor getVisitor() { @Override public J.MethodDeclaration visitMethodDeclaration(J.MethodDeclaration method, ExecutionContext ctx) { J.MethodDeclaration md = super.visitMethodDeclaration(method, ctx); - if (md.getComments().stream().anyMatch(it -> it instanceof Javadoc.DocComment)) { + if (md.getComments().stream().anyMatch(Javadoc.DocComment.class::isInstance)) { md = md.withComments(ListUtils.map(md.getComments(), it -> { if (it instanceof Javadoc.DocComment) { Javadoc.DocComment docComment = (Javadoc.DocComment) it; @@ -161,11 +161,11 @@ public Javadoc visitDocComment(Javadoc.DocComment javadoc, ExecutionContext ctx) } public boolean isEmptyParameter(Javadoc.Parameter parameter) { - return parameter.getDescription().stream().allMatch(it -> it instanceof Javadoc.LineBreak); + return parameter.getDescription().stream().allMatch(Javadoc.LineBreak.class::isInstance); } public boolean isEmptyReturn(Javadoc.Return aReturn) { - return aReturn.getDescription().stream().allMatch(it -> it instanceof Javadoc.LineBreak); + return aReturn.getDescription().stream().allMatch(Javadoc.LineBreak.class::isInstance); } public boolean isEmptyErroneous(Javadoc.Erroneous erroneous) { diff --git a/src/main/java/org/openrewrite/staticanalysis/RemoveExtraSemicolons.java b/src/main/java/org/openrewrite/staticanalysis/RemoveExtraSemicolons.java index 40e019c04..74b4b3a87 100644 --- a/src/main/java/org/openrewrite/staticanalysis/RemoveExtraSemicolons.java +++ b/src/main/java/org/openrewrite/staticanalysis/RemoveExtraSemicolons.java @@ -82,7 +82,7 @@ public J.Block visitBlock(final J.Block block, final ExecutionContext executionC @Override public J.Try.Resource visitTryResource(J.Try.Resource tr, ExecutionContext executionContext) { - J.Try _try = getCursor().dropParentUntil(is -> is instanceof J.Try).getValue(); + J.Try _try = getCursor().dropParentUntil(J.Try.class::isInstance).getValue(); if (_try.getResources().isEmpty() || _try.getResources().get(_try.getResources().size() - 1) != tr || !_try.getResources().get(_try.getResources().size() - 1).isTerminatedWithSemicolon()) { diff --git a/src/main/java/org/openrewrite/staticanalysis/RemoveUnneededBlock.java b/src/main/java/org/openrewrite/staticanalysis/RemoveUnneededBlock.java index 33f3503d5..f1b748b14 100644 --- a/src/main/java/org/openrewrite/staticanalysis/RemoveUnneededBlock.java +++ b/src/main/java/org/openrewrite/staticanalysis/RemoveUnneededBlock.java @@ -81,7 +81,7 @@ private J.Block maybeInlineBlock(J.Block block, ExecutionContext ctx) { } // blocks are relevant for scoping, so don't flatten them if they contain variable declarations - if (i < statements.size() - 1 && nested.getStatements().stream().anyMatch(s -> s instanceof J.VariableDeclarations)) { + if (i < statements.size() - 1 && nested.getStatements().stream().anyMatch(J.VariableDeclarations.class::isInstance)) { return stmt; } diff --git a/src/main/java/org/openrewrite/staticanalysis/RemoveUnusedLocalVariables.java b/src/main/java/org/openrewrite/staticanalysis/RemoveUnusedLocalVariables.java index 6e5c3528a..a8f4d9c3e 100644 --- a/src/main/java/org/openrewrite/staticanalysis/RemoveUnusedLocalVariables.java +++ b/src/main/java/org/openrewrite/staticanalysis/RemoveUnusedLocalVariables.java @@ -159,7 +159,7 @@ public J.VariableDeclarations visitVariableDeclarations(J.VariableDeclarations m J.VariableDeclarations mv = super.visitVariableDeclarations(multiVariable, ctx); if (mv.getVariables().isEmpty()) { if (!mv.getPrefix().getComments().isEmpty()) { - getCursor().dropParentUntil(is -> is instanceof J.ClassDeclaration).putMessage("COMMENTS_KEY", mv.getPrefix().getComments()); + getCursor().dropParentUntil(J.ClassDeclaration.class::isInstance).putMessage("COMMENTS_KEY", mv.getPrefix().getComments()); } doAfterVisit(new DeleteStatement<>(mv)); } diff --git a/src/main/java/org/openrewrite/staticanalysis/ReplaceDuplicateStringLiterals.java b/src/main/java/org/openrewrite/staticanalysis/ReplaceDuplicateStringLiterals.java index 437a64087..2a371aaf0 100644 --- a/src/main/java/org/openrewrite/staticanalysis/ReplaceDuplicateStringLiterals.java +++ b/src/main/java/org/openrewrite/staticanalysis/ReplaceDuplicateStringLiterals.java @@ -115,8 +115,8 @@ public J visitClassDeclaration(J.ClassDeclaration classDecl, ExecutionContext ct String insertStatement = "private static final String " + variableName + " = #{any(String)};"; if (classDecl.getKind() == J.ClassDeclaration.Kind.Type.Enum) { J.EnumValueSet enumValueSet = classDecl.getBody().getStatements().stream() - .filter(it -> it instanceof J.EnumValueSet) - .map(it -> (J.EnumValueSet) it) + .filter(J.EnumValueSet.class::isInstance) + .map(J.EnumValueSet.class::cast) .findFirst() .orElse(null); diff --git a/src/main/java/org/openrewrite/staticanalysis/ReplaceOptionalIsPresentWithIfPresent.java b/src/main/java/org/openrewrite/staticanalysis/ReplaceOptionalIsPresentWithIfPresent.java index bbce0ac02..77e4d83ee 100644 --- a/src/main/java/org/openrewrite/staticanalysis/ReplaceOptionalIsPresentWithIfPresent.java +++ b/src/main/java/org/openrewrite/staticanalysis/ReplaceOptionalIsPresentWithIfPresent.java @@ -213,7 +213,7 @@ private static List collectFields(J.ClassDeclaration classDecl) { return classDecl.getBody() .getStatements() .stream() - .filter(statement -> statement instanceof J.VariableDeclarations) + .filter(J.VariableDeclarations.class::isInstance) .map(J.VariableDeclarations.class::cast) .map(J.VariableDeclarations::getVariables) .flatMap(Collection::stream) diff --git a/src/main/java/org/openrewrite/staticanalysis/UseLambdaForFunctionalInterface.java b/src/main/java/org/openrewrite/staticanalysis/UseLambdaForFunctionalInterface.java index c97e0bac2..6e65b2d89 100644 --- a/src/main/java/org/openrewrite/staticanalysis/UseLambdaForFunctionalInterface.java +++ b/src/main/java/org/openrewrite/staticanalysis/UseLambdaForFunctionalInterface.java @@ -277,7 +277,7 @@ public J visitIdentifier(J.Identifier ident, Integer integer) { private static List parameterNames(J.MethodDeclaration method) { return method.getParameters().stream() - .filter(s -> s instanceof J.VariableDeclarations) + .filter(J.VariableDeclarations.class::isInstance) .map(v -> ((J.VariableDeclarations) v).getVariables().get(0).getSimpleName()) .collect(Collectors.toList()); } @@ -285,7 +285,7 @@ private static List parameterNames(J.MethodDeclaration method) { // This does not recursive descend extended classes for inherited fields. private static List classFields(J.ClassDeclaration classDeclaration) { return classDeclaration.getBody().getStatements().stream() - .filter(s -> s instanceof J.VariableDeclarations) + .filter(J.VariableDeclarations.class::isInstance) .map(v -> ((J.VariableDeclarations) v).getVariables().get(0).getSimpleName()) .collect(Collectors.toList()); } From 108c66df7d1e8a677ee43c2db90e5ed7e3b11ec0 Mon Sep 17 00:00:00 2001 From: Knut Wannheden Date: Sun, 22 Oct 2023 00:37:30 +0200 Subject: [PATCH 55/92] Manually create LST for enums in `MinimumSwitchCases` When the types are not known in advance the `JavaTemplate` classpath can also not be configured for these types. As a consequence the corresponding elements cannot be properly type-attributed. So that the result of `MinimumSwitchCases` is properly type-attributed when transforming a `switch` over an enum, the recipe now manually creates the LST. Then the `ShortenFullyQualifiedTypeReferences` visitor also works properly. --- .../staticanalysis/JavaElementFactory.java | 12 +++ .../staticanalysis/MinimumSwitchCases.java | 79 ++++++++++++------- .../MinimumSwitchCasesTest.java | 34 +++++++- 3 files changed, 95 insertions(+), 30 deletions(-) diff --git a/src/main/java/org/openrewrite/staticanalysis/JavaElementFactory.java b/src/main/java/org/openrewrite/staticanalysis/JavaElementFactory.java index ce9e4d057..ed66b14c4 100644 --- a/src/main/java/org/openrewrite/staticanalysis/JavaElementFactory.java +++ b/src/main/java/org/openrewrite/staticanalysis/JavaElementFactory.java @@ -27,6 +27,18 @@ final class JavaElementFactory { + static J.Binary newLogicalExpression(J.Binary.Type operator, Expression left, Expression right) { + return new J.Binary( + randomId(), + Space.EMPTY, + Markers.EMPTY, + left, + new JLeftPadded<>(Space.SINGLE_SPACE, operator, Markers.EMPTY), + right, + JavaType.Primitive.Boolean + ); + } + static J.MemberReference newStaticMethodReference(JavaType.Method method, boolean qualified, @Nullable JavaType type) { JavaType.FullyQualified declaringType = method.getDeclaringType(); Expression containing = className(declaringType, qualified); diff --git a/src/main/java/org/openrewrite/staticanalysis/MinimumSwitchCases.java b/src/main/java/org/openrewrite/staticanalysis/MinimumSwitchCases.java index c7d8e67ee..0c4dd9a56 100644 --- a/src/main/java/org/openrewrite/staticanalysis/MinimumSwitchCases.java +++ b/src/main/java/org/openrewrite/staticanalysis/MinimumSwitchCases.java @@ -18,9 +18,9 @@ import lombok.AllArgsConstructor; import lombok.Value; import lombok.With; +import org.jetbrains.annotations.NotNull; import org.openrewrite.ExecutionContext; import org.openrewrite.Recipe; -import org.openrewrite.Tree; import org.openrewrite.TreeVisitor; import org.openrewrite.internal.ListUtils; import org.openrewrite.internal.RecipeRunException; @@ -29,6 +29,7 @@ import org.openrewrite.java.ShortenFullyQualifiedTypeReferences; import org.openrewrite.java.tree.*; import org.openrewrite.marker.Marker; +import org.openrewrite.marker.Markers; import java.time.Duration; import java.util.ArrayList; @@ -39,6 +40,7 @@ import static java.util.Collections.singleton; import static java.util.Collections.singletonList; import static java.util.Objects.requireNonNull; +import static org.openrewrite.Tree.randomId; public class MinimumSwitchCases extends Recipe { @Override @@ -73,12 +75,7 @@ public TreeVisitor getVisitor() { final JavaTemplate ifElseIfString = JavaTemplate.builder("" + "if(#{any(java.lang.String)}.equals(#{any(java.lang.String)})) {\n" + "} else if(#{any(java.lang.String)}.equals(#{any(java.lang.String)})) {\n" + - "}").contextSensitive().build(); - - final JavaTemplate ifElseIfEnum = JavaTemplate.builder("" + - "if(#{any()} == #{}) {\n" + - "} else if(#{any()} == #{}) {\n" + - "}").contextSensitive().build(); + "}").build(); final JavaTemplate ifElsePrimitive = JavaTemplate.builder("" + "if(#{any()} == #{any()}) {\n" + @@ -88,12 +85,7 @@ public TreeVisitor getVisitor() { final JavaTemplate ifElseString = JavaTemplate.builder("" + "if(#{any(java.lang.String)}.equals(#{any(java.lang.String)})) {\n" + "} else {\n" + - "}").contextSensitive().build(); - - final JavaTemplate ifElseEnum = JavaTemplate.builder("" + - "if(#{any()} == #{}) {\n" + - "} else {\n" + - "}").contextSensitive().build(); + "}").build(); final JavaTemplate ifPrimitive = JavaTemplate.builder("" + "if(#{any()} == #{any()}) {\n" + @@ -101,11 +93,7 @@ public TreeVisitor getVisitor() { final JavaTemplate ifString = JavaTemplate.builder("" + "if(#{any(java.lang.String)}.equals(#{any(java.lang.String)})) {\n" + - "}").contextSensitive().build(); - - final JavaTemplate ifEnum = JavaTemplate.builder("" + - "if(#{any()} == #{}) {\n" + - "}").contextSensitive().build(); + "}").build(); @Override public J visitBlock(J.Block block, ExecutionContext executionContext) { @@ -174,18 +162,26 @@ public J visitSwitch(J.Switch switch_, ExecutionContext ctx) { generatedIf = ifElseIfString.apply(getCursor(), switch_.getCoordinates().replace(), cases[0].getPattern(), tree, cases[1].getPattern(), tree); } } else if (switchesOnEnum(switch_)) { - if (cases[1] == null) { - if (isDefault(cases[0])) { - return switch_.withMarkers(switch_.getMarkers().add(new DefaultOnly())); - } else { - generatedIf = ifEnum.apply(getCursor(), switch_.getCoordinates().replace(), tree, enumIdentToFieldAccessString(cases[0].getPattern())); + if (cases[1] == null && isDefault(cases[0])) { + return switch_.withMarkers(switch_.getMarkers().add(new DefaultOnly())); + } + + generatedIf = createIfForEnum(tree, cases[0].getPattern()); + if (cases[1] != null) { + Statement elseBody = J.Block.createEmptyBlock(); + if (!isDefault(cases[1])) { + elseBody = createIfForEnum(tree, cases[1].getPattern()); } - } else if (isDefault(cases[1])) { - generatedIf = ifElseEnum.apply(getCursor(), switch_.getCoordinates().replace(), tree, enumIdentToFieldAccessString(cases[0].getPattern())); - } else { - generatedIf = ifElseIfEnum.apply(getCursor(), switch_.getCoordinates().replace(), tree, enumIdentToFieldAccessString(cases[0].getPattern()), tree, enumIdentToFieldAccessString(cases[1].getPattern())); + generatedIf = generatedIf + .withElsePart(new J.If.Else( + randomId(), + Space.EMPTY, + Markers.EMPTY, + JRightPadded.build(elseBody) + ) + ); } - doAfterVisit(new ShortenFullyQualifiedTypeReferences().getVisitor()); + doAfterVisit(ShortenFullyQualifiedTypeReferences.modifyOnly(generatedIf)); } else { if (cases[1] == null) { if (isDefault(cases[0])) { @@ -264,6 +260,31 @@ private String enumIdentToFieldAccessString(Expression casePattern) { }; } + @NotNull + private static J.If createIfForEnum(Expression expression, Expression enumTree) { + J.If generatedIf; + if (enumTree instanceof J.Identifier) { + enumTree = new J.FieldAccess( + randomId(), + enumTree.getPrefix(), + Markers.EMPTY, + JavaElementFactory.className(enumTree.getType(), true), + JLeftPadded.build(enumTree.withPrefix(Space.EMPTY)), + enumTree.getType() + ); + } + J.Binary ifCond = JavaElementFactory.newLogicalExpression(J.Binary.Type.Equal, expression, enumTree); + generatedIf = new J.If( + randomId(), + Space.EMPTY, + Markers.EMPTY, + new J.ControlParentheses<>(randomId(), Space.EMPTY, Markers.EMPTY, JRightPadded.build(ifCond)), + JRightPadded.build(J.Block.createEmptyBlock()), + null + ); + return generatedIf; + } + @Value @With @AllArgsConstructor @@ -271,7 +292,7 @@ private static class DefaultOnly implements Marker { UUID id; public DefaultOnly() { - id = Tree.randomId(); + id = randomId(); } } } diff --git a/src/test/java/org/openrewrite/staticanalysis/MinimumSwitchCasesTest.java b/src/test/java/org/openrewrite/staticanalysis/MinimumSwitchCasesTest.java index 71a985064..97525eadc 100644 --- a/src/test/java/org/openrewrite/staticanalysis/MinimumSwitchCasesTest.java +++ b/src/test/java/org/openrewrite/staticanalysis/MinimumSwitchCasesTest.java @@ -494,7 +494,7 @@ void importsOnEnumImplied() { java( """ import java.time.LocalDate; - + class Test { void test(LocalDate date) { switch(date.getDayOfWeek()) { @@ -647,6 +647,38 @@ void doSomethingElse() {} ); } + @Test + void nestedEnum() { + rewriteRun( + //language=java + java( + """ + class Test { + int test(java.io.ObjectInputFilter filter) { + switch (filter.checkInput(null)) { + case ALLOWED: return 0; + default: return 1; + } + } + } + """, + """ + import java.io.ObjectInputFilter; + + class Test { + int test(java.io.ObjectInputFilter filter) { + if (filter.checkInput(null) == ObjectInputFilter.Status.ALLOWED) { + return 0; + } else { + return 1; + } + } + } + """ + ) + ); + } + @Test @Issue("https://github.com/openrewrite/rewrite/issues/3076") void multipleSwitchExpressions() { From fd5ce6c2f454fde5fd73500c8ec29245e1b27856 Mon Sep 17 00:00:00 2001 From: Knut Wannheden Date: Sun, 22 Oct 2023 01:13:38 +0200 Subject: [PATCH 56/92] Polish `MinimumSwitchCases` --- .../staticanalysis/MinimumSwitchCases.java | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/src/main/java/org/openrewrite/staticanalysis/MinimumSwitchCases.java b/src/main/java/org/openrewrite/staticanalysis/MinimumSwitchCases.java index 0c4dd9a56..055e98185 100644 --- a/src/main/java/org/openrewrite/staticanalysis/MinimumSwitchCases.java +++ b/src/main/java/org/openrewrite/staticanalysis/MinimumSwitchCases.java @@ -18,7 +18,6 @@ import lombok.AllArgsConstructor; import lombok.Value; import lombok.With; -import org.jetbrains.annotations.NotNull; import org.openrewrite.ExecutionContext; import org.openrewrite.Recipe; import org.openrewrite.TreeVisitor; @@ -39,7 +38,6 @@ import static java.util.Collections.singleton; import static java.util.Collections.singletonList; -import static java.util.Objects.requireNonNull; import static org.openrewrite.Tree.randomId; public class MinimumSwitchCases extends Recipe { @@ -247,20 +245,9 @@ private boolean switchesOnEnum(J.Switch switch_) { && ((JavaType.Class) selectorType).getKind() == JavaType.Class.Kind.Enum; } - private String enumIdentToFieldAccessString(Expression casePattern) { - String caseType = requireNonNull(TypeUtils.asFullyQualified(casePattern.getType())).getFullyQualifiedName(); - if (casePattern instanceof J.FieldAccess) { - // may be a field access in Groovy - return caseType + "." + ((J.FieldAccess) casePattern).getSimpleName(); - } - // must be an identifier in Java - return caseType + "." + ((J.Identifier) casePattern).getSimpleName(); - } - }; } - @NotNull private static J.If createIfForEnum(Expression expression, Expression enumTree) { J.If generatedIf; if (enumTree instanceof J.Identifier) { From 991d2d12f2d1b2029c8dc659c6c7e5439eff332e Mon Sep 17 00:00:00 2001 From: Knut Wannheden Date: Sun, 22 Oct 2023 14:43:21 +0200 Subject: [PATCH 57/92] Correct a few missing types in before sources --- .../java/org/openrewrite/staticanalysis/FallThroughTest.java | 2 +- .../org/openrewrite/staticanalysis/UseDiamondOperatorTest.java | 2 +- .../staticanalysis/UseLambdaForFunctionalInterfaceTest.java | 2 ++ 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/test/java/org/openrewrite/staticanalysis/FallThroughTest.java b/src/test/java/org/openrewrite/staticanalysis/FallThroughTest.java index 4dc0f3ffe..2ab675459 100644 --- a/src/test/java/org/openrewrite/staticanalysis/FallThroughTest.java +++ b/src/test/java/org/openrewrite/staticanalysis/FallThroughTest.java @@ -44,7 +44,7 @@ void switchInSwitch() { java( """ class Test { - void test() { + void test(int day) { switch (day) { case 1: int month = 1; diff --git a/src/test/java/org/openrewrite/staticanalysis/UseDiamondOperatorTest.java b/src/test/java/org/openrewrite/staticanalysis/UseDiamondOperatorTest.java index ac7ecb5d0..d7026ebdc 100644 --- a/src/test/java/org/openrewrite/staticanalysis/UseDiamondOperatorTest.java +++ b/src/test/java/org/openrewrite/staticanalysis/UseDiamondOperatorTest.java @@ -505,7 +505,7 @@ class Test { public R method(IVisitor visitor) { return null; } - private void test(Tree t) { + private void test(Object t) { String s = method(new IVisitor() { @Override public void visit(Integer object, String ret) { } diff --git a/src/test/java/org/openrewrite/staticanalysis/UseLambdaForFunctionalInterfaceTest.java b/src/test/java/org/openrewrite/staticanalysis/UseLambdaForFunctionalInterfaceTest.java index 63bc1a032..a74288059 100644 --- a/src/test/java/org/openrewrite/staticanalysis/UseLambdaForFunctionalInterfaceTest.java +++ b/src/test/java/org/openrewrite/staticanalysis/UseLambdaForFunctionalInterfaceTest.java @@ -89,10 +89,12 @@ void gson() { //language=java java( """ + import com.google.gson.JsonSerializationContext; import com.google.gson.GsonBuilder; import com.google.gson.JsonPrimitive; import com.google.gson.JsonSerializer; import java.time.LocalDateTime; + import java.lang.reflect.Type; class Test { void test() { From e048128b9c86d5eba7125dfb0c8e41cb5d5b283e Mon Sep 17 00:00:00 2001 From: Tyler Kindy Date: Mon, 23 Oct 2023 04:20:52 -0400 Subject: [PATCH 58/92] Simplify `if`s with literal conditions even if they jump (#193) * Get rid of dead code after returns or throws * Operate on all blocks * Handle break and continue * Fix style * Add failing test for return in else block * Check for jumps in branch we're keeping * Improve tests * Prefix J subclasses * Fix style * Do nothing if jump at end of block * Add nested test * Add license header * Add blockless tests * Remove unnecessary Lombok annotation * Remove jump check This isn't necessary; the visitor already look for jumps. I thought about keeping it as an optimization, but I don't think it actually optimizes anything since the visitor has to rediscover the jumps anyways. * Update RemoveUnreachableCodeVisitor.java Co-authored-by: Tim te Beek * Add test with try/catch/throws --------- Co-authored-by: Tim te Beek Co-authored-by: Tim te Beek Co-authored-by: Peter Streef --- .../RemoveUnreachableCodeVisitor.java | 74 +++ .../SimplifyConstantIfBranchExecution.java | 43 +- ...SimplifyConstantIfBranchExecutionTest.java | 504 +++++++++++++++++- 3 files changed, 565 insertions(+), 56 deletions(-) create mode 100644 src/main/java/org/openrewrite/staticanalysis/RemoveUnreachableCodeVisitor.java diff --git a/src/main/java/org/openrewrite/staticanalysis/RemoveUnreachableCodeVisitor.java b/src/main/java/org/openrewrite/staticanalysis/RemoveUnreachableCodeVisitor.java new file mode 100644 index 000000000..5d1b6868e --- /dev/null +++ b/src/main/java/org/openrewrite/staticanalysis/RemoveUnreachableCodeVisitor.java @@ -0,0 +1,74 @@ +/* + * Copyright 2023 the original author or authors. + *

+ * 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 + *

+ * https://www.apache.org/licenses/LICENSE-2.0 + *

+ * 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.ExecutionContext; +import org.openrewrite.internal.ListUtils; +import org.openrewrite.java.JavaVisitor; +import org.openrewrite.java.tree.J; +import org.openrewrite.java.tree.Statement; + +import java.util.Collections; +import java.util.List; +import java.util.Optional; + +class RemoveUnreachableCodeVisitor extends JavaVisitor { + + @Override + public J visitBlock(J.Block block, ExecutionContext executionContext) { + block = (J.Block) super.visitBlock(block, executionContext); + + List statements = block.getStatements(); + Optional maybeFirstJumpIndex = findFirstJump(statements); + if (!maybeFirstJumpIndex.isPresent()) { + return block; + } + int firstJumpIndex = maybeFirstJumpIndex.get(); + + if (firstJumpIndex == statements.size() - 1) { + // Jump is at the end of the block, so nothing to do + return block; + } + + List newStatements = + ListUtils.flatMap( + block.getStatements(), + (index, statement) -> { + if (index <= firstJumpIndex) { + return statement; + } + return Collections.emptyList(); + } + ); + + return block.withStatements(newStatements); + } + + private Optional findFirstJump(List statements) { + for (int i = 0; i < statements.size(); i++) { + Statement statement = statements.get(i); + if ( + statement instanceof J.Return || + statement instanceof J.Throw || + statement instanceof J.Break || + statement instanceof J.Continue + ) { + return Optional.of(i); + } + } + return Optional.empty(); + } +} diff --git a/src/main/java/org/openrewrite/staticanalysis/SimplifyConstantIfBranchExecution.java b/src/main/java/org/openrewrite/staticanalysis/SimplifyConstantIfBranchExecution.java index b3273e3e6..8e61a25f7 100644 --- a/src/main/java/org/openrewrite/staticanalysis/SimplifyConstantIfBranchExecution.java +++ b/src/main/java/org/openrewrite/staticanalysis/SimplifyConstantIfBranchExecution.java @@ -20,7 +20,6 @@ import org.openrewrite.SourceFile; import org.openrewrite.TreeVisitor; import org.openrewrite.internal.lang.Nullable; -import org.openrewrite.java.JavaIsoVisitor; import org.openrewrite.java.JavaVisitor; import org.openrewrite.java.cleanup.SimplifyBooleanExpressionVisitor; import org.openrewrite.java.cleanup.UnnecessaryParenthesesVisitor; @@ -32,7 +31,6 @@ import org.openrewrite.java.tree.Statement; import java.util.Optional; -import java.util.concurrent.atomic.AtomicBoolean; public class SimplifyConstantIfBranchExecution extends Recipe { @@ -93,10 +91,6 @@ public J visitIf(J.If if_, ExecutionContext context) { J.ControlParentheses cp = cleanupBooleanExpression(if__.getIfCondition(), context); if__ = if__.withIfCondition(cp); - if (visitsKeyWord(if__)) { - return if__; - } - // The compile-time constant value of the if condition control parentheses. final Optional compileTimeConstantBoolean; if (isLiteralTrue(cp.getTree())) { @@ -115,6 +109,7 @@ public J visitIf(J.If if_, ExecutionContext context) { // True branch // Only keep the `then` branch, and remove the `else` branch. Statement s = if__.getThenPart().withPrefix(if__.getPrefix()); + doAfterVisit(new RemoveUnreachableCodeVisitor()); return maybeAutoFormat( if__, s, @@ -126,6 +121,7 @@ public J visitIf(J.If if_, ExecutionContext context) { if (if__.getElsePart() != null) { // The `else` part needs to be kept Statement s = if__.getElsePart().getBody().withPrefix(if__.getPrefix()); + doAfterVisit(new RemoveUnreachableCodeVisitor()); return maybeAutoFormat( if__, s, @@ -151,41 +147,6 @@ public J visitIf(J.If if_, ExecutionContext context) { } } - private boolean visitsKeyWord(J.If iff) { - if (isLiteralFalse(iff.getIfCondition().getTree())) { - return false; - } - - AtomicBoolean visitedCFKeyword = new AtomicBoolean(false); - // if there is a return, break, continue, throws in _then, then set visitedKeyword to true - new JavaIsoVisitor() { - @Override - public J.Return visitReturn(J.Return _return, AtomicBoolean atomicBoolean) { - atomicBoolean.set(true); - return _return; - } - - @Override - public J.Continue visitContinue(J.Continue continueStatement, AtomicBoolean atomicBoolean) { - atomicBoolean.set(true); - return continueStatement; - } - - @Override - public J.Break visitBreak(J.Break breakStatement, AtomicBoolean atomicBoolean) { - atomicBoolean.set(true); - return breakStatement; - } - - @Override - public J.Throw visitThrow(J.Throw thrown, AtomicBoolean atomicBoolean) { - atomicBoolean.set(true); - return thrown; - } - }.visit(iff.getThenPart(), visitedCFKeyword); - return visitedCFKeyword.get(); - } - private static boolean isLiteralTrue(@Nullable Expression expression) { return J.Literal.isLiteralValue(expression, Boolean.TRUE); } diff --git a/src/test/java/org/openrewrite/staticanalysis/SimplifyConstantIfBranchExecutionTest.java b/src/test/java/org/openrewrite/staticanalysis/SimplifyConstantIfBranchExecutionTest.java index 6e1ab060c..9393e24df 100644 --- a/src/test/java/org/openrewrite/staticanalysis/SimplifyConstantIfBranchExecutionTest.java +++ b/src/test/java/org/openrewrite/staticanalysis/SimplifyConstantIfBranchExecutionTest.java @@ -666,18 +666,54 @@ public void test() { } @Test - void doesNotRemoveWhenReturnInIfBlock() { + void removesWhenReturnInThenBlock() { rewriteRun( //language=java java( """ public class A { public void test() { + System.out.println("before"); if (true) { - System.out.println("hello"); + System.out.println("then"); return; } - System.out.println("goodbye"); + System.out.println("after"); + } + } + """, + """ + public class A { + public void test() { + System.out.println("before"); + System.out.println("then"); + return; + } + } + """ + ) + ); + } + + @Test + void removesWhenReturnInThenNoBlock() { + rewriteRun( + //language=java + java( + """ + public class A { + public void test() { + System.out.println("before"); + if (true) return; + System.out.println("after"); + } + } + """, + """ + public class A { + public void test() { + System.out.println("before"); + return; } } """ @@ -686,18 +722,151 @@ public void test() { } @Test - void doesNotRemoveWhenThrowsInIfBlock() { + void removesWhenReturnInThenBlockWithElse() { rewriteRun( //language=java java( """ public class A { public void test() { + System.out.println("before"); if (true) { - System.out.println("hello"); + System.out.println("then"); + return; + } else { + System.out.println("else"); + } + System.out.println("after"); + } + } + """, + """ + public class A { + public void test() { + System.out.println("before"); + System.out.println("then"); + return; + } + }""" + ) + ); + } + + @Test + void removesWhenReturnInElseBlock() { + rewriteRun( + //language=java + java( + """ + public class A { + public void test() { + System.out.println("before"); + if (false) { + System.out.println("then"); + } else { + System.out.println("else"); + return; + } + System.out.println("after"); + } + } + """, + """ + public class A { + public void test() { + System.out.println("before"); + System.out.println("else"); + return; + } + } + """ + ) + ); + } + + @Test + void removesWhenReturnInElseNoBlock() { + rewriteRun( + //language=java + java( + """ + public class A { + public void test() { + System.out.println("before"); + if (false) { + System.out.println("then"); + } else return; + System.out.println("after"); + } + } + """, + """ + public class A { + public void test() { + System.out.println("before"); + return; + } + } + """ + ) + ); + } + + @Test + void removesWhenThrowsInThenBlock() { + rewriteRun( + //language=java + java( + """ + public class A { + public void test() { + System.out.println("before"); + if (true) { + System.out.println("then"); throw new RuntimeException(); } - System.out.println("goodbye"); + System.out.println("after"); + } + } + """, + """ + public class A { + public void test() { + System.out.println("before"); + System.out.println("then"); + throw new RuntimeException(); + } + } + """ + ) + ); + } + + @Test + void removesWhenThrowsInThenBlockWithElse() { + rewriteRun( + //language=java + java( + """ + public class A { + public void test() { + System.out.println("before"); + if (true) { + System.out.println("then"); + throw new RuntimeException(); + } else { + System.out.println("else"); + } + System.out.println("after"); + } + } + """, + """ + public class A { + public void test() { + System.out.println("before"); + System.out.println("then"); + throw new RuntimeException(); } } """ @@ -706,21 +875,68 @@ public void test() { } @Test - void doesNotRemoveWhenBreakInIfBlockWithinWhile() { + void removesWhenThrowsInElseBlock() { rewriteRun( //language=java java( """ public class A { public void test() { - while (true){ + System.out.println("before"); + if (false) { + System.out.println("then"); + } else { + System.out.println("else"); + throw new RuntimeException(); + } + System.out.println("after"); + } + } + """, + """ + public class A { + public void test() { + System.out.println("before"); + System.out.println("else"); + throw new RuntimeException(); + } + } + """ + ) + ); + } + + @Test + void removesWhenBreakInThenBlockWithinWhile() { + rewriteRun( + //language=java + java( + """ + public class A { + public void test() { + System.out.println("before while"); + while (true) { + System.out.println("before if"); if (true) { - System.out.println("hello"); + System.out.println("then"); break; } - System.out.println("goodbye"); + System.out.println("after if"); } - System.out.println("goodbye"); + System.out.println("after while"); + } + } + """, + """ + public class A { + public void test() { + System.out.println("before while"); + while (true) { + System.out.println("before if"); + System.out.println("then"); + break; + } + System.out.println("after while"); } } """ @@ -729,21 +945,231 @@ public void test() { } @Test - void doesNotRemoveWhenContinueInIfBlockWithinWhile() { + void removesWhenBreakInThenBlockWithElseWithinWhile() { rewriteRun( //language=java java( """ public class A { public void test() { + System.out.println("before while"); while (true) { + System.out.println("before if"); if (true) { - System.out.println("hello"); + System.out.println("then"); + break; + } else { + System.out.println("else"); + } + System.out.println("after if"); + } + System.out.println("after while"); + } + } + """, + """ + public class A { + public void test() { + System.out.println("before while"); + while (true) { + System.out.println("before if"); + System.out.println("then"); + break; + } + System.out.println("after while"); + } + } + """ + ) + ); + } + + @Test + void removesWhenBreakInElseBlockWithinWhile() { + rewriteRun( + //language=java + java( + """ + public class A { + public void test() { + System.out.println("before while"); + while (true) { + System.out.println("before if"); + if (false) { + System.out.println("then"); + } else { + System.out.println("else"); + break; + } + System.out.println("after if"); + } + System.out.println("after while"); + } + } + """, + """ + public class A { + public void test() { + System.out.println("before while"); + while (true) { + System.out.println("before if"); + System.out.println("else"); + break; + } + System.out.println("after while"); + } + } + """ + ) + ); + } + + @Test + void removesWhenContinueInThenBlockWithinWhile() { + rewriteRun( + //language=java + java( + """ + public class A { + public void test() { + System.out.println("before while"); + while (true) { + System.out.println("before if"); + if (true) { + System.out.println("then"); continue; } - System.out.println("goodbye"); + System.out.println("after if"); } - System.out.println("goodbye"); + System.out.println("after while"); + } + } + """, + """ + public class A { + public void test() { + System.out.println("before while"); + while (true) { + System.out.println("before if"); + System.out.println("then"); + continue; + } + System.out.println("after while"); + } + } + """ + ) + ); + } + + @Test + void removesWhenContinueInThenBlockWithElseWithinWhile() { + rewriteRun( + //language=java + java( + """ + public class A { + public void test() { + System.out.println("before while"); + while (true) { + System.out.println("before if"); + if (true) { + System.out.println("then"); + continue; + } else { + System.out.println("else"); + } + System.out.println("after if"); + } + System.out.println("after while"); + } + } + """, + """ + public class A { + public void test() { + System.out.println("before while"); + while (true) { + System.out.println("before if"); + System.out.println("then"); + continue; + } + System.out.println("after while"); + } + } + """ + ) + ); + } + + @Test + void removesWhenContinueInElseBlockWithinWhile() { + rewriteRun( + //language=java + java( + """ + public class A { + public void test() { + System.out.println("before while"); + while (true) { + System.out.println("before if"); + if (false) { + System.out.println("then"); + } else { + System.out.println("else"); + continue; + } + System.out.println("after if"); + } + System.out.println("after while"); + } + } + """, + """ + public class A { + public void test() { + System.out.println("before while"); + while (true) { + System.out.println("before if"); + System.out.println("else"); + continue; + } + System.out.println("after while"); + } + } + """ + ) + ); + } + + @Test + void removesNestedWithReturn() { + rewriteRun( + //language=java + java( + """ + public class A { + public void test() { + System.out.println("before outer if"); + if (true) { + System.out.println("outer then"); + if (true) { + System.out.println("inner then"); + return; + } + System.out.println("after inner if"); + } + System.out.println("after outer if"); + } + } + """, + """ + public class A { + public void test() { + System.out.println("before outer if"); + System.out.println("outer then"); + System.out.println("inner then"); + return; } } """ @@ -751,6 +1177,54 @@ public void test() { ); } + @Test + void simplifyNestedWithReturnAndThrowInTryCatch() { + rewriteRun( + //language=java + java( + """ + public class A { + public void test() { + System.out.println("before outer if"); + if (true) { + System.out.println("outer then"); + if (true) { + try { + if(true) { + throw new RuntimeException("Explosion"); + } + return; + } catch (Exception ex) { + System.out.println("catch"); + } + } + System.out.println("after inner if"); + } + System.out.println("after outer if"); + } + } + """, + """ + public class A { + public void test() { + System.out.println("before outer if"); + System.out.println("outer then"); + try { + throw new RuntimeException("Explosion"); + } catch (Exception ex) { + System.out.println("catch"); + } + System.out.println("after inner if"); + System.out.println("after outer if"); + } + } + """ + ) + ); + } + + + @Test void binaryOrIsAlwaysFalse() { rewriteRun( From 6fa86aef9d4bc1511c363a650a6bcf3e7651d499 Mon Sep 17 00:00:00 2001 From: Peter Streef Date: Mon, 23 Oct 2023 11:40:40 +0200 Subject: [PATCH 59/92] Refactor: Simplify RemoveUnreachableCodeVisitor (#206) Using subList instead of helper method --- .../staticanalysis/RemoveUnreachableCodeVisitor.java | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/src/main/java/org/openrewrite/staticanalysis/RemoveUnreachableCodeVisitor.java b/src/main/java/org/openrewrite/staticanalysis/RemoveUnreachableCodeVisitor.java index 5d1b6868e..ba01cb821 100644 --- a/src/main/java/org/openrewrite/staticanalysis/RemoveUnreachableCodeVisitor.java +++ b/src/main/java/org/openrewrite/staticanalysis/RemoveUnreachableCodeVisitor.java @@ -43,17 +43,7 @@ public J visitBlock(J.Block block, ExecutionContext executionContext) { return block; } - List newStatements = - ListUtils.flatMap( - block.getStatements(), - (index, statement) -> { - if (index <= firstJumpIndex) { - return statement; - } - return Collections.emptyList(); - } - ); - + List newStatements = statements.subList(0, firstJumpIndex + 1); return block.withStatements(newStatements); } From a4ea014f60c0594f3350d18c8cba78e1aa2e553d Mon Sep 17 00:00:00 2001 From: Peter Streef Date: Mon, 23 Oct 2023 16:52:33 +0200 Subject: [PATCH 60/92] RenameLocalVariablesToCamelCase: Do not rename method argument names. (#205) * Fix: Many frameworks depend on method parameter names to use for request parameter names or json field names. By renaming these without knowing how the frameworks use these we can make breaking changes. It's better to do no change. * remove annotations add issue --- .../RenameLocalVariablesToCamelCase.java | 9 ++++-- .../RenameLocalVariablesToCamelCaseTest.java | 29 ++++++++++++++++--- 2 files changed, 32 insertions(+), 6 deletions(-) diff --git a/src/main/java/org/openrewrite/staticanalysis/RenameLocalVariablesToCamelCase.java b/src/main/java/org/openrewrite/staticanalysis/RenameLocalVariablesToCamelCase.java index 568155a68..8fa99363c 100644 --- a/src/main/java/org/openrewrite/staticanalysis/RenameLocalVariablesToCamelCase.java +++ b/src/main/java/org/openrewrite/staticanalysis/RenameLocalVariablesToCamelCase.java @@ -55,7 +55,7 @@ public String getDescription() { return "Reformat local variable and method parameter names to camelCase to comply with Java naming convention. " + "The recipe will not rename variables declared in for loop controls or catches with a single character. " + "The first character is set to lower case and existing capital letters are preserved. " + - "Special characters that are allowed in java field names `$` and `_` are removed. " + + "Special characters that are allowed in java field names `$` and `_` are removed (unless the name starts with one). " + "If a special character is removed the next valid alphanumeric will be capitalized. " + "Currently, does not support renaming members of classes. " + "The recipe will not rename a variable if the result already exists in the class, conflicts with a java reserved keyword, or the result is blank."; @@ -105,7 +105,7 @@ public J.VariableDeclarations visitVariableDeclarations(J.VariableDeclarations m private boolean isLocalVariable(J.VariableDeclarations mv) { // The recipe will not rename variables declared in for loop controls or catches. - if (!isInMethodDeclarationBody() || isDeclaredInForLoopControl() || isDeclaredInCatch()) { + if (!isInMethodDeclarationBody() || isDeclaredInForLoopControl() || isDeclaredInCatch() || isMethodArgument()) { return false; } @@ -119,6 +119,11 @@ private boolean isLocalVariable(J.VariableDeclarations mv) { return true; } + private boolean isMethodArgument() { + return getCursor().getParentTreeCursor() + .getValue() instanceof J.MethodDeclaration; + } + private boolean isInMethodDeclarationBody() { return getCursor().dropParentUntil(p -> p instanceof J.MethodDeclaration || p instanceof J.ClassDeclaration || diff --git a/src/test/java/org/openrewrite/staticanalysis/RenameLocalVariablesToCamelCaseTest.java b/src/test/java/org/openrewrite/staticanalysis/RenameLocalVariablesToCamelCaseTest.java index 250264069..5cf663586 100644 --- a/src/test/java/org/openrewrite/staticanalysis/RenameLocalVariablesToCamelCaseTest.java +++ b/src/test/java/org/openrewrite/staticanalysis/RenameLocalVariablesToCamelCaseTest.java @@ -15,7 +15,9 @@ */ package org.openrewrite.staticanalysis; +import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; +import org.junitpioneer.jupiter.ExpectedToFail; import org.openrewrite.DocumentExample; import org.openrewrite.Issue; import org.openrewrite.Recipe; @@ -89,11 +91,11 @@ void renameLocalVariables() { class Test { int DoNoTChange; - public int addTen(int rename_one) { + public int addTen(int dont_rename_one) { double RenameTwo = 2.0; float __rename__three__ = 2.0; long _Rename__Four = 2.0; - return rename_one + RenameTwo + __rename__three__ + _Rename__Four + 10; + return dont_rename_one + RenameTwo + __rename__three__ + _Rename__Four + 10; } } """, @@ -101,11 +103,11 @@ public int addTen(int rename_one) { class Test { int DoNoTChange; - public int addTen(int renameOne) { + public int addTen(int dont_rename_one) { double renameTwo = 2.0; float renameThree = 2.0; long renameFour = 2.0; - return renameOne + renameTwo + renameThree + renameFour + 10; + return dont_rename_one + renameTwo + renameThree + renameFour + 10; } } """ @@ -114,6 +116,7 @@ public int addTen(int renameOne) { } @SuppressWarnings("JavadocDeclaration") + @Disabled @Issue("https://github.com/openrewrite/rewrite/issues/2437") @Test void renameJavaDocParam() { @@ -438,6 +441,7 @@ void test() { @Test void renameFinalLocalVariables() { + //language=java rewriteRun( java( """ @@ -458,4 +462,21 @@ void test() { ); } + @Issue("https://github.com/openrewrite/rewrite-static-analysis/pull/205") + @Test + void doNotRenameMethodArguments() { + //language=java + rewriteRun( + java( + """ + class MyController { + String getHello(String your_name) { + return "hello " + your_name; + } + } + """ + ) + ); + } + } From 20aba8c39de4a28d611252a84478ec7d42edea9b Mon Sep 17 00:00:00 2001 From: Knut Wannheden Date: Thu, 26 Oct 2023 17:11:46 +0200 Subject: [PATCH 61/92] `FallThrough` must only add `break` on normal termination A `break` must only be added if a case body terminates normally. --- .../staticanalysis/FallThroughVisitor.java | 89 +++++++++++-------- .../staticanalysis/FallThroughTest.java | 40 +++++++++ 2 files changed, 91 insertions(+), 38 deletions(-) diff --git a/src/main/java/org/openrewrite/staticanalysis/FallThroughVisitor.java b/src/main/java/org/openrewrite/staticanalysis/FallThroughVisitor.java index 45e6e674a..4ae196607 100644 --- a/src/main/java/org/openrewrite/staticanalysis/FallThroughVisitor.java +++ b/src/main/java/org/openrewrite/staticanalysis/FallThroughVisitor.java @@ -24,10 +24,7 @@ import org.openrewrite.java.tree.*; import org.openrewrite.marker.Markers; -import java.util.ArrayList; -import java.util.HashSet; -import java.util.List; -import java.util.Set; +import java.util.*; import java.util.function.Predicate; import java.util.regex.Pattern; @@ -73,11 +70,11 @@ public AddBreak(J.Case scope) { public J.Case visitCase(J.Case case_, P p) { J.Case c = super.visitCase(case_, p); if (scope.isScope(c) && - c.getStatements().stream().noneMatch(J.Break.class::isInstance) && - c.getStatements().stream() - .reduce((s1, s2) -> s2) - .map(s -> !(s instanceof J.Block)) - .orElse(true)) { + c.getStatements().stream().noneMatch(J.Break.class::isInstance) && + c.getStatements().stream() + .reduce((s1, s2) -> s2) + .map(s -> !(s instanceof J.Block)) + .orElse(true)) { List statements = new ArrayList<>(c.getStatements()); J.Break breakToAdd = autoFormat( new J.Break(Tree.randomId(), Space.EMPTY, Markers.EMPTY, null), @@ -93,11 +90,11 @@ public J.Case visitCase(J.Case case_, P p) { public J.Block visitBlock(J.Block block, P p) { J.Block b = super.visitBlock(block, p); if (getCursor().isScopeInPath(scope) && - b.getStatements().stream().noneMatch(J.Break.class::isInstance) && - b.getStatements().stream() - .reduce((s1, s2) -> s2) - .map(s -> !(s instanceof J.Block)) - .orElse(true)) { + b.getStatements().stream().noneMatch(J.Break.class::isInstance) && + b.getStatements().stream() + .reduce((s1, s2) -> s2) + .map(s -> !(s instanceof J.Block)) + .orElse(true)) { List statements = b.getStatements(); J.Break breakToAdd = autoFormat( new J.Break(Tree.randomId(), Space.EMPTY, Markers.EMPTY, null), @@ -132,25 +129,53 @@ private static Set find(J.Switch enclosingSwitch, J.Case scope) { private static class FindLastLineBreaksOrFallsThroughCommentsVisitor extends JavaIsoVisitor> { private static final Predicate HAS_RELIEF_PATTERN_COMMENT = comment -> comment instanceof TextComment && - RELIEF_PATTERN.matcher(((TextComment) comment).getText()).find(); + RELIEF_PATTERN.matcher(((TextComment) comment).getText()).find(); private final J.Case scope; public FindLastLineBreaksOrFallsThroughCommentsVisitor(J.Case scope) { this.scope = scope; } - private static boolean lastLineBreaksOrFallsThrough(List trees) { + private static boolean lastLineBreaksOrFallsThrough(List trees) { return trees.stream() .reduce((s1, s2) -> s2) // last statement - .map(s -> s instanceof J.Return || - s instanceof J.Break || - s instanceof J.Continue || - s instanceof J.Throw || - s instanceof J.Switch || // https://github.com/openrewrite/rewrite-static-analysis/issues/173 - ((J) s).getComments().stream().anyMatch(HAS_RELIEF_PATTERN_COMMENT) + .map(s -> breaks(s) || // https://github.com/openrewrite/rewrite-static-analysis/issues/173 + s.getComments().stream().anyMatch(HAS_RELIEF_PATTERN_COMMENT) || + s instanceof J.Block && ((J.Block) s).getEnd().getComments().stream().anyMatch(HAS_RELIEF_PATTERN_COMMENT) ).orElse(false); } + private static boolean breaks(Statement s) { + if (s instanceof J.Block) { + List statements = ((J.Block) s).getStatements(); + return !statements.isEmpty() && breaks(statements.get(statements.size() - 1)); + } else if (s instanceof J.If) { + J.If iff = (J.If) s; + return iff.getElsePart() != null && breaks(iff.getThenPart()) && breaks(iff.getThenPart()); + } else if (s instanceof J.Label) { + return breaks(((J.Label) s).getStatement()); + } else if (s instanceof J.Try) { + J.Try try_ = (J.Try) s; + if (try_.getFinally() != null && breaks(try_.getFinally())) { + return true; + } + if (!breaks(try_.getBody())) { + return false; + } + for (J.Try.Catch c : try_.getCatches()) { + if (!breaks(c.getBody())) { + return false; + } + } + return true; + } + return s instanceof J.Return || + s instanceof J.Break || + s instanceof J.Continue || + s instanceof J.Throw || + s instanceof J.Switch; + } + @Override public J.Switch visitSwitch(J.Switch switch_, Set ctx) { J.Switch s = super.visitSwitch(switch_, ctx); @@ -185,24 +210,12 @@ public J.Switch visitSwitch(J.Switch switch_, Set ctx) { @Override public J.Case visitCase(J.Case case_, Set ctx) { - J.Case c = super.visitCase(case_, ctx); - if (c == scope) { - if (c.getStatements().isEmpty() || lastLineBreaksOrFallsThrough(c.getStatements())) { - ctx.add(c); - } - } - return c; - } - - @Override - public J.Block visitBlock(J.Block block, Set ctx) { - J.Block b = super.visitBlock(block, ctx); - if (getCursor().isScopeInPath(scope)) { - if (lastLineBreaksOrFallsThrough(b.getStatements()) || b.getEnd().getComments().stream().anyMatch(HAS_RELIEF_PATTERN_COMMENT)) { - ctx.add(b); + if (case_ == scope) { + if (case_.getStatements().isEmpty() || lastLineBreaksOrFallsThrough(case_.getStatements())) { + ctx.add(case_); } } - return b; + return case_; } } diff --git a/src/test/java/org/openrewrite/staticanalysis/FallThroughTest.java b/src/test/java/org/openrewrite/staticanalysis/FallThroughTest.java index 2ab675459..1797ead16 100644 --- a/src/test/java/org/openrewrite/staticanalysis/FallThroughTest.java +++ b/src/test/java/org/openrewrite/staticanalysis/FallThroughTest.java @@ -269,6 +269,46 @@ public void oneCase(int i) { ); } + @Test + void abortOnAbruptCompletion() { + rewriteRun( + //language=java + java( + """ + public class A { + public void noCase(int i) { + for (;;) { + switch (i) { + case 0: + if (true) + return; + else + break; + case 1: + if (true) + return; + else { + { + continue; + } + } + case 1: + try { + return; + } catch (Exception e) { + break; + } + default: + System.out.println("default"); + } + } + } + } + """ + ) + ); + } + @Test void addBreaksFallthroughCasesComprehensive() { rewriteRun( From 3dd7f5d5508653b89cfae4403293a3313ae3c309 Mon Sep 17 00:00:00 2001 From: Knut Wannheden Date: Thu, 2 Nov 2023 11:31:47 +0100 Subject: [PATCH 62/92] Don't apply `InstanceOfPatternMatch` to Groovy and Kotlin --- .../staticanalysis/InstanceOfPatternMatch.java | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/src/main/java/org/openrewrite/staticanalysis/InstanceOfPatternMatch.java b/src/main/java/org/openrewrite/staticanalysis/InstanceOfPatternMatch.java index bc4b7ff6e..a9fa0dbca 100644 --- a/src/main/java/org/openrewrite/staticanalysis/InstanceOfPatternMatch.java +++ b/src/main/java/org/openrewrite/staticanalysis/InstanceOfPatternMatch.java @@ -26,6 +26,8 @@ import org.openrewrite.java.search.UsesJavaVersion; import org.openrewrite.java.tree.*; import org.openrewrite.marker.Markers; +import org.openrewrite.staticanalysis.groovy.GroovyFileChecker; +import org.openrewrite.staticanalysis.kotlin.KotlinFileChecker; import java.time.Duration; import java.util.*; @@ -60,7 +62,13 @@ public Duration getEstimatedEffortPerOccurrence() { @Override public TreeVisitor getVisitor() { - return Preconditions.check(new UsesJavaVersion<>(17), new JavaVisitor() { + TreeVisitor preconditions = Preconditions.and( + new UsesJavaVersion<>(17), + Preconditions.not(new KotlinFileChecker<>()), + Preconditions.not(new GroovyFileChecker<>()) + ); + + return Preconditions.check(preconditions, new JavaVisitor() { @Override public @Nullable J postVisit(J tree, ExecutionContext executionContext) { J result = super.postVisit(tree, executionContext); @@ -72,8 +80,8 @@ public TreeVisitor getVisitor() { } @Override - public J.InstanceOf visitInstanceOf(J.InstanceOf instanceOf, ExecutionContext executionContext) { - instanceOf = (J.InstanceOf) super.visitInstanceOf(instanceOf, executionContext); + 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; } @@ -125,8 +133,8 @@ public J.InstanceOf visitInstanceOf(J.InstanceOf instanceOf, ExecutionContext ex } @Override - public J visitTypeCast(J.TypeCast typeCast, ExecutionContext executionContext) { - J result = super.visitTypeCast(typeCast, executionContext); + 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) { From 649b0842d1fe50087a4c9a2c52f4c58e9fc347fe Mon Sep 17 00:00:00 2001 From: Knut Wannheden Date: Thu, 2 Nov 2023 14:43:54 +0100 Subject: [PATCH 63/92] Allow `RemoveRedundantTypeCast` to deal with generics The `RemoveRedundantTypeCast` recipe will now also remove casts when the types involve generics. Further, when the type cast is the expression of a `return` statement, then the target type will be derived from the corresponding method declaration if any (lambdas are not supported here yet). Note: This implementation relies on openrewrite/rewrite#3655. --- .../RemoveRedundantTypeCast.java | 71 +++++++++++++------ .../RemoveRedundantTypeCastTest.java | 71 ++++++++++++++++++- 2 files changed, 118 insertions(+), 24 deletions(-) diff --git a/src/main/java/org/openrewrite/staticanalysis/RemoveRedundantTypeCast.java b/src/main/java/org/openrewrite/staticanalysis/RemoveRedundantTypeCast.java index 2a37657fc..8104232f0 100644 --- a/src/main/java/org/openrewrite/staticanalysis/RemoveRedundantTypeCast.java +++ b/src/main/java/org/openrewrite/staticanalysis/RemoveRedundantTypeCast.java @@ -17,13 +17,11 @@ import org.openrewrite.*; import org.openrewrite.java.JavaVisitor; -import org.openrewrite.java.tree.J; -import org.openrewrite.java.tree.JavaType; -import org.openrewrite.java.tree.TypeTree; -import org.openrewrite.java.tree.TypeUtils; +import org.openrewrite.java.tree.*; import java.time.Duration; import java.util.Collections; +import java.util.List; import java.util.Set; @Incubating(since = "7.23.0") @@ -52,29 +50,60 @@ public Set getTags() { public TreeVisitor getVisitor() { return new JavaVisitor() { @Override - public J visitTypeCast(J.TypeCast typeCast, ExecutionContext executionContext) { + public J visitTypeCast(J.TypeCast typeCast, ExecutionContext ctx) { + J visited = super.visitTypeCast(typeCast, ctx); + if (!(visited instanceof J.TypeCast)) { + return visited; + } + Cursor parent = getCursor().dropParentUntil(is -> is instanceof J.VariableDeclarations || - is instanceof J.NewClass || - is instanceof J.Lambda || - is instanceof J.MethodInvocation || - is instanceof J.MethodDeclaration || - is instanceof J.ClassDeclaration); + is instanceof J.Lambda || + is instanceof J.Return || + is instanceof MethodCall || + is instanceof J.MethodDeclaration || + is instanceof J.ClassDeclaration || + is instanceof JavaSourceFile); - // Not currently supported, this will be more accurate with dataflow analysis. - if (!(parent.getValue() instanceof J.VariableDeclarations)) { - return typeCast; + J parentValue = parent.getValue(); + + JavaType targetType = null; + if (parentValue instanceof J.VariableDeclarations) { + targetType = ((J.VariableDeclarations) parentValue).getVariables().get(0).getType(); + } else if (parentValue instanceof MethodCall) { + MethodCall methodCall = (MethodCall) parentValue; + JavaType.Method methodType = methodCall.getMethodType(); + if (methodType != null && !methodType.getParameterTypes().isEmpty()) { + List arguments = methodCall.getArguments(); + for (int i = 0; i < arguments.size(); i++) { + Expression arg = arguments.get(i); + if (arg == typeCast) { + targetType = methodType.getParameterTypes().get(i); + break; + } + } + } + } else if (parentValue instanceof J.Return && ((J.Return) parentValue).getExpression() == typeCast) { + parent = parent.dropParentUntil(is -> is instanceof J.Lambda || + is instanceof J.MethodDeclaration || + is instanceof J.ClassDeclaration || + is instanceof JavaSourceFile); + if (parent.getValue() instanceof J.MethodDeclaration && ((J.MethodDeclaration) parent.getValue()).getMethodType() != null) { + targetType = ((J.MethodDeclaration) parent.getValue()).getMethodType().getReturnType(); + } } - TypeTree typeTree = typeCast.getClazz().getTree(); - JavaType expressionType = typeCast.getExpression().getType(); + J.TypeCast visitedTypeCast = (J.TypeCast) visited; + JavaType expressionType = visitedTypeCast.getExpression().getType(); - JavaType namedVariableType = ((J.VariableDeclarations) parent.getValue()).getVariables().get(0).getType(); - if (!(namedVariableType instanceof JavaType.Array) && TypeUtils.isOfClassType(namedVariableType, "java.lang.Object") || - (!(typeTree instanceof J.ParameterizedType) && (TypeUtils.isOfType(namedVariableType, expressionType) || TypeUtils.isAssignableTo(namedVariableType, expressionType)))) { - return typeCast.getExpression(); + if (targetType == null) { + // Not currently supported, this will be more accurate with dataflow analysis. + return visitedTypeCast; + } else if (!(targetType instanceof JavaType.Array) && TypeUtils.isOfClassType(targetType, "java.lang.Object") || + TypeUtils.isOfType(targetType, expressionType) || + TypeUtils.isAssignableTo(targetType, expressionType)) { + return visitedTypeCast.getExpression().withPrefix(visitedTypeCast.getPrefix()); } - - return super.visitTypeCast(typeCast, executionContext); + return visitedTypeCast; } }; } diff --git a/src/test/java/org/openrewrite/staticanalysis/RemoveRedundantTypeCastTest.java b/src/test/java/org/openrewrite/staticanalysis/RemoveRedundantTypeCastTest.java index c700d6af9..fd8981538 100644 --- a/src/test/java/org/openrewrite/staticanalysis/RemoveRedundantTypeCastTest.java +++ b/src/test/java/org/openrewrite/staticanalysis/RemoveRedundantTypeCastTest.java @@ -70,7 +70,7 @@ Class get() { @Issue("https://github.com/openrewrite/rewrite/issues/1739") @Test - void doNotChangeGenericTypeCast() { + void changeTypeCastInReturn() { rewriteRun( //language=java java( @@ -79,8 +79,19 @@ void doNotChangeGenericTypeCast() { class Test { public > T test() { - T t = (T) get(); - return t; + return (T) get(); + } + public List get() { + return List.of("a", "b", "c"); + } + } + """, + """ + import java.util.*; + + class Test { + public > T test() { + return get(); } public List get() { return List.of("a", "b", "c"); @@ -240,4 +251,58 @@ class ExtendTest extends Test { ) ); } + + + @Test + @Issue("https://github.com/moderneinc/support-app/issues/17") + void test() { + rewriteRun( + java( + """ + import java.util.LinkedHashMap; + import java.util.Map; + import java.util.function.Supplier; + import java.util.stream.Collectors; + + class Test { + void method() { + Object o2 = new MapDropdownChoice( + (Supplier>) () -> { + Map choices = Map.of("id1", 2); + return choices.entrySet().stream() + .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); + }); + } + } + + class MapDropdownChoice { + public MapDropdownChoice(Supplier> choiceMap) { + } + } + """, + """ + import java.util.LinkedHashMap; + import java.util.Map; + import java.util.function.Supplier; + import java.util.stream.Collectors; + + class Test { + void method() { + Object o2 = new MapDropdownChoice( + () -> { + Map choices = Map.of("id1", 2); + return choices.entrySet().stream() + .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); + }); + } + } + + class MapDropdownChoice { + public MapDropdownChoice(Supplier> choiceMap) { + } + } + """ + ) + ); + } } From 1cc6d60012b224a2647de366e35c2c1c60f2744b Mon Sep 17 00:00:00 2001 From: Knut Wannheden Date: Thu, 2 Nov 2023 15:08:45 +0100 Subject: [PATCH 64/92] Fix `MethodMatcher` syntax in `HideUtilityClassConstructorVisitor` --- .../staticanalysis/HideUtilityClassConstructorVisitor.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/openrewrite/staticanalysis/HideUtilityClassConstructorVisitor.java b/src/main/java/org/openrewrite/staticanalysis/HideUtilityClassConstructorVisitor.java index 3689de0f3..63f492596 100644 --- a/src/main/java/org/openrewrite/staticanalysis/HideUtilityClassConstructorVisitor.java +++ b/src/main/java/org/openrewrite/staticanalysis/HideUtilityClassConstructorVisitor.java @@ -190,7 +190,7 @@ static boolean hasMainMethod(J.ClassDeclaration c) { JavaType.Primitive.Void.equals(md.getReturnTypeExpression().getType()) && // note that the matcher for "main(String)" will match on "main(String[]) as expected. - new MethodMatcher(c.getType().getFullyQualifiedName() + " main(String\\[\\])") + new MethodMatcher(c.getType().getFullyQualifiedName() + " main(String[])") .matches(md, c)) { return true; } From 6e0693c344ea5aba4cafffbd1561e8f53869324a Mon Sep 17 00:00:00 2001 From: Knut Wannheden Date: Thu, 2 Nov 2023 16:08:26 +0100 Subject: [PATCH 65/92] Add another test for `RemoveRedundantTypeCast` --- .../RemoveRedundantTypeCastTest.java | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src/test/java/org/openrewrite/staticanalysis/RemoveRedundantTypeCastTest.java b/src/test/java/org/openrewrite/staticanalysis/RemoveRedundantTypeCastTest.java index fd8981538..7a9acffa0 100644 --- a/src/test/java/org/openrewrite/staticanalysis/RemoveRedundantTypeCastTest.java +++ b/src/test/java/org/openrewrite/staticanalysis/RemoveRedundantTypeCastTest.java @@ -132,6 +132,24 @@ String method() { ); } + @Test + void wildcardGenericsInTargetType() { + rewriteRun( + //language=java + java( + """ + import java.util.List; + + class Test { + Object o = null; + List l = (List) o; + List l2 = (List) o; + } + """ + ) + ); + } + @Issue("https://github.com/openrewrite/rewrite/issues/1647") @Test void downCast() { From 4f8df2a02cf8a7e5a7d25ce5d80d36ce0ac007c2 Mon Sep 17 00:00:00 2001 From: Knut Wannheden Date: Thu, 2 Nov 2023 20:57:00 +0100 Subject: [PATCH 66/92] Don't remove primitive casts --- .../RemoveRedundantTypeCast.java | 3 +- .../RemoveRedundantTypeCastTest.java | 73 ++++++++++++++++--- 2 files changed, 64 insertions(+), 12 deletions(-) diff --git a/src/main/java/org/openrewrite/staticanalysis/RemoveRedundantTypeCast.java b/src/main/java/org/openrewrite/staticanalysis/RemoveRedundantTypeCast.java index 8104232f0..cd203ea5f 100644 --- a/src/main/java/org/openrewrite/staticanalysis/RemoveRedundantTypeCast.java +++ b/src/main/java/org/openrewrite/staticanalysis/RemoveRedundantTypeCast.java @@ -94,8 +94,9 @@ public J visitTypeCast(J.TypeCast typeCast, ExecutionContext ctx) { J.TypeCast visitedTypeCast = (J.TypeCast) visited; JavaType expressionType = visitedTypeCast.getExpression().getType(); + JavaType castType = visitedTypeCast.getType(); - if (targetType == null) { + if (targetType == null || targetType instanceof JavaType.Primitive && castType != targetType) { // Not currently supported, this will be more accurate with dataflow analysis. return visitedTypeCast; } else if (!(targetType instanceof JavaType.Array) && TypeUtils.isOfClassType(targetType, "java.lang.Object") || diff --git a/src/test/java/org/openrewrite/staticanalysis/RemoveRedundantTypeCastTest.java b/src/test/java/org/openrewrite/staticanalysis/RemoveRedundantTypeCastTest.java index 7a9acffa0..4e1c9c1c6 100644 --- a/src/test/java/org/openrewrite/staticanalysis/RemoveRedundantTypeCastTest.java +++ b/src/test/java/org/openrewrite/staticanalysis/RemoveRedundantTypeCastTest.java @@ -58,7 +58,7 @@ void parametersDoNotMatch() { class Test { Class> test = (Class>) get(); - + Class get() { return null; } @@ -68,30 +68,81 @@ Class get() { ); } - @Issue("https://github.com/openrewrite/rewrite/issues/1739") @Test - void changeTypeCastInReturn() { + void primitiveCast() { rewriteRun( //language=java java( """ - import java.util.*; - + import java.io.DataOutputStream; + class Test { - public > T test() { - return (T) get(); + void m(DataOutputStream out) { + out.writeByte((byte) 0xff); } - public List get() { - return List.of("a", "b", "c"); + } + """ + ) + ); + } + + + @Test + void genericTypeVariableCast() { + rewriteRun( + //language=java + java( + """ + import java.util.Iterator; + + class GenericNumberIterable implements Iterable { + + private final Iterable wrappedIterable; + + GenericNumberIterable(Iterable wrap) { + this.wrappedIterable = wrap; + } + + @Override + public Iterator iterator() { + final Iterator iter = wrappedIterable.iterator(); + + return new Iterator() { + @Override + public boolean hasNext() { + return iter.hasNext(); + } + + @Override + @SuppressWarnings("unchecked") + public T next() { + return (T) iter.next(); + } + + @Override + public void remove() { + throw new UnsupportedOperationException(); + } + }; } } - """, + """ + ) + ); + } + + @Issue("https://github.com/openrewrite/rewrite/issues/1739") + @Test + void changeTypeCastInReturn() { + rewriteRun( + //language=java + java( """ import java.util.*; class Test { public > T test() { - return get(); + return (T) get(); } public List get() { return List.of("a", "b", "c"); From 4675a0b40c839ad7706907abbae8fdac66babea8 Mon Sep 17 00:00:00 2001 From: Knut Wannheden Date: Thu, 2 Nov 2023 21:35:58 +0100 Subject: [PATCH 67/92] Don't remove cast in presence of overloads --- .../LambdaBlockToExpression.java | 14 +++++++---- .../RemoveRedundantTypeCast.java | 9 ++++++-- .../RemoveRedundantTypeCastTest.java | 23 +++++++++++++++++++ 3 files changed, 39 insertions(+), 7 deletions(-) diff --git a/src/main/java/org/openrewrite/staticanalysis/LambdaBlockToExpression.java b/src/main/java/org/openrewrite/staticanalysis/LambdaBlockToExpression.java index 2e7c3456c..c139e19cf 100644 --- a/src/main/java/org/openrewrite/staticanalysis/LambdaBlockToExpression.java +++ b/src/main/java/org/openrewrite/staticanalysis/LambdaBlockToExpression.java @@ -71,8 +71,14 @@ public J.MethodInvocation visitMethodInvocation(J.MethodInvocation method, Execu // Check whether a method has overloading methods in the declaring class private static boolean hasMethodOverloading(J.MethodInvocation method) { - String methodName = method.getSimpleName(); - return Optional.ofNullable(method.getMethodType()) + JavaType.Method methodType = method.getMethodType(); + return methodType != null && hasMethodOverloading(methodType); + } + + // TODO this is actually more complex in the presence of generics and inheritance + static boolean hasMethodOverloading(JavaType.Method methodType) { + String methodName = methodType.getName(); + return Optional.of(methodType) .map(JavaType.Method::getDeclaringType) .filter(JavaType.Class.class::isInstance) .map(JavaType.Class.class::cast) @@ -81,9 +87,7 @@ private static boolean hasMethodOverloading(J.MethodInvocation method) { int overloadingCount = 0; for (JavaType.Method dm : methods) { if (dm.getName().equals(methodName)) { - overloadingCount++; - if (overloadingCount > 1) { - + if (++overloadingCount > 1) { return true; } } diff --git a/src/main/java/org/openrewrite/staticanalysis/RemoveRedundantTypeCast.java b/src/main/java/org/openrewrite/staticanalysis/RemoveRedundantTypeCast.java index cd203ea5f..969cb3a45 100644 --- a/src/main/java/org/openrewrite/staticanalysis/RemoveRedundantTypeCast.java +++ b/src/main/java/org/openrewrite/staticanalysis/RemoveRedundantTypeCast.java @@ -24,6 +24,8 @@ import java.util.List; import java.util.Set; +import static org.openrewrite.staticanalysis.LambdaBlockToExpression.hasMethodOverloading; + @Incubating(since = "7.23.0") public class RemoveRedundantTypeCast extends Recipe { @Override @@ -72,7 +74,9 @@ public J visitTypeCast(J.TypeCast typeCast, ExecutionContext ctx) { } else if (parentValue instanceof MethodCall) { MethodCall methodCall = (MethodCall) parentValue; JavaType.Method methodType = methodCall.getMethodType(); - if (methodType != null && !methodType.getParameterTypes().isEmpty()) { + if (methodType == null || hasMethodOverloading(methodType)) { + return visited; + } else if (!methodType.getParameterTypes().isEmpty()) { List arguments = methodCall.getArguments(); for (int i = 0; i < arguments.size(); i++) { Expression arg = arguments.get(i); @@ -88,7 +92,8 @@ public J visitTypeCast(J.TypeCast typeCast, ExecutionContext ctx) { is instanceof J.ClassDeclaration || is instanceof JavaSourceFile); if (parent.getValue() instanceof J.MethodDeclaration && ((J.MethodDeclaration) parent.getValue()).getMethodType() != null) { - targetType = ((J.MethodDeclaration) parent.getValue()).getMethodType().getReturnType(); + JavaType.Method methodType = ((J.MethodDeclaration) parent.getValue()).getMethodType(); + targetType = methodType.getReturnType(); } } diff --git a/src/test/java/org/openrewrite/staticanalysis/RemoveRedundantTypeCastTest.java b/src/test/java/org/openrewrite/staticanalysis/RemoveRedundantTypeCastTest.java index 4e1c9c1c6..bf020522d 100644 --- a/src/test/java/org/openrewrite/staticanalysis/RemoveRedundantTypeCastTest.java +++ b/src/test/java/org/openrewrite/staticanalysis/RemoveRedundantTypeCastTest.java @@ -201,6 +201,29 @@ class Test { ); } + @Test + void keepCastWithMethodOverloads() { + rewriteRun( + //language=java + java( + """ + class Test { + void visit(Integer i) { + visit((Number) i); + } + void visit(Number n) { + } + void visitAll(Integer... i) { + visitAll((Number[]) i); + } + void visitAll(Number... n) { + } + } + """ + ) + ); + } + @Issue("https://github.com/openrewrite/rewrite/issues/1647") @Test void downCast() { From d6dee9de9eff8136f77b5cf04748db147b51f591 Mon Sep 17 00:00:00 2001 From: Knut Wannheden Date: Thu, 2 Nov 2023 22:02:38 +0100 Subject: [PATCH 68/92] Handle varargs calls in `RemoveRedundantTypeCast` --- .../RemoveRedundantTypeCast.java | 12 ++++++++- .../RemoveRedundantTypeCastTest.java | 27 +++++++++++++++++++ 2 files changed, 38 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/openrewrite/staticanalysis/RemoveRedundantTypeCast.java b/src/main/java/org/openrewrite/staticanalysis/RemoveRedundantTypeCast.java index 969cb3a45..094493b1b 100644 --- a/src/main/java/org/openrewrite/staticanalysis/RemoveRedundantTypeCast.java +++ b/src/main/java/org/openrewrite/staticanalysis/RemoveRedundantTypeCast.java @@ -81,7 +81,7 @@ public J visitTypeCast(J.TypeCast typeCast, ExecutionContext ctx) { for (int i = 0; i < arguments.size(); i++) { Expression arg = arguments.get(i); if (arg == typeCast) { - targetType = methodType.getParameterTypes().get(i); + targetType = getParameterType(methodType, i); break; } } @@ -111,6 +111,16 @@ public J visitTypeCast(J.TypeCast typeCast, ExecutionContext ctx) { } return visitedTypeCast; } + + private JavaType getParameterType(JavaType.Method method, int arg) { + List parameterTypes = method.getParameterTypes(); + if (parameterTypes.size() > arg) { + return parameterTypes.get(arg); + } + // varargs? + JavaType type = parameterTypes.get(parameterTypes.size() - 1); + return type instanceof JavaType.Array ? ((JavaType.Array) type).getElemType() : type; + } }; } } diff --git a/src/test/java/org/openrewrite/staticanalysis/RemoveRedundantTypeCastTest.java b/src/test/java/org/openrewrite/staticanalysis/RemoveRedundantTypeCastTest.java index bf020522d..eac0661b7 100644 --- a/src/test/java/org/openrewrite/staticanalysis/RemoveRedundantTypeCastTest.java +++ b/src/test/java/org/openrewrite/staticanalysis/RemoveRedundantTypeCastTest.java @@ -224,6 +224,33 @@ void visitAll(Number... n) { ); } + @Test + void varargsCall() { + rewriteRun( + //language=java + java( + """ + class Test { + void m(String... s) { + } + void foo() { + m("1", (String) "2"); + } + } + """, + """ + class Test { + void m(String... s) { + } + void foo() { + m("1", "2"); + } + } + """ + ) + ); + } + @Issue("https://github.com/openrewrite/rewrite/issues/1647") @Test void downCast() { From 1e0ca37c50dc3ce9ee06b5847dae17dfcc47b0ef Mon Sep 17 00:00:00 2001 From: Knut Wannheden Date: Thu, 2 Nov 2023 22:55:34 +0100 Subject: [PATCH 69/92] Fix another edge case with primitives in `RemoveRedundantTypeCast` --- .../org/openrewrite/staticanalysis/RemoveRedundantTypeCast.java | 2 +- .../openrewrite/staticanalysis/RemoveRedundantTypeCastTest.java | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/openrewrite/staticanalysis/RemoveRedundantTypeCast.java b/src/main/java/org/openrewrite/staticanalysis/RemoveRedundantTypeCast.java index 094493b1b..6e6c81444 100644 --- a/src/main/java/org/openrewrite/staticanalysis/RemoveRedundantTypeCast.java +++ b/src/main/java/org/openrewrite/staticanalysis/RemoveRedundantTypeCast.java @@ -101,7 +101,7 @@ public J visitTypeCast(J.TypeCast typeCast, ExecutionContext ctx) { JavaType expressionType = visitedTypeCast.getExpression().getType(); JavaType castType = visitedTypeCast.getType(); - if (targetType == null || targetType instanceof JavaType.Primitive && castType != targetType) { + if (targetType == null || targetType instanceof JavaType.Primitive && castType != expressionType) { // Not currently supported, this will be more accurate with dataflow analysis. return visitedTypeCast; } else if (!(targetType instanceof JavaType.Array) && TypeUtils.isOfClassType(targetType, "java.lang.Object") || diff --git a/src/test/java/org/openrewrite/staticanalysis/RemoveRedundantTypeCastTest.java b/src/test/java/org/openrewrite/staticanalysis/RemoveRedundantTypeCastTest.java index eac0661b7..8ec941d3c 100644 --- a/src/test/java/org/openrewrite/staticanalysis/RemoveRedundantTypeCastTest.java +++ b/src/test/java/org/openrewrite/staticanalysis/RemoveRedundantTypeCastTest.java @@ -79,6 +79,7 @@ void primitiveCast() { class Test { void m(DataOutputStream out) { out.writeByte((byte) 0xff); + out.writeDouble((double) 42); } } """ From b866e74d08c0b1655cc17481226f2509840d2cee Mon Sep 17 00:00:00 2001 From: Tim te Beek Date: Fri, 3 Nov 2023 08:37:28 +0000 Subject: [PATCH 70/92] refactor: Replace Gradle Enterprise with Develocity Use this link to re-run the recipe: https://app.moderne.io/recipes/builder/Ss9anWRAZ?organizationId=T3BlblJld3JpdGU%3D Co-authored-by: Moderne --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index f1e65a8ce..8eb85a7c8 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ [![ci](https://github.com/openrewrite/rewrite-static-analysis/actions/workflows/ci.yml/badge.svg)](https://github.com/openrewrite/rewrite-static-analysis/actions/workflows/ci.yml) [![Apache 2.0](https://img.shields.io/github/license/openrewrite/rewrite-static-analysis.svg)](https://www.apache.org/licenses/LICENSE-2.0) [![Maven Central](https://img.shields.io/maven-central/v/org.openrewrite.recipe/rewrite-static-analysis.svg)](https://mvnrepository.com/artifact/org.openrewrite.recipe/rewrite-static-analysis) -[![Revved up by Gradle Enterprise](https://img.shields.io/badge/Revved%20up%20by-Gradle%20Enterprise-06A0CE?logo=Gradle&labelColor=02303A)](https://ge.openrewrite.org/scans) +[![Revved up by Develocity](https://img.shields.io/badge/Revved%20up%20by-Develocity-06A0CE?logo=Gradle&labelColor=02303A)](https://ge.openrewrite.org/scans) [![Contributing Guide](https://img.shields.io/badge/Contributing-Guide-informational)](https://github.com/openrewrite/.github/blob/main/CONTRIBUTING.md) ### What is this? From fe991449c434cce192673dbf8eaf6dcdd58ee9db Mon Sep 17 00:00:00 2001 From: Tim te Beek Date: Mon, 6 Nov 2023 21:57:35 +0100 Subject: [PATCH 71/92] No method reference on new class select (#212) As shared instance might behave different from new instances --- .../ReplaceLambdaWithMethodReference.java | 4 ++- .../ReplaceLambdaWithMethodReferenceTest.java | 28 +++++++++++++++++++ 2 files changed, 31 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/openrewrite/staticanalysis/ReplaceLambdaWithMethodReference.java b/src/main/java/org/openrewrite/staticanalysis/ReplaceLambdaWithMethodReference.java index 4fb32dab1..71cca4332 100644 --- a/src/main/java/org/openrewrite/staticanalysis/ReplaceLambdaWithMethodReference.java +++ b/src/main/java/org/openrewrite/staticanalysis/ReplaceLambdaWithMethodReference.java @@ -179,7 +179,9 @@ public J visitLambda(J.Lambda lambda, ExecutionContext executionContext) { .build() .apply(getCursor(), l.getCoordinates().replace(), className((J.NewClass) method)); } else if (select != null) { - return newInstanceMethodReference(methodType, select, lambda.getType()).withPrefix(lambda.getPrefix()); + if (!(select instanceof J.NewClass)) { + return newInstanceMethodReference(methodType, select, lambda.getType()).withPrefix(lambda.getPrefix()); + } } else { String templ = "#{}::#{}"; return JavaTemplate.builder(templ) diff --git a/src/test/java/org/openrewrite/staticanalysis/ReplaceLambdaWithMethodReferenceTest.java b/src/test/java/org/openrewrite/staticanalysis/ReplaceLambdaWithMethodReferenceTest.java index 276c3ec39..845ffe267 100644 --- a/src/test/java/org/openrewrite/staticanalysis/ReplaceLambdaWithMethodReferenceTest.java +++ b/src/test/java/org/openrewrite/staticanalysis/ReplaceLambdaWithMethodReferenceTest.java @@ -1290,4 +1290,32 @@ private Integer foo(String bar) { ) ); } + + @Test + void newInstanceMethodCall() { + rewriteRun( + //language=java + java( + """ + class A { + int seen = 0; + String lower(String s) { + seen++; + return s.toLowerCase(); + } + } + """ + ), + java( + """ + import java.util.stream.Stream; + class B { + void bar(Stream stream) { + stream.map(s -> new A().lower(s)); + } + } + """ + ) + ); + } } From 8c803a9c50b480841a4af031f60bac5ee443eb4e Mon Sep 17 00:00:00 2001 From: Knut Wannheden Date: Tue, 7 Nov 2023 08:34:26 +0100 Subject: [PATCH 72/92] Slight refactor of #212 --- .../ReplaceLambdaWithMethodReference.java | 10 ++++------ .../ReplaceLambdaWithMethodReferenceTest.java | 2 +- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/src/main/java/org/openrewrite/staticanalysis/ReplaceLambdaWithMethodReference.java b/src/main/java/org/openrewrite/staticanalysis/ReplaceLambdaWithMethodReference.java index 71cca4332..f29c78e46 100644 --- a/src/main/java/org/openrewrite/staticanalysis/ReplaceLambdaWithMethodReference.java +++ b/src/main/java/org/openrewrite/staticanalysis/ReplaceLambdaWithMethodReference.java @@ -158,7 +158,7 @@ public J visitLambda(J.Lambda lambda, ExecutionContext executionContext) { } } - if (multipleMethodInvocations(method) || + if (hasSelectWithPotentialSideEffects(method) || !methodArgumentsMatchLambdaParameters(method, lambda) || method instanceof J.MemberReference) { return l; @@ -179,9 +179,7 @@ public J visitLambda(J.Lambda lambda, ExecutionContext executionContext) { .build() .apply(getCursor(), l.getCoordinates().replace(), className((J.NewClass) method)); } else if (select != null) { - if (!(select instanceof J.NewClass)) { - return newInstanceMethodReference(methodType, select, lambda.getType()).withPrefix(lambda.getPrefix()); - } + return newInstanceMethodReference(methodType, select, lambda.getType()).withPrefix(lambda.getPrefix()); } else { String templ = "#{}::#{}"; return JavaTemplate.builder(templ) @@ -203,9 +201,9 @@ private String className(J.NewClass method) { Objects.toString(clazz); } - private boolean multipleMethodInvocations(MethodCall method) { + private boolean hasSelectWithPotentialSideEffects(MethodCall method) { return method instanceof J.MethodInvocation && - ((J.MethodInvocation) method).getSelect() instanceof J.MethodInvocation; + ((J.MethodInvocation) method).getSelect() instanceof MethodCall; } private boolean methodArgumentsMatchLambdaParameters(MethodCall method, J.Lambda lambda) { diff --git a/src/test/java/org/openrewrite/staticanalysis/ReplaceLambdaWithMethodReferenceTest.java b/src/test/java/org/openrewrite/staticanalysis/ReplaceLambdaWithMethodReferenceTest.java index 845ffe267..1da23d4d3 100644 --- a/src/test/java/org/openrewrite/staticanalysis/ReplaceLambdaWithMethodReferenceTest.java +++ b/src/test/java/org/openrewrite/staticanalysis/ReplaceLambdaWithMethodReferenceTest.java @@ -1292,7 +1292,7 @@ private Integer foo(String bar) { } @Test - void newInstanceMethodCall() { + void newClassSelector() { rewriteRun( //language=java java( From 9523b4efc45aa54cc4be09feda3f033ea7624096 Mon Sep 17 00:00:00 2001 From: Knut Wannheden Date: Wed, 8 Nov 2023 08:50:10 +0100 Subject: [PATCH 73/92] `FallThrough`: Make sure to not recursively add `break` Fixes: #213 --- .../staticanalysis/FallThroughVisitor.java | 33 ++++---- .../staticanalysis/FallThroughTest.java | 80 +++++++++++++++---- 2 files changed, 79 insertions(+), 34 deletions(-) diff --git a/src/main/java/org/openrewrite/staticanalysis/FallThroughVisitor.java b/src/main/java/org/openrewrite/staticanalysis/FallThroughVisitor.java index 4ae196607..3635a9d3a 100644 --- a/src/main/java/org/openrewrite/staticanalysis/FallThroughVisitor.java +++ b/src/main/java/org/openrewrite/staticanalysis/FallThroughVisitor.java @@ -50,9 +50,9 @@ public J.Case visitCase(J.Case case_, P p) { J.Case c = super.visitCase(case_, p); if (getCursor().firstEnclosing(J.Switch.class) != null) { J.Switch switch_ = getCursor().dropParentUntil(J.Switch.class::isInstance).getValue(); - if ((Boolean.TRUE.equals(style.getCheckLastCaseGroup()) || !isLastCase(c, switch_))) { + if (Boolean.TRUE.equals(style.getCheckLastCaseGroup()) || !isLastCase(case_, switch_)) { if (FindLastLineBreaksOrFallsThroughComments.find(switch_, c).isEmpty()) { - doAfterVisit(new AddBreak<>(c)); + c = (J.Case) new AddBreak<>(c).visit(c, p, getCursor().getParent()); } } } @@ -68,34 +68,29 @@ public AddBreak(J.Case scope) { @Override public J.Case visitCase(J.Case case_, P p) { - J.Case c = super.visitCase(case_, p); - if (scope.isScope(c) && - c.getStatements().stream().noneMatch(J.Break.class::isInstance) && - c.getStatements().stream() - .reduce((s1, s2) -> s2) - .map(s -> !(s instanceof J.Block)) - .orElse(true)) { - List statements = new ArrayList<>(c.getStatements()); + if (scope.isScope(case_)) { + List statements = case_.getStatements(); + if (statements.size() == 1 && statements.get(0) instanceof J.Block) { + return super.visitCase(case_, p); + } J.Break breakToAdd = autoFormat( new J.Break(Tree.randomId(), Space.EMPTY, Markers.EMPTY, null), p ); statements.add(breakToAdd); - c = c.withStatements(ListUtils.map(statements, stmt -> autoFormat(stmt, p, getCursor()))); + return case_.withStatements(ListUtils.map(statements, stmt -> autoFormat(stmt, p, getCursor()))); } - return c; + return case_; } @Override public J.Block visitBlock(J.Block block, P p) { - J.Block b = super.visitBlock(block, p); - if (getCursor().isScopeInPath(scope) && - b.getStatements().stream().noneMatch(J.Break.class::isInstance) && - b.getStatements().stream() - .reduce((s1, s2) -> s2) - .map(s -> !(s instanceof J.Block)) - .orElse(true)) { + J.Block b = block; + if (getCursor().isScopeInPath(scope)) { List statements = b.getStatements(); + if (statements.size() == 1 && statements.get(0) instanceof J.Block) { + return super.visitBlock(b, p); + } J.Break breakToAdd = autoFormat( new J.Break(Tree.randomId(), Space.EMPTY, Markers.EMPTY, null), p diff --git a/src/test/java/org/openrewrite/staticanalysis/FallThroughTest.java b/src/test/java/org/openrewrite/staticanalysis/FallThroughTest.java index 1797ead16..ee621d703 100644 --- a/src/test/java/org/openrewrite/staticanalysis/FallThroughTest.java +++ b/src/test/java/org/openrewrite/staticanalysis/FallThroughTest.java @@ -269,6 +269,56 @@ public void oneCase(int i) { ); } + @Test + void nestedBlocks() { + rewriteRun( + //language=java + java( + """ + public class A { + public int n(int i) { + switch (i) { + case 1: + try { + if (true) { + return 1; + } + } catch (Exception e) { + if (true) { + return 1; + } + } + default: + throw new IllegalStateException(); + } + } + } + """, + """ + public class A { + public int n(int i) { + switch (i) { + case 1: + try { + if (true) { + return 1; + } + } catch (Exception e) { + if (true) { + return 1; + } + } + break; + default: + throw new IllegalStateException(); + } + } + } + """ + ) + ); + } + @Test void abortOnAbruptCompletion() { rewriteRun( @@ -292,11 +342,11 @@ public void noCase(int i) { continue; } } - case 1: + case 2: try { return; } catch (Exception e) { - break; + return; } default: System.out.println("default"); @@ -403,12 +453,12 @@ void foo(Enum a) { switch(a) { case A: default: - switch(a) { - case B: - System.out.println("B"); - default: - System.out.print("other"); - } + switch (a) { + case B: + System.out.println("B"); + default: + System.out.print("other"); + } } } } @@ -422,13 +472,13 @@ void foo(Enum a) { switch(a) { case A: default: - switch(a) { - case B: - System.out.println("B"); - break; - default: - System.out.print("other"); - } + switch (a) { + case B: + System.out.println("B"); + break; + default: + System.out.print("other"); + } } } } From 30d61b6181ba740ad9d070b9959c3f83385081ec Mon Sep 17 00:00:00 2001 From: Joan Viladrosa Date: Wed, 8 Nov 2023 09:32:28 +0100 Subject: [PATCH 74/92] Cast when needed on lambda expressions (#207) * Simplify test a bit * remove redundant type cast * Fix one more edge case * Don't remove cast when expression is lambda being cast to generic type --------- Co-authored-by: Knut Wannheden --- .../RemoveRedundantTypeCast.java | 4 +- .../UseLambdaForFunctionalInterface.java | 69 ++++++++++++------ .../RemoveRedundantTypeCastTest.java | 46 ++++++------ .../UseLambdaForFunctionalInterfaceTest.java | 71 +++++++++++++++++++ 4 files changed, 141 insertions(+), 49 deletions(-) diff --git a/src/main/java/org/openrewrite/staticanalysis/RemoveRedundantTypeCast.java b/src/main/java/org/openrewrite/staticanalysis/RemoveRedundantTypeCast.java index 6e6c81444..ab73be507 100644 --- a/src/main/java/org/openrewrite/staticanalysis/RemoveRedundantTypeCast.java +++ b/src/main/java/org/openrewrite/staticanalysis/RemoveRedundantTypeCast.java @@ -101,7 +101,9 @@ public J visitTypeCast(J.TypeCast typeCast, ExecutionContext ctx) { JavaType expressionType = visitedTypeCast.getExpression().getType(); JavaType castType = visitedTypeCast.getType(); - if (targetType == null || targetType instanceof JavaType.Primitive && castType != expressionType) { + if (targetType == null || + targetType instanceof JavaType.Primitive && castType != expressionType || + typeCast.getExpression() instanceof J.Lambda && castType instanceof JavaType.Parameterized) { // Not currently supported, this will be more accurate with dataflow analysis. return visitedTypeCast; } else if (!(targetType instanceof JavaType.Array) && TypeUtils.isOfClassType(targetType, "java.lang.Object") || diff --git a/src/main/java/org/openrewrite/staticanalysis/UseLambdaForFunctionalInterface.java b/src/main/java/org/openrewrite/staticanalysis/UseLambdaForFunctionalInterface.java index 6e65b2d89..843f883b0 100644 --- a/src/main/java/org/openrewrite/staticanalysis/UseLambdaForFunctionalInterface.java +++ b/src/main/java/org/openrewrite/staticanalysis/UseLambdaForFunctionalInterface.java @@ -132,30 +132,11 @@ public J visitNewClass(J.NewClass newClass, ExecutionContext ctx) { return n; } - @Nullable - private JavaType.Method getSamCompatible(JavaType type) { - JavaType.Method sam = null; - JavaType.FullyQualified fullyQualified = TypeUtils.asFullyQualified(type); - if (fullyQualified == null) { - return null; - } - for (JavaType.Method method : fullyQualified.getMethods()) { - if (method.hasFlags(Flag.Default) || method.hasFlags(Flag.Static)) { - continue; - } - if (sam != null) { - return null; - } - sam = method; - } - return sam; - } - private J maybeAddCast(J.Lambda lambda, J.NewClass original) { J parent = getCursor().getParentTreeCursor().getValue(); - if (parent instanceof J.MethodInvocation) { - J.MethodInvocation method = (J.MethodInvocation) parent; + if (parent instanceof MethodCall) { + MethodCall method = (MethodCall) parent; List arguments = method.getArguments(); for (int i = 0; i < arguments.size(); i++) { Expression argument = arguments.get(i); @@ -180,7 +161,7 @@ private J maybeAddCast(J.Lambda lambda, J.NewClass original) { return lambda; } - private boolean methodArgumentRequiresCast(J.Lambda lambda, J.MethodInvocation method, int argumentIndex) { + private boolean methodArgumentRequiresCast(J.Lambda lambda, MethodCall method, int argumentIndex) { JavaType.FullyQualified lambdaType = TypeUtils.asFullyQualified(lambda.getType()); if (lambdaType == null) { return false; @@ -207,7 +188,11 @@ private boolean methodArgumentRequiresCast(J.Lambda lambda, J.MethodInvocation m } } } - return count >= 2; + if (count >= 2) { + return true; + } + + return hasGenerics(lambda); } private boolean areMethodsAmbiguous(@Nullable JavaType.Method m1, @Nullable JavaType.Method m2) { @@ -419,4 +404,42 @@ public J visitVariable(J.VariableDeclarations.NamedVariable variable, Integer in return hasShadow.get(); } + + private static boolean hasGenerics(J.Lambda lambda) { + AtomicBoolean atomicBoolean = new AtomicBoolean(); + new JavaVisitor() { + @Override + public J visitMethodInvocation(J.MethodInvocation method, AtomicBoolean atomicBoolean) { + if (method.getMethodType() != null && + method.getMethodType().getParameterTypes().stream() + .anyMatch(p -> p instanceof JavaType.Parameterized && + ((JavaType.Parameterized) p).getTypeParameters().stream().anyMatch(t -> t instanceof JavaType.GenericTypeVariable)) + ) { + atomicBoolean.set(true); + } + return super.visitMethodInvocation(method, atomicBoolean); + } + }.visit(lambda.getBody(), atomicBoolean); + return atomicBoolean.get(); + } + + // TODO consider moving to TypeUtils + @Nullable + private static JavaType.Method getSamCompatible(@Nullable JavaType type) { + JavaType.Method sam = null; + JavaType.FullyQualified fullyQualified = TypeUtils.asFullyQualified(type); + if (fullyQualified == null) { + return null; + } + for (JavaType.Method method : fullyQualified.getMethods()) { + if (method.hasFlags(Flag.Default) || method.hasFlags(Flag.Static)) { + continue; + } + if (sam != null) { + return null; + } + sam = method; + } + return sam; + } } diff --git a/src/test/java/org/openrewrite/staticanalysis/RemoveRedundantTypeCastTest.java b/src/test/java/org/openrewrite/staticanalysis/RemoveRedundantTypeCastTest.java index 8ec941d3c..e2466561f 100644 --- a/src/test/java/org/openrewrite/staticanalysis/RemoveRedundantTypeCastTest.java +++ b/src/test/java/org/openrewrite/staticanalysis/RemoveRedundantTypeCastTest.java @@ -154,6 +154,24 @@ public List get() { ); } + @Test + void nonSamParameter() { + rewriteRun( + //language=java + java( + """ + import java.util.*; + + class Test { + public boolean foo() { + return Objects.equals("x", (Comparable) (s) -> 1); + } + } + """ + ) + ); + } + @Issue("https://github.com/openrewrite/rewrite/issues/1647") @Test void redundantTypeCast() { @@ -374,8 +392,7 @@ class ExtendTest extends Test { @Test - @Issue("https://github.com/moderneinc/support-app/issues/17") - void test() { + void lambdaWithComplexTypeInference() { rewriteRun( java( """ @@ -390,29 +407,8 @@ void method() { (Supplier>) () -> { Map choices = Map.of("id1", 2); return choices.entrySet().stream() - .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); - }); - } - } - - class MapDropdownChoice { - public MapDropdownChoice(Supplier> choiceMap) { - } - } - """, - """ - import java.util.LinkedHashMap; - import java.util.Map; - import java.util.function.Supplier; - import java.util.stream.Collectors; - - class Test { - void method() { - Object o2 = new MapDropdownChoice( - () -> { - Map choices = Map.of("id1", 2); - return choices.entrySet().stream() - .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); + .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, + (e1, e2) -> e1, LinkedHashMap::new)); }); } } diff --git a/src/test/java/org/openrewrite/staticanalysis/UseLambdaForFunctionalInterfaceTest.java b/src/test/java/org/openrewrite/staticanalysis/UseLambdaForFunctionalInterfaceTest.java index a74288059..991bca6da 100644 --- a/src/test/java/org/openrewrite/staticanalysis/UseLambdaForFunctionalInterfaceTest.java +++ b/src/test/java/org/openrewrite/staticanalysis/UseLambdaForFunctionalInterfaceTest.java @@ -664,4 +664,75 @@ public void run() { ) ); } + + @Test + @Issue("https://github.com/moderneinc/support-app/issues/17") + void lambdaWithComplexTypeInference() { + rewriteRun( + java( + """ + import java.util.LinkedHashMap; + import java.util.Map; + import java.util.function.Supplier; + import java.util.stream.Collectors; + + class Test { + void method() { + Object o = new MapDropdownChoice( + new Supplier>() { + @Override + public Map getObject() { + Map choices = Map.of("id1", 1); + return choices.entrySet().stream() + .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (e1, e2) -> e1, LinkedHashMap::new)); + } + }); + Object o2 = new MapDropdownChoice( + new Supplier>() { + @Override + public Map getObject() { + Map choices = Map.of("id1", 2); + return choices.entrySet().stream() + .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); + } + }); + } + } + + class MapDropdownChoice { + public MapDropdownChoice(Supplier> choiceMap) { + } + } + """, + """ + import java.util.LinkedHashMap; + import java.util.Map; + import java.util.function.Supplier; + import java.util.stream.Collectors; + + class Test { + void method() { + Object o = new MapDropdownChoice( + (Supplier>) () -> { + Map choices = Map.of("id1", 1); + return choices.entrySet().stream() + .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (e1, e2) -> e1, LinkedHashMap::new)); + }); + Object o2 = new MapDropdownChoice( + (Supplier>) () -> { + Map choices = Map.of("id1", 2); + return choices.entrySet().stream() + .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); + }); + } + } + + class MapDropdownChoice { + public MapDropdownChoice(Supplier> choiceMap) { + } + } + """ + ) + ); + } } From 18674161b77afdfb77d8d36c8336cb631c963619 Mon Sep 17 00:00:00 2001 From: Knut Wannheden Date: Wed, 8 Nov 2023 10:23:20 +0100 Subject: [PATCH 75/92] `UseDiamondOperator` should not touch annotated type parameters Fixes: #214 --- build.gradle.kts | 1 + .../staticanalysis/UseDiamondOperator.java | 3 ++- .../UseDiamondOperatorTest.java | 23 +++++++++++++++++++ 3 files changed, 26 insertions(+), 1 deletion(-) diff --git a/build.gradle.kts b/build.gradle.kts index 0e2ee2e1a..de1b9168c 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -20,6 +20,7 @@ dependencies { implementation("org.openrewrite.meta:rewrite-analysis:${rewriteVersion}") implementation("org.apache.commons:commons-text:latest.release") + testImplementation("org.jetbrains:annotations:24.+") testImplementation("org.openrewrite:rewrite-groovy") testImplementation("org.junit-pioneer:junit-pioneer:2.0.1") testImplementation("junit:junit:4.13.2") diff --git a/src/main/java/org/openrewrite/staticanalysis/UseDiamondOperator.java b/src/main/java/org/openrewrite/staticanalysis/UseDiamondOperator.java index f7060efed..9134e7e76 100644 --- a/src/main/java/org/openrewrite/staticanalysis/UseDiamondOperator.java +++ b/src/main/java/org/openrewrite/staticanalysis/UseDiamondOperator.java @@ -213,7 +213,8 @@ private J.NewClass maybeRemoveParams(@Nullable List paramTypes, J.NewC if (paramTypes != null && (java9 || newClass.getBody() == null) && newClass.getClazz() instanceof J.ParameterizedType) { J.ParameterizedType newClassType = (J.ParameterizedType) newClass.getClazz(); if (newClassType.getTypeParameters() != null) { - if (paramTypes.size() != newClassType.getTypeParameters().size()) { + if (paramTypes.size() != newClassType.getTypeParameters().size() || + newClassType.getTypeParameters().stream().anyMatch(p -> p instanceof J.AnnotatedType)) { return newClass; } else { for (int i = 0; i < paramTypes.size(); i++) { diff --git a/src/test/java/org/openrewrite/staticanalysis/UseDiamondOperatorTest.java b/src/test/java/org/openrewrite/staticanalysis/UseDiamondOperatorTest.java index d7026ebdc..8e03e2035 100644 --- a/src/test/java/org/openrewrite/staticanalysis/UseDiamondOperatorTest.java +++ b/src/test/java/org/openrewrite/staticanalysis/UseDiamondOperatorTest.java @@ -19,6 +19,7 @@ import org.junit.jupiter.api.Test; import org.openrewrite.DocumentExample; import org.openrewrite.Issue; +import org.openrewrite.java.JavaParser; import org.openrewrite.test.RecipeSpec; import org.openrewrite.test.RewriteTest; @@ -517,4 +518,26 @@ public void visit(Integer object, String ret) { } ); } + @Test + void doNotChangeAnnotatedTypeParameters() { + rewriteRun( + spec -> spec + .allSources(s -> s.markers(javaVersion(9))) + .parser(JavaParser.fromJavaVersion().classpath("annotations-24.0.1")), + //language=java + java(""" + import org.jetbrains.annotations.Nullable; + import java.util.ArrayList; + import java.util.List; + + class Test { + private void test(Object t) { + List l = new ArrayList<@Nullable String>(); + } + } + """ + ) + ); + } + } From 326798a879a824be7dbe374e03710d46dedb4abb Mon Sep 17 00:00:00 2001 From: Knut Wannheden Date: Sat, 11 Nov 2023 16:01:34 +0100 Subject: [PATCH 76/92] `RemoveRedundantTypeCast`: Fix some more cases --- .../RemoveRedundantTypeCast.java | 4 +-- .../RemoveRedundantTypeCastTest.java | 34 +++++++++++++++++++ 2 files changed, 36 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/openrewrite/staticanalysis/RemoveRedundantTypeCast.java b/src/main/java/org/openrewrite/staticanalysis/RemoveRedundantTypeCast.java index ab73be507..9d6c75343 100644 --- a/src/main/java/org/openrewrite/staticanalysis/RemoveRedundantTypeCast.java +++ b/src/main/java/org/openrewrite/staticanalysis/RemoveRedundantTypeCast.java @@ -102,8 +102,8 @@ public J visitTypeCast(J.TypeCast typeCast, ExecutionContext ctx) { JavaType castType = visitedTypeCast.getType(); if (targetType == null || - targetType instanceof JavaType.Primitive && castType != expressionType || - typeCast.getExpression() instanceof J.Lambda && castType instanceof JavaType.Parameterized) { + (targetType instanceof JavaType.Primitive || castType instanceof JavaType.Primitive) && castType != expressionType || + (typeCast.getExpression() instanceof J.Lambda || typeCast.getExpression() instanceof J.MemberReference) && castType instanceof JavaType.Parameterized) { // Not currently supported, this will be more accurate with dataflow analysis. return visitedTypeCast; } else if (!(targetType instanceof JavaType.Array) && TypeUtils.isOfClassType(targetType, "java.lang.Object") || diff --git a/src/test/java/org/openrewrite/staticanalysis/RemoveRedundantTypeCastTest.java b/src/test/java/org/openrewrite/staticanalysis/RemoveRedundantTypeCastTest.java index e2466561f..d7432dc04 100644 --- a/src/test/java/org/openrewrite/staticanalysis/RemoveRedundantTypeCastTest.java +++ b/src/test/java/org/openrewrite/staticanalysis/RemoveRedundantTypeCastTest.java @@ -421,4 +421,38 @@ public MapDropdownChoice(Supplier> choiceMap) { ) ); } + + @Test + void returnPrimitiveIntToWrapperLong() { + rewriteRun( + java( + """ + class Test { + Long method() { + return (long) 1; + } + } + """ + ) + ); + } + + @Test + void castWildcard() { + rewriteRun( + java( + """ + import java.util.ArrayList; + import java.util.List; + + class Test { + void method() { + List list = new ArrayList<>(); + List n = (List) list; + } + } + """ + ) + ); + } } From 74ff6f5cbdac39692c99496f01670bc965ef04e2 Mon Sep 17 00:00:00 2001 From: Knut Wannheden Date: Sat, 11 Nov 2023 21:27:54 +0100 Subject: [PATCH 77/92] `MinimumSwitchCases`: Improve formatting --- .../staticanalysis/JavaElementFactory.java | 2 +- .../staticanalysis/MinimumSwitchCases.java | 2 +- .../MinimumSwitchCasesTest.java | 51 +++++++++++++++++++ 3 files changed, 53 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/openrewrite/staticanalysis/JavaElementFactory.java b/src/main/java/org/openrewrite/staticanalysis/JavaElementFactory.java index ed66b14c4..3c46659e9 100644 --- a/src/main/java/org/openrewrite/staticanalysis/JavaElementFactory.java +++ b/src/main/java/org/openrewrite/staticanalysis/JavaElementFactory.java @@ -52,7 +52,7 @@ static Expression className(JavaType type, boolean qualified) { type = ((JavaType.Parameterized) type).getType(); } if (qualified && type instanceof JavaType.FullyQualified && ((JavaType.FullyQualified) type).getOwningClass() != null) { - J.FieldAccess expression = (J.FieldAccess) className(((JavaType.FullyQualified) type).getOwningClass(), true); + Expression expression = className(((JavaType.FullyQualified) type).getOwningClass(), true); String simpleName = ((JavaType.FullyQualified) type).getClassName(); return new J.FieldAccess( randomId(), diff --git a/src/main/java/org/openrewrite/staticanalysis/MinimumSwitchCases.java b/src/main/java/org/openrewrite/staticanalysis/MinimumSwitchCases.java index 055e98185..bca871178 100644 --- a/src/main/java/org/openrewrite/staticanalysis/MinimumSwitchCases.java +++ b/src/main/java/org/openrewrite/staticanalysis/MinimumSwitchCases.java @@ -164,7 +164,7 @@ public J visitSwitch(J.Switch switch_, ExecutionContext ctx) { return switch_.withMarkers(switch_.getMarkers().add(new DefaultOnly())); } - generatedIf = createIfForEnum(tree, cases[0].getPattern()); + generatedIf = createIfForEnum(tree, cases[0].getPattern()).withPrefix(switch_.getPrefix()); if (cases[1] != null) { Statement elseBody = J.Block.createEmptyBlock(); if (!isDefault(cases[1])) { diff --git a/src/test/java/org/openrewrite/staticanalysis/MinimumSwitchCasesTest.java b/src/test/java/org/openrewrite/staticanalysis/MinimumSwitchCasesTest.java index 97525eadc..3035b3076 100644 --- a/src/test/java/org/openrewrite/staticanalysis/MinimumSwitchCasesTest.java +++ b/src/test/java/org/openrewrite/staticanalysis/MinimumSwitchCasesTest.java @@ -679,6 +679,57 @@ int test(java.io.ObjectInputFilter filter) { ); } + @SuppressWarnings({"ConstantValue", "DataFlowIssue"}) + @Test + void nestedSwitches() { + rewriteRun( + //language=java + java( + """ + class Test { + int test(E e) { + switch (e) { + case A: + switch (e) { + case A: + return 0; + default: + return 1; + } + case B: + default: + return 1; + } + } + enum E { + A, B + } + } + """, + """ + class Test { + int test(E e) { + switch (e) { + case A: + if (e == Test.E.A) { + return 0; + } else { + return 1; + } + case B: + default: + return 1; + } + } + enum E { + A, B + } + } + """ + ) + ); + } + @Test @Issue("https://github.com/openrewrite/rewrite/issues/3076") void multipleSwitchExpressions() { From 6a38549b00cad1088fc1588c4ba1f645113bb991 Mon Sep 17 00:00:00 2001 From: Knut Wannheden Date: Sat, 11 Nov 2023 21:30:54 +0100 Subject: [PATCH 78/92] `ExplicitInitialization`: Fix `ClassCastException` --- .../ExplicitInitializationVisitor.java | 2 +- .../staticanalysis/ExplicitInitializationTest.java | 14 ++++++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/openrewrite/staticanalysis/ExplicitInitializationVisitor.java b/src/main/java/org/openrewrite/staticanalysis/ExplicitInitializationVisitor.java index 5061e141b..852e58520 100644 --- a/src/main/java/org/openrewrite/staticanalysis/ExplicitInitializationVisitor.java +++ b/src/main/java/org/openrewrite/staticanalysis/ExplicitInitializationVisitor.java @@ -86,7 +86,7 @@ public J.VariableDeclarations.NamedVariable visitVariable(J.VariableDeclarations case Int: case Long: case Short: - if (literalInit.getValue() != null && ((Number) literalInit.getValue()).intValue() == 0) { + if (literalInit.getValue() instanceof Number && ((Number) literalInit.getValue()).intValue() == 0) { v = v.withInitializer(null); } break; diff --git a/src/test/java/org/openrewrite/staticanalysis/ExplicitInitializationTest.java b/src/test/java/org/openrewrite/staticanalysis/ExplicitInitializationTest.java index d76baaedb..9655dea0d 100644 --- a/src/test/java/org/openrewrite/staticanalysis/ExplicitInitializationTest.java +++ b/src/test/java/org/openrewrite/staticanalysis/ExplicitInitializationTest.java @@ -115,6 +115,20 @@ void doSomething() { ); } + @Test + void charLiteral() { + rewriteRun( + //language=java + java( + """ + class Test { + int n = '0'; + } + """ + ) + ); + } + @DocumentExample @Test void removeExplicitInitialization() { From f2d66e5df36068c404ac0876b381a034bb9a81f3 Mon Sep 17 00:00:00 2001 From: Knut Wannheden Date: Sun, 12 Nov 2023 20:44:38 +0100 Subject: [PATCH 79/92] `ExplicitInitialization`: Optimize performance --- .../ExplicitInitializationVisitor.java | 28 +++++++++---------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/src/main/java/org/openrewrite/staticanalysis/ExplicitInitializationVisitor.java b/src/main/java/org/openrewrite/staticanalysis/ExplicitInitializationVisitor.java index 852e58520..7df75c27c 100644 --- a/src/main/java/org/openrewrite/staticanalysis/ExplicitInitializationVisitor.java +++ b/src/main/java/org/openrewrite/staticanalysis/ExplicitInitializationVisitor.java @@ -15,22 +15,24 @@ */ package org.openrewrite.staticanalysis; +import lombok.EqualsAndHashCode; +import lombok.Value; import org.openrewrite.Cursor; import org.openrewrite.internal.ListUtils; +import org.openrewrite.java.AnnotationMatcher; import org.openrewrite.java.JavaIsoVisitor; -import org.openrewrite.java.search.FindAnnotations; import org.openrewrite.java.style.ExplicitInitializationStyle; 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 lombok.EqualsAndHashCode; -import lombok.Value; - @Value @EqualsAndHashCode(callSuper = true) public class ExplicitInitializationVisitor

extends JavaIsoVisitor

{ + private static final AnnotationMatcher LOMBOK_VALUE = new AnnotationMatcher("@lombok.Value"); + private static final AnnotationMatcher LOMBOK_BUILDER_DEFAULT = new AnnotationMatcher("@lombok.Builder.Default"); + ExplicitInitializationStyle style; @Override @@ -46,22 +48,20 @@ public J.VariableDeclarations.NamedVariable visitVariable(J.VariableDeclarations .getParentTreeCursor() // maybe J.ClassDecl .getValue(); if (!(maybeClassDecl instanceof J.ClassDeclaration) || - J.ClassDeclaration.Kind.Type.Class != ((J.ClassDeclaration) maybeClassDecl).getKind()) { + J.ClassDeclaration.Kind.Type.Class != ((J.ClassDeclaration) maybeClassDecl).getKind() || + !(variableDeclsCursor.getValue() instanceof J.VariableDeclarations)) { return v; } } - J clz = getCursor().firstEnclosing(J.ClassDeclaration.class); - if (clz != null && !FindAnnotations.find(clz, "@lombok.Value").isEmpty()) { + J.ClassDeclaration clz = getCursor().firstEnclosing(J.ClassDeclaration.class); + if (clz != null && clz.getAllAnnotations().stream().anyMatch(LOMBOK_VALUE::matches)) { return v; } JavaType.Primitive primitive = TypeUtils.asPrimitive(variable.getType()); JavaType.Array array = TypeUtils.asArray(variable.getType()); - J tree = variableDeclsCursor.getValue(); - if (!(tree instanceof J.VariableDeclarations)) { - return v; - } - J.VariableDeclarations variableDecls = (J.VariableDeclarations) tree; - if (!FindAnnotations.find(variableDecls, "@lombok.Builder.Default").isEmpty()) { + + J.VariableDeclarations variableDecls = variableDeclsCursor.getValue(); + if (variableDecls.getAllAnnotations().stream().anyMatch(LOMBOK_BUILDER_DEFAULT::matches)) { return v; } J.Literal literalInit = variable.getInitializer() instanceof J.Literal @@ -69,7 +69,7 @@ public J.VariableDeclarations.NamedVariable visitVariable(J.VariableDeclarations : null; if (literalInit != null && !variableDecls.hasModifier(J.Modifier.Type.Final)) { if (TypeUtils.asFullyQualified(variable.getType()) != null && - JavaType.Primitive.Null.equals(literalInit.getType())) { + JavaType.Primitive.Null.equals(literalInit.getType())) { v = v.withInitializer(null); } else if (primitive != null && !Boolean.TRUE.equals(style.getOnlyObjectReferences())) { switch (primitive) { From 860d82e8ff379c72625401ebd3a562cd2da4cf02 Mon Sep 17 00:00:00 2001 From: Knut Wannheden Date: Sun, 12 Nov 2023 20:57:00 +0100 Subject: [PATCH 80/92] `ReplaceStringBuilderWithString`: Fix NPE --- .../ReplaceStringBuilderWithString.java | 3 +++ .../ReplaceStringBuilderWithStringTest.java | 21 +++++++++++++++++++ 2 files changed, 24 insertions(+) diff --git a/src/main/java/org/openrewrite/staticanalysis/ReplaceStringBuilderWithString.java b/src/main/java/org/openrewrite/staticanalysis/ReplaceStringBuilderWithString.java index 1776e6936..9690b969e 100644 --- a/src/main/java/org/openrewrite/staticanalysis/ReplaceStringBuilderWithString.java +++ b/src/main/java/org/openrewrite/staticanalysis/ReplaceStringBuilderWithString.java @@ -70,6 +70,9 @@ public J visitMethodInvocation(J.MethodInvocation method, ExecutionContext execu Collections.reverse(arguments); adjustExpressions(arguments); + if (arguments.isEmpty()) { + return m; + } Expression additive = ChainStringBuilderAppendCalls.additiveExpression(arguments) .withPrefix(method.getPrefix()); diff --git a/src/test/java/org/openrewrite/staticanalysis/ReplaceStringBuilderWithStringTest.java b/src/test/java/org/openrewrite/staticanalysis/ReplaceStringBuilderWithStringTest.java index a3ec9f117..750b44085 100644 --- a/src/test/java/org/openrewrite/staticanalysis/ReplaceStringBuilderWithStringTest.java +++ b/src/test/java/org/openrewrite/staticanalysis/ReplaceStringBuilderWithStringTest.java @@ -213,4 +213,25 @@ int count() { ) ); } + + @Test + void builderToString() { + rewriteRun( + //language=java + java( + """ + class A { + void method1() { + CharSequence foo = "foo"; + print("foo", new StringBuilder(foo).toString()); + } + + void print(String str, String str2) { + new StringBuilder(str).append(str2); + } + } + """ + ) + ); + } } From bbd286967b431fc19679fd92d66d31f97a82e010 Mon Sep 17 00:00:00 2001 From: Knut Wannheden Date: Sun, 12 Nov 2023 21:00:11 +0100 Subject: [PATCH 81/92] `MinimumSwitchCases`: Performance improvements --- .../staticanalysis/MinimumSwitchCases.java | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/src/main/java/org/openrewrite/staticanalysis/MinimumSwitchCases.java b/src/main/java/org/openrewrite/staticanalysis/MinimumSwitchCases.java index bca871178..25fc5aa0e 100644 --- a/src/main/java/org/openrewrite/staticanalysis/MinimumSwitchCases.java +++ b/src/main/java/org/openrewrite/staticanalysis/MinimumSwitchCases.java @@ -30,7 +30,6 @@ import org.openrewrite.marker.Marker; import org.openrewrite.marker.Markers; -import java.time.Duration; import java.util.ArrayList; import java.util.List; import java.util.Set; @@ -57,18 +56,13 @@ public Set getTags() { return singleton("RSPEC-1301"); } - @Override - public Duration getEstimatedEffortPerOccurrence() { - return Duration.ofMinutes(5); - } - @Override public TreeVisitor getVisitor() { return new JavaVisitor() { final JavaTemplate ifElseIfPrimitive = JavaTemplate.builder("" + "if(#{any()} == #{any()}) {\n" + "} else if(#{any()} == #{any()}) {\n" + - "}").contextSensitive().build(); + "}").build(); final JavaTemplate ifElseIfString = JavaTemplate.builder("" + "if(#{any(java.lang.String)}.equals(#{any(java.lang.String)})) {\n" + @@ -78,7 +72,7 @@ public TreeVisitor getVisitor() { final JavaTemplate ifElsePrimitive = JavaTemplate.builder("" + "if(#{any()} == #{any()}) {\n" + "} else {\n" + - "}").contextSensitive().build(); + "}").build(); final JavaTemplate ifElseString = JavaTemplate.builder("" + "if(#{any(java.lang.String)}.equals(#{any(java.lang.String)})) {\n" + @@ -87,17 +81,17 @@ public TreeVisitor getVisitor() { final JavaTemplate ifPrimitive = JavaTemplate.builder("" + "if(#{any()} == #{any()}) {\n" + - "}").contextSensitive().build(); + "}").build(); final JavaTemplate ifString = JavaTemplate.builder("" + "if(#{any(java.lang.String)}.equals(#{any(java.lang.String)})) {\n" + "}").build(); @Override - public J visitBlock(J.Block block, ExecutionContext executionContext) { + public J visitBlock(J.Block block, ExecutionContext ctx) { // Handle the edge case of the extra-pointless switch statement which contains _only_ the default case return block.withStatements(ListUtils.flatMap(block.getStatements(), (statement) -> { - Statement visited = (Statement) visit(statement, executionContext, getCursor()); + Statement visited = (Statement) visit(statement, ctx, getCursor()); if (!(visited instanceof J.Switch) || !visited.getMarkers().findFirst(DefaultOnly.class).isPresent()) { return visited; } @@ -108,7 +102,7 @@ public J visitBlock(J.Block block, ExecutionContext executionContext) { if (caseStatement instanceof J.Break) { return null; } - return autoFormat(caseStatement, executionContext, getCursor()); + return autoFormat(caseStatement, ctx, getCursor()); }); })); } From c8d4439c18b7773d3b2467d9236b3bea698d71a8 Mon Sep 17 00:00:00 2001 From: Knut Wannheden Date: Sun, 12 Nov 2023 21:15:24 +0100 Subject: [PATCH 82/92] `MinimumSwitchCases`: Fix failing test --- .../staticanalysis/MinimumSwitchCases.java | 36 +++++++++---------- .../groovy/MinimumSwitchCasesTest.java | 35 +++++++++++++++++- 2 files changed, 52 insertions(+), 19 deletions(-) diff --git a/src/main/java/org/openrewrite/staticanalysis/MinimumSwitchCases.java b/src/main/java/org/openrewrite/staticanalysis/MinimumSwitchCases.java index 25fc5aa0e..ae3bfe0df 100644 --- a/src/main/java/org/openrewrite/staticanalysis/MinimumSwitchCases.java +++ b/src/main/java/org/openrewrite/staticanalysis/MinimumSwitchCases.java @@ -48,7 +48,7 @@ public String getDisplayName() { @Override public String getDescription() { return "`switch` statements are useful when many code paths branch depending on the value of a single expression. " + - "For just one or two code paths, the code will be more readable with `if` statements."; + "For just one or two code paths, the code will be more readable with `if` statements."; } @Override @@ -60,32 +60,32 @@ public Set getTags() { public TreeVisitor getVisitor() { return new JavaVisitor() { final JavaTemplate ifElseIfPrimitive = JavaTemplate.builder("" + - "if(#{any()} == #{any()}) {\n" + - "} else if(#{any()} == #{any()}) {\n" + - "}").build(); + "if(#{any()} == #{any()}) {\n" + + "} else if(#{any()} == #{any()}) {\n" + + "}").build(); final JavaTemplate ifElseIfString = JavaTemplate.builder("" + - "if(#{any(java.lang.String)}.equals(#{any(java.lang.String)})) {\n" + - "} else if(#{any(java.lang.String)}.equals(#{any(java.lang.String)})) {\n" + - "}").build(); + "if(#{any(java.lang.String)}.equals(#{any(java.lang.String)})) {\n" + + "} else if(#{any(java.lang.String)}.equals(#{any(java.lang.String)})) {\n" + + "}").build(); final JavaTemplate ifElsePrimitive = JavaTemplate.builder("" + - "if(#{any()} == #{any()}) {\n" + - "} else {\n" + - "}").build(); + "if(#{any()} == #{any()}) {\n" + + "} else {\n" + + "}").build(); final JavaTemplate ifElseString = JavaTemplate.builder("" + - "if(#{any(java.lang.String)}.equals(#{any(java.lang.String)})) {\n" + - "} else {\n" + - "}").build(); + "if(#{any(java.lang.String)}.equals(#{any(java.lang.String)})) {\n" + + "} else {\n" + + "}").build(); final JavaTemplate ifPrimitive = JavaTemplate.builder("" + - "if(#{any()} == #{any()}) {\n" + - "}").build(); + "if(#{any()} == #{any()}) {\n" + + "}").build(); final JavaTemplate ifString = JavaTemplate.builder("" + - "if(#{any(java.lang.String)}.equals(#{any(java.lang.String)})) {\n" + - "}").build(); + "if(#{any(java.lang.String)}.equals(#{any(java.lang.String)})) {\n" + + "}").build(); @Override public J visitBlock(J.Block block, ExecutionContext ctx) { @@ -236,7 +236,7 @@ private boolean isDefault(J.Case case_) { private boolean switchesOnEnum(J.Switch switch_) { JavaType selectorType = switch_.getSelector().getTree().getType(); return selectorType instanceof JavaType.Class - && ((JavaType.Class) selectorType).getKind() == JavaType.Class.Kind.Enum; + && ((JavaType.Class) selectorType).getKind() == JavaType.Class.Kind.Enum; } }; diff --git a/src/test/java/org/openrewrite/staticanalysis/groovy/MinimumSwitchCasesTest.java b/src/test/java/org/openrewrite/staticanalysis/groovy/MinimumSwitchCasesTest.java index 540ec55d0..c84a9a615 100644 --- a/src/test/java/org/openrewrite/staticanalysis/groovy/MinimumSwitchCasesTest.java +++ b/src/test/java/org/openrewrite/staticanalysis/groovy/MinimumSwitchCasesTest.java @@ -35,6 +35,7 @@ public void defaults(RecipeSpec spec) { @Test void nonIdentifierEnum() { rewriteRun( + //language=groovy groovy( """ import java.nio.file.* @@ -62,8 +63,9 @@ void test(OpenOption o) { } @Test - void twoCase() { + void twoCases() { rewriteRun( + //language=groovy groovy( """ def s = "prod" @@ -75,6 +77,37 @@ void twoCase() { println("default") break } + """, + """ + def s = "prod" + if (s == "prod") { + println("prod") + } else { + println("default") + } + """ + ) + ); + } + + @Test + void threeCases() { + rewriteRun( + //language=groovy + groovy( + """ + def s = "prod" + switch(s) { + case "prod": + println("prod") + break + case "test": + println("test") + break + default: + println("default") + break + } """ ) ); From abd38cbb5432f27434f2cfe97cbf2fa134cb6492 Mon Sep 17 00:00:00 2001 From: Knut Wannheden Date: Sun, 12 Nov 2023 21:26:51 +0100 Subject: [PATCH 83/92] `IsEmptyCallOnCollections`: Performance --- .../org/openrewrite/staticanalysis/IsEmptyCallOnCollections.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/java/org/openrewrite/staticanalysis/IsEmptyCallOnCollections.java b/src/main/java/org/openrewrite/staticanalysis/IsEmptyCallOnCollections.java index fb6259293..8270ce05f 100755 --- a/src/main/java/org/openrewrite/staticanalysis/IsEmptyCallOnCollections.java +++ b/src/main/java/org/openrewrite/staticanalysis/IsEmptyCallOnCollections.java @@ -58,7 +58,6 @@ public Duration getEstimatedEffortPerOccurrence() { public TreeVisitor getVisitor() { return Preconditions.check(new UsesMethod<>(COLLECTION_SIZE), new JavaVisitor() { final JavaTemplate isEmpty = JavaTemplate.builder("#{}#{any(java.util.Collection)}.isEmpty()") - .contextSensitive() .build(); final JavaTemplate isEmptyNoReceiver = JavaTemplate.builder("#{}isEmpty()") .contextSensitive() From b825a5c8a9decaea20bb36a35ff5edcba1ee8334 Mon Sep 17 00:00:00 2001 From: Knut Wannheden Date: Sun, 12 Nov 2023 22:06:51 +0100 Subject: [PATCH 84/92] `CovariantEquals`: Performance --- .../staticanalysis/CovariantEqualsVisitor.java | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/src/main/java/org/openrewrite/staticanalysis/CovariantEqualsVisitor.java b/src/main/java/org/openrewrite/staticanalysis/CovariantEqualsVisitor.java index 89d4fafbc..bd58a4233 100644 --- a/src/main/java/org/openrewrite/staticanalysis/CovariantEqualsVisitor.java +++ b/src/main/java/org/openrewrite/staticanalysis/CovariantEqualsVisitor.java @@ -34,8 +34,10 @@ public class CovariantEqualsVisitor

extends JavaIsoVisitor

{ @Override public J.ClassDeclaration visitClassDeclaration(J.ClassDeclaration classDecl, P p) { J.ClassDeclaration cd = super.visitClassDeclaration(classDecl, p); - Stream mds = cd.getBody().getStatements().stream().filter(J.MethodDeclaration.class::isInstance).map(J.MethodDeclaration.class::cast); - if (mds.noneMatch(m -> OBJECT_EQUALS.matches(m, classDecl)) && cd.getKind() != J.ClassDeclaration.Kind.Type.Interface) { + Stream mds = cd.getBody().getStatements().stream() + .filter(J.MethodDeclaration.class::isInstance) + .map(J.MethodDeclaration.class::cast); + if (cd.getKind() != J.ClassDeclaration.Kind.Type.Interface && mds.noneMatch(m -> OBJECT_EQUALS.matches(m, classDecl))) { cd = (J.ClassDeclaration) new ChangeCovariantEqualsMethodVisitor<>(cd).visit(cd, p, getCursor().getParentOrThrow()); assert cd != null; } @@ -64,15 +66,15 @@ public J.MethodDeclaration visitMethodDeclaration(J.MethodDeclaration method, P * We'll replace it with "public boolean equals(Object)" */ JavaType.FullyQualified type = enclosingClass.getType(); - if (type == null || type == JavaType.Unknown.getInstance()) { + if (type == null || type instanceof JavaType.Unknown) { return m; } String ecfqn = type.getFullyQualifiedName(); - if (new MethodMatcher(ecfqn + " equals(" + ecfqn + ")").matches(m, enclosingClass) && - m.hasModifier(J.Modifier.Type.Public) && + if (m.hasModifier(J.Modifier.Type.Public) && m.getReturnTypeExpression() != null && - JavaType.Primitive.Boolean.equals(m.getReturnTypeExpression().getType())) { + JavaType.Primitive.Boolean.equals(m.getReturnTypeExpression().getType()) && + new MethodMatcher(ecfqn + " equals(" + ecfqn + ")").matches(m, enclosingClass)) { if (m.getAllAnnotations().stream().noneMatch(OVERRIDE_ANNOTATION::matches)) { m = JavaTemplate.builder("@Override").build() @@ -85,9 +87,9 @@ public J.MethodDeclaration visitMethodDeclaration(J.MethodDeclaration method, P * This is because we prepend these type-checking replacement statements to the existing "equals(..)" body. * Therefore we don't want to collide with any existing variable names. */ - J.VariableDeclarations.NamedVariable oldParamName = ((J.VariableDeclarations) m.getParameters().iterator().next()).getVariables().iterator().next(); + J.VariableDeclarations.NamedVariable oldParamName = ((J.VariableDeclarations) m.getParameters().get(0)).getVariables().get(0); String paramName = "obj".equals(oldParamName.getSimpleName()) ? "other" : "obj"; - m = JavaTemplate.builder("Object #{}").contextSensitive().build() + m = JavaTemplate.builder("Object #{}").build() .apply(updateCursor(m), m.getCoordinates().replaceParameters(), paramName); From 44b99034737c5d2eff40c466622cf39fbf066bff Mon Sep 17 00:00:00 2001 From: Knut Wannheden Date: Sun, 12 Nov 2023 23:11:28 +0100 Subject: [PATCH 85/92] `ReplaceLambdaWithMethodReference`: Performance --- .../staticanalysis/JavaElementFactory.java | 4 ++++ .../ReplaceLambdaWithMethodReference.java | 17 +++++++++-------- 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/src/main/java/org/openrewrite/staticanalysis/JavaElementFactory.java b/src/main/java/org/openrewrite/staticanalysis/JavaElementFactory.java index 3c46659e9..6c66ad50c 100644 --- a/src/main/java/org/openrewrite/staticanalysis/JavaElementFactory.java +++ b/src/main/java/org/openrewrite/staticanalysis/JavaElementFactory.java @@ -167,4 +167,8 @@ private static JavaType.Class getClassType(@Nullable JavaType type) { } return null; } + + public static J.Identifier newThis(JavaType type) { + return new J.Identifier(randomId(), Space.EMPTY, Markers.EMPTY, emptyList(), "this", type, null); + } } diff --git a/src/main/java/org/openrewrite/staticanalysis/ReplaceLambdaWithMethodReference.java b/src/main/java/org/openrewrite/staticanalysis/ReplaceLambdaWithMethodReference.java index f29c78e46..78300e0cf 100644 --- a/src/main/java/org/openrewrite/staticanalysis/ReplaceLambdaWithMethodReference.java +++ b/src/main/java/org/openrewrite/staticanalysis/ReplaceLambdaWithMethodReference.java @@ -73,8 +73,8 @@ private static class ReplaceLambdaWithMethodReferenceKotlinVisitor extends Kotli private static class ReplaceLambdaWithMethodReferenceJavaVisitor extends JavaVisitor { @Override - public J visitLambda(J.Lambda lambda, ExecutionContext executionContext) { - J.Lambda l = (J.Lambda) super.visitLambda(lambda, executionContext); + public J visitLambda(J.Lambda lambda, ExecutionContext ctx) { + J.Lambda l = (J.Lambda) super.visitLambda(lambda, ctx); updateCursor(l); String code = ""; @@ -181,12 +181,13 @@ public J visitLambda(J.Lambda lambda, ExecutionContext executionContext) { } else if (select != null) { return newInstanceMethodReference(methodType, select, lambda.getType()).withPrefix(lambda.getPrefix()); } else { - String templ = "#{}::#{}"; - return JavaTemplate.builder(templ) - .contextSensitive() - .build() - .apply(getCursor(), l.getCoordinates().replace(), "this", - method.getMethodType().getName()); + Cursor owner = getCursor().dropParentUntil(is -> is instanceof J.ClassDeclaration || + (is instanceof J.NewClass && ((J.NewClass) is).getBody() != null) || + is instanceof J.Lambda); + return JavaElementFactory.newInstanceMethodReference( + method.getMethodType(), + JavaElementFactory.newThis(owner.getValue().getType()), lambda.getType() + ).withPrefix(lambda.getPrefix()); } } } From aa570125a74c8175ab2103d1c34c28e366f49efc Mon Sep 17 00:00:00 2001 From: Tim te Beek Date: Wed, 15 Nov 2023 11:39:16 +0100 Subject: [PATCH 86/92] Remove duplicated semicolons, without removing newline (#216) * Remove duplicated semicolons, without removing newline * Review suggestions * No need for StringBuilder --- .../staticanalysis/RemoveExtraSemicolons.java | 22 ++++++------- .../RemoveExtraSemicolonsTest.java | 33 +++++++++++++++++++ 2 files changed, 43 insertions(+), 12 deletions(-) diff --git a/src/main/java/org/openrewrite/staticanalysis/RemoveExtraSemicolons.java b/src/main/java/org/openrewrite/staticanalysis/RemoveExtraSemicolons.java index 74b4b3a87..313d3a84c 100644 --- a/src/main/java/org/openrewrite/staticanalysis/RemoveExtraSemicolons.java +++ b/src/main/java/org/openrewrite/staticanalysis/RemoveExtraSemicolons.java @@ -15,15 +15,6 @@ */ package org.openrewrite.staticanalysis; -import java.time.Duration; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Iterator; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Optional; -import java.util.Set; - import org.openrewrite.ExecutionContext; import org.openrewrite.Recipe; import org.openrewrite.TreeVisitor; @@ -33,6 +24,9 @@ import org.openrewrite.java.tree.Space; import org.openrewrite.java.tree.Statement; +import java.time.Duration; +import java.util.*; + public class RemoveExtraSemicolons extends Recipe { @Override @@ -69,9 +63,13 @@ public J.Block visitBlock(final J.Block block, final ExecutionContext executionC if (statement instanceof J.Empty) { nextNonEmptyAggregatedWithComments(statement, iterator) .ifPresent(nextLine -> { - Space updatedPrefix = nextLine.getPrefix() - .withWhitespace(statement.getPrefix().getWhitespace()); - result.add(nextLine.withPrefix(updatedPrefix)); + String whitespace = statement.getPrefix().getWhitespace(); + if (!whitespace.contains("\n") && nextLine.getComments().isEmpty()) { + result.add(nextLine); + } else { + Space updatedPrefix = nextLine.getPrefix().withWhitespace(whitespace); + result.add(nextLine.withPrefix(updatedPrefix)); + } }); } else { result.add(statement); diff --git a/src/test/java/org/openrewrite/staticanalysis/RemoveExtraSemicolonsTest.java b/src/test/java/org/openrewrite/staticanalysis/RemoveExtraSemicolonsTest.java index 079350f59..e92110296 100644 --- a/src/test/java/org/openrewrite/staticanalysis/RemoveExtraSemicolonsTest.java +++ b/src/test/java/org/openrewrite/staticanalysis/RemoveExtraSemicolonsTest.java @@ -213,4 +213,37 @@ int test() { ) ); } + + @Test + void repeatedSemicolon() { + rewriteRun( + //language=java + java( + """ + class Test { + void test() { + int a = 1;; + int b = 2; + int c = 3;;; + int d = 4; + int e = 5; ; + int f = 6; + } + } + """, + """ + class Test { + void test() { + int a = 1; + int b = 2; + int c = 3; + int d = 4; + int e = 5; + int f = 6; + } + } + """ + ) + ); + } } From a95d0505fa32ddb4b7edfb6853d973bafc13a442 Mon Sep 17 00:00:00 2001 From: Knut Wannheden Date: Thu, 16 Nov 2023 13:12:09 +0100 Subject: [PATCH 87/92] Fix failing `UseDiamondOperatorTest` test case --- .../org/openrewrite/staticanalysis/UseDiamondOperatorTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/java/org/openrewrite/staticanalysis/UseDiamondOperatorTest.java b/src/test/java/org/openrewrite/staticanalysis/UseDiamondOperatorTest.java index 8e03e2035..d29d5840e 100644 --- a/src/test/java/org/openrewrite/staticanalysis/UseDiamondOperatorTest.java +++ b/src/test/java/org/openrewrite/staticanalysis/UseDiamondOperatorTest.java @@ -523,7 +523,7 @@ void doNotChangeAnnotatedTypeParameters() { rewriteRun( spec -> spec .allSources(s -> s.markers(javaVersion(9))) - .parser(JavaParser.fromJavaVersion().classpath("annotations-24.0.1")), + .parser(JavaParser.fromJavaVersion().classpath("annotations-24.1.0")), //language=java java(""" import org.jetbrains.annotations.Nullable; From cba8ef0a14f87056d42e1d7f4d26b0617af4e614 Mon Sep 17 00:00:00 2001 From: Knut Wannheden Date: Fri, 17 Nov 2023 09:26:11 +0100 Subject: [PATCH 88/92] Use `ImportService` rather than `ShortenFullyQualifiedTypeReferences` --- .../openrewrite/staticanalysis/MinimumSwitchCases.java | 4 ++-- .../ReplaceLambdaWithMethodReference.java | 10 +++++----- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/main/java/org/openrewrite/staticanalysis/MinimumSwitchCases.java b/src/main/java/org/openrewrite/staticanalysis/MinimumSwitchCases.java index ae3bfe0df..9bfd23854 100644 --- a/src/main/java/org/openrewrite/staticanalysis/MinimumSwitchCases.java +++ b/src/main/java/org/openrewrite/staticanalysis/MinimumSwitchCases.java @@ -25,7 +25,7 @@ import org.openrewrite.internal.RecipeRunException; import org.openrewrite.java.JavaTemplate; import org.openrewrite.java.JavaVisitor; -import org.openrewrite.java.ShortenFullyQualifiedTypeReferences; +import org.openrewrite.java.service.ImportService; import org.openrewrite.java.tree.*; import org.openrewrite.marker.Marker; import org.openrewrite.marker.Markers; @@ -173,7 +173,7 @@ public J visitSwitch(J.Switch switch_, ExecutionContext ctx) { ) ); } - doAfterVisit(ShortenFullyQualifiedTypeReferences.modifyOnly(generatedIf)); + doAfterVisit(service(ImportService.class).shortenFullyQualifiedTypeReferencesIn(generatedIf)); } else { if (cases[1] == null) { if (isDefault(cases[0])) { diff --git a/src/main/java/org/openrewrite/staticanalysis/ReplaceLambdaWithMethodReference.java b/src/main/java/org/openrewrite/staticanalysis/ReplaceLambdaWithMethodReference.java index 78300e0cf..d66c7e029 100644 --- a/src/main/java/org/openrewrite/staticanalysis/ReplaceLambdaWithMethodReference.java +++ b/src/main/java/org/openrewrite/staticanalysis/ReplaceLambdaWithMethodReference.java @@ -19,7 +19,7 @@ import org.openrewrite.internal.lang.Nullable; import org.openrewrite.java.JavaTemplate; import org.openrewrite.java.JavaVisitor; -import org.openrewrite.java.ShortenFullyQualifiedTypeReferences; +import org.openrewrite.java.service.ImportService; import org.openrewrite.java.tree.*; import org.openrewrite.kotlin.KotlinVisitor; import org.openrewrite.kotlin.tree.K; @@ -99,7 +99,7 @@ public J visitLambda(J.Lambda lambda, ExecutionContext ctx) { Optional isInstanceMethod = rawClassType.getMethods().stream().filter(m -> m.getName().equals("isInstance")).findFirst(); if (isInstanceMethod.isPresent()) { J.MemberReference updated = newInstanceMethodReference(isInstanceMethod.get(), classLiteral, lambda.getType()).withPrefix(lambda.getPrefix()); - doAfterVisit(ShortenFullyQualifiedTypeReferences.modifyOnly(updated)); + doAfterVisit(service(ImportService.class).shortenFullyQualifiedTypeReferencesIn(updated)); return updated; } } @@ -120,7 +120,7 @@ public J visitLambda(J.Lambda lambda, ExecutionContext ctx) { Optional castMethod = classType.getMethods().stream().filter(m -> m.getName().equals("cast")).findFirst(); if (castMethod.isPresent()) { J.MemberReference updated = newInstanceMethodReference(castMethod.get(), classLiteral, lambda.getType()).withPrefix(lambda.getPrefix()); - doAfterVisit(ShortenFullyQualifiedTypeReferences.modifyOnly(updated)); + doAfterVisit(service(ImportService.class).shortenFullyQualifiedTypeReferencesIn(updated)); return updated; } } @@ -138,7 +138,7 @@ public J visitLambda(J.Lambda lambda, ExecutionContext ctx) { .contextSensitive() .build() .apply(getCursor(), l.getCoordinates().replace()); - doAfterVisit(ShortenFullyQualifiedTypeReferences.modifyOnly(updated)); + doAfterVisit(service(ImportService.class).shortenFullyQualifiedTypeReferencesIn(updated)); return updated; } } else if (body instanceof MethodCall) { @@ -171,7 +171,7 @@ public J visitLambda(J.Lambda lambda, ExecutionContext ctx) { if (methodType.hasFlags(Flag.Static) || methodSelectMatchesFirstLambdaParameter(method, lambda)) { J.MemberReference updated = newStaticMethodReference(methodType, true, lambda.getType()).withPrefix(lambda.getPrefix()); - doAfterVisit(ShortenFullyQualifiedTypeReferences.modifyOnly(updated)); + doAfterVisit(service(ImportService.class).shortenFullyQualifiedTypeReferencesIn(updated)); return updated; } else if (method instanceof J.NewClass) { return JavaTemplate.builder("#{}::new") From 23ee04cc6c9baff6677a5c7efc233f28cd244b1c Mon Sep 17 00:00:00 2001 From: Tim te Beek Date: Fri, 17 Nov 2023 11:37:04 +0100 Subject: [PATCH 89/92] RemoveUnusedPrivateFields should remove imports (#218) As discovered on - https://github.com/openrewrite/rewrite-launchdarkly/pull/10 --- .../RemoveUnusedPrivateFields.java | 11 ++++++----- .../RemoveUnusedPrivateFieldsTest.java | 19 +++++++++++++++++++ 2 files changed, 25 insertions(+), 5 deletions(-) diff --git a/src/main/java/org/openrewrite/staticanalysis/RemoveUnusedPrivateFields.java b/src/main/java/org/openrewrite/staticanalysis/RemoveUnusedPrivateFields.java index 616dccddf..7d4c0f657 100644 --- a/src/main/java/org/openrewrite/staticanalysis/RemoveUnusedPrivateFields.java +++ b/src/main/java/org/openrewrite/staticanalysis/RemoveUnusedPrivateFields.java @@ -24,10 +24,7 @@ import org.openrewrite.internal.lang.Nullable; import org.openrewrite.java.JavaIsoVisitor; import org.openrewrite.java.JavaVisitor; -import org.openrewrite.java.tree.J; -import org.openrewrite.java.tree.Space; -import org.openrewrite.java.tree.Statement; -import org.openrewrite.java.tree.TypeUtils; +import org.openrewrite.java.tree.*; import java.time.Duration; import java.util.*; @@ -113,7 +110,11 @@ public J.ClassDeclaration visitClassDeclaration(J.ClassDeclaration classDecl, Ex for (Map.Entry> entry : inUse.entrySet()) { if (entry.getValue().isEmpty()) { AtomicBoolean declarationDeleted = new AtomicBoolean(); - cd = (J.ClassDeclaration) new RemoveUnusedField(entry.getKey()).visitNonNull(cd, declarationDeleted); + J.VariableDeclarations.NamedVariable fieldToRemove = entry.getKey(); + cd = (J.ClassDeclaration) new RemoveUnusedField(fieldToRemove).visitNonNull(cd, declarationDeleted); + if (fieldToRemove.getType() != null) { + maybeRemoveImport(fieldToRemove.getType().toString()); + } // Maybe remove next statement comment if variable declarations is removed if (declarationDeleted.get()) { cd = (J.ClassDeclaration) new MaybeRemoveComment(checkField.nextStatement, cd).visitNonNull(cd, executionContext); diff --git a/src/test/java/org/openrewrite/staticanalysis/RemoveUnusedPrivateFieldsTest.java b/src/test/java/org/openrewrite/staticanalysis/RemoveUnusedPrivateFieldsTest.java index be8f4e380..692a91e21 100644 --- a/src/test/java/org/openrewrite/staticanalysis/RemoveUnusedPrivateFieldsTest.java +++ b/src/test/java/org/openrewrite/staticanalysis/RemoveUnusedPrivateFieldsTest.java @@ -141,6 +141,25 @@ public class Test { ); } + @Test + void removeUnusedPrivateFieldImport() { + rewriteRun( + //language=java + java( + """ + import java.util.List; + public class Test { + private List notUsed; + } + """, + """ + public class Test { + } + """ + ) + ); + } + @SuppressWarnings("UnnecessaryLocalVariable") @Test void nameIsShadowed() { From f48b184e62d82566e5f8ee8194b6a985a359c530 Mon Sep 17 00:00:00 2001 From: Tim te Beek Date: Fri, 17 Nov 2023 14:58:50 +0100 Subject: [PATCH 90/92] Do not finalize private static fields assigned in constructor (#215) * Do not finalize private static fields assigned in constructor Fixes https://github.com/openrewrite/rewrite-static-analysis/issues/177 * Add additional test from review * Check whether static fields are set in static blocks --- .../staticanalysis/FinalizePrivateFields.java | 41 +++++++++---------- .../FinalizePrivateFieldsTest.java | 36 ++++++++++++++++ 2 files changed, 55 insertions(+), 22 deletions(-) diff --git a/src/main/java/org/openrewrite/staticanalysis/FinalizePrivateFields.java b/src/main/java/org/openrewrite/staticanalysis/FinalizePrivateFields.java index d43e3dcd2..4bdc74c71 100644 --- a/src/main/java/org/openrewrite/staticanalysis/FinalizePrivateFields.java +++ b/src/main/java/org/openrewrite/staticanalysis/FinalizePrivateFields.java @@ -213,7 +213,7 @@ private static void updateAssignmentCount(Cursor cursor, int increment; if (isInLoop(cursor) || isInLambda(cursor)) { increment = 2; - } else if (isInitializedByClass(cursor)) { + } else if (isInitializedByClass(cursor, privateField.hasFlags(Flag.Static))) { increment = 1; } else { increment = 2; @@ -227,31 +227,26 @@ private static boolean isInLoop(Cursor cursor) { return isInForLoop(cursor) || isInDoWhileLoopLoop(cursor) || isInWhileLoop(cursor); } - private static boolean isConstructor(Object parent) { - if (parent instanceof J.MethodDeclaration) { - return ((J.MethodDeclaration) parent).isConstructor(); - } - return false; - } - - private static boolean isInitializerBlock(Object parent) { - return parent instanceof J.ClassDeclaration; - } - /** - * @param cursor current assignment position + * @param cursor current assignment position + * @param privateFieldIsStatic true if the private field is static * @return true if the cursor is in a constructor or an initializer block (both static or non-static) */ - private static boolean isInitializedByClass(Cursor cursor) { - Object parent = cursor.dropParentWhile(p -> p instanceof J.Block - || p instanceof JRightPadded - || p instanceof JLeftPadded) - .getValue(); - - if (parent instanceof J.MethodDeclaration || parent instanceof J.ClassDeclaration) { - return (isConstructor(parent) || isInitializerBlock(parent)); + private static boolean isInitializedByClass(Cursor cursor, boolean privateFieldIsStatic) { + Object parent = cursor.dropParentWhile(p -> (p instanceof J.Block && !((J.Block) p).isStatic()) + || p instanceof JRightPadded + || p instanceof JLeftPadded) + .getValue(); + if (parent instanceof J.Block) { + return privateFieldIsStatic; } - return false; + if (privateFieldIsStatic) { + return false; + } + if (parent instanceof J.MethodDeclaration) { + return ((J.MethodDeclaration) parent).isConstructor(); + } + return parent instanceof J.ClassDeclaration; } /** @@ -263,6 +258,7 @@ private static boolean dropCursorEndCondition(Object parent) { /** * Drop until meet endCondition or condition + * * @return true if meet the condition, or false if not meet the condition until the end. */ private static boolean dropUntilMeetCondition(Cursor cursor, @@ -302,6 +298,7 @@ private static boolean isInLambda(Cursor cursor) { private static class FindLastIdentifier extends JavaIsoVisitor> { /** * Find the last identifier in a J.FieldAccess. The purpose is to check whether it's a private field. + * * @param j the subtree to search, supposed to be a J.FieldAccess * @return the last Identifier if found, otherwise null. */ diff --git a/src/test/java/org/openrewrite/staticanalysis/FinalizePrivateFieldsTest.java b/src/test/java/org/openrewrite/staticanalysis/FinalizePrivateFieldsTest.java index d1f866dad..b81f2a24b 100644 --- a/src/test/java/org/openrewrite/staticanalysis/FinalizePrivateFieldsTest.java +++ b/src/test/java/org/openrewrite/staticanalysis/FinalizePrivateFieldsTest.java @@ -859,4 +859,40 @@ private Reproducer() { ) ); } + + @Test + @Issue("https://github.com/openrewrite/rewrite-static-analysis/issues/177") + void staticFieldAssignedInConstructorNotMadeFinal() { + //language=java + rewriteRun( + java( + """ + public class Reproducer { + private static Reproducer instance; + public Reproducer() { + instance = this; + } + } + """ + ) + ); + } + + @Test + @Issue("https://github.com/openrewrite/rewrite-static-analysis/issues/177") + void staticFieldAssignedInBlockNotMadeFinal() { + //language=java + rewriteRun( + java( + """ + public class Reproducer { + private static Reproducer instance; + { + instance = new Reproducer(); + } + } + """ + ) + ); + } } From 4dcedf6a87b45e383fc3cf398c35698c6ceb5078 Mon Sep 17 00:00:00 2001 From: Knut Wannheden Date: Sat, 18 Nov 2023 11:03:14 +0100 Subject: [PATCH 91/92] Revert "Use `ImportService` rather than `ShortenFullyQualifiedTypeReferences`" This reverts commit cba8ef0a14f87056d42e1d7f4d26b0617af4e614. --- .../openrewrite/staticanalysis/MinimumSwitchCases.java | 4 ++-- .../ReplaceLambdaWithMethodReference.java | 10 +++++----- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/main/java/org/openrewrite/staticanalysis/MinimumSwitchCases.java b/src/main/java/org/openrewrite/staticanalysis/MinimumSwitchCases.java index 9bfd23854..ae3bfe0df 100644 --- a/src/main/java/org/openrewrite/staticanalysis/MinimumSwitchCases.java +++ b/src/main/java/org/openrewrite/staticanalysis/MinimumSwitchCases.java @@ -25,7 +25,7 @@ import org.openrewrite.internal.RecipeRunException; import org.openrewrite.java.JavaTemplate; import org.openrewrite.java.JavaVisitor; -import org.openrewrite.java.service.ImportService; +import org.openrewrite.java.ShortenFullyQualifiedTypeReferences; import org.openrewrite.java.tree.*; import org.openrewrite.marker.Marker; import org.openrewrite.marker.Markers; @@ -173,7 +173,7 @@ public J visitSwitch(J.Switch switch_, ExecutionContext ctx) { ) ); } - doAfterVisit(service(ImportService.class).shortenFullyQualifiedTypeReferencesIn(generatedIf)); + doAfterVisit(ShortenFullyQualifiedTypeReferences.modifyOnly(generatedIf)); } else { if (cases[1] == null) { if (isDefault(cases[0])) { diff --git a/src/main/java/org/openrewrite/staticanalysis/ReplaceLambdaWithMethodReference.java b/src/main/java/org/openrewrite/staticanalysis/ReplaceLambdaWithMethodReference.java index d66c7e029..78300e0cf 100644 --- a/src/main/java/org/openrewrite/staticanalysis/ReplaceLambdaWithMethodReference.java +++ b/src/main/java/org/openrewrite/staticanalysis/ReplaceLambdaWithMethodReference.java @@ -19,7 +19,7 @@ import org.openrewrite.internal.lang.Nullable; import org.openrewrite.java.JavaTemplate; import org.openrewrite.java.JavaVisitor; -import org.openrewrite.java.service.ImportService; +import org.openrewrite.java.ShortenFullyQualifiedTypeReferences; import org.openrewrite.java.tree.*; import org.openrewrite.kotlin.KotlinVisitor; import org.openrewrite.kotlin.tree.K; @@ -99,7 +99,7 @@ public J visitLambda(J.Lambda lambda, ExecutionContext ctx) { Optional isInstanceMethod = rawClassType.getMethods().stream().filter(m -> m.getName().equals("isInstance")).findFirst(); if (isInstanceMethod.isPresent()) { J.MemberReference updated = newInstanceMethodReference(isInstanceMethod.get(), classLiteral, lambda.getType()).withPrefix(lambda.getPrefix()); - doAfterVisit(service(ImportService.class).shortenFullyQualifiedTypeReferencesIn(updated)); + doAfterVisit(ShortenFullyQualifiedTypeReferences.modifyOnly(updated)); return updated; } } @@ -120,7 +120,7 @@ public J visitLambda(J.Lambda lambda, ExecutionContext ctx) { Optional castMethod = classType.getMethods().stream().filter(m -> m.getName().equals("cast")).findFirst(); if (castMethod.isPresent()) { J.MemberReference updated = newInstanceMethodReference(castMethod.get(), classLiteral, lambda.getType()).withPrefix(lambda.getPrefix()); - doAfterVisit(service(ImportService.class).shortenFullyQualifiedTypeReferencesIn(updated)); + doAfterVisit(ShortenFullyQualifiedTypeReferences.modifyOnly(updated)); return updated; } } @@ -138,7 +138,7 @@ public J visitLambda(J.Lambda lambda, ExecutionContext ctx) { .contextSensitive() .build() .apply(getCursor(), l.getCoordinates().replace()); - doAfterVisit(service(ImportService.class).shortenFullyQualifiedTypeReferencesIn(updated)); + doAfterVisit(ShortenFullyQualifiedTypeReferences.modifyOnly(updated)); return updated; } } else if (body instanceof MethodCall) { @@ -171,7 +171,7 @@ public J visitLambda(J.Lambda lambda, ExecutionContext ctx) { if (methodType.hasFlags(Flag.Static) || methodSelectMatchesFirstLambdaParameter(method, lambda)) { J.MemberReference updated = newStaticMethodReference(methodType, true, lambda.getType()).withPrefix(lambda.getPrefix()); - doAfterVisit(service(ImportService.class).shortenFullyQualifiedTypeReferencesIn(updated)); + doAfterVisit(ShortenFullyQualifiedTypeReferences.modifyOnly(updated)); return updated; } else if (method instanceof J.NewClass) { return JavaTemplate.builder("#{}::new") From 039558fb3d7573aef7097bb1218fa3602adc5841 Mon Sep 17 00:00:00 2001 From: Knut Wannheden Date: Fri, 17 Nov 2023 09:26:11 +0100 Subject: [PATCH 92/92] Use `ImportService` rather than `ShortenFullyQualifiedTypeReferences` (cherry picked from commit cba8ef0a14f87056d42e1d7f4d26b0617af4e614) --- .../openrewrite/staticanalysis/MinimumSwitchCases.java | 4 ++-- .../ReplaceLambdaWithMethodReference.java | 10 +++++----- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/main/java/org/openrewrite/staticanalysis/MinimumSwitchCases.java b/src/main/java/org/openrewrite/staticanalysis/MinimumSwitchCases.java index ae3bfe0df..9bfd23854 100644 --- a/src/main/java/org/openrewrite/staticanalysis/MinimumSwitchCases.java +++ b/src/main/java/org/openrewrite/staticanalysis/MinimumSwitchCases.java @@ -25,7 +25,7 @@ import org.openrewrite.internal.RecipeRunException; import org.openrewrite.java.JavaTemplate; import org.openrewrite.java.JavaVisitor; -import org.openrewrite.java.ShortenFullyQualifiedTypeReferences; +import org.openrewrite.java.service.ImportService; import org.openrewrite.java.tree.*; import org.openrewrite.marker.Marker; import org.openrewrite.marker.Markers; @@ -173,7 +173,7 @@ public J visitSwitch(J.Switch switch_, ExecutionContext ctx) { ) ); } - doAfterVisit(ShortenFullyQualifiedTypeReferences.modifyOnly(generatedIf)); + doAfterVisit(service(ImportService.class).shortenFullyQualifiedTypeReferencesIn(generatedIf)); } else { if (cases[1] == null) { if (isDefault(cases[0])) { diff --git a/src/main/java/org/openrewrite/staticanalysis/ReplaceLambdaWithMethodReference.java b/src/main/java/org/openrewrite/staticanalysis/ReplaceLambdaWithMethodReference.java index 78300e0cf..d66c7e029 100644 --- a/src/main/java/org/openrewrite/staticanalysis/ReplaceLambdaWithMethodReference.java +++ b/src/main/java/org/openrewrite/staticanalysis/ReplaceLambdaWithMethodReference.java @@ -19,7 +19,7 @@ import org.openrewrite.internal.lang.Nullable; import org.openrewrite.java.JavaTemplate; import org.openrewrite.java.JavaVisitor; -import org.openrewrite.java.ShortenFullyQualifiedTypeReferences; +import org.openrewrite.java.service.ImportService; import org.openrewrite.java.tree.*; import org.openrewrite.kotlin.KotlinVisitor; import org.openrewrite.kotlin.tree.K; @@ -99,7 +99,7 @@ public J visitLambda(J.Lambda lambda, ExecutionContext ctx) { Optional isInstanceMethod = rawClassType.getMethods().stream().filter(m -> m.getName().equals("isInstance")).findFirst(); if (isInstanceMethod.isPresent()) { J.MemberReference updated = newInstanceMethodReference(isInstanceMethod.get(), classLiteral, lambda.getType()).withPrefix(lambda.getPrefix()); - doAfterVisit(ShortenFullyQualifiedTypeReferences.modifyOnly(updated)); + doAfterVisit(service(ImportService.class).shortenFullyQualifiedTypeReferencesIn(updated)); return updated; } } @@ -120,7 +120,7 @@ public J visitLambda(J.Lambda lambda, ExecutionContext ctx) { Optional castMethod = classType.getMethods().stream().filter(m -> m.getName().equals("cast")).findFirst(); if (castMethod.isPresent()) { J.MemberReference updated = newInstanceMethodReference(castMethod.get(), classLiteral, lambda.getType()).withPrefix(lambda.getPrefix()); - doAfterVisit(ShortenFullyQualifiedTypeReferences.modifyOnly(updated)); + doAfterVisit(service(ImportService.class).shortenFullyQualifiedTypeReferencesIn(updated)); return updated; } } @@ -138,7 +138,7 @@ public J visitLambda(J.Lambda lambda, ExecutionContext ctx) { .contextSensitive() .build() .apply(getCursor(), l.getCoordinates().replace()); - doAfterVisit(ShortenFullyQualifiedTypeReferences.modifyOnly(updated)); + doAfterVisit(service(ImportService.class).shortenFullyQualifiedTypeReferencesIn(updated)); return updated; } } else if (body instanceof MethodCall) { @@ -171,7 +171,7 @@ public J visitLambda(J.Lambda lambda, ExecutionContext ctx) { if (methodType.hasFlags(Flag.Static) || methodSelectMatchesFirstLambdaParameter(method, lambda)) { J.MemberReference updated = newStaticMethodReference(methodType, true, lambda.getType()).withPrefix(lambda.getPrefix()); - doAfterVisit(ShortenFullyQualifiedTypeReferences.modifyOnly(updated)); + doAfterVisit(service(ImportService.class).shortenFullyQualifiedTypeReferencesIn(updated)); return updated; } else if (method instanceof J.NewClass) { return JavaTemplate.builder("#{}::new")