diff --git a/build.gradle.kts b/build.gradle.kts index d8b5f48297..e59a089ccb 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -60,8 +60,6 @@ dependencies { testImplementation("org.assertj:assertj-core:latest.release") - testImplementation("com.google.errorprone:error_prone_annotations:latest.release") - testRuntimeOnly("com.fasterxml.jackson.datatype:jackson-datatype-jsr353") testRuntimeOnly("com.fasterxml.jackson.core:jackson-core") testRuntimeOnly("com.fasterxml.jackson.core:jackson-databind") diff --git a/src/main/java/org/openrewrite/java/migrate/InlineMethodCalls.java b/src/main/java/org/openrewrite/java/migrate/InlineMethodCalls.java deleted file mode 100644 index 08e2e147b5..0000000000 --- a/src/main/java/org/openrewrite/java/migrate/InlineMethodCalls.java +++ /dev/null @@ -1,309 +0,0 @@ -/* - * Copyright 2025 the original author or authors. - *

- * Licensed under the Moderne Source Available License (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - *

- * https://docs.moderne.io/licensing/moderne-source-available-license - *

- * 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.java.migrate; - -import lombok.AccessLevel; -import lombok.Getter; -import lombok.Value; -import org.jspecify.annotations.Nullable; -import org.openrewrite.Cursor; -import org.openrewrite.ExecutionContext; -import org.openrewrite.Recipe; -import org.openrewrite.TreeVisitor; -import org.openrewrite.java.JavaParser; -import org.openrewrite.java.JavaTemplate; -import org.openrewrite.java.JavaVisitor; -import org.openrewrite.java.tree.*; - -import java.util.*; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -import static java.util.Collections.emptySet; -import static java.util.Objects.requireNonNull; -import static java.util.stream.Collectors.toMap; -import static java.util.stream.Collectors.toSet; - -public class InlineMethodCalls extends Recipe { - - private static final String INLINE_ME = "InlineMe"; - - @Override - public String getDisplayName() { - return "Inline methods annotated with `@InlineMe`"; - } - - @Override - public String getDescription() { - return "Apply inlinings defined by Error Prone's [`@InlineMe` annotation](https://errorprone.info/docs/inlineme)."; - } - - @Override - public TreeVisitor getVisitor() { - // XXX Preconditions can not yet pick up the `@InlineMe` annotation on methods used - return new JavaVisitor() { - @Override - public J visitMethodInvocation(J.MethodInvocation method, ExecutionContext ctx) { - J.MethodInvocation mi = (J.MethodInvocation) super.visitMethodInvocation(method, ctx); - InlineMeValues values = findInlineMeValues(mi.getMethodType()); - if (values == null) { - return mi; - } - Template template = values.template(mi); - if (template == null) { - return mi; - } - removeAndAddImports(method, values.getImports(), values.getStaticImports()); - J replacement = JavaTemplate.builder(template.getString()) - .contextSensitive() - .imports(values.getImports().toArray(new String[0])) - .staticImports(values.getStaticImports().toArray(new String[0])) - .javaParser(JavaParser.fromJavaVersion().classpath(JavaParser.runtimeClasspath())) - .build() - .apply(updateCursor(mi), mi.getCoordinates().replace(), template.getParameters()); - return avoidMethodSelfReferences(mi, replacement); - } - - @Override - public J visitNewClass(J.NewClass newClass, ExecutionContext ctx) { - J.NewClass nc = (J.NewClass) super.visitNewClass(newClass, ctx); - InlineMeValues values = findInlineMeValues(nc.getConstructorType()); - if (values == null) { - return nc; - } - Template template = values.template(nc); - if (template == null) { - return nc; - } - removeAndAddImports(newClass, values.getImports(), values.getStaticImports()); - J replacement = JavaTemplate.builder(template.getString()) - .contextSensitive() - .imports(values.getImports().toArray(new String[0])) - .staticImports(values.getStaticImports().toArray(new String[0])) - .javaParser(JavaParser.fromJavaVersion().classpath(JavaParser.runtimeClasspath())) - .build() - .apply(updateCursor(nc), nc.getCoordinates().replace(), template.getParameters()); - return avoidMethodSelfReferences(nc, replacement); - } - - private @Nullable InlineMeValues findInlineMeValues(JavaType.@Nullable Method methodType) { - if (methodType == null) { - return null; - } - List parameterNames = methodType.getParameterNames(); - if (!parameterNames.isEmpty() && "arg0".equals(parameterNames.get(0))) { - return null; // We need `-parameters` before we're able to substitute parameters in the template - } - - List annotations = methodType.getAnnotations(); - for (JavaType.FullyQualified annotation : annotations) { - if (INLINE_ME.equals(annotation.getClassName())) { - return InlineMeValues.parse((JavaType.Annotation) annotation); - } - } - return null; - } - - private void removeAndAddImports(MethodCall method, Set templateImports, Set templateStaticImports) { - Set originalImports = findOriginalImports(method); - - // Remove regular and static imports that are no longer needed - for (String originalImport : originalImports) { - if (!templateImports.contains(originalImport) && - !templateStaticImports.contains(originalImport)) { - maybeRemoveImport(originalImport); - } - } - - // Add new regular imports needed by the template - for (String importStr : templateImports) { - if (!originalImports.contains(importStr)) { - maybeAddImport(importStr); - } - } - - // Add new static imports needed by the template - for (String staticImport : templateStaticImports) { - if (!originalImports.contains(staticImport)) { - int lastDot = staticImport.lastIndexOf('.'); - if (0 < lastDot) { - maybeAddImport( - staticImport.substring(0, lastDot), - staticImport.substring(lastDot + 1)); - } - } - } - } - - private Set findOriginalImports(MethodCall method) { - // Collect all regular and static imports used in the original method call - return new JavaVisitor>() { - @Override - public @Nullable JavaType visitType(@Nullable JavaType javaType, Set strings) { - JavaType jt = super.visitType(javaType, strings); - if (jt instanceof JavaType.FullyQualified) { - strings.add(((JavaType.FullyQualified) jt).getFullyQualifiedName()); - } - return jt; - } - - @Override - public J visitMethodInvocation(J.MethodInvocation methodInvocation, Set staticImports) { - J.MethodInvocation mi = (J.MethodInvocation) super.visitMethodInvocation(methodInvocation, staticImports); - // Check if this is a static method invocation without a select (meaning it might be statically imported) - JavaType.Method methodType = mi.getMethodType(); - if (mi.getSelect() == null && methodType != null && methodType.hasFlags(Flag.Static)) { - staticImports.add(String.format("%s.%s", - methodType.getDeclaringType().getFullyQualifiedName(), - methodType.getName())); - } - return mi; - } - - @Override - public J visitIdentifier(J.Identifier identifier, Set staticImports) { - J.Identifier id = (J.Identifier) super.visitIdentifier(identifier, staticImports); - // Check if this is a static field reference - JavaType.Variable fieldType = id.getFieldType(); - if (fieldType != null && fieldType.hasFlags(Flag.Static)) { - if (fieldType.getOwner() instanceof JavaType.FullyQualified) { - staticImports.add(String.format("%s.%s", - ((JavaType.FullyQualified) fieldType.getOwner()).getFullyQualifiedName(), - fieldType.getName())); - } - } - return id; - } - }.reduce(method, new HashSet<>()); - } - - private J avoidMethodSelfReferences(MethodCall original, J replacement) { - JavaType.Method replacementMethodType = replacement instanceof MethodCall ? - ((MethodCall) replacement).getMethodType() : null; - if (replacementMethodType == null) { - return replacement; - } - - Cursor cursor = getCursor(); - while ((cursor = cursor.getParent()) != null) { - Object value = cursor.getValue(); - - JavaType.Method cursorMethodType; - if (value instanceof MethodCall) { - cursorMethodType = ((MethodCall) value).getMethodType(); - } else if (value instanceof J.MethodDeclaration) { - cursorMethodType = ((J.MethodDeclaration) value).getMethodType(); - } else { - continue; - } - if (TypeUtils.isOfType(replacementMethodType, cursorMethodType)) { - return original; - } - } - return replacement; - } - }; - } - - @Value - private static class InlineMeValues { - private static final Pattern TEMPLATE_IDENTIFIER = Pattern.compile("#\\{(\\p{javaJavaIdentifierStart}\\p{javaJavaIdentifierPart}*):any\\(.*?\\)}"); - - @Getter(AccessLevel.NONE) - String replacement; - - Set imports; - Set staticImports; - - static InlineMeValues parse(JavaType.Annotation annotation) { - Map collect = annotation.getValues().stream().collect(toMap( - e -> ((JavaType.Method) e.getElement()).getName(), - JavaType.Annotation.ElementValue::getValue - )); - // Parse imports and static imports from the annotation values - return new InlineMeValues( - (String) collect.get("replacement"), - parseImports(collect.get("imports")), - parseImports(collect.get("staticImports"))); - } - - private static Set parseImports(@Nullable Object importsValue) { - if (importsValue instanceof List) { - return ((List) importsValue).stream() - .map(Object::toString) - .collect(toSet()); - } - return emptySet(); - } - - @Nullable - Template template(MethodCall original) { - JavaType.Method methodType = original.getMethodType(); - if (methodType == null) { - return null; - } - String templateString = createTemplateString(original, replacement, methodType.getParameterNames()); - List parameters = createParameters(templateString, original); - return new Template(templateString, parameters.toArray(new Object[0])); - } - - private static String createTemplateString(MethodCall original, String replacement, List originalParameterNames) { - String templateString = original instanceof J.MethodInvocation && - ((J.MethodInvocation) original).getSelect() == null && - replacement.startsWith("this.") ? - replacement.replaceFirst("^this.\\b", "") : - replacement.replaceAll("\\bthis\\b", "#{this:any()}"); - for (String parameterName : originalParameterNames) { - // Replace parameter names with their values in the templateString - templateString = templateString.replaceAll( - String.format("\\b%s\\b", parameterName), - String.format("#{%s:any()}", parameterName)); // TODO 2nd, 3rd etc should use shorthand `#{a}` - } - return templateString; - } - - private static List createParameters(String templateString, MethodCall original) { - Map lookup = new HashMap<>(); - if (original instanceof J.MethodInvocation) { - Expression select = ((J.MethodInvocation) original).getSelect(); - if (select != null) { - lookup.put("this", select); - } - } - List originalParameterNames = requireNonNull(original.getMethodType()).getParameterNames(); - for (int i = 0; i < originalParameterNames.size(); i++) { - String originalName = originalParameterNames.get(i); - Expression originalValue = original.getArguments().get(i); - lookup.put(originalName, originalValue); - } - List parameters = new ArrayList<>(); - Matcher matcher = TEMPLATE_IDENTIFIER.matcher(templateString); - while (matcher.find()) { - Expression o = lookup.get(matcher.group(1)); - if (o != null) { - parameters.add(o); - } - } - return parameters; - } - } - - @Value - private static class Template { - String string; - Object[] parameters; - } -} diff --git a/src/main/resources/META-INF/rewrite/no-guava.yml b/src/main/resources/META-INF/rewrite/no-guava.yml index 87863a9a47..adbfd4680c 100644 --- a/src/main/resources/META-INF/rewrite/no-guava.yml +++ b/src/main/resources/META-INF/rewrite/no-guava.yml @@ -121,7 +121,7 @@ preconditions: - org.openrewrite.analysis.search.FindMethods: methodPattern: com.google.common..* *(..) recipeList: - - org.openrewrite.java.migrate.InlineMethodCalls + - org.openrewrite.java.InlineMethodCalls --- type: specs.openrewrite.org/v1beta/recipe diff --git a/src/test/java/org/openrewrite/java/migrate/InlineMethodCallsTest.java b/src/test/java/org/openrewrite/java/migrate/InlineMethodCallsTest.java deleted file mode 100644 index a046b214e9..0000000000 --- a/src/test/java/org/openrewrite/java/migrate/InlineMethodCallsTest.java +++ /dev/null @@ -1,440 +0,0 @@ -/* - * Copyright 2025 the original author or authors. - *

- * Licensed under the Moderne Source Available License (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - *

- * https://docs.moderne.io/licensing/moderne-source-available-license - *

- * 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.java.migrate; - -import org.junit.jupiter.api.Test; -import org.openrewrite.DocumentExample; -import org.openrewrite.java.JavaParser; -import org.openrewrite.test.RecipeSpec; -import org.openrewrite.test.RewriteTest; - -import static org.openrewrite.java.Assertions.java; - -class InlineMethodCallsTest implements RewriteTest { - - @Override - public void defaults(RecipeSpec spec) { - spec.recipe(new InlineMethodCalls()) - .parser(JavaParser.fromJavaVersion().classpath("guava", "error_prone_annotations")); - } - - @DocumentExample - @Test - void inlineMeSimple() { - //language=java - rewriteRun( - java( - """ - import com.google.errorprone.annotations.InlineMe; - - class Lib { - @Deprecated - @InlineMe(replacement = "this.replacement()") - public void deprecated() {} - public void replacement() {} - - public static void usage(Lib lib) { - lib.deprecated(); - } - } - """, - """ - import com.google.errorprone.annotations.InlineMe; - - class Lib { - @Deprecated - @InlineMe(replacement = "this.replacement()") - public void deprecated() {} - public void replacement() {} - - public static void usage(Lib lib) { - lib.replacement(); - } - } - """ - ) - ); - } - - @Test - void inlineMeNonStatic() { - //language=java - rewriteRun( - java( - """ - import com.google.errorprone.annotations.InlineMe; - - class Lib { - @Deprecated - @InlineMe(replacement = "this.replacement()") - public void deprecated() {} - public void replacement() {} - - public void usage() { - deprecated(); - } - } - """, - """ - import com.google.errorprone.annotations.InlineMe; - - class Lib { - @Deprecated - @InlineMe(replacement = "this.replacement()") - public void deprecated() {} - public void replacement() {} - - public void usage() { - replacement(); - } - } - """ - ) - ); - } - - @Test - void inlineMeChained() { - //language=java - rewriteRun( - java( - """ - import com.google.errorprone.annotations.InlineMe; - import java.time.Duration; - - class Lib { - private final Duration deadline; - - public Duration getDeadline() { - return deadline; - } - - @Deprecated - @InlineMe(replacement = "this.getDeadline().toMillis()") - public long getDeadlineMillis() { - return getDeadline().toMillis(); - } - - long usage() { - return getDeadlineMillis(); - } - } - """, - """ - import com.google.errorprone.annotations.InlineMe; - import java.time.Duration; - - class Lib { - private final Duration deadline; - - public Duration getDeadline() { - return deadline; - } - - @Deprecated - @InlineMe(replacement = "this.getDeadline().toMillis()") - public long getDeadlineMillis() { - return getDeadline().toMillis(); - } - - long usage() { - return getDeadline().toMillis(); - } - } - """ - ) - ); - } - - @Test - void instanceMethodWithImports() { - //language=java - rewriteRun( - java( - """ - import com.google.errorprone.annotations.InlineMe; - - class MyClass { - private java.time.Duration deadline; - - public void setDeadline(java.time.Duration deadline) { - this.deadline = deadline; - } - - @Deprecated - @InlineMe( - replacement = "this.setDeadline(Duration.ofMillis(millis))", - imports = {"java.time.Duration"}) - public void setDeadline(long millis) { - setDeadline(java.time.Duration.ofMillis(millis)); - } - - void usage() { - setDeadline(1000L); - } - } - """, - """ - import com.google.errorprone.annotations.InlineMe; - - import java.time.Duration; - - class MyClass { - private Duration deadline; - - public void setDeadline(Duration deadline) { - this.deadline = deadline; - } - - @Deprecated - @InlineMe( - replacement = "this.setDeadline(Duration.ofMillis(millis))", - imports = {"java.time.Duration"}) - public void setDeadline(long millis) { - setDeadline(Duration.ofMillis(millis)); - } - - void usage() { - setDeadline(Duration.ofMillis(1000L)); - } - } - """ - ) - ); - } - - @Test - void staticMethodReplacement() { - //language=java - rewriteRun( - java( - """ - package com.google.frobber; - - import com.google.errorprone.annotations.InlineMe; - - class Frobber { - - public static Frobber fromName(String name) { - return new Frobber(); - } - - @Deprecated - @InlineMe( - replacement = "Frobber.fromName(name)", - imports = {"com.google.frobber.Frobber"}) - public static Frobber create(String name) { - return fromName(name); - } - - void usage() { - Frobber f = Frobber.create("test"); - } - } - """, - """ - package com.google.frobber; - - import com.google.errorprone.annotations.InlineMe; - - class Frobber { - - public static Frobber fromName(String name) { - return new Frobber(); - } - - @Deprecated - @InlineMe( - replacement = "Frobber.fromName(name)", - imports = {"com.google.frobber.Frobber"}) - public static Frobber create(String name) { - return fromName(name); - } - - void usage() { - Frobber f = Frobber.fromName("test"); - } - } - """ - ) - ); - } - - @Test - void constructorToFactoryMethod() { - //language=java - rewriteRun( - java( - """ - package com.google.frobber; - - import com.google.errorprone.annotations.InlineMe; - import com.google.errorprone.annotations.InlineMeValidationDisabled; - - class MyClass { - - @InlineMeValidationDisabled - @Deprecated - @InlineMe( - replacement = "MyClass.create()", - imports = {"com.google.frobber.MyClass"}) - public MyClass() { - } - - public static MyClass create() { - return new MyClass(); - } - - void usage() { - MyClass obj = new MyClass(); - } - } - """, - """ - package com.google.frobber; - - import com.google.errorprone.annotations.InlineMe; - import com.google.errorprone.annotations.InlineMeValidationDisabled; - - class MyClass { - - @InlineMeValidationDisabled - @Deprecated - @InlineMe( - replacement = "MyClass.create()", - imports = {"com.google.frobber.MyClass"}) - public MyClass() { - } - - public static MyClass create() { - return new MyClass(); - } - - void usage() { - MyClass obj = MyClass.create(); - } - } - """ - ) - ); - } - - @Test - void multipleParameters() { - //language=java - rewriteRun( - java( - """ - import com.google.errorprone.annotations.InlineMe; - - class Calculator { - - public int addAndMultiply(int a, int b, int c) { - return (a + b) * c; - } - - @Deprecated - @InlineMe(replacement = "this.addAndMultiply(x, y, z)") - public int compute(int x, int y, int z) { - return addAndMultiply(x, y, z); - } - - void foo(Calculator calc) { - int result = calc.compute(1, 2, 3); - } - } - """, - """ - import com.google.errorprone.annotations.InlineMe; - - class Calculator { - - public int addAndMultiply(int a, int b, int c) { - return (a + b) * c; - } - - @Deprecated - @InlineMe(replacement = "this.addAndMultiply(x, y, z)") - public int compute(int x, int y, int z) { - return addAndMultiply(x, y, z); - } - - void foo(Calculator calc) { - int result = calc.addAndMultiply(1, 2, 3); - } - } - """ - ) - ); - } - - @Test - void nestedMethodCalls() { - //language=java - rewriteRun( - java( - """ - import com.google.errorprone.annotations.InlineMe; - - class Builder { - - public Builder withName(String name) { - return this; - } - - public Builder withAge(int age) { - return this; - } - - @Deprecated - @InlineMe(replacement = "this.withName(name).withAge(age)") - public Builder configure(String name, int age) { - return withName(name).withAge(age); - } - - void foo(Builder builder) { - builder.configure("John", 30); - } - } - """, - """ - import com.google.errorprone.annotations.InlineMe; - - class Builder { - - public Builder withName(String name) { - return this; - } - - public Builder withAge(int age) { - return this; - } - - @Deprecated - @InlineMe(replacement = "this.withName(name).withAge(age)") - public Builder configure(String name, int age) { - return withName(name).withAge(age); - } - - void foo(Builder builder) { - builder.withName("John").withAge(30); - } - } - """ - ) - ); - } -} diff --git a/src/test/java/org/openrewrite/java/migrate/guava/NoGuavaInlineMeMethods.java b/src/test/java/org/openrewrite/java/migrate/guava/NoGuavaInlineMeMethodsTest.java similarity index 97% rename from src/test/java/org/openrewrite/java/migrate/guava/NoGuavaInlineMeMethods.java rename to src/test/java/org/openrewrite/java/migrate/guava/NoGuavaInlineMeMethodsTest.java index a39c82d524..d91488b760 100644 --- a/src/test/java/org/openrewrite/java/migrate/guava/NoGuavaInlineMeMethods.java +++ b/src/test/java/org/openrewrite/java/migrate/guava/NoGuavaInlineMeMethodsTest.java @@ -22,7 +22,7 @@ import static org.openrewrite.java.Assertions.java; -class NoGuavaInlineMeMethods implements RewriteTest { +class NoGuavaInlineMeMethodsTest implements RewriteTest { @Override public void defaults(RecipeSpec spec) {