-
Notifications
You must be signed in to change notification settings - Fork 101
Recipe to convert explicit getters to the Lombok annotation #623
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
20 commits
Select commit
Hold shift + click to select a range
a3e22b4
feat: add recipe that converts explicit getters to the lombok annotation
timo-a d6eebf2
chore: IntelliJ auto-formatter
timo-a b38f90d
add licence header
timo-a c35b7d6
Apply suggestions from code review
timo-a f06e270
Apply suggestions from code review
timtebeek e6fd1bb
roll back nullable annotation
timo-a 4b7fb67
Light polish
timtebeek 665e73a
Rename and add Lombok tag
timtebeek 91c579b
Also handle field access
timtebeek c7d3feb
Push down method and variable name matching into utils
timtebeek f51e00f
Demonstrate failing case of nested inner class getter
timtebeek 80ec548
fix: year in licence header
timo-a 87495fd
rename method
timo-a 40a44b1
Check that getter methods access fields from the method declaring type
timtebeek 9d2ed73
Remove the need for cursor messaging
timtebeek 8376783
From `visitMethodDeclaration` call `doAfterVisit`
timtebeek 6766243
Drop Guava dependency from LombokUtils
timtebeek 74ddd7d
Merge branch 'main' into lombok/getter
timtebeek 5a7cae1
Compare identity of method type
timtebeek 0fef789
Remove documented limitations
timtebeek File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
97 changes: 97 additions & 0 deletions
97
src/main/java/org/openrewrite/java/migrate/lombok/LombokUtils.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,97 @@ | ||
/* | ||
* Copyright 2024 the original author or authors. | ||
* <p> | ||
* 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 | ||
* <p> | ||
* https://www.apache.org/licenses/LICENSE-2.0 | ||
* <p> | ||
* 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.lombok; | ||
|
||
import lombok.AccessLevel; | ||
import org.jspecify.annotations.Nullable; | ||
import org.openrewrite.internal.StringUtils; | ||
import org.openrewrite.java.tree.Expression; | ||
import org.openrewrite.java.tree.J; | ||
import org.openrewrite.java.tree.JavaType; | ||
|
||
import static lombok.AccessLevel.*; | ||
import static org.openrewrite.java.tree.J.Modifier.Type.*; | ||
|
||
class LombokUtils { | ||
|
||
static boolean isGetter(J.MethodDeclaration method) { | ||
if (method.getMethodType() == null) { | ||
return false; | ||
} | ||
// Check signature: no parameters | ||
if (!(method.getParameters().get(0) instanceof J.Empty) || method.getReturnTypeExpression() == null) { | ||
return false; | ||
} | ||
// Check body: just a return statement | ||
if (method.getBody() == null || | ||
method.getBody().getStatements().size() != 1 || | ||
!(method.getBody().getStatements().get(0) instanceof J.Return)) { | ||
return false; | ||
} | ||
// Check field is declared on method type | ||
JavaType.FullyQualified declaringType = method.getMethodType().getDeclaringType(); | ||
Expression returnExpression = ((J.Return) method.getBody().getStatements().get(0)).getExpression(); | ||
if (returnExpression instanceof J.Identifier) { | ||
J.Identifier identifier = (J.Identifier) returnExpression; | ||
if (identifier.getFieldType() != null && declaringType == identifier.getFieldType().getOwner()) { | ||
// Check return: type and matching field name | ||
return hasMatchingTypeAndName(method, identifier.getType(), identifier.getSimpleName()); | ||
} | ||
} else if (returnExpression instanceof J.FieldAccess) { | ||
J.FieldAccess fieldAccess = (J.FieldAccess) returnExpression; | ||
Expression target = fieldAccess.getTarget(); | ||
if (target instanceof J.Identifier && ((J.Identifier) target).getFieldType() != null && | ||
declaringType == ((J.Identifier) target).getFieldType().getOwner()) { | ||
// Check return: type and matching field name | ||
return hasMatchingTypeAndName(method, fieldAccess.getType(), fieldAccess.getSimpleName()); | ||
} | ||
} | ||
return false; | ||
} | ||
|
||
private static boolean hasMatchingTypeAndName(J.MethodDeclaration method, @Nullable JavaType type, String simpleName) { | ||
if (method.getType() == type) { | ||
String deriveGetterMethodName = deriveGetterMethodName(type, simpleName); | ||
return method.getSimpleName().equals(deriveGetterMethodName); | ||
} | ||
return false; | ||
} | ||
|
||
private static String deriveGetterMethodName(@Nullable JavaType type, String fieldName) { | ||
if (type == JavaType.Primitive.Boolean) { | ||
boolean alreadyStartsWithIs = fieldName.length() >= 3 && | ||
fieldName.substring(0, 3).matches("is[A-Z]"); | ||
if (alreadyStartsWithIs) { | ||
return fieldName; | ||
} else { | ||
return "is" + StringUtils.capitalize(fieldName); | ||
} | ||
} | ||
return "get" + StringUtils.capitalize(fieldName); | ||
} | ||
|
||
static AccessLevel getAccessLevel(J.MethodDeclaration modifiers) { | ||
if (modifiers.hasModifier(Public)) { | ||
return PUBLIC; | ||
} else if (modifiers.hasModifier(Protected)) { | ||
return PROTECTED; | ||
} else if (modifiers.hasModifier(Private)) { | ||
return PRIVATE; | ||
} | ||
return PACKAGE; | ||
} | ||
|
||
} |
109 changes: 109 additions & 0 deletions
109
src/main/java/org/openrewrite/java/migrate/lombok/UseLombokGetter.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,109 @@ | ||
/* | ||
* Copyright 2024 the original author or authors. | ||
* <p> | ||
* 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 | ||
* <p> | ||
* https://www.apache.org/licenses/LICENSE-2.0 | ||
* <p> | ||
* 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.lombok; | ||
|
||
import lombok.AccessLevel; | ||
import lombok.EqualsAndHashCode; | ||
import lombok.Value; | ||
import org.jspecify.annotations.Nullable; | ||
import org.openrewrite.ExecutionContext; | ||
import org.openrewrite.Recipe; | ||
import org.openrewrite.TreeVisitor; | ||
import org.openrewrite.java.JavaIsoVisitor; | ||
import org.openrewrite.java.JavaParser; | ||
import org.openrewrite.java.JavaTemplate; | ||
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.List; | ||
import java.util.Set; | ||
|
||
import static java.util.Comparator.comparing; | ||
import static lombok.AccessLevel.PUBLIC; | ||
|
||
@Value | ||
@EqualsAndHashCode(callSuper = false) | ||
public class UseLombokGetter extends Recipe { | ||
|
||
@Override | ||
public String getDisplayName() { | ||
return "Convert getter methods to annotations"; | ||
} | ||
|
||
@Override | ||
public String getDescription() { | ||
//language=markdown | ||
return "Convert trivial getter methods to `@Getter` annotations on their respective fields."; | ||
} | ||
|
||
@Override | ||
public Set<String> getTags() { | ||
return Collections.singleton("lombok"); | ||
} | ||
|
||
@Override | ||
public TreeVisitor<?, ExecutionContext> getVisitor() { | ||
return new JavaIsoVisitor<ExecutionContext>() { | ||
@Override | ||
public J.@Nullable MethodDeclaration visitMethodDeclaration(J.MethodDeclaration method, ExecutionContext ctx) { | ||
if (LombokUtils.isGetter(method)) { | ||
Expression returnExpression = ((J.Return) method.getBody().getStatements().get(0)).getExpression(); | ||
if (returnExpression instanceof J.Identifier && | ||
((J.Identifier) returnExpression).getFieldType() != null) { | ||
doAfterVisit(new FieldAnnotator( | ||
((J.Identifier) returnExpression).getFieldType(), | ||
LombokUtils.getAccessLevel(method))); | ||
return null; | ||
} else if (returnExpression instanceof J.FieldAccess && | ||
((J.FieldAccess) returnExpression).getName().getFieldType() != null) { | ||
doAfterVisit(new FieldAnnotator( | ||
((J.FieldAccess) returnExpression).getName().getFieldType(), | ||
LombokUtils.getAccessLevel(method))); | ||
return null; | ||
} | ||
} | ||
return method; | ||
} | ||
}; | ||
} | ||
|
||
|
||
@Value | ||
@EqualsAndHashCode(callSuper = false) | ||
static class FieldAnnotator extends JavaIsoVisitor<ExecutionContext> { | ||
|
||
JavaType field; | ||
AccessLevel accessLevel; | ||
|
||
@Override | ||
public J.VariableDeclarations visitVariableDeclarations(J.VariableDeclarations multiVariable, ExecutionContext ctx) { | ||
for (J.VariableDeclarations.NamedVariable variable : multiVariable.getVariables()) { | ||
if (variable.getName().getFieldType() == field) { | ||
maybeAddImport("lombok.Getter"); | ||
maybeAddImport("lombok.AccessLevel"); | ||
String suffix = accessLevel == PUBLIC ? "" : String.format("(AccessLevel.%s)", accessLevel.name()); | ||
return JavaTemplate.builder("@Getter" + suffix) | ||
.imports("lombok.Getter", "lombok.AccessLevel") | ||
.javaParser(JavaParser.fromJavaVersion().classpath("lombok")) | ||
.build().apply(getCursor(), multiVariable.getCoordinates().addAnnotation(comparing(J.Annotation::getSimpleName))); | ||
} | ||
} | ||
return multiVariable; | ||
} | ||
} | ||
} |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.