From f906b6bf9810b919cc9862e2e694ce3dde2331f3 Mon Sep 17 00:00:00 2001 From: Tim te Beek Date: Fri, 22 Aug 2025 20:49:29 +0200 Subject: [PATCH 1/4] Add `@InlineMe` and `InlineMethodCalls` recipe to replace annotated methods --- .../java/org/openrewrite/java/InlineMe.java | 30 ++ .../openrewrite/java/InlineMethodCalls.java | 306 ++++++++++++ .../java/InlineMethodCallsTest.java | 436 ++++++++++++++++++ 3 files changed, 772 insertions(+) create mode 100644 rewrite-java/src/main/java/org/openrewrite/java/InlineMe.java create mode 100644 rewrite-java/src/main/java/org/openrewrite/java/InlineMethodCalls.java create mode 100644 rewrite-java/src/test/java/org/openrewrite/java/InlineMethodCallsTest.java diff --git a/rewrite-java/src/main/java/org/openrewrite/java/InlineMe.java b/rewrite-java/src/main/java/org/openrewrite/java/InlineMe.java new file mode 100644 index 00000000000..a2d84c7fec6 --- /dev/null +++ b/rewrite-java/src/main/java/org/openrewrite/java/InlineMe.java @@ -0,0 +1,30 @@ +/* + * Copyright 2025 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.java; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Target; + +@Documented +@Target({ElementType.METHOD, ElementType.CONSTRUCTOR}) +public @interface InlineMe { + String replacement(); + + String[] imports() default {}; + + String[] staticImports() default {}; +} diff --git a/rewrite-java/src/main/java/org/openrewrite/java/InlineMethodCalls.java b/rewrite-java/src/main/java/org/openrewrite/java/InlineMethodCalls.java new file mode 100644 index 00000000000..8a023742b22 --- /dev/null +++ b/rewrite-java/src/main/java/org/openrewrite/java/InlineMethodCalls.java @@ -0,0 +1,306 @@ +/* + * Copyright 2025 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.java; + +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.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/rewrite-java/src/test/java/org/openrewrite/java/InlineMethodCallsTest.java b/rewrite-java/src/test/java/org/openrewrite/java/InlineMethodCallsTest.java new file mode 100644 index 00000000000..5bb7cfd4733 --- /dev/null +++ b/rewrite-java/src/test/java/org/openrewrite/java/InlineMethodCallsTest.java @@ -0,0 +1,436 @@ +/* + * Copyright 2025 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.java; + +import org.junit.jupiter.api.Test; +import org.openrewrite.DocumentExample; +import org.openrewrite.test.RecipeSpec; +import org.openrewrite.test.RewriteTest; + +import static org.openrewrite.java.Assertions.java; + +class InlineMethodCallsTest implements RewriteTest { + + @Override + public void defaults(RecipeSpec spec) { + spec.recipe(new InlineMethodCalls()); + } + + @DocumentExample + @Test + void inlineMeSimple() { + //language=java + rewriteRun( + java( + """ + import org.openrewrite.java.InlineMe; + + class Lib { + @Deprecated + @InlineMe(replacement = "this.replacement()") + public void deprecated() {} + + public void replacement() {} + + public static void usage(Lib lib) { + lib.deprecated(); + } + } + """, + """ + import org.openrewrite.java.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 org.openrewrite.java.InlineMe; + + class Lib { + @Deprecated + @InlineMe(replacement = "this.replacement()") + public void deprecated() {} + public void replacement() {} + + public void usage() { + deprecated(); + } + } + """, + """ + import org.openrewrite.java.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 org.openrewrite.java.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 org.openrewrite.java.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 org.openrewrite.java.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 org.openrewrite.java.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 org.openrewrite.java.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 org.openrewrite.java.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 org.openrewrite.java.InlineMe; + + class MyClass { + + @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 org.openrewrite.java.InlineMe; + + class MyClass { + + @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 org.openrewrite.java.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 org.openrewrite.java.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 org.openrewrite.java.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 org.openrewrite.java.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); + } + } + """ + ) + ); + } +} From 2c5d6176051ae4d5e7620674fb7f83afba8cd9c8 Mon Sep 17 00:00:00 2001 From: Tim te Beek Date: Fri, 22 Aug 2025 20:56:38 +0200 Subject: [PATCH 2/4] Handle repeated arguments --- .../java/org/openrewrite/java/InlineMethodCalls.java | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/rewrite-java/src/main/java/org/openrewrite/java/InlineMethodCalls.java b/rewrite-java/src/main/java/org/openrewrite/java/InlineMethodCalls.java index 8a023742b22..3f731b5da35 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/InlineMethodCalls.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/InlineMethodCalls.java @@ -29,6 +29,7 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; +import static java.lang.String.format; import static java.util.Collections.emptySet; import static java.util.Objects.requireNonNull; import static java.util.stream.Collectors.toMap; @@ -163,7 +164,7 @@ public J visitMethodInvocation(J.MethodInvocation methodInvocation, Set // 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", + staticImports.add(format("%s.%s", methodType.getDeclaringType().getFullyQualifiedName(), methodType.getName())); } @@ -177,7 +178,7 @@ public J visitIdentifier(J.Identifier identifier, Set staticImports) { JavaType.Variable fieldType = id.getFieldType(); if (fieldType != null && fieldType.hasFlags(Flag.Static)) { if (fieldType.getOwner() instanceof JavaType.FullyQualified) { - staticImports.add(String.format("%s.%s", + staticImports.add(format("%s.%s", ((JavaType.FullyQualified) fieldType.getOwner()).getFullyQualifiedName(), fieldType.getName())); } @@ -265,9 +266,9 @@ private static String createTemplateString(MethodCall original, String replaceme 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}` + templateString = templateString + .replaceFirst(format("\\b%s\\b", parameterName), format("#{%s:any()}", parameterName)) + .replaceAll(format("(? Date: Fri, 22 Aug 2025 21:02:27 +0200 Subject: [PATCH 3/4] Polish description --- .../src/main/java/org/openrewrite/java/InlineMethodCalls.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/rewrite-java/src/main/java/org/openrewrite/java/InlineMethodCalls.java b/rewrite-java/src/main/java/org/openrewrite/java/InlineMethodCalls.java index 3f731b5da35..eb1b7316349 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/InlineMethodCalls.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/InlineMethodCalls.java @@ -46,7 +46,9 @@ public String getDisplayName() { @Override public String getDescription() { - return "Apply inlinings defined by Error Prone's [`@InlineMe` annotation](https://errorprone.info/docs/inlineme)."; + return "Apply inlinings as defined by Error Prone's [`@InlineMe` annotation](https://errorprone.info/docs/inlineme), " + + "or compatible annotations. Uses the template and method arguments to replace method calls. " + + "Supports both methods invocations and constructor calls, with optional new imports."; } @Override From fd408f71f6503dc14918aca0216398c7e220bd8f Mon Sep 17 00:00:00 2001 From: Tim te Beek Date: Fri, 22 Aug 2025 21:10:52 +0200 Subject: [PATCH 4/4] Add a test for using an argument twice --- .../java/InlineMethodCallsTest.java | 49 +++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/rewrite-java/src/test/java/org/openrewrite/java/InlineMethodCallsTest.java b/rewrite-java/src/test/java/org/openrewrite/java/InlineMethodCallsTest.java index 5bb7cfd4733..2e9ddb33a15 100644 --- a/rewrite-java/src/test/java/org/openrewrite/java/InlineMethodCallsTest.java +++ b/rewrite-java/src/test/java/org/openrewrite/java/InlineMethodCallsTest.java @@ -433,4 +433,53 @@ void foo(Builder builder) { ) ); } + + @Test + void sameArgumentUsedTwice() { + //language=java + rewriteRun( + java( + """ + import org.openrewrite.java.InlineMe; + + class MathUtils { + + public int square(int n) { + return n * n; + } + + @Deprecated + @InlineMe(replacement = "this.square(x + x)") + public int doubleAndSquare(int x) { + return square(x + x); + } + + void usage() { + int result = doubleAndSquare(5); + } + } + """, + """ + import org.openrewrite.java.InlineMe; + + class MathUtils { + + public int square(int n) { + return n * n; + } + + @Deprecated + @InlineMe(replacement = "this.square(x + x)") + public int doubleAndSquare(int x) { + return square(x + x); + } + + void usage() { + int result = square(5 + 5); + } + } + """ + ) + ); + } }