Skip to content

Commit ccd18d3

Browse files
timo-agithub-actions[bot]timtebeek
authored
New recipe to adopt Lombok getter method names (#631)
* migrate recipes as-is * fix year in license * minor polish * remove line in recipe spec * Update src/test/java/org/openrewrite/java/migrate/lombok/NormalizeGetterTest.java Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> * fix build * apply suggestions from review bot * Merge the `deriveGetterMethodName` methods * Inline MethodAcc * Inline MethodRecorder & rename list of methods not renamed * Remove unnecessary packages * Capture current behavior with a running test * markdown and comments * add fieldaccess * add document missing behaviour * deal wth subtypes * Apply suggestions from code review Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> * Group the tests that expect no change * Use early returns in `isEffectivelyGetter` * Adopt `TypeUtils.isOverride` * Use `MethodMatcher.methodPattern(method)` and `getTypesInUse().getDeclaredMethods()` * Update src/main/java/org/openrewrite/java/migrate/lombok/NormalizeGetter.java Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> * Expect to fail for a case not covered yet * Expect partial rename outside of `NoChange` nested class * refactor: rename to sth. less ambiguous --------- Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Tim te Beek <[email protected]> Co-authored-by: Tim te Beek <[email protected]>
1 parent 12b2b0c commit ccd18d3

File tree

3 files changed

+771
-1
lines changed

3 files changed

+771
-1
lines changed
Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
/*
2+
* Copyright 2024 the original author or authors.
3+
* <p>
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
* <p>
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
* <p>
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.openrewrite.java.migrate.lombok;
17+
18+
import lombok.EqualsAndHashCode;
19+
import lombok.Value;
20+
import org.jspecify.annotations.Nullable;
21+
import org.openrewrite.ExecutionContext;
22+
import org.openrewrite.ScanningRecipe;
23+
import org.openrewrite.Tree;
24+
import org.openrewrite.TreeVisitor;
25+
import org.openrewrite.java.ChangeMethodName;
26+
import org.openrewrite.java.JavaIsoVisitor;
27+
import org.openrewrite.java.MethodMatcher;
28+
import org.openrewrite.java.tree.Expression;
29+
import org.openrewrite.java.tree.J;
30+
import org.openrewrite.java.tree.JavaType;
31+
import org.openrewrite.java.tree.TypeUtils;
32+
33+
import java.util.ArrayList;
34+
import java.util.List;
35+
import java.util.Set;
36+
37+
@Value
38+
@EqualsAndHashCode(callSuper = false)
39+
public class AdoptLombokGetterMethodNames extends ScanningRecipe<List<AdoptLombokGetterMethodNames.RenameRecord>> {
40+
41+
private final static String DO_NOT_RENAME = "DO_NOT_RENAME";
42+
43+
@Override
44+
public String getDisplayName() {
45+
return "Rename getter methods to fit Lombok";
46+
}
47+
48+
@Override
49+
public String getDescription() {
50+
return "Rename methods that are effectively getter to the name Lombok would give them.\n\n" +
51+
"Limitations:\n" +
52+
" - If two methods in a class are effectively the same getter then one's name will be corrected and the others name will be left as it is.\n" +
53+
" - If the correct name for a method is already taken by another method then the name will not be corrected.\n" +
54+
" - Method name swaps or circular renaming within a class cannot be performed because the names block each other.\n" +
55+
"E.g. `int getFoo() { return ba; } int getBa() { return foo; }` stays as it is.";
56+
}
57+
58+
@Value
59+
public static class RenameRecord {
60+
String methodPattern;
61+
String newMethodName;
62+
}
63+
64+
@Override
65+
public List<RenameRecord> getInitialValue(ExecutionContext ctx) {
66+
return new ArrayList<>();
67+
}
68+
69+
@Override
70+
public TreeVisitor<?, ExecutionContext> getScanner(List<RenameRecord> renameRecords) {
71+
return new JavaIsoVisitor<ExecutionContext>() {
72+
@Override
73+
public J.CompilationUnit visitCompilationUnit(J.CompilationUnit cu, ExecutionContext ctx) {
74+
// Cheaply collect all declared methods; this also means we do not support clashing nested class methods
75+
Set<JavaType.Method> declaredMethods = cu.getTypesInUse().getDeclaredMethods();
76+
List<String> existingMethodNames = new ArrayList<>();
77+
for (JavaType.Method method : declaredMethods) {
78+
existingMethodNames.add(method.getName());
79+
}
80+
getCursor().putMessage(DO_NOT_RENAME, existingMethodNames);
81+
return super.visitCompilationUnit(cu, ctx);
82+
}
83+
84+
@Override
85+
public J.MethodDeclaration visitMethodDeclaration(J.MethodDeclaration method, ExecutionContext ctx) {
86+
if (method.getMethodType() == null || method.getBody() == null ||
87+
!LombokUtils.isEffectivelyGetter(method) ||
88+
TypeUtils.isOverride(method.getMethodType())) {
89+
return method;
90+
}
91+
92+
String simpleName;
93+
Expression returnExpression = ((J.Return) method.getBody().getStatements().get(0)).getExpression();
94+
if (returnExpression instanceof J.Identifier) {
95+
simpleName = ((J.Identifier) returnExpression).getSimpleName();
96+
} else if (returnExpression instanceof J.FieldAccess) {
97+
simpleName = ((J.FieldAccess) returnExpression).getSimpleName();
98+
} else {
99+
return method;
100+
}
101+
102+
// If method already has the name it should have, then nothing to be done
103+
String expectedMethodName = LombokUtils.deriveGetterMethodName(returnExpression.getType(), simpleName);
104+
if (expectedMethodName.equals(method.getSimpleName())) {
105+
return method;
106+
}
107+
108+
// If the desired method name is already taken by an existing method, the current method cannot be renamed
109+
List<String> doNotRename = getCursor().getNearestMessage(DO_NOT_RENAME);
110+
assert doNotRename != null;
111+
if (doNotRename.contains(expectedMethodName)) {
112+
return method;
113+
}
114+
115+
renameRecords.add(new RenameRecord(MethodMatcher.methodPattern(method), expectedMethodName));
116+
doNotRename.remove(method.getSimpleName()); //actual method name becomes available again
117+
doNotRename.add(expectedMethodName); //expected method name now blocked
118+
return method;
119+
}
120+
};
121+
}
122+
123+
@Override
124+
public TreeVisitor<?, ExecutionContext> getVisitor(List<RenameRecord> renameRecords) {
125+
return new TreeVisitor<Tree, ExecutionContext>() {
126+
@Override
127+
public @Nullable Tree visit(@Nullable Tree tree, ExecutionContext ctx) {
128+
for (RenameRecord rr : renameRecords) {
129+
tree = new ChangeMethodName(rr.methodPattern, rr.newMethodName, true, null)
130+
.getVisitor().visit(tree, ctx);
131+
}
132+
return tree;
133+
}
134+
};
135+
}
136+
}

src/main/java/org/openrewrite/java/migrate/lombok/LombokUtils.java

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,25 @@ private static boolean hasMatchingTypeAndGetterName(J.MethodDeclaration method,
8585
return false;
8686
}
8787

88-
private static String deriveGetterMethodName(@Nullable JavaType type, String fieldName) {
88+
public static boolean isEffectivelyGetter(J.MethodDeclaration method) {
89+
if (!method.getParameters().isEmpty() && !(method.getParameters().get(0) instanceof J.Empty)) {
90+
return false;
91+
}
92+
if (method.getBody() == null ||
93+
method.getBody().getStatements().size() != 1 ||
94+
!(method.getBody().getStatements().get(0) instanceof J.Return)) {
95+
return false;
96+
}
97+
Expression returnExpression = ((J.Return) method.getBody().getStatements().get(0)).getExpression();
98+
if (!(returnExpression instanceof J.Identifier) && !(returnExpression instanceof J.FieldAccess)) {
99+
return false;
100+
}
101+
// compiler already guarantees that the returned variable is a subtype of the method type, but we want an exact match
102+
return method.getType() == returnExpression.getType();
103+
}
104+
105+
public static String deriveGetterMethodName(@Nullable JavaType type, String fieldName) {
106+
89107
if (type == JavaType.Primitive.Boolean) {
90108
boolean alreadyStartsWithIs = fieldName.length() >= 3 &&
91109
fieldName.substring(0, 3).matches("is[A-Z]");

0 commit comments

Comments
 (0)