From a3e22b40659f0fc6ff6f8e85bbcb177b5bbf9cc6 Mon Sep 17 00:00:00 2001 From: timo <1398557+timo-a@users.noreply.github.com> Date: Sun, 8 Dec 2024 00:45:37 +0100 Subject: [PATCH 01/31] feat: add recipe that converts explicit getters to the lombok annotation --- .../java/migrate/lombok/ConvertGetter.java | 162 ++++++ .../java/migrate/lombok/LombokUtils.java | 67 +++ .../migrate/lombok/ConvertGetterTest.java | 463 ++++++++++++++++++ 3 files changed, 692 insertions(+) create mode 100644 src/main/java/org/openrewrite/java/migrate/lombok/ConvertGetter.java create mode 100644 src/main/java/org/openrewrite/java/migrate/lombok/LombokUtils.java create mode 100644 src/test/java/org/openrewrite/java/migrate/lombok/ConvertGetterTest.java diff --git a/src/main/java/org/openrewrite/java/migrate/lombok/ConvertGetter.java b/src/main/java/org/openrewrite/java/migrate/lombok/ConvertGetter.java new file mode 100644 index 0000000000..d54d339b9a --- /dev/null +++ b/src/main/java/org/openrewrite/java/migrate/lombok/ConvertGetter.java @@ -0,0 +1,162 @@ +/* + * Copyright 2021 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.migrate.lombok; + +import lombok.AccessLevel; +import lombok.EqualsAndHashCode; +import lombok.Value; +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.J; + +import java.util.HashSet; +import java.util.Optional; +import java.util.Set; +import java.util.StringJoiner; + +import static java.util.Comparator.comparing; +import static org.openrewrite.java.tree.JavaType.Variable; + +@Value +@EqualsAndHashCode(callSuper = false) +public class ConvertGetter extends Recipe { + + @Override + public String getDisplayName() { + //language=markdown + return "Convert getter methods to annotations"; + } + + @Override + public String getDescription() { + //language=markdown + return new StringJoiner("\n") + .add("Convert trivial getter methods to `@Getter` annotations on their respective fields.") + .add("") + .add("Limitations:") + .add("") + .add(" - Does not add a dependency to Lombok, users need to do that manually") + .add(" - Ignores fields that are declared on the same line as others, e.g. `private int foo, bar;" + + "Users who have such fields are advised to separate them beforehand with " + + "[org.openrewrite.staticanalysis.MultipleVariableDeclaration]" + + "(https://docs.openrewrite.org/recipes/staticanalysis/multiplevariabledeclarations).") + .add(" - Does not offer any of the configuration keys listed in https://projectlombok.org/features/GetterSetter.") + .toString(); + } + + @Override + public TreeVisitor getVisitor() { + return new MethodRemover(); + } + + + @Value + @EqualsAndHashCode(callSuper = false) + private static class MethodRemover extends JavaIsoVisitor { + private static final String FIELDS_TO_DECORATE_KEY = "FIELDS_TO_DECORATE"; + + @Override + public J.ClassDeclaration visitClassDeclaration(J.ClassDeclaration classDecl, ExecutionContext ctx) { + + //initialize set of fields to annotate + getCursor().putMessage(FIELDS_TO_DECORATE_KEY, new HashSet()); + + //delete methods, note down corresponding fields + J.ClassDeclaration classDeclAfterVisit = super.visitClassDeclaration(classDecl, ctx); + + //only thing that can have changed is removal of getter methods + if (classDeclAfterVisit != classDecl) { + //this set collects the fields for which existing methods have already been removed + Set fieldsToDecorate = getCursor().pollNearestMessage(FIELDS_TO_DECORATE_KEY); + doAfterVisit(new FieldAnnotator(fieldsToDecorate)); + } + return classDeclAfterVisit; + } + + @Override + public J.MethodDeclaration visitMethodDeclaration(J.MethodDeclaration method, ExecutionContext ctx) { + assert method.getMethodType() != null; + + if (LombokUtils.isEffectivelyGetter(method)) { + J.Return return_ = (J.Return) method.getBody().getStatements().get(0); + Variable fieldType = ((J.Identifier) return_.getExpression()).getFieldType(); + boolean nameMatch = method.getSimpleName().equals(LombokUtils.deriveGetterMethodName(fieldType)); + if (nameMatch){ + ((Set) getCursor().getNearestMessage(FIELDS_TO_DECORATE_KEY)) + .add(new Finding(fieldType.getName(), LombokUtils.getAccessLevel(method.getModifiers()))); + return null; //delete + } + } + return method; + } + } + + @Value + private static class Finding { + String fieldName; + AccessLevel accessLevel; + } + + + @Value + @EqualsAndHashCode(callSuper = false) + static class FieldAnnotator extends JavaIsoVisitor{ + + Set fieldsToDecorate; + + private JavaTemplate getAnnotation(AccessLevel accessLevel) { + JavaTemplate.Builder builder = AccessLevel.PUBLIC.equals(accessLevel) + ? JavaTemplate.builder("@Getter\n") + : JavaTemplate.builder("@Getter(AccessLevel." + accessLevel.name() + ")\n") + .imports("lombok.AccessLevel"); + + return builder + .imports("lombok.Getter") + .javaParser(JavaParser.fromJavaVersion() + .classpath("lombok")) + .build(); + } + + @Override + public J.VariableDeclarations visitVariableDeclarations(J.VariableDeclarations multiVariable, ExecutionContext ctx) { + + //we accept only one var decl per line, see description + if (multiVariable.getVariables().size() > 1) { + return multiVariable; + } + + J.VariableDeclarations.NamedVariable variable = multiVariable.getVariables().get(0); + Optional field = fieldsToDecorate.stream() + .filter(f -> f.fieldName.equals(variable.getSimpleName())) + .findFirst(); + + if (!field.isPresent()) { + return multiVariable; //not the field we are looking for + } + + J.VariableDeclarations annotated = getAnnotation(field.get().getAccessLevel()).apply( + getCursor(), + multiVariable.getCoordinates().addAnnotation(comparing(J.Annotation::getSimpleName))); + maybeAddImport("lombok.Getter"); + maybeAddImport("lombok.AccessLevel"); + return annotated; + } + } +} diff --git a/src/main/java/org/openrewrite/java/migrate/lombok/LombokUtils.java b/src/main/java/org/openrewrite/java/migrate/lombok/LombokUtils.java new file mode 100644 index 0000000000..49388a97cd --- /dev/null +++ b/src/main/java/org/openrewrite/java/migrate/lombok/LombokUtils.java @@ -0,0 +1,67 @@ +package org.openrewrite.java.migrate.lombok; + +import com.google.common.collect.ImmutableMap; +import lombok.AccessLevel; +import org.openrewrite.internal.StringUtils; +import org.openrewrite.java.tree.Expression; +import org.openrewrite.java.tree.J; +import org.openrewrite.java.tree.JavaType; + +import java.util.Collection; +import java.util.Map; + +import static lombok.AccessLevel.*; +import static org.openrewrite.java.tree.J.Modifier.Type.*; + +public class LombokUtils { + + public static boolean isEffectivelyGetter(J.MethodDeclaration method) { + boolean takesNoParameters = method.getParameters().get(0) instanceof J.Empty; + boolean singularReturn = method.getBody() != null //abstract methods can be null + && method.getBody().getStatements().size() == 1 + && method.getBody().getStatements().get(0) instanceof J.Return; + + if (takesNoParameters && singularReturn) { + Expression returnExpression = ((J.Return) method.getBody().getStatements().get(0)).getExpression(); + //returns just an identifier + if (returnExpression instanceof J.Identifier) { + J.Identifier identifier = (J.Identifier) returnExpression; + JavaType.Variable fieldType = identifier.getFieldType(); + boolean typeMatch = method.getType().equals(fieldType.getType()); + return typeMatch; + } + } + return false; + } + + public static String deriveGetterMethodName(JavaType.Variable fieldType) { + boolean isPrimitiveBoolean = JavaType.Variable.Primitive.Boolean.equals(fieldType.getType()); + + final String fieldName = fieldType.getName(); + + boolean alreadyStartsWithIs = fieldName.length() >= 3 + && fieldName.substring(0, 3).matches("is[A-Z]"); + + if (isPrimitiveBoolean) + if (alreadyStartsWithIs) + return fieldName; + else + return "is" + StringUtils.capitalize(fieldName); + + return "get" + StringUtils.capitalize(fieldName); + } + + public static AccessLevel getAccessLevel(Collection modifiers) { + Map map = ImmutableMap.builder() + .put(Public, PUBLIC) + .put(Protected, PROTECTED) + .put(Private, PRIVATE) + .build(); + + return modifiers.stream() + .map(modifier -> map.getOrDefault(modifier.getType(), AccessLevel.NONE)) + .filter(a -> a != AccessLevel.NONE) + .findAny().orElse(AccessLevel.PACKAGE); + } + +} diff --git a/src/test/java/org/openrewrite/java/migrate/lombok/ConvertGetterTest.java b/src/test/java/org/openrewrite/java/migrate/lombok/ConvertGetterTest.java new file mode 100644 index 0000000000..4996feca45 --- /dev/null +++ b/src/test/java/org/openrewrite/java/migrate/lombok/ConvertGetterTest.java @@ -0,0 +1,463 @@ +/* + * Copyright 2021 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.migrate.lombok; + +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; + +// This is a test for the ConvertToNoArgsConstructor recipe, as an example of how to write a test for an imperative recipe. +class ConvertGetterTest implements RewriteTest { + + // Note, you can define defaults for the RecipeSpec and these defaults will be used for all tests. + // In this case, the recipe and the parser are common. See below, on how the defaults can be overridden + // per test. + @Override + public void defaults(RecipeSpec spec) { + spec.recipe(new ConvertGetter()) + .parser(JavaParser.fromJavaVersion().logCompilationWarningsAndErrors(true)); + } + + @DocumentExample + @Test + void replaceGetter() { + rewriteRun(// language=java + java( + """ + class A { + + int foo = 9; + + public int getFoo() { + return foo; + } + } + """, + """ + import lombok.Getter; + + class A { + + @Getter + int foo = 9; + } + """ + ) + ); + } + + @Test + void replacePackageGetter() { + rewriteRun(// language=java + java( + """ + class A { + + int foo = 9; + + int getFoo() { + return foo; + } + } + """, + """ + import lombok.AccessLevel; + import lombok.Getter; + + class A { + + @Getter(AccessLevel.PACKAGE) + int foo = 9; + } + """ + ) + ); + } + + @Test + void replaceProtectedGetter() { + rewriteRun(// language=java + java( + """ + class A { + + int foo = 9; + + protected int getFoo() { + return foo; + } + } + """, + """ + import lombok.AccessLevel; + import lombok.Getter; + + class A { + + @Getter(AccessLevel.PROTECTED) + int foo = 9; + } + """ + ) + ); + } + + @Test + void replacePrivateGetter() { + rewriteRun(// language=java + java( + """ + class A { + + int foo = 9; + + private int getFoo() { + return foo; + } + } + """, + """ + import lombok.AccessLevel; + import lombok.Getter; + + class A { + + @Getter(AccessLevel.PRIVATE) + int foo = 9; + } + """ + ) + ); + } + + @Test + void replaceJustTheMatchingGetter() { + rewriteRun(// language=java + java( + """ + class A { + + int foo = 9; + + int ba; + + public A() { + ba = 1; + } + + public int getFoo() { + return foo; + } + + public int getMoo() {//method name wrong + return ba; + } + } + """, + """ + import lombok.Getter; + + class A { + + @Getter + int foo = 9; + + int ba; + + public A() { + ba = 1; + } + + public int getMoo() {//method name wrong + return ba; + } + } + """ + ) + ); + } + + @Test + void noChangeWhenMethodNameDoesntMatch() { + rewriteRun(// language=java + java( + """ + class A { + + int foo = 9; + + public A() { + } + + int getfoo() {//method name wrong + return foo; + } + } + """ + ) + ); + } + + @Test + void noChangeWhenReturnTypeDoesntMatch() { + rewriteRun(// language=java + java( + """ + class A { + + int foo = 9; + + public A() { + } + + long getfoo() {//return type wrong + return foo; + } + } + """ + ) + ); + } + + @Test + void noChangeWhenFieldIsNotReturned() { + rewriteRun(// language=java + java( + """ + class A { + + int foo = 9; + int ba = 10; + + public A() { + } + + int getFoo() { + return 5;//does not return variable + } + } + """ + ) + ); + } + + @Test + void noChangeWhenDifferentFieldIsReturned() { + rewriteRun(// language=java + java( + """ + class A { + + int foo = 9; + int ba = 10; + + public A() { + } + + int getFoo() { + return ba;//returns wrong variable + } + } + """ + ) + ); + } + + @Test + void noChangeWhenSideEffects1() { + rewriteRun(// language=java + java( + """ + class A { + + int foo = 9; + int ba = 10; + + public A() { + } + + int getfoo() { + foo++;//does extra stuff + return foo; + } + } + """ + ) + ); + } + + @Test + void noChangeWhenSideEffects2() { + rewriteRun(// language=java + java( + """ + class A { + + int foo = 9; + int ba = 10; + + public A() { + } + + int getFoo() { + ba++;//does unrelated extra stuff + return foo; + } + } + """ + ) + ); + } + + @Test + void replacePrimitiveBoolean() { + rewriteRun(// language=java + java( + """ + class A { + + boolean foo = true; + + public boolean isFoo() { + return foo; + } + } + """, + """ + import lombok.Getter; + + class A { + + @Getter + boolean foo = true; + } + """ + ) + ); + } + + @Test + void replacePrimitiveBooleanStartingWithIs() { + rewriteRun(// language=java + java( + """ + class A { + + boolean isFoo = true; + + public boolean isFoo() { + return isFoo; + } + } + """, + """ + import lombok.Getter; + + class A { + + @Getter + boolean isFoo = true; + } + """ + ) + ); + } + + @Test + void noChangeWhenPrimitiveBooleanUsesGet() { + rewriteRun(// language=java + java( + """ + class A { + + boolean foo = true; + + boolean getFoo() { + return foo; + } + } + """ + ) + ); + } + + @Test + void replaceBoolean() { + rewriteRun(// language=java + java( + """ + class A { + + Boolean foo = true; + + public Boolean getFoo() { + return foo; + } + } + """, + """ + import lombok.Getter; + + class A { + + @Getter + Boolean foo = true; + } + """ + ) + ); + } + + @Test + void noChangeWhenBooleanUsesIs() { + rewriteRun(// language=java + java( + """ + class A { + + Boolean foo = true; + + Boolean isFoo() { + return foo; + } + } + """ + ) + ); + } + + @Test + void noChangeWhenBooleanUsesIs2() { + rewriteRun(// language=java + java( + """ + class A { + + Boolean isfoo = true; + + Boolean isFoo() { + return isfoo; + } + } + """ + ) + ); + } +} From d6eebf2fbcee9579b24c339fa9dff96ea7eb4cc6 Mon Sep 17 00:00:00 2001 From: timo <1398557+timo-a@users.noreply.github.com> Date: Sun, 8 Dec 2024 01:13:38 +0100 Subject: [PATCH 02/31] chore: IntelliJ auto-formatter --- .../org/openrewrite/java/migrate/lombok/ConvertGetter.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/openrewrite/java/migrate/lombok/ConvertGetter.java b/src/main/java/org/openrewrite/java/migrate/lombok/ConvertGetter.java index d54d339b9a..5366101974 100644 --- a/src/main/java/org/openrewrite/java/migrate/lombok/ConvertGetter.java +++ b/src/main/java/org/openrewrite/java/migrate/lombok/ConvertGetter.java @@ -98,7 +98,7 @@ public J.MethodDeclaration visitMethodDeclaration(J.MethodDeclaration method, Ex J.Return return_ = (J.Return) method.getBody().getStatements().get(0); Variable fieldType = ((J.Identifier) return_.getExpression()).getFieldType(); boolean nameMatch = method.getSimpleName().equals(LombokUtils.deriveGetterMethodName(fieldType)); - if (nameMatch){ + if (nameMatch) { ((Set) getCursor().getNearestMessage(FIELDS_TO_DECORATE_KEY)) .add(new Finding(fieldType.getName(), LombokUtils.getAccessLevel(method.getModifiers()))); return null; //delete @@ -117,7 +117,7 @@ private static class Finding { @Value @EqualsAndHashCode(callSuper = false) - static class FieldAnnotator extends JavaIsoVisitor{ + static class FieldAnnotator extends JavaIsoVisitor { Set fieldsToDecorate; From b38f90d407b769608e189981e520c12c3d0bdedd Mon Sep 17 00:00:00 2001 From: timo-a <1398557+timo-a@users.noreply.github.com> Date: Sun, 8 Dec 2024 13:20:01 +0100 Subject: [PATCH 03/31] add licence header Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- .../java/migrate/lombok/LombokUtils.java | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/main/java/org/openrewrite/java/migrate/lombok/LombokUtils.java b/src/main/java/org/openrewrite/java/migrate/lombok/LombokUtils.java index 49388a97cd..0e7481a198 100644 --- a/src/main/java/org/openrewrite/java/migrate/lombok/LombokUtils.java +++ b/src/main/java/org/openrewrite/java/migrate/lombok/LombokUtils.java @@ -1,3 +1,18 @@ +/* + * Copyright 2024 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.migrate.lombok; import com.google.common.collect.ImmutableMap; From c35b7d685ab22b595c2431c7dae9ab07406dcdae Mon Sep 17 00:00:00 2001 From: timo-a <1398557+timo-a@users.noreply.github.com> Date: Sun, 8 Dec 2024 13:22:35 +0100 Subject: [PATCH 04/31] Apply suggestions from code review Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- .../org/openrewrite/java/migrate/lombok/ConvertGetter.java | 3 ++- .../java/org/openrewrite/java/migrate/lombok/LombokUtils.java | 3 +-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/java/org/openrewrite/java/migrate/lombok/ConvertGetter.java b/src/main/java/org/openrewrite/java/migrate/lombok/ConvertGetter.java index 5366101974..baa3ddcf1f 100644 --- a/src/main/java/org/openrewrite/java/migrate/lombok/ConvertGetter.java +++ b/src/main/java/org/openrewrite/java/migrate/lombok/ConvertGetter.java @@ -18,6 +18,7 @@ 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; @@ -91,7 +92,7 @@ public J.ClassDeclaration visitClassDeclaration(J.ClassDeclaration classDecl, Ex } @Override - public J.MethodDeclaration visitMethodDeclaration(J.MethodDeclaration method, ExecutionContext ctx) { + public @Nullable J.MethodDeclaration visitMethodDeclaration(J.MethodDeclaration method, ExecutionContext ctx) { assert method.getMethodType() != null; if (LombokUtils.isEffectivelyGetter(method)) { diff --git a/src/main/java/org/openrewrite/java/migrate/lombok/LombokUtils.java b/src/main/java/org/openrewrite/java/migrate/lombok/LombokUtils.java index 0e7481a198..eb411e4d20 100644 --- a/src/main/java/org/openrewrite/java/migrate/lombok/LombokUtils.java +++ b/src/main/java/org/openrewrite/java/migrate/lombok/LombokUtils.java @@ -42,8 +42,7 @@ public static boolean isEffectivelyGetter(J.MethodDeclaration method) { if (returnExpression instanceof J.Identifier) { J.Identifier identifier = (J.Identifier) returnExpression; JavaType.Variable fieldType = identifier.getFieldType(); - boolean typeMatch = method.getType().equals(fieldType.getType()); - return typeMatch; + return method.getType().equals(fieldType.getType()); } } return false; From f06e2706c021272b388b282e0c2e7c07eac0b9fb Mon Sep 17 00:00:00 2001 From: Tim te Beek Date: Sun, 8 Dec 2024 13:23:56 +0100 Subject: [PATCH 05/31] Apply suggestions from code review Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- .../openrewrite/java/migrate/lombok/ConvertGetter.java | 6 +++--- .../org/openrewrite/java/migrate/lombok/LombokUtils.java | 8 ++++---- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/main/java/org/openrewrite/java/migrate/lombok/ConvertGetter.java b/src/main/java/org/openrewrite/java/migrate/lombok/ConvertGetter.java index baa3ddcf1f..5dd2490336 100644 --- a/src/main/java/org/openrewrite/java/migrate/lombok/ConvertGetter.java +++ b/src/main/java/org/openrewrite/java/migrate/lombok/ConvertGetter.java @@ -123,9 +123,9 @@ static class FieldAnnotator extends JavaIsoVisitor { Set fieldsToDecorate; private JavaTemplate getAnnotation(AccessLevel accessLevel) { - JavaTemplate.Builder builder = AccessLevel.PUBLIC.equals(accessLevel) - ? JavaTemplate.builder("@Getter\n") - : JavaTemplate.builder("@Getter(AccessLevel." + accessLevel.name() + ")\n") + JavaTemplate.Builder builder = AccessLevel.PUBLIC.equals(accessLevel) ? + JavaTemplate.builder("@Getter\n") : + JavaTemplate.builder("@Getter(AccessLevel." + accessLevel.name() + ")\n") .imports("lombok.AccessLevel"); return builder diff --git a/src/main/java/org/openrewrite/java/migrate/lombok/LombokUtils.java b/src/main/java/org/openrewrite/java/migrate/lombok/LombokUtils.java index eb411e4d20..84a448dd97 100644 --- a/src/main/java/org/openrewrite/java/migrate/lombok/LombokUtils.java +++ b/src/main/java/org/openrewrite/java/migrate/lombok/LombokUtils.java @@ -33,8 +33,8 @@ public class LombokUtils { public static boolean isEffectivelyGetter(J.MethodDeclaration method) { boolean takesNoParameters = method.getParameters().get(0) instanceof J.Empty; boolean singularReturn = method.getBody() != null //abstract methods can be null - && method.getBody().getStatements().size() == 1 - && method.getBody().getStatements().get(0) instanceof J.Return; + && method.getBody().getStatements().size() == 1 && + method.getBody().getStatements().get(0) instanceof J.Return; if (takesNoParameters && singularReturn) { Expression returnExpression = ((J.Return) method.getBody().getStatements().get(0)).getExpression(); @@ -53,8 +53,8 @@ public static String deriveGetterMethodName(JavaType.Variable fieldType) { final String fieldName = fieldType.getName(); - boolean alreadyStartsWithIs = fieldName.length() >= 3 - && fieldName.substring(0, 3).matches("is[A-Z]"); + boolean alreadyStartsWithIs = fieldName.length() >= 3 && + fieldName.substring(0, 3).matches("is[A-Z]"); if (isPrimitiveBoolean) if (alreadyStartsWithIs) From e6fd1bb4b1eabd3e639c8c7f32f06c10b0fce75a Mon Sep 17 00:00:00 2001 From: timo <1398557+timo-a@users.noreply.github.com> Date: Sun, 8 Dec 2024 13:30:50 +0100 Subject: [PATCH 06/31] roll back nullable annotation --- .../org/openrewrite/java/migrate/lombok/ConvertGetter.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/main/java/org/openrewrite/java/migrate/lombok/ConvertGetter.java b/src/main/java/org/openrewrite/java/migrate/lombok/ConvertGetter.java index 5dd2490336..365f1d8f92 100644 --- a/src/main/java/org/openrewrite/java/migrate/lombok/ConvertGetter.java +++ b/src/main/java/org/openrewrite/java/migrate/lombok/ConvertGetter.java @@ -18,7 +18,6 @@ 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; @@ -92,7 +91,7 @@ public J.ClassDeclaration visitClassDeclaration(J.ClassDeclaration classDecl, Ex } @Override - public @Nullable J.MethodDeclaration visitMethodDeclaration(J.MethodDeclaration method, ExecutionContext ctx) { + public J.MethodDeclaration visitMethodDeclaration(J.MethodDeclaration method, ExecutionContext ctx) { assert method.getMethodType() != null; if (LombokUtils.isEffectivelyGetter(method)) { From 4b7fb671151ab9580a4718234a5ac1304d5d7142 Mon Sep 17 00:00:00 2001 From: Tim te Beek Date: Sun, 8 Dec 2024 16:48:53 +0100 Subject: [PATCH 07/31] Light polish --- .../java/migrate/lombok/ConvertGetter.java | 29 +++++++------------ .../java/migrate/lombok/LombokUtils.java | 21 ++++++-------- .../migrate/lombok/ConvertGetterTest.java | 8 +---- 3 files changed, 20 insertions(+), 38 deletions(-) diff --git a/src/main/java/org/openrewrite/java/migrate/lombok/ConvertGetter.java b/src/main/java/org/openrewrite/java/migrate/lombok/ConvertGetter.java index 365f1d8f92..dde3a88601 100644 --- a/src/main/java/org/openrewrite/java/migrate/lombok/ConvertGetter.java +++ b/src/main/java/org/openrewrite/java/migrate/lombok/ConvertGetter.java @@ -18,6 +18,7 @@ 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; @@ -29,7 +30,6 @@ import java.util.HashSet; import java.util.Optional; import java.util.Set; -import java.util.StringJoiner; import static java.util.Comparator.comparing; import static org.openrewrite.java.tree.JavaType.Variable; @@ -40,25 +40,18 @@ public class ConvertGetter extends Recipe { @Override public String getDisplayName() { - //language=markdown return "Convert getter methods to annotations"; } @Override public String getDescription() { //language=markdown - return new StringJoiner("\n") - .add("Convert trivial getter methods to `@Getter` annotations on their respective fields.") - .add("") - .add("Limitations:") - .add("") - .add(" - Does not add a dependency to Lombok, users need to do that manually") - .add(" - Ignores fields that are declared on the same line as others, e.g. `private int foo, bar;" + - "Users who have such fields are advised to separate them beforehand with " + - "[org.openrewrite.staticanalysis.MultipleVariableDeclaration]" + - "(https://docs.openrewrite.org/recipes/staticanalysis/multiplevariabledeclarations).") - .add(" - Does not offer any of the configuration keys listed in https://projectlombok.org/features/GetterSetter.") - .toString(); + return "Convert trivial getter methods to `@Getter` annotations on their respective fields.\n\n" + + "Limitations:\n\n" + + " - Does not add a dependency to Lombok, users need to do that manually\n" + + " - Ignores fields that are declared on the same line as others, e.g. `private int foo, bar; " + + "Users who have such fields are advised to separate them beforehand with [org.openrewrite.staticanalysis.MultipleVariableDeclaration](https://docs.openrewrite.org/recipes/staticanalysis/multiplevariabledeclarations).\n" + + " - Does not offer any of the configuration keys listed in https://projectlombok.org/features/GetterSetter."; } @Override @@ -66,7 +59,6 @@ public TreeVisitor getVisitor() { return new MethodRemover(); } - @Value @EqualsAndHashCode(callSuper = false) private static class MethodRemover extends JavaIsoVisitor { @@ -91,7 +83,7 @@ public J.ClassDeclaration visitClassDeclaration(J.ClassDeclaration classDecl, Ex } @Override - public J.MethodDeclaration visitMethodDeclaration(J.MethodDeclaration method, ExecutionContext ctx) { + public J.@Nullable MethodDeclaration visitMethodDeclaration(J.MethodDeclaration method, ExecutionContext ctx) { assert method.getMethodType() != null; if (LombokUtils.isEffectivelyGetter(method)) { @@ -125,12 +117,11 @@ private JavaTemplate getAnnotation(AccessLevel accessLevel) { JavaTemplate.Builder builder = AccessLevel.PUBLIC.equals(accessLevel) ? JavaTemplate.builder("@Getter\n") : JavaTemplate.builder("@Getter(AccessLevel." + accessLevel.name() + ")\n") - .imports("lombok.AccessLevel"); + .imports("lombok.AccessLevel"); return builder .imports("lombok.Getter") - .javaParser(JavaParser.fromJavaVersion() - .classpath("lombok")) + .javaParser(JavaParser.fromJavaVersion().classpath("lombok")) .build(); } diff --git a/src/main/java/org/openrewrite/java/migrate/lombok/LombokUtils.java b/src/main/java/org/openrewrite/java/migrate/lombok/LombokUtils.java index 84a448dd97..fafb0fa056 100644 --- a/src/main/java/org/openrewrite/java/migrate/lombok/LombokUtils.java +++ b/src/main/java/org/openrewrite/java/migrate/lombok/LombokUtils.java @@ -28,7 +28,7 @@ import static lombok.AccessLevel.*; import static org.openrewrite.java.tree.J.Modifier.Type.*; -public class LombokUtils { +class LombokUtils { public static boolean isEffectivelyGetter(J.MethodDeclaration method) { boolean takesNoParameters = method.getParameters().get(0) instanceof J.Empty; @@ -49,19 +49,16 @@ public static boolean isEffectivelyGetter(J.MethodDeclaration method) { } public static String deriveGetterMethodName(JavaType.Variable fieldType) { - boolean isPrimitiveBoolean = JavaType.Variable.Primitive.Boolean.equals(fieldType.getType()); - - final String fieldName = fieldType.getName(); - - boolean alreadyStartsWithIs = fieldName.length() >= 3 && - fieldName.substring(0, 3).matches("is[A-Z]"); - - if (isPrimitiveBoolean) - if (alreadyStartsWithIs) + String fieldName = fieldType.getName(); + if (fieldType.getType() == JavaType.Variable.Primitive.Boolean) { + boolean alreadyStartsWithIs = fieldName.length() >= 3 && + fieldName.substring(0, 3).matches("is[A-Z]"); + if (alreadyStartsWithIs) { return fieldName; - else + } else { return "is" + StringUtils.capitalize(fieldName); - + } + } return "get" + StringUtils.capitalize(fieldName); } diff --git a/src/test/java/org/openrewrite/java/migrate/lombok/ConvertGetterTest.java b/src/test/java/org/openrewrite/java/migrate/lombok/ConvertGetterTest.java index 4996feca45..72b51828b2 100644 --- a/src/test/java/org/openrewrite/java/migrate/lombok/ConvertGetterTest.java +++ b/src/test/java/org/openrewrite/java/migrate/lombok/ConvertGetterTest.java @@ -17,22 +17,16 @@ 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; -// This is a test for the ConvertToNoArgsConstructor recipe, as an example of how to write a test for an imperative recipe. class ConvertGetterTest implements RewriteTest { - // Note, you can define defaults for the RecipeSpec and these defaults will be used for all tests. - // In this case, the recipe and the parser are common. See below, on how the defaults can be overridden - // per test. @Override public void defaults(RecipeSpec spec) { - spec.recipe(new ConvertGetter()) - .parser(JavaParser.fromJavaVersion().logCompilationWarningsAndErrors(true)); + spec.recipe(new ConvertGetter()); } @DocumentExample From 665e73a0987c7b1ac3d1298374c736cb6fc91a94 Mon Sep 17 00:00:00 2001 From: Tim te Beek Date: Mon, 9 Dec 2024 09:49:13 +0100 Subject: [PATCH 08/31] Rename and add Lombok tag --- .../{ConvertGetter.java => UseLombokGetter.java} | 13 ++++++++----- ...vertGetterTest.java => UseLombokGetterTest.java} | 4 ++-- 2 files changed, 10 insertions(+), 7 deletions(-) rename src/main/java/org/openrewrite/java/migrate/lombok/{ConvertGetter.java => UseLombokGetter.java} (95%) rename src/test/java/org/openrewrite/java/migrate/lombok/{ConvertGetterTest.java => UseLombokGetterTest.java} (98%) diff --git a/src/main/java/org/openrewrite/java/migrate/lombok/ConvertGetter.java b/src/main/java/org/openrewrite/java/migrate/lombok/UseLombokGetter.java similarity index 95% rename from src/main/java/org/openrewrite/java/migrate/lombok/ConvertGetter.java rename to src/main/java/org/openrewrite/java/migrate/lombok/UseLombokGetter.java index dde3a88601..fdc9ed8250 100644 --- a/src/main/java/org/openrewrite/java/migrate/lombok/ConvertGetter.java +++ b/src/main/java/org/openrewrite/java/migrate/lombok/UseLombokGetter.java @@ -27,6 +27,7 @@ import org.openrewrite.java.JavaTemplate; import org.openrewrite.java.tree.J; +import java.util.Collections; import java.util.HashSet; import java.util.Optional; import java.util.Set; @@ -36,7 +37,7 @@ @Value @EqualsAndHashCode(callSuper = false) -public class ConvertGetter extends Recipe { +public class UseLombokGetter extends Recipe { @Override public String getDisplayName() { @@ -54,6 +55,11 @@ public String getDescription() { " - Does not offer any of the configuration keys listed in https://projectlombok.org/features/GetterSetter."; } + @Override + public Set getTags() { + return Collections.singleton("lombok"); + } + @Override public TreeVisitor getVisitor() { return new MethodRemover(); @@ -84,9 +90,7 @@ public J.ClassDeclaration visitClassDeclaration(J.ClassDeclaration classDecl, Ex @Override public J.@Nullable MethodDeclaration visitMethodDeclaration(J.MethodDeclaration method, ExecutionContext ctx) { - assert method.getMethodType() != null; - - if (LombokUtils.isEffectivelyGetter(method)) { + if (method.getMethodType() != null && LombokUtils.isEffectivelyGetter(method)) { J.Return return_ = (J.Return) method.getBody().getStatements().get(0); Variable fieldType = ((J.Identifier) return_.getExpression()).getFieldType(); boolean nameMatch = method.getSimpleName().equals(LombokUtils.deriveGetterMethodName(fieldType)); @@ -106,7 +110,6 @@ private static class Finding { AccessLevel accessLevel; } - @Value @EqualsAndHashCode(callSuper = false) static class FieldAnnotator extends JavaIsoVisitor { diff --git a/src/test/java/org/openrewrite/java/migrate/lombok/ConvertGetterTest.java b/src/test/java/org/openrewrite/java/migrate/lombok/UseLombokGetterTest.java similarity index 98% rename from src/test/java/org/openrewrite/java/migrate/lombok/ConvertGetterTest.java rename to src/test/java/org/openrewrite/java/migrate/lombok/UseLombokGetterTest.java index 72b51828b2..748fff7cf3 100644 --- a/src/test/java/org/openrewrite/java/migrate/lombok/ConvertGetterTest.java +++ b/src/test/java/org/openrewrite/java/migrate/lombok/UseLombokGetterTest.java @@ -22,11 +22,11 @@ import static org.openrewrite.java.Assertions.java; -class ConvertGetterTest implements RewriteTest { +class UseLombokGetterTest implements RewriteTest { @Override public void defaults(RecipeSpec spec) { - spec.recipe(new ConvertGetter()); + spec.recipe(new UseLombokGetter()); } @DocumentExample From 91c579b6b27b370b15b8f6d4c90d655d565481b9 Mon Sep 17 00:00:00 2001 From: Tim te Beek Date: Mon, 9 Dec 2024 10:23:21 +0100 Subject: [PATCH 09/31] Also handle field access --- .../java/migrate/lombok/LombokUtils.java | 42 +++++++++++-------- .../java/migrate/lombok/UseLombokGetter.java | 24 ++++++++--- .../migrate/lombok/UseLombokGetterTest.java | 27 ++++++++++++ 3 files changed, 69 insertions(+), 24 deletions(-) diff --git a/src/main/java/org/openrewrite/java/migrate/lombok/LombokUtils.java b/src/main/java/org/openrewrite/java/migrate/lombok/LombokUtils.java index fafb0fa056..179c0094b0 100644 --- a/src/main/java/org/openrewrite/java/migrate/lombok/LombokUtils.java +++ b/src/main/java/org/openrewrite/java/migrate/lombok/LombokUtils.java @@ -30,27 +30,33 @@ class LombokUtils { - public static boolean isEffectivelyGetter(J.MethodDeclaration method) { - boolean takesNoParameters = method.getParameters().get(0) instanceof J.Empty; - boolean singularReturn = method.getBody() != null //abstract methods can be null - && method.getBody().getStatements().size() == 1 && - method.getBody().getStatements().get(0) instanceof J.Return; - - if (takesNoParameters && singularReturn) { - Expression returnExpression = ((J.Return) method.getBody().getStatements().get(0)).getExpression(); - //returns just an identifier - if (returnExpression instanceof J.Identifier) { - J.Identifier identifier = (J.Identifier) returnExpression; - JavaType.Variable fieldType = identifier.getFieldType(); - return method.getType().equals(fieldType.getType()); - } + static boolean isEffectivelyGetter(J.MethodDeclaration method) { + // 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; + } + Expression returnExpression = ((J.Return) method.getBody().getStatements().get(0)).getExpression(); + //returns just an identifier + if (returnExpression instanceof J.Identifier) { + J.Identifier identifier = (J.Identifier) returnExpression; + JavaType.Variable fieldType = identifier.getFieldType(); + return method.getType().equals(fieldType.getType()); + } else if (returnExpression instanceof J.FieldAccess) { + J.FieldAccess fieldAccess = (J.FieldAccess) returnExpression; + JavaType fieldType = fieldAccess.getType(); + return method.getType().equals(fieldType); } return false; } - public static String deriveGetterMethodName(JavaType.Variable fieldType) { - String fieldName = fieldType.getName(); - if (fieldType.getType() == JavaType.Variable.Primitive.Boolean) { + static String deriveGetterMethodName(JavaType type, String fieldName) { + if (type == JavaType.Primitive.Boolean) { boolean alreadyStartsWithIs = fieldName.length() >= 3 && fieldName.substring(0, 3).matches("is[A-Z]"); if (alreadyStartsWithIs) { @@ -62,7 +68,7 @@ public static String deriveGetterMethodName(JavaType.Variable fieldType) { return "get" + StringUtils.capitalize(fieldName); } - public static AccessLevel getAccessLevel(Collection modifiers) { + static AccessLevel getAccessLevel(Collection modifiers) { Map map = ImmutableMap.builder() .put(Public, PUBLIC) .put(Protected, PROTECTED) diff --git a/src/main/java/org/openrewrite/java/migrate/lombok/UseLombokGetter.java b/src/main/java/org/openrewrite/java/migrate/lombok/UseLombokGetter.java index fdc9ed8250..062d4178d8 100644 --- a/src/main/java/org/openrewrite/java/migrate/lombok/UseLombokGetter.java +++ b/src/main/java/org/openrewrite/java/migrate/lombok/UseLombokGetter.java @@ -25,6 +25,7 @@ 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 java.util.Collections; @@ -92,12 +93,23 @@ public J.ClassDeclaration visitClassDeclaration(J.ClassDeclaration classDecl, Ex public J.@Nullable MethodDeclaration visitMethodDeclaration(J.MethodDeclaration method, ExecutionContext ctx) { if (method.getMethodType() != null && LombokUtils.isEffectivelyGetter(method)) { J.Return return_ = (J.Return) method.getBody().getStatements().get(0); - Variable fieldType = ((J.Identifier) return_.getExpression()).getFieldType(); - boolean nameMatch = method.getSimpleName().equals(LombokUtils.deriveGetterMethodName(fieldType)); - if (nameMatch) { - ((Set) getCursor().getNearestMessage(FIELDS_TO_DECORATE_KEY)) - .add(new Finding(fieldType.getName(), LombokUtils.getAccessLevel(method.getModifiers()))); - return null; //delete + Expression returnExpression = return_.getExpression(); + if (returnExpression instanceof J.Identifier) { + J.Identifier identifier = (J.Identifier) returnExpression; + String deriveGetterMethodName = LombokUtils.deriveGetterMethodName(identifier.getType(), identifier.getSimpleName()); + if (method.getSimpleName().equals(deriveGetterMethodName)) { + ((Set) getCursor().getNearestMessage(FIELDS_TO_DECORATE_KEY)) + .add(new Finding(identifier.getSimpleName(), LombokUtils.getAccessLevel(method.getModifiers()))); + return null; + } + } else if (returnExpression instanceof J.FieldAccess) { + J.FieldAccess fieldAccess = (J.FieldAccess) returnExpression; + String deriveGetterMethodName = LombokUtils.deriveGetterMethodName(fieldAccess.getType(), fieldAccess.getSimpleName()); + if (method.getSimpleName().equals(deriveGetterMethodName)) { + ((Set) getCursor().getNearestMessage(FIELDS_TO_DECORATE_KEY)) + .add(new Finding(fieldAccess.getSimpleName(), LombokUtils.getAccessLevel(method.getModifiers()))); + return null; + } } } return method; diff --git a/src/test/java/org/openrewrite/java/migrate/lombok/UseLombokGetterTest.java b/src/test/java/org/openrewrite/java/migrate/lombok/UseLombokGetterTest.java index 748fff7cf3..77074fe6cf 100644 --- a/src/test/java/org/openrewrite/java/migrate/lombok/UseLombokGetterTest.java +++ b/src/test/java/org/openrewrite/java/migrate/lombok/UseLombokGetterTest.java @@ -57,6 +57,33 @@ class A { ); } + @Test + void replaceGetterWithFieldAccess() { + rewriteRun(// language=java + java( + """ + class A { + + int foo = 9; + + public int getFoo() { + return this.foo; + } + } + """, + """ + import lombok.Getter; + + class A { + + @Getter + int foo = 9; + } + """ + ) + ); + } + @Test void replacePackageGetter() { rewriteRun(// language=java From c7d3febbb28b310043e5e0dc56c2aa979d12869c Mon Sep 17 00:00:00 2001 From: Tim te Beek Date: Mon, 9 Dec 2024 11:00:46 +0100 Subject: [PATCH 10/31] Push down method and variable name matching into utils --- .../java/migrate/lombok/LombokUtils.java | 19 ++++++++----- .../java/migrate/lombok/UseLombokGetter.java | 27 +++++++------------ .../migrate/lombok/UseLombokGetterTest.java | 2 +- 3 files changed, 24 insertions(+), 24 deletions(-) diff --git a/src/main/java/org/openrewrite/java/migrate/lombok/LombokUtils.java b/src/main/java/org/openrewrite/java/migrate/lombok/LombokUtils.java index 179c0094b0..e2eba1332b 100644 --- a/src/main/java/org/openrewrite/java/migrate/lombok/LombokUtils.java +++ b/src/main/java/org/openrewrite/java/migrate/lombok/LombokUtils.java @@ -17,6 +17,7 @@ import com.google.common.collect.ImmutableMap; 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; @@ -41,21 +42,27 @@ static boolean isEffectivelyGetter(J.MethodDeclaration method) { !(method.getBody().getStatements().get(0) instanceof J.Return)) { return false; } + // Check return: type and matching field name Expression returnExpression = ((J.Return) method.getBody().getStatements().get(0)).getExpression(); - //returns just an identifier if (returnExpression instanceof J.Identifier) { J.Identifier identifier = (J.Identifier) returnExpression; - JavaType.Variable fieldType = identifier.getFieldType(); - return method.getType().equals(fieldType.getType()); + return hasMatchingTypeAndName(method, identifier.getType(), identifier.getSimpleName()); } else if (returnExpression instanceof J.FieldAccess) { J.FieldAccess fieldAccess = (J.FieldAccess) returnExpression; - JavaType fieldType = fieldAccess.getType(); - return method.getType().equals(fieldType); + return hasMatchingTypeAndName(method, fieldAccess.getType(), fieldAccess.getSimpleName()); } return false; } - static String deriveGetterMethodName(JavaType type, String fieldName) { + private static boolean hasMatchingTypeAndName(J.MethodDeclaration method, @Nullable JavaType type, String simpleName) { + if (method.getType().equals(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]"); diff --git a/src/main/java/org/openrewrite/java/migrate/lombok/UseLombokGetter.java b/src/main/java/org/openrewrite/java/migrate/lombok/UseLombokGetter.java index 062d4178d8..655a6e271f 100644 --- a/src/main/java/org/openrewrite/java/migrate/lombok/UseLombokGetter.java +++ b/src/main/java/org/openrewrite/java/migrate/lombok/UseLombokGetter.java @@ -34,7 +34,6 @@ import java.util.Set; import static java.util.Comparator.comparing; -import static org.openrewrite.java.tree.JavaType.Variable; @Value @EqualsAndHashCode(callSuper = false) @@ -92,24 +91,18 @@ public J.ClassDeclaration visitClassDeclaration(J.ClassDeclaration classDecl, Ex @Override public J.@Nullable MethodDeclaration visitMethodDeclaration(J.MethodDeclaration method, ExecutionContext ctx) { if (method.getMethodType() != null && LombokUtils.isEffectivelyGetter(method)) { - J.Return return_ = (J.Return) method.getBody().getStatements().get(0); - Expression returnExpression = return_.getExpression(); + Set set = getCursor().getNearestMessage(FIELDS_TO_DECORATE_KEY); + Expression returnExpression = ((J.Return) method.getBody().getStatements().get(0)).getExpression(); if (returnExpression instanceof J.Identifier) { - J.Identifier identifier = (J.Identifier) returnExpression; - String deriveGetterMethodName = LombokUtils.deriveGetterMethodName(identifier.getType(), identifier.getSimpleName()); - if (method.getSimpleName().equals(deriveGetterMethodName)) { - ((Set) getCursor().getNearestMessage(FIELDS_TO_DECORATE_KEY)) - .add(new Finding(identifier.getSimpleName(), LombokUtils.getAccessLevel(method.getModifiers()))); - return null; - } + set.add(new Finding( + ((J.Identifier) returnExpression).getSimpleName(), + LombokUtils.getAccessLevel(method.getModifiers()))); + return null; } else if (returnExpression instanceof J.FieldAccess) { - J.FieldAccess fieldAccess = (J.FieldAccess) returnExpression; - String deriveGetterMethodName = LombokUtils.deriveGetterMethodName(fieldAccess.getType(), fieldAccess.getSimpleName()); - if (method.getSimpleName().equals(deriveGetterMethodName)) { - ((Set) getCursor().getNearestMessage(FIELDS_TO_DECORATE_KEY)) - .add(new Finding(fieldAccess.getSimpleName(), LombokUtils.getAccessLevel(method.getModifiers()))); - return null; - } + set.add(new Finding( + ((J.FieldAccess) returnExpression).getSimpleName(), + LombokUtils.getAccessLevel(method.getModifiers()))); + return null; } } return method; diff --git a/src/test/java/org/openrewrite/java/migrate/lombok/UseLombokGetterTest.java b/src/test/java/org/openrewrite/java/migrate/lombok/UseLombokGetterTest.java index 77074fe6cf..90ea33c0a4 100644 --- a/src/test/java/org/openrewrite/java/migrate/lombok/UseLombokGetterTest.java +++ b/src/test/java/org/openrewrite/java/migrate/lombok/UseLombokGetterTest.java @@ -248,7 +248,7 @@ class A { public A() { } - long getfoo() {//return type wrong + long getFoo() { //return type wrong return foo; } } From f51e00f950f167aa32fc6770fdbff96e13d6ad4f Mon Sep 17 00:00:00 2001 From: Tim te Beek Date: Mon, 9 Dec 2024 11:09:42 +0100 Subject: [PATCH 11/31] Demonstrate failing case of nested inner class getter --- .../migrate/lombok/UseLombokGetterTest.java | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/src/test/java/org/openrewrite/java/migrate/lombok/UseLombokGetterTest.java b/src/test/java/org/openrewrite/java/migrate/lombok/UseLombokGetterTest.java index 90ea33c0a4..1f8da6bbf7 100644 --- a/src/test/java/org/openrewrite/java/migrate/lombok/UseLombokGetterTest.java +++ b/src/test/java/org/openrewrite/java/migrate/lombok/UseLombokGetterTest.java @@ -481,4 +481,23 @@ Boolean isFoo() { ) ); } + + @Test + void noChangeNestedClassGetter() { + rewriteRun(// language=java + java( + """ + class Outer { + int foo = 9; + + class Inner { + public int getFoo() { + return foo; + } + } + } + """ + ) + ); + } } From 80ec54875fe93870550b6d23a93f44e1f01e7c08 Mon Sep 17 00:00:00 2001 From: timo <1398557+timo-a@users.noreply.github.com> Date: Tue, 10 Dec 2024 23:00:07 +0100 Subject: [PATCH 12/31] fix: year in licence header had copy-pasted from the example recipe --- .../org/openrewrite/java/migrate/lombok/UseLombokGetter.java | 2 +- .../openrewrite/java/migrate/lombok/UseLombokGetterTest.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/openrewrite/java/migrate/lombok/UseLombokGetter.java b/src/main/java/org/openrewrite/java/migrate/lombok/UseLombokGetter.java index 655a6e271f..23dc2a0af4 100644 --- a/src/main/java/org/openrewrite/java/migrate/lombok/UseLombokGetter.java +++ b/src/main/java/org/openrewrite/java/migrate/lombok/UseLombokGetter.java @@ -1,5 +1,5 @@ /* - * Copyright 2021 the original author or authors. + * Copyright 2024 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. diff --git a/src/test/java/org/openrewrite/java/migrate/lombok/UseLombokGetterTest.java b/src/test/java/org/openrewrite/java/migrate/lombok/UseLombokGetterTest.java index 1f8da6bbf7..f11556ae7d 100644 --- a/src/test/java/org/openrewrite/java/migrate/lombok/UseLombokGetterTest.java +++ b/src/test/java/org/openrewrite/java/migrate/lombok/UseLombokGetterTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2021 the original author or authors. + * Copyright 2024 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. From 0bb8f7367b2d8702be25475cc7264e2728f4aac9 Mon Sep 17 00:00:00 2001 From: timo <1398557+timo-a@users.noreply.github.com> Date: Tue, 10 Dec 2024 22:47:14 +0100 Subject: [PATCH 13/31] migrate existing recipe as-is --- .../java/migrate/lombok/ConvertSetter.java | 172 +++++++ .../java/migrate/lombok/LombokUtils.java | 40 ++ .../migrate/lombok/ConvertSetterTest.java | 471 ++++++++++++++++++ 3 files changed, 683 insertions(+) create mode 100644 src/main/java/org/openrewrite/java/migrate/lombok/ConvertSetter.java create mode 100644 src/test/java/org/openrewrite/java/migrate/lombok/ConvertSetterTest.java diff --git a/src/main/java/org/openrewrite/java/migrate/lombok/ConvertSetter.java b/src/main/java/org/openrewrite/java/migrate/lombok/ConvertSetter.java new file mode 100644 index 0000000000..1b98055d53 --- /dev/null +++ b/src/main/java/org/openrewrite/java/migrate/lombok/ConvertSetter.java @@ -0,0 +1,172 @@ +/* + * Copyright 2021 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.migrate.lombok; + +import lombok.AccessLevel; +import lombok.EqualsAndHashCode; +import lombok.Value; +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.J; + +import java.util.HashSet; +import java.util.Optional; +import java.util.Set; +import java.util.StringJoiner; + +import static java.util.Comparator.comparing; +import static org.openrewrite.java.tree.JavaType.Variable; + +@Value +@EqualsAndHashCode(callSuper = false) +public class ConvertSetter extends Recipe { + + @Override + public String getDisplayName() { + //language=markdown + return "Convert setter methods to annotations"; + } + + @Override + public String getDescription() { + //language=markdown + return new StringJoiner("\n") + .add("Convert trivial setter methods to `@Setter` annotations on their respective fields.") + .add("") + .add("Limitations:") + .add("") + .add(" - Does not add a dependency to Lombok, users need to do that manually") + .add(" - Ignores fields that are declared on the same line as others, e.g. `private int foo, bar;" + + "Users who have such fields are advised to separate them beforehand with " + + "[org.openrewrite.staticanalysis.MultipleVariableDeclaration]" + + "(https://docs.openrewrite.org/recipes/staticanalysis/multiplevariabledeclarations).") + .add(" - Does not offer any of the configuration keys listed in https://projectlombok.org/features/GetterSetter.") + .toString(); + } + + @Override + public TreeVisitor getVisitor() { + return new MethodRemover(); + } + + + @Value + @EqualsAndHashCode(callSuper = false) + private static class MethodRemover extends JavaIsoVisitor { + private static final String FIELDS_TO_DECORATE_KEY = "FIELDS_TO_DECORATE"; + + @Override + public J.ClassDeclaration visitClassDeclaration(J.ClassDeclaration classDecl, ExecutionContext ctx) { + + //initialize set of fields to annotate + getCursor().putMessage(FIELDS_TO_DECORATE_KEY, new HashSet()); + + //delete methods, note down corresponding fields + J.ClassDeclaration classDeclAfterVisit = super.visitClassDeclaration(classDecl, ctx); + + //only thing that can have changed is removal of setter methods + if (classDeclAfterVisit != classDecl) { + //this set collects the fields for which existing methods have already been removed + Set fieldsToDecorate = getCursor().pollNearestMessage(FIELDS_TO_DECORATE_KEY); + doAfterVisit(new FieldAnnotator(fieldsToDecorate)); + } + return classDeclAfterVisit; + } + + @Override + public J.MethodDeclaration visitMethodDeclaration(J.MethodDeclaration method, ExecutionContext ctx) { + assert method.getMethodType() != null; + + if (LombokUtils.isEffectivelySetter(method)) { + J.Assignment assignment_ = (J.Assignment) method.getBody().getStatements().get(0); + J.FieldAccess fieldAccess = (J.FieldAccess) assignment_.getVariable(); + + Variable fieldType = fieldAccess.getName().getFieldType(); + boolean nameMatch = method.getSimpleName().equals(LombokUtils.deriveSetterMethodName(fieldType)); + if (nameMatch){ + ((Set) getCursor().getNearestMessage(FIELDS_TO_DECORATE_KEY)) + .add(new Finding(fieldType.getName(), LombokUtils.getAccessLevel(method.getModifiers()))); + return null; //delete + } + } + return method; + } + } + + @Value + private static class Finding { + String fieldName; + AccessLevel accessLevel; + } + + + @Value + @EqualsAndHashCode(callSuper = false) + static class FieldAnnotator extends JavaIsoVisitor{ + + Set fieldsToDecorate; + + private JavaTemplate getAnnotation(AccessLevel accessLevel) { + JavaTemplate.Builder builder = AccessLevel.PUBLIC.equals(accessLevel) + ? JavaTemplate.builder("@Setter\n") + : JavaTemplate.builder("@Setter(AccessLevel." + accessLevel.name() + ")\n") + .imports("lombok.AccessLevel"); + + return builder + .imports("lombok.Setter") + .javaParser(JavaParser.fromJavaVersion() + .classpath("lombok")) + .build(); + } + + @Override + public J.VariableDeclarations visitVariableDeclarations(J.VariableDeclarations multiVariable, ExecutionContext ctx) { + + //we accept only one var decl per line, see description + if (multiVariable.getVariables().size() > 1) { + return multiVariable; + } + + //we only want to annotate fields and not e.g. method parameters, so we require a lass declaration to be close in the cursor. + if (getCursor().getPathAsStream().limit( 4 ).noneMatch( e -> e instanceof J.ClassDeclaration )) { + return multiVariable; + } + + J.VariableDeclarations.NamedVariable variable = multiVariable.getVariables().get(0); + Optional field = fieldsToDecorate.stream() + .filter(f -> f.fieldName.equals(variable.getSimpleName())) + .findFirst(); + + if (!field.isPresent()) { + return multiVariable; //not the field we are looking for + } + + //remove it from list + //fieldsToDecorate.remove(field.get()); + + J.VariableDeclarations annotated = getAnnotation(field.get().getAccessLevel()).apply( + getCursor(), + multiVariable.getCoordinates().addAnnotation(comparing(J.Annotation::getSimpleName))); + maybeAddImport("lombok.Setter"); + maybeAddImport("lombok.AccessLevel"); + return annotated; + } + } +} diff --git a/src/main/java/org/openrewrite/java/migrate/lombok/LombokUtils.java b/src/main/java/org/openrewrite/java/migrate/lombok/LombokUtils.java index e2eba1332b..043409dcff 100644 --- a/src/main/java/org/openrewrite/java/migrate/lombok/LombokUtils.java +++ b/src/main/java/org/openrewrite/java/migrate/lombok/LombokUtils.java @@ -22,9 +22,12 @@ import org.openrewrite.java.tree.Expression; import org.openrewrite.java.tree.J; import org.openrewrite.java.tree.JavaType; +import org.openrewrite.java.tree.Statement; import java.util.Collection; +import java.util.List; import java.util.Map; +import java.util.stream.Collectors; import static lombok.AccessLevel.*; import static org.openrewrite.java.tree.J.Modifier.Type.*; @@ -54,6 +57,39 @@ static boolean isEffectivelyGetter(J.MethodDeclaration method) { return false; } + public static boolean isEffectivelySetter(J.MethodDeclaration method) { + boolean isVoid = "void".equals(method.getType().toString()); + List actualParameters = method.getParameters().stream() + .filter(s -> !(s instanceof J.Empty)) + .collect(Collectors.toList()); + boolean oneParam = actualParameters.size() == 1; + if (!isVoid || !oneParam) + return false; + + J.VariableDeclarations variableDeclarations = (J.VariableDeclarations) actualParameters.get(0); + J.VariableDeclarations.NamedVariable param = variableDeclarations.getVariables().get(0); + String paramName = param.getName().toString(); + + boolean singularStatement = method.getBody() != null //abstract methods can be null + && method.getBody().getStatements().size() == 1 + && method.getBody().getStatements().get(0) instanceof J.Assignment; + + if (!singularStatement) { + return false; + } + J.Assignment assignment = (J.Assignment) method.getBody().getStatements().get(0); + + J.FieldAccess fieldAccess = (J.FieldAccess) assignment.getVariable(); + + return + // assigned value is exactly the parameter + assignment.getAssignment().toString().equals(paramName) + + // type of parameter and field have to match + && param.getType().equals(fieldAccess.getType()); + + } + private static boolean hasMatchingTypeAndName(J.MethodDeclaration method, @Nullable JavaType type, String simpleName) { if (method.getType().equals(type)) { String deriveGetterMethodName = deriveGetterMethodName(type, simpleName); @@ -75,6 +111,10 @@ private static String deriveGetterMethodName(@Nullable JavaType type, String fie return "get" + StringUtils.capitalize(fieldName); } + public static String deriveSetterMethodName(JavaType.Variable fieldType) { + return "set" + StringUtils.capitalize(fieldType.getName()); + } + static AccessLevel getAccessLevel(Collection modifiers) { Map map = ImmutableMap.builder() .put(Public, PUBLIC) diff --git a/src/test/java/org/openrewrite/java/migrate/lombok/ConvertSetterTest.java b/src/test/java/org/openrewrite/java/migrate/lombok/ConvertSetterTest.java new file mode 100644 index 0000000000..2b9719dc54 --- /dev/null +++ b/src/test/java/org/openrewrite/java/migrate/lombok/ConvertSetterTest.java @@ -0,0 +1,471 @@ +/* + * Copyright 2021 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.migrate.lombok; + +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; + +// This is a test for the ConvertToNoArgsConstructor recipe, as an example of how to write a test for an imperative recipe. +class ConvertSetterTest implements RewriteTest { + + // Note, you can define defaults for the RecipeSpec and these defaults will be used for all tests. + // In this case, the recipe and the parser are common. See below, on how the defaults can be overridden + // per test. + @Override + public void defaults(RecipeSpec spec) { + spec.recipe(new ConvertSetter()) + .parser(JavaParser.fromJavaVersion() + .logCompilationWarningsAndErrors(true)); + } + + int foo; + + @DocumentExample + @Test + void replaceSetter() { + rewriteRun(// language=java + java( + """ + class A { + + int foo = 9; + + public void setFoo(int foo) { + this.foo = foo; + } + } + """, + """ + import lombok.Setter; + + class A { + + @Setter + int foo = 9; + } + """ + ) + ); + } + + @Test + void tolerantToNonstandardParameterNames() { + rewriteRun(// language=java + java( + """ + class A { + + int foo = 9; + + public void setFoo(int fub) { + this.foo = fub; + } + } + """, + """ + import lombok.Setter; + + class A { + + @Setter + int foo = 9; + } + """ + ) + ); + } + + @Test + void replacePackageSetter() { + rewriteRun(// language=java + java( + """ + class A { + + int foo = 9; + + void setFoo(int foo) { + this.foo = foo; + } + } + """, + """ + import lombok.AccessLevel; + import lombok.Setter; + + class A { + + @Setter(AccessLevel.PACKAGE) + int foo = 9; + } + """ + ) + ); + } + + @Test + void replaceProtectedSetter() { + rewriteRun(// language=java + java( + """ + class A { + + int foo = 9; + + protected void setFoo(int foo) { + this.foo = foo; + } + } + """, + """ + import lombok.AccessLevel; + import lombok.Setter; + + class A { + + @Setter(AccessLevel.PROTECTED) + int foo = 9; + } + """ + ) + ); + } + + @Test + void replacePrivateSetter() { + rewriteRun(// language=java + java( + """ + class A { + + int foo = 9; + + private void setFoo(int foo) { + this.foo = foo; + } + } + """, + """ + import lombok.AccessLevel; + import lombok.Setter; + + class A { + + @Setter(AccessLevel.PRIVATE) + int foo = 9; + } + """ + ) + ); + } + + @Test + void replaceJustTheMatchingSetter() { + rewriteRun(// language=java + java( + """ + class A { + + int foo = 9; + + int ba; + + public A() { + ba = 1; + } + + public void setFoo(int foo) { + this.foo = foo; + } + + public void setMoo(int foo) {//method name wrong + this.foo = foo; + } + } + """, + """ + import lombok.Setter; + + class A { + + @Setter + int foo = 9; + + int ba; + + public A() { + ba = 1; + } + + public void setMoo(int foo) {//method name wrong + this.foo = foo; + } + } + """ + ) + ); + } + + @Test + void noChangeWhenMethodNameDoesntMatch() { + rewriteRun(// language=java + java( + """ + class A { + + int foo = 9; + + public A() { + } + + public void setfoo(int foo) {//method name wrong + this.foo = foo; + } + } + """ + ) + ); + } + + @Test + void noChangeWhenParameterTypeDoesntMatch() { + rewriteRun(// language=java + java( + """ + class A { + + int foo = 9; + + public A() { + } + + public void setFoo(long foo) {//parameter type wrong + this.foo = (int) foo; + } + } + """ + ) + ); + } + + @Test + void noChangeWhenFieldIsNotAssigned() { + rewriteRun(// language=java + java( + """ + class A { + + int foo = 9; + int ba = 10; + + public A() { + } + + public void setFoo(int foo) { + int foo = foo; + } + } + """ + ) + ); + } + + @Test + void noChangeWhenDifferentFieldIsAssigned() { + rewriteRun(// language=java + java( + """ + class A { + + int foo = 9; + int ba = 10; + + public A() { + } + + public void setFoo(int foo) { + this.ba = foo; //assigns wrong variable + } + } + """ + ) + ); + } + + @Test + void noChangeWhenSideEffects1() { + rewriteRun(// language=java + java( + """ + class A { + + int foo = 9; + int ba = 10; + + public A() { + } + + public void setFoo(int foo) { + foo++;//does extra stuff + this.foo = foo; + } + } + """ + ) + ); + } + + @Test + void replacePrimitiveBoolean() { + rewriteRun(// language=java + java( + """ + class A { + + boolean foo = true; + + public void setFoo(boolean foo) { + this.foo = foo; + } + } + """, + """ + import lombok.Setter; + + class A { + + @Setter + boolean foo = true; + } + """ + ) + ); + } + + @Test + void replaceBoolean() { + rewriteRun(// language=java + java( + """ + class A { + + Boolean foo = true; + + public void setFoo(Boolean foo) { + this.foo = foo; + } + } + """, + """ + import lombok.Setter; + + class A { + + @Setter + Boolean foo = true; + } + """ + ) + ); + } + + @Test + void annotateOnlyFields() { + rewriteRun(// language=java + java( + """ + class A { + + int foo = 9; + + public void setFoo(int foo) { + this.foo = foo; + } + + public void unrelated1(int foo) { + } + public void unrelated2() { + int foo = 10; + } + } + """, + """ + import lombok.Setter; + + class A { + + @Setter + int foo = 9; + + public void unrelated1(int foo) { + } + public void unrelated2() { + int foo = 10; + } + } + """ + ) + ); + } + + @Test + void annotateOnlyFields2() { + rewriteRun(// language=java + java( + """ + class A { + + public void setFoo(int foo) { + this.foo = foo; + } + + public void unrelated1(int foo) { + } + public void unrelated2() { + int foo = 10; + } + + int foo = 9; + } + """, + """ + import lombok.Setter; + + class A { + + public void unrelated1(int foo) { + } + public void unrelated2() { + int foo = 10; + } + + @Setter + int foo = 9; + } + """ + ) + ); + } + + + + +} From 5e9e379396d0954a681b97cbd80d89f4cc510b04 Mon Sep 17 00:00:00 2001 From: timo <1398557+timo-a@users.noreply.github.com> Date: Tue, 10 Dec 2024 22:48:17 +0100 Subject: [PATCH 14/31] deactivate getter test for development --- .../openrewrite/java/migrate/lombok/UseLombokGetterTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/java/org/openrewrite/java/migrate/lombok/UseLombokGetterTest.java b/src/test/java/org/openrewrite/java/migrate/lombok/UseLombokGetterTest.java index f11556ae7d..59c2def8dd 100644 --- a/src/test/java/org/openrewrite/java/migrate/lombok/UseLombokGetterTest.java +++ b/src/test/java/org/openrewrite/java/migrate/lombok/UseLombokGetterTest.java @@ -482,7 +482,7 @@ Boolean isFoo() { ); } - @Test + //@Test todo activate again void noChangeNestedClassGetter() { rewriteRun(// language=java java( From 7510f63a5b3ab6763d5f01e0ef6392cdeede8a38 Mon Sep 17 00:00:00 2001 From: timo <1398557+timo-a@users.noreply.github.com> Date: Tue, 10 Dec 2024 22:55:36 +0100 Subject: [PATCH 15/31] Rename and add Lombok tag --- .../{ConvertSetter.java => UseLombokSetter.java} | 12 +++++++----- ...nvertSetterTest.java => UseLombokSetterTest.java} | 4 ++-- 2 files changed, 9 insertions(+), 7 deletions(-) rename src/main/java/org/openrewrite/java/migrate/lombok/{ConvertSetter.java => UseLombokSetter.java} (97%) rename src/test/java/org/openrewrite/java/migrate/lombok/{ConvertSetterTest.java => UseLombokSetterTest.java} (99%) diff --git a/src/main/java/org/openrewrite/java/migrate/lombok/ConvertSetter.java b/src/main/java/org/openrewrite/java/migrate/lombok/UseLombokSetter.java similarity index 97% rename from src/main/java/org/openrewrite/java/migrate/lombok/ConvertSetter.java rename to src/main/java/org/openrewrite/java/migrate/lombok/UseLombokSetter.java index 1b98055d53..722215d7c7 100644 --- a/src/main/java/org/openrewrite/java/migrate/lombok/ConvertSetter.java +++ b/src/main/java/org/openrewrite/java/migrate/lombok/UseLombokSetter.java @@ -26,17 +26,14 @@ import org.openrewrite.java.JavaTemplate; import org.openrewrite.java.tree.J; -import java.util.HashSet; -import java.util.Optional; -import java.util.Set; -import java.util.StringJoiner; +import java.util.*; import static java.util.Comparator.comparing; import static org.openrewrite.java.tree.JavaType.Variable; @Value @EqualsAndHashCode(callSuper = false) -public class ConvertSetter extends Recipe { +public class UseLombokSetter extends Recipe { @Override public String getDisplayName() { @@ -61,6 +58,11 @@ public String getDescription() { .toString(); } + @Override + public Set getTags() { + return Collections.singleton("lombok"); + } + @Override public TreeVisitor getVisitor() { return new MethodRemover(); diff --git a/src/test/java/org/openrewrite/java/migrate/lombok/ConvertSetterTest.java b/src/test/java/org/openrewrite/java/migrate/lombok/UseLombokSetterTest.java similarity index 99% rename from src/test/java/org/openrewrite/java/migrate/lombok/ConvertSetterTest.java rename to src/test/java/org/openrewrite/java/migrate/lombok/UseLombokSetterTest.java index 2b9719dc54..680db52b79 100644 --- a/src/test/java/org/openrewrite/java/migrate/lombok/ConvertSetterTest.java +++ b/src/test/java/org/openrewrite/java/migrate/lombok/UseLombokSetterTest.java @@ -24,14 +24,14 @@ import static org.openrewrite.java.Assertions.java; // This is a test for the ConvertToNoArgsConstructor recipe, as an example of how to write a test for an imperative recipe. -class ConvertSetterTest implements RewriteTest { +class UseLombokSetterTest implements RewriteTest { // Note, you can define defaults for the RecipeSpec and these defaults will be used for all tests. // In this case, the recipe and the parser are common. See below, on how the defaults can be overridden // per test. @Override public void defaults(RecipeSpec spec) { - spec.recipe(new ConvertSetter()) + spec.recipe(new UseLombokSetter()) .parser(JavaParser.fromJavaVersion() .logCompilationWarningsAndErrors(true)); } From 46519ddd5e9b53f2121934e61b608dd5f5bf7998 Mon Sep 17 00:00:00 2001 From: timo <1398557+timo-a@users.noreply.github.com> Date: Tue, 10 Dec 2024 22:57:01 +0100 Subject: [PATCH 16/31] fix year in licence header --- .../org/openrewrite/java/migrate/lombok/UseLombokSetter.java | 2 +- .../openrewrite/java/migrate/lombok/UseLombokSetterTest.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/openrewrite/java/migrate/lombok/UseLombokSetter.java b/src/main/java/org/openrewrite/java/migrate/lombok/UseLombokSetter.java index 722215d7c7..4ab44f991c 100644 --- a/src/main/java/org/openrewrite/java/migrate/lombok/UseLombokSetter.java +++ b/src/main/java/org/openrewrite/java/migrate/lombok/UseLombokSetter.java @@ -1,5 +1,5 @@ /* - * Copyright 2021 the original author or authors. + * Copyright 2024 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. diff --git a/src/test/java/org/openrewrite/java/migrate/lombok/UseLombokSetterTest.java b/src/test/java/org/openrewrite/java/migrate/lombok/UseLombokSetterTest.java index 680db52b79..33936b5303 100644 --- a/src/test/java/org/openrewrite/java/migrate/lombok/UseLombokSetterTest.java +++ b/src/test/java/org/openrewrite/java/migrate/lombok/UseLombokSetterTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2021 the original author or authors. + * Copyright 2024 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. From 9bba4fe3c8f2ba1b3a361a7262715fd2eaabb964 Mon Sep 17 00:00:00 2001 From: timo <1398557+timo-a@users.noreply.github.com> Date: Tue, 10 Dec 2024 22:58:33 +0100 Subject: [PATCH 17/31] chore: IntelliJ auto-formatter --- .../openrewrite/java/migrate/lombok/UseLombokSetter.java | 6 +++--- .../java/migrate/lombok/UseLombokSetterTest.java | 2 -- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/src/main/java/org/openrewrite/java/migrate/lombok/UseLombokSetter.java b/src/main/java/org/openrewrite/java/migrate/lombok/UseLombokSetter.java index 4ab44f991c..65c21304ea 100644 --- a/src/main/java/org/openrewrite/java/migrate/lombok/UseLombokSetter.java +++ b/src/main/java/org/openrewrite/java/migrate/lombok/UseLombokSetter.java @@ -102,7 +102,7 @@ public J.MethodDeclaration visitMethodDeclaration(J.MethodDeclaration method, Ex Variable fieldType = fieldAccess.getName().getFieldType(); boolean nameMatch = method.getSimpleName().equals(LombokUtils.deriveSetterMethodName(fieldType)); - if (nameMatch){ + if (nameMatch) { ((Set) getCursor().getNearestMessage(FIELDS_TO_DECORATE_KEY)) .add(new Finding(fieldType.getName(), LombokUtils.getAccessLevel(method.getModifiers()))); return null; //delete @@ -121,7 +121,7 @@ private static class Finding { @Value @EqualsAndHashCode(callSuper = false) - static class FieldAnnotator extends JavaIsoVisitor{ + static class FieldAnnotator extends JavaIsoVisitor { Set fieldsToDecorate; @@ -147,7 +147,7 @@ public J.VariableDeclarations visitVariableDeclarations(J.VariableDeclarations m } //we only want to annotate fields and not e.g. method parameters, so we require a lass declaration to be close in the cursor. - if (getCursor().getPathAsStream().limit( 4 ).noneMatch( e -> e instanceof J.ClassDeclaration )) { + if (getCursor().getPathAsStream().limit(4).noneMatch(e -> e instanceof J.ClassDeclaration)) { return multiVariable; } diff --git a/src/test/java/org/openrewrite/java/migrate/lombok/UseLombokSetterTest.java b/src/test/java/org/openrewrite/java/migrate/lombok/UseLombokSetterTest.java index 33936b5303..8ab26f4aa1 100644 --- a/src/test/java/org/openrewrite/java/migrate/lombok/UseLombokSetterTest.java +++ b/src/test/java/org/openrewrite/java/migrate/lombok/UseLombokSetterTest.java @@ -466,6 +466,4 @@ public void unrelated2() { } - - } From 7f4d48600dd9e2800bff27e0070c9d0c2bcab0bb Mon Sep 17 00:00:00 2001 From: timo <1398557+timo-a@users.noreply.github.com> Date: Tue, 10 Dec 2024 23:23:17 +0100 Subject: [PATCH 18/31] apply best practices --- .../org/openrewrite/java/migrate/lombok/LombokUtils.java | 9 +++++---- .../openrewrite/java/migrate/lombok/UseLombokSetter.java | 9 +++++---- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/src/main/java/org/openrewrite/java/migrate/lombok/LombokUtils.java b/src/main/java/org/openrewrite/java/migrate/lombok/LombokUtils.java index 043409dcff..2d741b3e96 100644 --- a/src/main/java/org/openrewrite/java/migrate/lombok/LombokUtils.java +++ b/src/main/java/org/openrewrite/java/migrate/lombok/LombokUtils.java @@ -71,8 +71,8 @@ public static boolean isEffectivelySetter(J.MethodDeclaration method) { String paramName = param.getName().toString(); boolean singularStatement = method.getBody() != null //abstract methods can be null - && method.getBody().getStatements().size() == 1 - && method.getBody().getStatements().get(0) instanceof J.Assignment; + && method.getBody().getStatements().size() == 1 && + method.getBody().getStatements().get(0) instanceof J.Assignment; if (!singularStatement) { return false; @@ -83,10 +83,11 @@ public static boolean isEffectivelySetter(J.MethodDeclaration method) { return // assigned value is exactly the parameter - assignment.getAssignment().toString().equals(paramName) + assignment.getAssignment().toString().equals(paramName) // type of parameter and field have to match + && // type of parameter and field have to match - && param.getType().equals(fieldAccess.getType()); + param.getType().equals(fieldAccess.getType()); } diff --git a/src/main/java/org/openrewrite/java/migrate/lombok/UseLombokSetter.java b/src/main/java/org/openrewrite/java/migrate/lombok/UseLombokSetter.java index 65c21304ea..c18807a1e8 100644 --- a/src/main/java/org/openrewrite/java/migrate/lombok/UseLombokSetter.java +++ b/src/main/java/org/openrewrite/java/migrate/lombok/UseLombokSetter.java @@ -18,6 +18,7 @@ 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; @@ -93,7 +94,7 @@ public J.ClassDeclaration visitClassDeclaration(J.ClassDeclaration classDecl, Ex } @Override - public J.MethodDeclaration visitMethodDeclaration(J.MethodDeclaration method, ExecutionContext ctx) { + public J.@Nullable MethodDeclaration visitMethodDeclaration(J.MethodDeclaration method, ExecutionContext ctx) { assert method.getMethodType() != null; if (LombokUtils.isEffectivelySetter(method)) { @@ -126,9 +127,9 @@ static class FieldAnnotator extends JavaIsoVisitor { Set fieldsToDecorate; private JavaTemplate getAnnotation(AccessLevel accessLevel) { - JavaTemplate.Builder builder = AccessLevel.PUBLIC.equals(accessLevel) - ? JavaTemplate.builder("@Setter\n") - : JavaTemplate.builder("@Setter(AccessLevel." + accessLevel.name() + ")\n") + JavaTemplate.Builder builder = AccessLevel.PUBLIC.equals(accessLevel) ? + JavaTemplate.builder("@Setter\n") : + JavaTemplate.builder("@Setter(AccessLevel." + accessLevel.name() + ")\n") .imports("lombok.AccessLevel"); return builder From f68e252815523ad72909e7db2deef7c4521873d8 Mon Sep 17 00:00:00 2001 From: timo <1398557+timo-a@users.noreply.github.com> Date: Tue, 10 Dec 2024 23:32:46 +0100 Subject: [PATCH 19/31] light polish --- .../java/migrate/lombok/UseLombokSetter.java | 25 ++++++------------- 1 file changed, 8 insertions(+), 17 deletions(-) diff --git a/src/main/java/org/openrewrite/java/migrate/lombok/UseLombokSetter.java b/src/main/java/org/openrewrite/java/migrate/lombok/UseLombokSetter.java index c18807a1e8..6ee613618e 100644 --- a/src/main/java/org/openrewrite/java/migrate/lombok/UseLombokSetter.java +++ b/src/main/java/org/openrewrite/java/migrate/lombok/UseLombokSetter.java @@ -38,25 +38,18 @@ public class UseLombokSetter extends Recipe { @Override public String getDisplayName() { - //language=markdown return "Convert setter methods to annotations"; } @Override public String getDescription() { //language=markdown - return new StringJoiner("\n") - .add("Convert trivial setter methods to `@Setter` annotations on their respective fields.") - .add("") - .add("Limitations:") - .add("") - .add(" - Does not add a dependency to Lombok, users need to do that manually") - .add(" - Ignores fields that are declared on the same line as others, e.g. `private int foo, bar;" + - "Users who have such fields are advised to separate them beforehand with " + - "[org.openrewrite.staticanalysis.MultipleVariableDeclaration]" + - "(https://docs.openrewrite.org/recipes/staticanalysis/multiplevariabledeclarations).") - .add(" - Does not offer any of the configuration keys listed in https://projectlombok.org/features/GetterSetter.") - .toString(); + return "Convert trivial setter methods to `@Setter` annotations on their respective fields.\n\n" + + "Limitations:\n\n" + + " - Does not add a dependency to Lombok, users need to do that manually\n" + + " - Ignores fields that are declared on the same line as others, e.g. `private int foo, bar; " + + "Users who have such fields are advised to separate them beforehand with [org.openrewrite.staticanalysis.MultipleVariableDeclaration](https://docs.openrewrite.org/recipes/staticanalysis/multiplevariabledeclarations).\n" + + " - Does not offer any of the configuration keys listed in https://projectlombok.org/features/GetterSetter."; } @Override @@ -69,7 +62,6 @@ public TreeVisitor getVisitor() { return new MethodRemover(); } - @Value @EqualsAndHashCode(callSuper = false) private static class MethodRemover extends JavaIsoVisitor { @@ -130,12 +122,11 @@ private JavaTemplate getAnnotation(AccessLevel accessLevel) { JavaTemplate.Builder builder = AccessLevel.PUBLIC.equals(accessLevel) ? JavaTemplate.builder("@Setter\n") : JavaTemplate.builder("@Setter(AccessLevel." + accessLevel.name() + ")\n") - .imports("lombok.AccessLevel"); + .imports("lombok.AccessLevel"); return builder .imports("lombok.Setter") - .javaParser(JavaParser.fromJavaVersion() - .classpath("lombok")) + .javaParser(JavaParser.fromJavaVersion().classpath("lombok")) .build(); } From 0700c201afc639688c2e99377b5ddf3f12387728 Mon Sep 17 00:00:00 2001 From: timo <1398557+timo-a@users.noreply.github.com> Date: Tue, 10 Dec 2024 23:52:04 +0100 Subject: [PATCH 20/31] copy from: Also handle field access --- .../java/org/openrewrite/java/migrate/lombok/LombokUtils.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/openrewrite/java/migrate/lombok/LombokUtils.java b/src/main/java/org/openrewrite/java/migrate/lombok/LombokUtils.java index 2d741b3e96..92d126052b 100644 --- a/src/main/java/org/openrewrite/java/migrate/lombok/LombokUtils.java +++ b/src/main/java/org/openrewrite/java/migrate/lombok/LombokUtils.java @@ -57,7 +57,7 @@ static boolean isEffectivelyGetter(J.MethodDeclaration method) { return false; } - public static boolean isEffectivelySetter(J.MethodDeclaration method) { + static boolean isEffectivelySetter(J.MethodDeclaration method) { boolean isVoid = "void".equals(method.getType().toString()); List actualParameters = method.getParameters().stream() .filter(s -> !(s instanceof J.Empty)) @@ -112,7 +112,7 @@ private static String deriveGetterMethodName(@Nullable JavaType type, String fie return "get" + StringUtils.capitalize(fieldName); } - public static String deriveSetterMethodName(JavaType.Variable fieldType) { + static String deriveSetterMethodName(JavaType.Variable fieldType) { return "set" + StringUtils.capitalize(fieldType.getName()); } From 60ee08f11f594ab80c1f9c8183c39c5f692fda50 Mon Sep 17 00:00:00 2001 From: timo <1398557+timo-a@users.noreply.github.com> Date: Wed, 11 Dec 2024 00:14:15 +0100 Subject: [PATCH 21/31] minor changes --- .../java/migrate/lombok/LombokUtils.java | 16 ++++++++-------- .../java/migrate/lombok/UseLombokSetter.java | 9 ++------- .../java/migrate/lombok/UseLombokSetterTest.java | 2 -- 3 files changed, 10 insertions(+), 17 deletions(-) diff --git a/src/main/java/org/openrewrite/java/migrate/lombok/LombokUtils.java b/src/main/java/org/openrewrite/java/migrate/lombok/LombokUtils.java index 92d126052b..49200ea1d4 100644 --- a/src/main/java/org/openrewrite/java/migrate/lombok/LombokUtils.java +++ b/src/main/java/org/openrewrite/java/migrate/lombok/LombokUtils.java @@ -57,6 +57,14 @@ static boolean isEffectivelyGetter(J.MethodDeclaration method) { return false; } + private static boolean hasMatchingTypeAndName(J.MethodDeclaration method, @Nullable JavaType type, String simpleName) { + if (method.getType().equals(type)) { + String deriveGetterMethodName = deriveGetterMethodName(type, simpleName); + return method.getSimpleName().equals(deriveGetterMethodName); + } + return false; + } + static boolean isEffectivelySetter(J.MethodDeclaration method) { boolean isVoid = "void".equals(method.getType().toString()); List actualParameters = method.getParameters().stream() @@ -91,14 +99,6 @@ static boolean isEffectivelySetter(J.MethodDeclaration method) { } - private static boolean hasMatchingTypeAndName(J.MethodDeclaration method, @Nullable JavaType type, String simpleName) { - if (method.getType().equals(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 && diff --git a/src/main/java/org/openrewrite/java/migrate/lombok/UseLombokSetter.java b/src/main/java/org/openrewrite/java/migrate/lombok/UseLombokSetter.java index 6ee613618e..f678ff9343 100644 --- a/src/main/java/org/openrewrite/java/migrate/lombok/UseLombokSetter.java +++ b/src/main/java/org/openrewrite/java/migrate/lombok/UseLombokSetter.java @@ -87,8 +87,6 @@ public J.ClassDeclaration visitClassDeclaration(J.ClassDeclaration classDecl, Ex @Override public J.@Nullable MethodDeclaration visitMethodDeclaration(J.MethodDeclaration method, ExecutionContext ctx) { - assert method.getMethodType() != null; - if (LombokUtils.isEffectivelySetter(method)) { J.Assignment assignment_ = (J.Assignment) method.getBody().getStatements().get(0); J.FieldAccess fieldAccess = (J.FieldAccess) assignment_.getVariable(); @@ -96,8 +94,8 @@ public J.ClassDeclaration visitClassDeclaration(J.ClassDeclaration classDecl, Ex Variable fieldType = fieldAccess.getName().getFieldType(); boolean nameMatch = method.getSimpleName().equals(LombokUtils.deriveSetterMethodName(fieldType)); if (nameMatch) { - ((Set) getCursor().getNearestMessage(FIELDS_TO_DECORATE_KEY)) - .add(new Finding(fieldType.getName(), LombokUtils.getAccessLevel(method.getModifiers()))); + Set set = getCursor().getNearestMessage(FIELDS_TO_DECORATE_KEY); + set.add(new Finding(fieldType.getName(), LombokUtils.getAccessLevel(method.getModifiers()))); return null; //delete } } @@ -152,9 +150,6 @@ public J.VariableDeclarations visitVariableDeclarations(J.VariableDeclarations m return multiVariable; //not the field we are looking for } - //remove it from list - //fieldsToDecorate.remove(field.get()); - J.VariableDeclarations annotated = getAnnotation(field.get().getAccessLevel()).apply( getCursor(), multiVariable.getCoordinates().addAnnotation(comparing(J.Annotation::getSimpleName))); diff --git a/src/test/java/org/openrewrite/java/migrate/lombok/UseLombokSetterTest.java b/src/test/java/org/openrewrite/java/migrate/lombok/UseLombokSetterTest.java index 8ab26f4aa1..33e02601f7 100644 --- a/src/test/java/org/openrewrite/java/migrate/lombok/UseLombokSetterTest.java +++ b/src/test/java/org/openrewrite/java/migrate/lombok/UseLombokSetterTest.java @@ -464,6 +464,4 @@ public void unrelated2() { ) ); } - - } From d43806171455182fc1fb9f05a4bb618cf4456e45 Mon Sep 17 00:00:00 2001 From: Tim te Beek Date: Sat, 14 Dec 2024 20:02:54 +0100 Subject: [PATCH 22/31] Minimize changes with `main` branch ahead of rebase to avoid conflicts --- .../java/migrate/lombok/LombokUtils.java | 85 ++++++------ .../java/migrate/lombok/UseLombokGetter.java | 126 +++++------------- .../migrate/lombok/UseLombokGetterTest.java | 30 ++++- 3 files changed, 110 insertions(+), 131 deletions(-) diff --git a/src/main/java/org/openrewrite/java/migrate/lombok/LombokUtils.java b/src/main/java/org/openrewrite/java/migrate/lombok/LombokUtils.java index 49200ea1d4..3612bbc99f 100644 --- a/src/main/java/org/openrewrite/java/migrate/lombok/LombokUtils.java +++ b/src/main/java/org/openrewrite/java/migrate/lombok/LombokUtils.java @@ -15,7 +15,6 @@ */ package org.openrewrite.java.migrate.lombok; -import com.google.common.collect.ImmutableMap; import lombok.AccessLevel; import org.jspecify.annotations.Nullable; import org.openrewrite.internal.StringUtils; @@ -24,17 +23,18 @@ import org.openrewrite.java.tree.JavaType; import org.openrewrite.java.tree.Statement; -import java.util.Collection; import java.util.List; -import java.util.Map; -import java.util.stream.Collectors; +import static java.util.stream.Collectors.toList; import static lombok.AccessLevel.*; import static org.openrewrite.java.tree.J.Modifier.Type.*; class LombokUtils { - static boolean isEffectivelyGetter(J.MethodDeclaration method) { + 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; @@ -45,31 +45,65 @@ static boolean isEffectivelyGetter(J.MethodDeclaration method) { !(method.getBody().getStatements().get(0) instanceof J.Return)) { return false; } - // Check return: type and matching field name + // 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; - return hasMatchingTypeAndName(method, identifier.getType(), identifier.getSimpleName()); + 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; - return hasMatchingTypeAndName(method, fieldAccess.getType(), fieldAccess.getSimpleName()); + 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().equals(type)) { + 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; + } + + static boolean isEffectivelySetter(J.MethodDeclaration method) { boolean isVoid = "void".equals(method.getType().toString()); List actualParameters = method.getParameters().stream() .filter(s -> !(s instanceof J.Empty)) - .collect(Collectors.toList()); + .collect(toList()); boolean oneParam = actualParameters.size() == 1; if (!isVoid || !oneParam) return false; @@ -98,35 +132,4 @@ static boolean isEffectivelySetter(J.MethodDeclaration method) { param.getType().equals(fieldAccess.getType()); } - - 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 String deriveSetterMethodName(JavaType.Variable fieldType) { - return "set" + StringUtils.capitalize(fieldType.getName()); - } - - static AccessLevel getAccessLevel(Collection modifiers) { - Map map = ImmutableMap.builder() - .put(Public, PUBLIC) - .put(Protected, PROTECTED) - .put(Private, PRIVATE) - .build(); - - return modifiers.stream() - .map(modifier -> map.getOrDefault(modifier.getType(), AccessLevel.NONE)) - .filter(a -> a != AccessLevel.NONE) - .findAny().orElse(AccessLevel.PACKAGE); - } - } diff --git a/src/main/java/org/openrewrite/java/migrate/lombok/UseLombokGetter.java b/src/main/java/org/openrewrite/java/migrate/lombok/UseLombokGetter.java index 23dc2a0af4..b8de2d23f4 100644 --- a/src/main/java/org/openrewrite/java/migrate/lombok/UseLombokGetter.java +++ b/src/main/java/org/openrewrite/java/migrate/lombok/UseLombokGetter.java @@ -27,13 +27,14 @@ 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.HashSet; -import java.util.Optional; +import java.util.List; import java.util.Set; import static java.util.Comparator.comparing; +import static lombok.AccessLevel.PUBLIC; @Value @EqualsAndHashCode(callSuper = false) @@ -47,12 +48,7 @@ public String getDisplayName() { @Override public String getDescription() { //language=markdown - return "Convert trivial getter methods to `@Getter` annotations on their respective fields.\n\n" + - "Limitations:\n\n" + - " - Does not add a dependency to Lombok, users need to do that manually\n" + - " - Ignores fields that are declared on the same line as others, e.g. `private int foo, bar; " + - "Users who have such fields are advised to separate them beforehand with [org.openrewrite.staticanalysis.MultipleVariableDeclaration](https://docs.openrewrite.org/recipes/staticanalysis/multiplevariabledeclarations).\n" + - " - Does not offer any of the configuration keys listed in https://projectlombok.org/features/GetterSetter."; + return "Convert trivial getter methods to `@Getter` annotations on their respective fields."; } @Override @@ -62,100 +58,52 @@ public Set getTags() { @Override public TreeVisitor getVisitor() { - return new MethodRemover(); - } - - @Value - @EqualsAndHashCode(callSuper = false) - private static class MethodRemover extends JavaIsoVisitor { - private static final String FIELDS_TO_DECORATE_KEY = "FIELDS_TO_DECORATE"; - - @Override - public J.ClassDeclaration visitClassDeclaration(J.ClassDeclaration classDecl, ExecutionContext ctx) { - - //initialize set of fields to annotate - getCursor().putMessage(FIELDS_TO_DECORATE_KEY, new HashSet()); - - //delete methods, note down corresponding fields - J.ClassDeclaration classDeclAfterVisit = super.visitClassDeclaration(classDecl, ctx); - - //only thing that can have changed is removal of getter methods - if (classDeclAfterVisit != classDecl) { - //this set collects the fields for which existing methods have already been removed - Set fieldsToDecorate = getCursor().pollNearestMessage(FIELDS_TO_DECORATE_KEY); - doAfterVisit(new FieldAnnotator(fieldsToDecorate)); - } - return classDeclAfterVisit; - } - - @Override - public J.@Nullable MethodDeclaration visitMethodDeclaration(J.MethodDeclaration method, ExecutionContext ctx) { - if (method.getMethodType() != null && LombokUtils.isEffectivelyGetter(method)) { - Set set = getCursor().getNearestMessage(FIELDS_TO_DECORATE_KEY); - Expression returnExpression = ((J.Return) method.getBody().getStatements().get(0)).getExpression(); - if (returnExpression instanceof J.Identifier) { - set.add(new Finding( - ((J.Identifier) returnExpression).getSimpleName(), - LombokUtils.getAccessLevel(method.getModifiers()))); - return null; - } else if (returnExpression instanceof J.FieldAccess) { - set.add(new Finding( - ((J.FieldAccess) returnExpression).getSimpleName(), - LombokUtils.getAccessLevel(method.getModifiers()))); - return null; + return new JavaIsoVisitor() { + @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; } - return method; - } + }; } - @Value - private static class Finding { - String fieldName; - AccessLevel accessLevel; - } @Value @EqualsAndHashCode(callSuper = false) static class FieldAnnotator extends JavaIsoVisitor { - Set fieldsToDecorate; - - private JavaTemplate getAnnotation(AccessLevel accessLevel) { - JavaTemplate.Builder builder = AccessLevel.PUBLIC.equals(accessLevel) ? - JavaTemplate.builder("@Getter\n") : - JavaTemplate.builder("@Getter(AccessLevel." + accessLevel.name() + ")\n") - .imports("lombok.AccessLevel"); - - return builder - .imports("lombok.Getter") - .javaParser(JavaParser.fromJavaVersion().classpath("lombok")) - .build(); - } + JavaType field; + AccessLevel accessLevel; @Override public J.VariableDeclarations visitVariableDeclarations(J.VariableDeclarations multiVariable, ExecutionContext ctx) { - - //we accept only one var decl per line, see description - if (multiVariable.getVariables().size() > 1) { - return multiVariable; - } - - J.VariableDeclarations.NamedVariable variable = multiVariable.getVariables().get(0); - Optional field = fieldsToDecorate.stream() - .filter(f -> f.fieldName.equals(variable.getSimpleName())) - .findFirst(); - - if (!field.isPresent()) { - return multiVariable; //not the field we are looking for + 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))); + } } - - J.VariableDeclarations annotated = getAnnotation(field.get().getAccessLevel()).apply( - getCursor(), - multiVariable.getCoordinates().addAnnotation(comparing(J.Annotation::getSimpleName))); - maybeAddImport("lombok.Getter"); - maybeAddImport("lombok.AccessLevel"); - return annotated; + return multiVariable; } } } diff --git a/src/test/java/org/openrewrite/java/migrate/lombok/UseLombokGetterTest.java b/src/test/java/org/openrewrite/java/migrate/lombok/UseLombokGetterTest.java index 59c2def8dd..ec62019a51 100644 --- a/src/test/java/org/openrewrite/java/migrate/lombok/UseLombokGetterTest.java +++ b/src/test/java/org/openrewrite/java/migrate/lombok/UseLombokGetterTest.java @@ -84,6 +84,34 @@ class A { ); } + @Test + void replaceGetterWithMultiVariable() { + // Technically this adds a new public getter not there previously, but we'll tolerate it + rewriteRun(// language=java + java( + """ + class A { + + int foo = 9, bar = 10; + + public int getFoo() { + return foo; + } + } + """, + """ + import lombok.Getter; + + class A { + + @Getter + int foo = 9, bar = 10; + } + """ + ) + ); + } + @Test void replacePackageGetter() { rewriteRun(// language=java @@ -482,7 +510,7 @@ Boolean isFoo() { ); } - //@Test todo activate again + @Test void noChangeNestedClassGetter() { rewriteRun(// language=java java( From 6fd95b7d55f3d4abaab73ae0bc3be55d24d83117 Mon Sep 17 00:00:00 2001 From: Tim te Beek Date: Sun, 15 Dec 2024 16:51:16 +0100 Subject: [PATCH 23/31] Resolve compilation issues --- .../java/migrate/lombok/LombokUtils.java | 68 ++++++++++--------- .../java/migrate/lombok/UseLombokSetter.java | 2 +- 2 files changed, 37 insertions(+), 33 deletions(-) diff --git a/src/main/java/org/openrewrite/java/migrate/lombok/LombokUtils.java b/src/main/java/org/openrewrite/java/migrate/lombok/LombokUtils.java index c90bb6f0ad..2da112d7a7 100644 --- a/src/main/java/org/openrewrite/java/migrate/lombok/LombokUtils.java +++ b/src/main/java/org/openrewrite/java/migrate/lombok/LombokUtils.java @@ -66,38 +66,6 @@ static boolean isGetter(J.MethodDeclaration method) { 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; - } - static boolean isEffectivelySetter(J.MethodDeclaration method) { boolean isVoid = "void".equals(method.getType().toString()); List actualParameters = method.getParameters().stream() @@ -131,4 +99,40 @@ static boolean isEffectivelySetter(J.MethodDeclaration method) { param.getType().equals(fieldAccess.getType()); } + + 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 String deriveSetterMethodName(JavaType.Variable fieldType) { + return "set" + StringUtils.capitalize(fieldType.getName()); + } + + static AccessLevel getAccessLevel(J.MethodDeclaration methodDeclaration) { + if (methodDeclaration.hasModifier(Public)) { + return PUBLIC; + } else if (methodDeclaration.hasModifier(Protected)) { + return PROTECTED; + } else if (methodDeclaration.hasModifier(Private)) { + return PRIVATE; + } + return PACKAGE; + } } diff --git a/src/main/java/org/openrewrite/java/migrate/lombok/UseLombokSetter.java b/src/main/java/org/openrewrite/java/migrate/lombok/UseLombokSetter.java index f678ff9343..d2fad57492 100644 --- a/src/main/java/org/openrewrite/java/migrate/lombok/UseLombokSetter.java +++ b/src/main/java/org/openrewrite/java/migrate/lombok/UseLombokSetter.java @@ -95,7 +95,7 @@ public J.ClassDeclaration visitClassDeclaration(J.ClassDeclaration classDecl, Ex boolean nameMatch = method.getSimpleName().equals(LombokUtils.deriveSetterMethodName(fieldType)); if (nameMatch) { Set set = getCursor().getNearestMessage(FIELDS_TO_DECORATE_KEY); - set.add(new Finding(fieldType.getName(), LombokUtils.getAccessLevel(method.getModifiers()))); + set.add(new Finding(fieldType.getName(), LombokUtils.getAccessLevel(method))); return null; //delete } } From 22810ec21e6991d4d0ecb7ef65791e9bc1618d99 Mon Sep 17 00:00:00 2001 From: Tim te Beek Date: Sun, 15 Dec 2024 17:25:58 +0100 Subject: [PATCH 24/31] Ensure there is no change for a nested Setter --- .../migrate/lombok/UseLombokSetterTest.java | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/src/test/java/org/openrewrite/java/migrate/lombok/UseLombokSetterTest.java b/src/test/java/org/openrewrite/java/migrate/lombok/UseLombokSetterTest.java index 33e02601f7..bab9ddf258 100644 --- a/src/test/java/org/openrewrite/java/migrate/lombok/UseLombokSetterTest.java +++ b/src/test/java/org/openrewrite/java/migrate/lombok/UseLombokSetterTest.java @@ -464,4 +464,23 @@ public void unrelated2() { ) ); } + + @Test + void noChangeNestedClassSetter() { + rewriteRun(// language=java + java( + """ + class Outer { + int foo = 9; + + class Inner { + public void setFoo(int foo) { + Outer.this.foo = foo; + } + } + } + """ + ) + ); + } } From a1288ef140efe0a385759c1846bfb441cbb4d6d8 Mon Sep 17 00:00:00 2001 From: Tim te Beek Date: Sun, 15 Dec 2024 17:32:04 +0100 Subject: [PATCH 25/31] Extract a reusable FieldAnnotator class --- .../java/migrate/lombok/FieldAnnotator.java | 54 +++++++++++++++++++ .../java/migrate/lombok/UseLombokGetter.java | 34 ++---------- 2 files changed, 57 insertions(+), 31 deletions(-) create mode 100644 src/main/java/org/openrewrite/java/migrate/lombok/FieldAnnotator.java diff --git a/src/main/java/org/openrewrite/java/migrate/lombok/FieldAnnotator.java b/src/main/java/org/openrewrite/java/migrate/lombok/FieldAnnotator.java new file mode 100644 index 0000000000..2a42531b80 --- /dev/null +++ b/src/main/java/org/openrewrite/java/migrate/lombok/FieldAnnotator.java @@ -0,0 +1,54 @@ +/* + * Copyright 2024 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.lombok; + +import lombok.AccessLevel; +import lombok.EqualsAndHashCode; +import lombok.Value; +import org.openrewrite.ExecutionContext; +import org.openrewrite.java.JavaIsoVisitor; +import org.openrewrite.java.JavaParser; +import org.openrewrite.java.JavaTemplate; +import org.openrewrite.java.tree.J; +import org.openrewrite.java.tree.JavaType; + +import static java.util.Comparator.comparing; +import static lombok.AccessLevel.PUBLIC; + +@Value +@EqualsAndHashCode(callSuper = false) +class FieldAnnotator extends JavaIsoVisitor { + + Class annotation; + 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(annotation.getName()); + maybeAddImport("lombok.AccessLevel"); + String suffix = accessLevel == PUBLIC ? "" : String.format("(AccessLevel.%s)", accessLevel.name()); + return JavaTemplate.builder("@" + annotation.getSimpleName() + suffix) + .imports(annotation.getName(), "lombok.AccessLevel") + .javaParser(JavaParser.fromJavaVersion().classpath("lombok")) + .build().apply(getCursor(), multiVariable.getCoordinates().addAnnotation(comparing(J.Annotation::getSimpleName))); + } + } + return multiVariable; + } +} diff --git a/src/main/java/org/openrewrite/java/migrate/lombok/UseLombokGetter.java b/src/main/java/org/openrewrite/java/migrate/lombok/UseLombokGetter.java index e7c99bbdff..b3584d79ab 100644 --- a/src/main/java/org/openrewrite/java/migrate/lombok/UseLombokGetter.java +++ b/src/main/java/org/openrewrite/java/migrate/lombok/UseLombokGetter.java @@ -15,26 +15,21 @@ */ package org.openrewrite.java.migrate.lombok; -import lombok.AccessLevel; import lombok.EqualsAndHashCode; +import lombok.Getter; 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) @@ -66,12 +61,14 @@ public TreeVisitor getVisitor() { if (returnExpression instanceof J.Identifier && ((J.Identifier) returnExpression).getFieldType() != null) { doAfterVisit(new FieldAnnotator( + Getter.class, ((J.Identifier) returnExpression).getFieldType(), LombokUtils.getAccessLevel(method))); return null; } else if (returnExpression instanceof J.FieldAccess && ((J.FieldAccess) returnExpression).getName().getFieldType() != null) { doAfterVisit(new FieldAnnotator( + Getter.class, ((J.FieldAccess) returnExpression).getName().getFieldType(), LombokUtils.getAccessLevel(method))); return null; @@ -81,29 +78,4 @@ public TreeVisitor getVisitor() { } }; } - - - @Value - @EqualsAndHashCode(callSuper = false) - static class FieldAnnotator extends JavaIsoVisitor { - - 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; - } - } } From 108fdfd92a75753706a1552bc67f9771b132f1ad Mon Sep 17 00:00:00 2001 From: Tim te Beek Date: Sun, 15 Dec 2024 17:35:25 +0100 Subject: [PATCH 26/31] Adopt now shared `FieldAnnotator` for setters as well --- .../java/migrate/lombok/UseLombokSetter.java | 90 ++----------------- 1 file changed, 8 insertions(+), 82 deletions(-) diff --git a/src/main/java/org/openrewrite/java/migrate/lombok/UseLombokSetter.java b/src/main/java/org/openrewrite/java/migrate/lombok/UseLombokSetter.java index d2fad57492..3265a6b61c 100644 --- a/src/main/java/org/openrewrite/java/migrate/lombok/UseLombokSetter.java +++ b/src/main/java/org/openrewrite/java/migrate/lombok/UseLombokSetter.java @@ -15,21 +15,19 @@ */ package org.openrewrite.java.migrate.lombok; -import lombok.AccessLevel; import lombok.EqualsAndHashCode; +import lombok.Setter; 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.J; -import java.util.*; +import java.util.Collections; +import java.util.Set; -import static java.util.Comparator.comparing; import static org.openrewrite.java.tree.JavaType.Variable; @Value @@ -65,25 +63,6 @@ public TreeVisitor getVisitor() { @Value @EqualsAndHashCode(callSuper = false) private static class MethodRemover extends JavaIsoVisitor { - private static final String FIELDS_TO_DECORATE_KEY = "FIELDS_TO_DECORATE"; - - @Override - public J.ClassDeclaration visitClassDeclaration(J.ClassDeclaration classDecl, ExecutionContext ctx) { - - //initialize set of fields to annotate - getCursor().putMessage(FIELDS_TO_DECORATE_KEY, new HashSet()); - - //delete methods, note down corresponding fields - J.ClassDeclaration classDeclAfterVisit = super.visitClassDeclaration(classDecl, ctx); - - //only thing that can have changed is removal of setter methods - if (classDeclAfterVisit != classDecl) { - //this set collects the fields for which existing methods have already been removed - Set fieldsToDecorate = getCursor().pollNearestMessage(FIELDS_TO_DECORATE_KEY); - doAfterVisit(new FieldAnnotator(fieldsToDecorate)); - } - return classDeclAfterVisit; - } @Override public J.@Nullable MethodDeclaration visitMethodDeclaration(J.MethodDeclaration method, ExecutionContext ctx) { @@ -94,68 +73,15 @@ public J.ClassDeclaration visitClassDeclaration(J.ClassDeclaration classDecl, Ex Variable fieldType = fieldAccess.getName().getFieldType(); boolean nameMatch = method.getSimpleName().equals(LombokUtils.deriveSetterMethodName(fieldType)); if (nameMatch) { - Set set = getCursor().getNearestMessage(FIELDS_TO_DECORATE_KEY); - set.add(new Finding(fieldType.getName(), LombokUtils.getAccessLevel(method))); + doAfterVisit(new FieldAnnotator( + Setter.class, + fieldType, + LombokUtils.getAccessLevel(method) + )); return null; //delete } } return method; } } - - @Value - private static class Finding { - String fieldName; - AccessLevel accessLevel; - } - - - @Value - @EqualsAndHashCode(callSuper = false) - static class FieldAnnotator extends JavaIsoVisitor { - - Set fieldsToDecorate; - - private JavaTemplate getAnnotation(AccessLevel accessLevel) { - JavaTemplate.Builder builder = AccessLevel.PUBLIC.equals(accessLevel) ? - JavaTemplate.builder("@Setter\n") : - JavaTemplate.builder("@Setter(AccessLevel." + accessLevel.name() + ")\n") - .imports("lombok.AccessLevel"); - - return builder - .imports("lombok.Setter") - .javaParser(JavaParser.fromJavaVersion().classpath("lombok")) - .build(); - } - - @Override - public J.VariableDeclarations visitVariableDeclarations(J.VariableDeclarations multiVariable, ExecutionContext ctx) { - - //we accept only one var decl per line, see description - if (multiVariable.getVariables().size() > 1) { - return multiVariable; - } - - //we only want to annotate fields and not e.g. method parameters, so we require a lass declaration to be close in the cursor. - if (getCursor().getPathAsStream().limit(4).noneMatch(e -> e instanceof J.ClassDeclaration)) { - return multiVariable; - } - - J.VariableDeclarations.NamedVariable variable = multiVariable.getVariables().get(0); - Optional field = fieldsToDecorate.stream() - .filter(f -> f.fieldName.equals(variable.getSimpleName())) - .findFirst(); - - if (!field.isPresent()) { - return multiVariable; //not the field we are looking for - } - - J.VariableDeclarations annotated = getAnnotation(field.get().getAccessLevel()).apply( - getCursor(), - multiVariable.getCoordinates().addAnnotation(comparing(J.Annotation::getSimpleName))); - maybeAddImport("lombok.Setter"); - maybeAddImport("lombok.AccessLevel"); - return annotated; - } - } } From 81c96990f161e363d8d002ab6cd66ef1d895e07d Mon Sep 17 00:00:00 2001 From: Tim te Beek Date: Sun, 15 Dec 2024 18:12:42 +0100 Subject: [PATCH 27/31] Convert most of the checks as used for UseLombokGetter --- .../java/migrate/lombok/LombokUtils.java | 80 +++++++++---------- .../java/migrate/lombok/UseLombokSetter.java | 40 +++------- 2 files changed, 51 insertions(+), 69 deletions(-) diff --git a/src/main/java/org/openrewrite/java/migrate/lombok/LombokUtils.java b/src/main/java/org/openrewrite/java/migrate/lombok/LombokUtils.java index 2da112d7a7..267cf778aa 100644 --- a/src/main/java/org/openrewrite/java/migrate/lombok/LombokUtils.java +++ b/src/main/java/org/openrewrite/java/migrate/lombok/LombokUtils.java @@ -21,11 +21,7 @@ import org.openrewrite.java.tree.Expression; import org.openrewrite.java.tree.J; import org.openrewrite.java.tree.JavaType; -import org.openrewrite.java.tree.Statement; -import java.util.List; - -import static java.util.stream.Collectors.toList; import static lombok.AccessLevel.*; import static org.openrewrite.java.tree.J.Modifier.Type.*; @@ -66,40 +62,6 @@ static boolean isGetter(J.MethodDeclaration method) { return false; } - static boolean isEffectivelySetter(J.MethodDeclaration method) { - boolean isVoid = "void".equals(method.getType().toString()); - List actualParameters = method.getParameters().stream() - .filter(s -> !(s instanceof J.Empty)) - .collect(toList()); - boolean oneParam = actualParameters.size() == 1; - if (!isVoid || !oneParam) - return false; - - J.VariableDeclarations variableDeclarations = (J.VariableDeclarations) actualParameters.get(0); - J.VariableDeclarations.NamedVariable param = variableDeclarations.getVariables().get(0); - String paramName = param.getName().toString(); - - boolean singularStatement = method.getBody() != null //abstract methods can be null - && method.getBody().getStatements().size() == 1 && - method.getBody().getStatements().get(0) instanceof J.Assignment; - - if (!singularStatement) { - return false; - } - J.Assignment assignment = (J.Assignment) method.getBody().getStatements().get(0); - - J.FieldAccess fieldAccess = (J.FieldAccess) assignment.getVariable(); - - return - // assigned value is exactly the parameter - assignment.getAssignment().toString().equals(paramName) // type of parameter and field have to match - && - - // type of parameter and field have to match - param.getType().equals(fieldAccess.getType()); - - } - private static boolean hasMatchingTypeAndName(J.MethodDeclaration method, @Nullable JavaType type, String simpleName) { if (method.getType() == type) { String deriveGetterMethodName = deriveGetterMethodName(type, simpleName); @@ -121,8 +83,46 @@ private static String deriveGetterMethodName(@Nullable JavaType type, String fie return "get" + StringUtils.capitalize(fieldName); } - static String deriveSetterMethodName(JavaType.Variable fieldType) { - return "set" + StringUtils.capitalize(fieldType.getName()); + static boolean isSetter(J.MethodDeclaration method) { + // Check return type: void + if (method.getType() != JavaType.Primitive.Void) { + return false; + } + // Check signature: single parameter + if (method.getParameters().size() != 1 || method.getParameters().get(0) instanceof J.Empty) { + return false; + } + // Check body: just an assignment + if (method.getBody() == null || //abstract methods can be null + method.getBody().getStatements().size() != 1 || + !(method.getBody().getStatements().get(0) instanceof J.Assignment)) { + return false; + } + J.Assignment assignment = (J.Assignment) method.getBody().getStatements().get(0); + J.FieldAccess fieldAccess = (J.FieldAccess) assignment.getVariable(); + if (!method.getSimpleName().equals(deriveSetterMethodName(fieldAccess))) { + return false; + } + + // Check argument is assigned to field + J.VariableDeclarations variableDeclarations = (J.VariableDeclarations) method.getParameters().get(0); + J.VariableDeclarations.NamedVariable param = variableDeclarations.getVariables().get(0); + JavaType paramType = param.getType(); + String paramName = param.getName().toString(); + + + return + // assigned value is exactly the parameter + assignment.getAssignment().toString().equals(paramName) // type of parameter and field have to match + && + + // type of parameter and field have to match + param.getType().equals(fieldAccess.getType()); + + } + + private static String deriveSetterMethodName(J.FieldAccess fieldAccess) { + return "set" + StringUtils.capitalize(fieldAccess.getSimpleName()); } static AccessLevel getAccessLevel(J.MethodDeclaration methodDeclaration) { diff --git a/src/main/java/org/openrewrite/java/migrate/lombok/UseLombokSetter.java b/src/main/java/org/openrewrite/java/migrate/lombok/UseLombokSetter.java index 3265a6b61c..c02702d89a 100644 --- a/src/main/java/org/openrewrite/java/migrate/lombok/UseLombokSetter.java +++ b/src/main/java/org/openrewrite/java/migrate/lombok/UseLombokSetter.java @@ -42,12 +42,7 @@ public String getDisplayName() { @Override public String getDescription() { //language=markdown - return "Convert trivial setter methods to `@Setter` annotations on their respective fields.\n\n" + - "Limitations:\n\n" + - " - Does not add a dependency to Lombok, users need to do that manually\n" + - " - Ignores fields that are declared on the same line as others, e.g. `private int foo, bar; " + - "Users who have such fields are advised to separate them beforehand with [org.openrewrite.staticanalysis.MultipleVariableDeclaration](https://docs.openrewrite.org/recipes/staticanalysis/multiplevariabledeclarations).\n" + - " - Does not offer any of the configuration keys listed in https://projectlombok.org/features/GetterSetter."; + return "Convert trivial setter methods to `@Setter` annotations on their respective fields."; } @Override @@ -57,31 +52,18 @@ public Set getTags() { @Override public TreeVisitor getVisitor() { - return new MethodRemover(); - } - - @Value - @EqualsAndHashCode(callSuper = false) - private static class MethodRemover extends JavaIsoVisitor { - - @Override - public J.@Nullable MethodDeclaration visitMethodDeclaration(J.MethodDeclaration method, ExecutionContext ctx) { - if (LombokUtils.isEffectivelySetter(method)) { - J.Assignment assignment_ = (J.Assignment) method.getBody().getStatements().get(0); - J.FieldAccess fieldAccess = (J.FieldAccess) assignment_.getVariable(); - - Variable fieldType = fieldAccess.getName().getFieldType(); - boolean nameMatch = method.getSimpleName().equals(LombokUtils.deriveSetterMethodName(fieldType)); - if (nameMatch) { - doAfterVisit(new FieldAnnotator( - Setter.class, - fieldType, - LombokUtils.getAccessLevel(method) - )); + return new JavaIsoVisitor() { + @Override + public J.@Nullable MethodDeclaration visitMethodDeclaration(J.MethodDeclaration method, ExecutionContext ctx) { + if (LombokUtils.isSetter(method)) { + J.Assignment assignment_ = (J.Assignment) method.getBody().getStatements().get(0); + J.FieldAccess fieldAccess = (J.FieldAccess) assignment_.getVariable(); + Variable fieldType = fieldAccess.getName().getFieldType(); + doAfterVisit(new FieldAnnotator(Setter.class, fieldType, LombokUtils.getAccessLevel(method))); return null; //delete } + return method; } - return method; - } + }; } } From 868328ba7813668436c23a71fdf56c7654e9d0d6 Mon Sep 17 00:00:00 2001 From: Tim te Beek Date: Sun, 15 Dec 2024 18:23:35 +0100 Subject: [PATCH 28/31] Add one more style we ought to cover --- .../java/migrate/lombok/LombokUtils.java | 28 ++++++---- .../migrate/lombok/UseLombokSetterTest.java | 54 +++++++++++++++++++ 2 files changed, 71 insertions(+), 11 deletions(-) diff --git a/src/main/java/org/openrewrite/java/migrate/lombok/LombokUtils.java b/src/main/java/org/openrewrite/java/migrate/lombok/LombokUtils.java index 267cf778aa..4343ed8513 100644 --- a/src/main/java/org/openrewrite/java/migrate/lombok/LombokUtils.java +++ b/src/main/java/org/openrewrite/java/migrate/lombok/LombokUtils.java @@ -99,26 +99,32 @@ static boolean isSetter(J.MethodDeclaration method) { return false; } J.Assignment assignment = (J.Assignment) method.getBody().getStatements().get(0); - J.FieldAccess fieldAccess = (J.FieldAccess) assignment.getVariable(); - if (!method.getSimpleName().equals(deriveSetterMethodName(fieldAccess))) { + J.FieldAccess assignedField = (J.FieldAccess) assignment.getVariable(); + if (!method.getSimpleName().equals(deriveSetterMethodName(assignedField))) { return false; } // Check argument is assigned to field J.VariableDeclarations variableDeclarations = (J.VariableDeclarations) method.getParameters().get(0); J.VariableDeclarations.NamedVariable param = variableDeclarations.getVariables().get(0); - JavaType paramType = param.getType(); - String paramName = param.getName().toString(); + // type of parameter and field have to match + if (!param.getType().equals(assignedField.getType())) { + return false; + } - return - // assigned value is exactly the parameter - assignment.getAssignment().toString().equals(paramName) // type of parameter and field have to match - && - - // type of parameter and field have to match - param.getType().equals(fieldAccess.getType()); + // Check field is declared on method type + JavaType.FullyQualified declaringType = method.getMethodType().getDeclaringType(); + if (assignedField.getTarget() instanceof J.Identifier) { + J.Identifier target = (J.Identifier) assignedField.getTarget(); + return target.getFieldType() != null && declaringType == target.getFieldType().getOwner(); + } else if (assignedField.getTarget() instanceof J.FieldAccess) { + Expression target = ((J.FieldAccess) assignedField.getTarget()).getTarget(); + return target instanceof J.Identifier && ((J.Identifier) target).getFieldType() != null && + declaringType == ((J.Identifier) target).getFieldType().getOwner(); + } + return false; } private static String deriveSetterMethodName(J.FieldAccess fieldAccess) { diff --git a/src/test/java/org/openrewrite/java/migrate/lombok/UseLombokSetterTest.java b/src/test/java/org/openrewrite/java/migrate/lombok/UseLombokSetterTest.java index bab9ddf258..5116b15e02 100644 --- a/src/test/java/org/openrewrite/java/migrate/lombok/UseLombokSetterTest.java +++ b/src/test/java/org/openrewrite/java/migrate/lombok/UseLombokSetterTest.java @@ -66,6 +66,60 @@ class A { ); } + @Test + void replaceSetterWhenArgNameWithUnderscore() { + rewriteRun(// language=java + java( + """ + class A { + + int foo = 9; + + public void setFoo(int foo_) { + this.foo = foo_; + } + } + """, + """ + import lombok.Setter; + + class A { + + @Setter + int foo = 9; + } + """ + ) + ); + } + + @Test + void replaceSetterWhenArgNameWithUnderscoreAndUnqualifiedFieldAccess() { + rewriteRun(// language=java + java( + """ + class A { + + int foo = 9; + + public void setFoo(int foo_) { + foo = foo_; + } + } + """, + """ + import lombok.Setter; + + class A { + + @Setter + int foo = 9; + } + """ + ) + ); + } + @Test void tolerantToNonstandardParameterNames() { rewriteRun(// language=java From a1184771f20df6f60ada37f6dca48866c0c2577c Mon Sep 17 00:00:00 2001 From: Tim te Beek Date: Sun, 15 Dec 2024 18:59:26 +0100 Subject: [PATCH 29/31] Add remaining checks to make all tests pass --- .../java/migrate/lombok/LombokUtils.java | 52 ++++++++++--------- .../java/migrate/lombok/UseLombokSetter.java | 28 ++++++---- .../migrate/lombok/UseLombokSetterTest.java | 4 +- 3 files changed, 48 insertions(+), 36 deletions(-) diff --git a/src/main/java/org/openrewrite/java/migrate/lombok/LombokUtils.java b/src/main/java/org/openrewrite/java/migrate/lombok/LombokUtils.java index 4343ed8513..66bc1282ae 100644 --- a/src/main/java/org/openrewrite/java/migrate/lombok/LombokUtils.java +++ b/src/main/java/org/openrewrite/java/migrate/lombok/LombokUtils.java @@ -48,7 +48,7 @@ static boolean isGetter(J.MethodDeclaration method) { 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()); + return hasMatchingTypeAndGetterName(method, identifier.getType(), identifier.getSimpleName()); } } else if (returnExpression instanceof J.FieldAccess) { J.FieldAccess fieldAccess = (J.FieldAccess) returnExpression; @@ -56,13 +56,13 @@ static boolean isGetter(J.MethodDeclaration method) { 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 hasMatchingTypeAndGetterName(method, fieldAccess.getType(), fieldAccess.getSimpleName()); } } return false; } - private static boolean hasMatchingTypeAndName(J.MethodDeclaration method, @Nullable JavaType type, String simpleName) { + private static boolean hasMatchingTypeAndGetterName(J.MethodDeclaration method, @Nullable JavaType type, String simpleName) { if (method.getType() == type) { String deriveGetterMethodName = deriveGetterMethodName(type, simpleName); return method.getSimpleName().equals(deriveGetterMethodName); @@ -83,6 +83,10 @@ private static String deriveGetterMethodName(@Nullable JavaType type, String fie return "get" + StringUtils.capitalize(fieldName); } + private static boolean hasMatchingSetterMethodName(J.MethodDeclaration method, String simpleName) { + return method.getSimpleName().equals("set" + StringUtils.capitalize(simpleName)); + } + static boolean isSetter(J.MethodDeclaration method) { // Check return type: void if (method.getType() != JavaType.Primitive.Void) { @@ -98,39 +102,39 @@ static boolean isSetter(J.MethodDeclaration method) { !(method.getBody().getStatements().get(0) instanceof J.Assignment)) { return false; } - J.Assignment assignment = (J.Assignment) method.getBody().getStatements().get(0); - J.FieldAccess assignedField = (J.FieldAccess) assignment.getVariable(); - if (!method.getSimpleName().equals(deriveSetterMethodName(assignedField))) { - return false; - } + JavaType.FullyQualified declaringType = method.getMethodType().getDeclaringType(); - // Check argument is assigned to field + // Method parameter J.VariableDeclarations variableDeclarations = (J.VariableDeclarations) method.getParameters().get(0); J.VariableDeclarations.NamedVariable param = variableDeclarations.getVariables().get(0); - // type of parameter and field have to match - if (!param.getType().equals(assignedField.getType())) { + // Check there's no up/down cast between parameter and field + J.Assignment assignment = (J.Assignment) method.getBody().getStatements().get(0); + Expression variable = assignment.getVariable(); + if (param.getType() != variable.getType()) { return false; } - // Check field is declared on method type - JavaType.FullyQualified declaringType = method.getMethodType().getDeclaringType(); - if (assignedField.getTarget() instanceof J.Identifier) { - J.Identifier target = (J.Identifier) assignedField.getTarget(); - return target.getFieldType() != null && declaringType == target.getFieldType().getOwner(); - } else if (assignedField.getTarget() instanceof J.FieldAccess) { - Expression target = ((J.FieldAccess) assignedField.getTarget()).getTarget(); - return target instanceof J.Identifier && ((J.Identifier) target).getFieldType() != null && - declaringType == ((J.Identifier) target).getFieldType().getOwner(); + // Method name has to match + if (variable instanceof J.Identifier) { + J.Identifier assignedVar = (J.Identifier) variable; + if (hasMatchingSetterMethodName(method, assignedVar.getSimpleName())) { + // Check field is declared on method type + return assignedVar.getFieldType() != null && declaringType == assignedVar.getFieldType().getOwner(); + } + } else if (variable instanceof J.FieldAccess) { + J.FieldAccess assignedField = (J.FieldAccess) variable; + if (hasMatchingSetterMethodName(method, assignedField.getSimpleName())) { + Expression target = assignedField.getTarget(); + // Check field is declared on method type + return target instanceof J.Identifier && ((J.Identifier) target).getFieldType() != null && + declaringType == ((J.Identifier) target).getFieldType().getOwner(); + } } return false; } - private static String deriveSetterMethodName(J.FieldAccess fieldAccess) { - return "set" + StringUtils.capitalize(fieldAccess.getSimpleName()); - } - static AccessLevel getAccessLevel(J.MethodDeclaration methodDeclaration) { if (methodDeclaration.hasModifier(Public)) { return PUBLIC; diff --git a/src/main/java/org/openrewrite/java/migrate/lombok/UseLombokSetter.java b/src/main/java/org/openrewrite/java/migrate/lombok/UseLombokSetter.java index c02702d89a..9bf659606a 100644 --- a/src/main/java/org/openrewrite/java/migrate/lombok/UseLombokSetter.java +++ b/src/main/java/org/openrewrite/java/migrate/lombok/UseLombokSetter.java @@ -1,11 +1,11 @@ /* * Copyright 2024 the original author or authors. *

- * Licensed under the Apache License, Version 2.0 (the "License"); + * 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://www.apache.org/licenses/LICENSE-2.0 + * 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, @@ -23,13 +23,12 @@ import org.openrewrite.Recipe; import org.openrewrite.TreeVisitor; import org.openrewrite.java.JavaIsoVisitor; +import org.openrewrite.java.tree.Expression; import org.openrewrite.java.tree.J; import java.util.Collections; import java.util.Set; -import static org.openrewrite.java.tree.JavaType.Variable; - @Value @EqualsAndHashCode(callSuper = false) public class UseLombokSetter extends Recipe { @@ -41,7 +40,6 @@ public String getDisplayName() { @Override public String getDescription() { - //language=markdown return "Convert trivial setter methods to `@Setter` annotations on their respective fields."; } @@ -56,11 +54,21 @@ public TreeVisitor getVisitor() { @Override public J.@Nullable MethodDeclaration visitMethodDeclaration(J.MethodDeclaration method, ExecutionContext ctx) { if (LombokUtils.isSetter(method)) { - J.Assignment assignment_ = (J.Assignment) method.getBody().getStatements().get(0); - J.FieldAccess fieldAccess = (J.FieldAccess) assignment_.getVariable(); - Variable fieldType = fieldAccess.getName().getFieldType(); - doAfterVisit(new FieldAnnotator(Setter.class, fieldType, LombokUtils.getAccessLevel(method))); - return null; //delete + Expression assignmentVariable = ((J.Assignment) method.getBody().getStatements().get(0)).getVariable(); + if (assignmentVariable instanceof J.FieldAccess && + ((J.FieldAccess) assignmentVariable).getName().getFieldType() != null) { + doAfterVisit(new FieldAnnotator(Setter.class, + ((J.FieldAccess) assignmentVariable).getName().getFieldType(), + LombokUtils.getAccessLevel(method))); + return null; //delete + + } else if (assignmentVariable instanceof J.Identifier && + ((J.Identifier) assignmentVariable).getFieldType() != null) { + doAfterVisit(new FieldAnnotator(Setter.class, + ((J.Identifier) assignmentVariable).getFieldType(), + LombokUtils.getAccessLevel(method))); + return null; //delete + } } return method; } diff --git a/src/test/java/org/openrewrite/java/migrate/lombok/UseLombokSetterTest.java b/src/test/java/org/openrewrite/java/migrate/lombok/UseLombokSetterTest.java index 5116b15e02..3b0bb02c4c 100644 --- a/src/test/java/org/openrewrite/java/migrate/lombok/UseLombokSetterTest.java +++ b/src/test/java/org/openrewrite/java/migrate/lombok/UseLombokSetterTest.java @@ -1,11 +1,11 @@ /* * Copyright 2024 the original author or authors. *

- * Licensed under the Apache License, Version 2.0 (the "License"); + * 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://www.apache.org/licenses/LICENSE-2.0 + * 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, From 0356976a6be696d75552b63acb3b03996d67ae17 Mon Sep 17 00:00:00 2001 From: Tim te Beek Date: Sun, 15 Dec 2024 19:04:29 +0100 Subject: [PATCH 30/31] Move down variable and method closer to usage --- .../openrewrite/java/migrate/lombok/LombokUtils.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/main/java/org/openrewrite/java/migrate/lombok/LombokUtils.java b/src/main/java/org/openrewrite/java/migrate/lombok/LombokUtils.java index 66bc1282ae..99689c44e2 100644 --- a/src/main/java/org/openrewrite/java/migrate/lombok/LombokUtils.java +++ b/src/main/java/org/openrewrite/java/migrate/lombok/LombokUtils.java @@ -83,10 +83,6 @@ private static String deriveGetterMethodName(@Nullable JavaType type, String fie return "get" + StringUtils.capitalize(fieldName); } - private static boolean hasMatchingSetterMethodName(J.MethodDeclaration method, String simpleName) { - return method.getSimpleName().equals("set" + StringUtils.capitalize(simpleName)); - } - static boolean isSetter(J.MethodDeclaration method) { // Check return type: void if (method.getType() != JavaType.Primitive.Void) { @@ -102,7 +98,6 @@ static boolean isSetter(J.MethodDeclaration method) { !(method.getBody().getStatements().get(0) instanceof J.Assignment)) { return false; } - JavaType.FullyQualified declaringType = method.getMethodType().getDeclaringType(); // Method parameter J.VariableDeclarations variableDeclarations = (J.VariableDeclarations) method.getParameters().get(0); @@ -116,6 +111,7 @@ static boolean isSetter(J.MethodDeclaration method) { } // Method name has to match + JavaType.FullyQualified declaringType = method.getMethodType().getDeclaringType(); if (variable instanceof J.Identifier) { J.Identifier assignedVar = (J.Identifier) variable; if (hasMatchingSetterMethodName(method, assignedVar.getSimpleName())) { @@ -135,6 +131,10 @@ static boolean isSetter(J.MethodDeclaration method) { return false; } + private static boolean hasMatchingSetterMethodName(J.MethodDeclaration method, String simpleName) { + return method.getSimpleName().equals("set" + StringUtils.capitalize(simpleName)); + } + static AccessLevel getAccessLevel(J.MethodDeclaration methodDeclaration) { if (methodDeclaration.hasModifier(Public)) { return PUBLIC; From 3bbb8ad2efc1cad0c7bb601fddc5b432d3bcba79 Mon Sep 17 00:00:00 2001 From: Tim te Beek Date: Sun, 15 Dec 2024 19:29:42 +0100 Subject: [PATCH 31/31] Inline variables used only once --- .../org/openrewrite/java/migrate/lombok/LombokUtils.java | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/main/java/org/openrewrite/java/migrate/lombok/LombokUtils.java b/src/main/java/org/openrewrite/java/migrate/lombok/LombokUtils.java index 99689c44e2..a397bf9042 100644 --- a/src/main/java/org/openrewrite/java/migrate/lombok/LombokUtils.java +++ b/src/main/java/org/openrewrite/java/migrate/lombok/LombokUtils.java @@ -99,13 +99,9 @@ static boolean isSetter(J.MethodDeclaration method) { return false; } - // Method parameter - J.VariableDeclarations variableDeclarations = (J.VariableDeclarations) method.getParameters().get(0); - J.VariableDeclarations.NamedVariable param = variableDeclarations.getVariables().get(0); - // Check there's no up/down cast between parameter and field - J.Assignment assignment = (J.Assignment) method.getBody().getStatements().get(0); - Expression variable = assignment.getVariable(); + J.VariableDeclarations.NamedVariable param = ((J.VariableDeclarations) method.getParameters().get(0)).getVariables().get(0); + Expression variable = ((J.Assignment) method.getBody().getStatements().get(0)).getVariable(); if (param.getType() != variable.getType()) { return false; }