From a6e524cccef1417e2461fb33bfae25846c9166a2 Mon Sep 17 00:00:00 2001 From: Aleksandar A Simpson Date: Mon, 26 Jun 2023 02:56:51 -0700 Subject: [PATCH] Remove hashCode calls on arrays (#128) * added first tests for recipe. All tests pass right now * updated recipe displayName and description * added RSPEC tags, switched assert statement for if statement and updated description * Polish - Remove `Preconditions.or` for single element - Collapse `if` statements for select type check - Inline `builder_string`, as variable name does not conform to Java standards & limited use & scope allows for inlining - Remove unnecessary elements from test cases to prevent confusion - Remove public visibility from test class --------- Co-authored-by: Tim te Beek --- ...RemoveHashCodeCallsFromArrayInstances.java | 76 +++++++++++++ ...veHashCodeCallsFromArrayInstancesTest.java | 101 ++++++++++++++++++ 2 files changed, 177 insertions(+) create mode 100644 src/main/java/org/openrewrite/staticanalysis/RemoveHashCodeCallsFromArrayInstances.java create mode 100644 src/test/java/org/openrewrite/staticanalysis/RemoveHashCodeCallsFromArrayInstancesTest.java diff --git a/src/main/java/org/openrewrite/staticanalysis/RemoveHashCodeCallsFromArrayInstances.java b/src/main/java/org/openrewrite/staticanalysis/RemoveHashCodeCallsFromArrayInstances.java new file mode 100644 index 000000000..fa01557bd --- /dev/null +++ b/src/main/java/org/openrewrite/staticanalysis/RemoveHashCodeCallsFromArrayInstances.java @@ -0,0 +1,76 @@ +/* + * 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.Preconditions; +import org.openrewrite.Recipe; +import org.openrewrite.TreeVisitor; +import org.openrewrite.java.JavaIsoVisitor; +import org.openrewrite.java.JavaTemplate; +import org.openrewrite.java.MethodMatcher; +import org.openrewrite.java.search.UsesMethod; +import org.openrewrite.java.tree.Expression; +import org.openrewrite.java.tree.J; +import org.openrewrite.java.tree.JavaType; + +import java.util.Collections; +import java.util.Set; + +public class RemoveHashCodeCallsFromArrayInstances extends Recipe { + private static final MethodMatcher HASHCODE_MATCHER = new MethodMatcher("java.lang.Object hashCode()"); + + @Override + public String getDisplayName() { + return "`hashCode()` should not be called on array instances"; + } + + @Override + public String getDescription() { + return "Replace `hashCode()` calls on arrays with `Arrays.hashCode()` because the results from `hashCode()`" + + " are not helpful."; + } + + @Override + public Set getTags() { + return Collections.singleton("RSPEC-2116"); + } + + @Override + public TreeVisitor getVisitor() { + return Preconditions.check(new UsesMethod<>(HASHCODE_MATCHER), new RemoveHashCodeCallsFromArrayInstancesVisitor()); + } + + private static class RemoveHashCodeCallsFromArrayInstancesVisitor extends JavaIsoVisitor { + @Override + public J.MethodInvocation visitMethodInvocation(J.MethodInvocation methodInvocation, ExecutionContext ctx) { + J.MethodInvocation mi = super.visitMethodInvocation(methodInvocation, ctx); + + if (HASHCODE_MATCHER.matches(mi)) { + Expression select = mi.getSelect(); + if (select != null && select.getType() instanceof JavaType.Array) { + maybeAddImport("java.util.Arrays"); + return JavaTemplate.builder("Arrays.hashCode(#{anyArray(java.lang.Object)})") + .imports("java.util.Arrays") + .build() + .apply(getCursor(), mi.getCoordinates().replace(), select); + } + } + + return mi; + } + } +} diff --git a/src/test/java/org/openrewrite/staticanalysis/RemoveHashCodeCallsFromArrayInstancesTest.java b/src/test/java/org/openrewrite/staticanalysis/RemoveHashCodeCallsFromArrayInstancesTest.java new file mode 100644 index 000000000..4bce2fe12 --- /dev/null +++ b/src/test/java/org/openrewrite/staticanalysis/RemoveHashCodeCallsFromArrayInstancesTest.java @@ -0,0 +1,101 @@ +/* + * 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.junit.jupiter.api.Test; +import org.openrewrite.DocumentExample; +import org.openrewrite.Issue; +import org.openrewrite.test.RecipeSpec; +import org.openrewrite.test.RewriteTest; + +import static org.openrewrite.java.Assertions.java; + +@SuppressWarnings("ArrayHashCode") +class RemoveHashCodeCallsFromArrayInstancesTest implements RewriteTest { + @Override + public void defaults(RecipeSpec spec) { + spec.recipe(new RemoveHashCodeCallsFromArrayInstances()); + } + + @Test + @DocumentExample + @Issue("https://github.com/openrewrite/rewrite-static-analysis/issues/44") + void replaceHashCodeCalls() { + //language=java + rewriteRun( + java(""" + class SomeClass { + public static void main(String[] args) { + int argHash = args.hashCode(); + } + } + """, """ + import java.util.Arrays; + + class SomeClass { + public static void main(String[] args) { + int argHash = Arrays.hashCode(args); + } + } + """) + ); + } + + @Test + void selectIsAMethod() { + //language=java + rewriteRun( + java(""" + class SomeClass { + void foo() { + int hashCode = getArr().hashCode(); + } + + public int[] getArr() { + return new int[]{1, 2, 3}; + } + } + """, """ + import java.util.Arrays; + + class SomeClass { + void foo() { + int hashCode = Arrays.hashCode(getArr()); + } + + public int[] getArr() { + return new int[]{1, 2, 3}; + } + } + """) + ); + } + + @Test + void onlyRunOnArrayInstances() { + //language=java + rewriteRun( + java(""" + class SomeClass { + void foo() { + String name = "bill"; + int hashCode = name.hashCode(); + } + } + """) + ); + } +}