From 52339a8c41418309a643086ba363554c2ad8139c Mon Sep 17 00:00:00 2001 From: timo <1398557+timo-a@users.noreply.github.com> Date: Tue, 24 Dec 2024 12:01:13 +0100 Subject: [PATCH 1/5] migrate recipe as-is --- .../migrate/lombok/UseNoArgsConstructor.java | 104 ++++++++++++++ .../lombok/UseNoArgsConstructorTest.java | 133 ++++++++++++++++++ 2 files changed, 237 insertions(+) create mode 100644 src/main/java/org/openrewrite/java/migrate/lombok/UseNoArgsConstructor.java create mode 100644 src/test/java/org/openrewrite/java/migrate/lombok/UseNoArgsConstructorTest.java diff --git a/src/main/java/org/openrewrite/java/migrate/lombok/UseNoArgsConstructor.java b/src/main/java/org/openrewrite/java/migrate/lombok/UseNoArgsConstructor.java new file mode 100644 index 000000000..9e08c9af7 --- /dev/null +++ b/src/main/java/org/openrewrite/java/migrate/lombok/UseNoArgsConstructor.java @@ -0,0 +1,104 @@ +/* + * 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 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 static java.util.Comparator.comparing; + +@Value +@EqualsAndHashCode(callSuper = false) +public class UseNoArgsConstructor extends Recipe { + + @Override + public String getDisplayName() { + //language=markdown + return "Use `@NoArgsConstructor` where applicable"; + } + + @Override + public String getDescription() { + //language=markdown + return "Prefer the lombok annotation `@NoArgsConstructor` over explicitly written out constructors.\n" + + "This recipe does not create annotations for implicit constructors."; + } + + @Override + public TreeVisitor getVisitor() { + return new JavaIsoVisitor() { + public static final String FOUND_EMPTY_CONSTRUCTOR = "FOUND_EMPTY_CONSTRUCTOR"; + + + @Override + public J.ClassDeclaration visitClassDeclaration(J.ClassDeclaration classDecl, ExecutionContext ctx) { + J.ClassDeclaration classDeclAfterVisit = super.visitClassDeclaration(classDecl, ctx); + + J.MethodDeclaration message = getCursor().pollMessage(FOUND_EMPTY_CONSTRUCTOR); + + //if no constructor is found return immediately + if (message == null) { + return classDecl;//since nothing changed the original can be returned + } + + maybeAddImport("lombok.NoArgsConstructor"); + + AccessLevel accessLevel = LombokUtils.getAccessLevel(message); + + return getAnnotation(accessLevel).apply( + updateCursor(classDeclAfterVisit), + classDeclAfterVisit.getCoordinates().addAnnotation(comparing(J.Annotation::getSimpleName))); + } + + private JavaTemplate getAnnotation(AccessLevel accessLevel) { + + JavaTemplate.Builder builder = AccessLevel.PUBLIC.equals(accessLevel) + ? JavaTemplate.builder("@NoArgsConstructor()\n") + : JavaTemplate.builder("@NoArgsConstructor(access = AccessLevel." + accessLevel.name() + ")\n") + .imports("lombok.AccessLevel"); + + return builder + .imports("lombok.NoArgsConstructor") + .javaParser(JavaParser.fromJavaVersion() + .classpath("lombok")) + .build(); + } + + @Override + public J.MethodDeclaration visitMethodDeclaration(J.MethodDeclaration method, ExecutionContext ctx) { + assert method.getMethodType() != null; + if (method.getMethodType().getName().equals("") //it's a constructor + && method.getParameters().get(0) instanceof J.Empty //no parameters + && method.getBody().getStatements().isEmpty() //no side effects (=> does nothing) + ) { + getCursor().putMessageOnFirstEnclosing(J.ClassDeclaration.class, FOUND_EMPTY_CONSTRUCTOR, method); + return null; + } + return super.visitMethodDeclaration(method, ctx); + } + + }; + } + +} diff --git a/src/test/java/org/openrewrite/java/migrate/lombok/UseNoArgsConstructorTest.java b/src/test/java/org/openrewrite/java/migrate/lombok/UseNoArgsConstructorTest.java new file mode 100644 index 000000000..e450708c4 --- /dev/null +++ b/src/test/java/org/openrewrite/java/migrate/lombok/UseNoArgsConstructorTest.java @@ -0,0 +1,133 @@ +/* + * 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 org.junit.jupiter.api.Test; +import org.openrewrite.DocumentExample; +import org.openrewrite.java.JavaParser; +import org.openrewrite.test.RecipeSpec; +import org.openrewrite.test.RewriteTest; + +import static org.openrewrite.java.Assertions.java; + +class UseNoArgsConstructorTest implements RewriteTest { + + @Override + public void defaults(RecipeSpec spec) { + spec.recipe(new UseNoArgsConstructor()) + .parser(JavaParser.fromJavaVersion().logCompilationWarningsAndErrors(true)); + } + + @DocumentExample + @Test + void replaceEmptyPublicConstructor() { + rewriteRun(// language=java + java( + """ + class A { + public A() {} + } + """, + """ + import lombok.NoArgsConstructor; + + @NoArgsConstructor() + class A { + } + """ + ) + ); + } + + @Test + void keepNonEmptyPublicConstructor() { + rewriteRun( + java( + """ + class A { + + int foo; + + public A() { + foo = 7; + } + } + """ + ) + ); + } + + @Test + void replaceEmptyProtectedConstructor() { + rewriteRun( + java( + """ + class A { + protected A() {} + } + """, + """ + import lombok.NoArgsConstructor; + + @NoArgsConstructor(access = AccessLevel.PROTECTED) + class A { + } + """ + ) + ); + } + + @Test + void replaceEmptyPrivateConstructor() { + rewriteRun( + java( + """ + class A { + private A() {} + } + """, + """ + import lombok.NoArgsConstructor; + + @NoArgsConstructor(access = AccessLevel.PRIVATE) + class A { + } + """ + ) + ); + } + + @Test + void replaceEmptyPackageConstructor() { + rewriteRun( + java( + """ + class A { + A() {} + } + """, + """ + import lombok.NoArgsConstructor; + + @NoArgsConstructor(access = AccessLevel.PACKAGE) + class A { + } + """ + ) + ); + } + +} From 3652d0615cda9af7c4543b532d7d942457e78672 Mon Sep 17 00:00:00 2001 From: Tim te Beek Date: Sun, 5 Jan 2025 18:53:51 +0100 Subject: [PATCH 2/5] Apply suggestions from code review Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- .../java/migrate/lombok/UseNoArgsConstructor.java | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/main/java/org/openrewrite/java/migrate/lombok/UseNoArgsConstructor.java b/src/main/java/org/openrewrite/java/migrate/lombok/UseNoArgsConstructor.java index 9e08c9af7..24bead0e2 100644 --- a/src/main/java/org/openrewrite/java/migrate/lombok/UseNoArgsConstructor.java +++ b/src/main/java/org/openrewrite/java/migrate/lombok/UseNoArgsConstructor.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; @@ -73,9 +74,9 @@ public J.ClassDeclaration visitClassDeclaration(J.ClassDeclaration classDecl, Ex private JavaTemplate getAnnotation(AccessLevel accessLevel) { - JavaTemplate.Builder builder = AccessLevel.PUBLIC.equals(accessLevel) - ? JavaTemplate.builder("@NoArgsConstructor()\n") - : JavaTemplate.builder("@NoArgsConstructor(access = AccessLevel." + accessLevel.name() + ")\n") + JavaTemplate.Builder builder = AccessLevel.PUBLIC.equals(accessLevel) ? + JavaTemplate.builder("@NoArgsConstructor()\n") : + JavaTemplate.builder("@NoArgsConstructor(access = AccessLevel." + accessLevel.name() + ")\n") .imports("lombok.AccessLevel"); return builder @@ -86,7 +87,7 @@ private JavaTemplate getAnnotation(AccessLevel accessLevel) { } @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 (method.getMethodType().getName().equals("") //it's a constructor && method.getParameters().get(0) instanceof J.Empty //no parameters From 35494ea3177017b417f98478cf98810c49f53c22 Mon Sep 17 00:00:00 2001 From: Tim te Beek Date: Sun, 5 Jan 2025 19:09:35 +0100 Subject: [PATCH 3/5] Minimize implementation --- .../migrate/lombok/UseNoArgsConstructor.java | 76 +++++++------------ .../lombok/UseNoArgsConstructorTest.java | 9 ++- 2 files changed, 32 insertions(+), 53 deletions(-) diff --git a/src/main/java/org/openrewrite/java/migrate/lombok/UseNoArgsConstructor.java b/src/main/java/org/openrewrite/java/migrate/lombok/UseNoArgsConstructor.java index 24bead0e2..9130db7d6 100644 --- a/src/main/java/org/openrewrite/java/migrate/lombok/UseNoArgsConstructor.java +++ b/src/main/java/org/openrewrite/java/migrate/lombok/UseNoArgsConstructor.java @@ -1,11 +1,11 @@ /* - * Copyright 2024 the original author or authors. + * Copyright 2025 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, @@ -26,6 +26,7 @@ import org.openrewrite.java.JavaParser; import org.openrewrite.java.JavaTemplate; import org.openrewrite.java.tree.J; +import org.openrewrite.java.tree.TypeUtils; import static java.util.Comparator.comparing; @@ -42,64 +43,39 @@ public String getDisplayName() { @Override public String getDescription() { //language=markdown - return "Prefer the lombok annotation `@NoArgsConstructor` over explicitly written out constructors.\n" + - "This recipe does not create annotations for implicit constructors."; + return "Prefer the Lombok `@NoArgsConstructor` annotation over explicitly written out constructors."; } @Override public TreeVisitor getVisitor() { return new JavaIsoVisitor() { - public static final String FOUND_EMPTY_CONSTRUCTOR = "FOUND_EMPTY_CONSTRUCTOR"; - - - @Override - public J.ClassDeclaration visitClassDeclaration(J.ClassDeclaration classDecl, ExecutionContext ctx) { - J.ClassDeclaration classDeclAfterVisit = super.visitClassDeclaration(classDecl, ctx); - - J.MethodDeclaration message = getCursor().pollMessage(FOUND_EMPTY_CONSTRUCTOR); - - //if no constructor is found return immediately - if (message == null) { - return classDecl;//since nothing changed the original can be returned - } - - maybeAddImport("lombok.NoArgsConstructor"); - - AccessLevel accessLevel = LombokUtils.getAccessLevel(message); - - return getAnnotation(accessLevel).apply( - updateCursor(classDeclAfterVisit), - classDeclAfterVisit.getCoordinates().addAnnotation(comparing(J.Annotation::getSimpleName))); - } - - private JavaTemplate getAnnotation(AccessLevel accessLevel) { - - JavaTemplate.Builder builder = AccessLevel.PUBLIC.equals(accessLevel) ? - JavaTemplate.builder("@NoArgsConstructor()\n") : - JavaTemplate.builder("@NoArgsConstructor(access = AccessLevel." + accessLevel.name() + ")\n") - .imports("lombok.AccessLevel"); - - return builder - .imports("lombok.NoArgsConstructor") - .javaParser(JavaParser.fromJavaVersion() - .classpath("lombok")) - .build(); - } - @Override public J.@Nullable MethodDeclaration visitMethodDeclaration(J.MethodDeclaration method, ExecutionContext ctx) { - assert method.getMethodType() != null; - if (method.getMethodType().getName().equals("") //it's a constructor - && method.getParameters().get(0) instanceof J.Empty //no parameters - && method.getBody().getStatements().isEmpty() //no side effects (=> does nothing) - ) { - getCursor().putMessageOnFirstEnclosing(J.ClassDeclaration.class, FOUND_EMPTY_CONSTRUCTOR, method); + if (method.isConstructor() && + method.getParameters().get(0) instanceof J.Empty && + method.getBody() != null && method.getBody().getStatements().isEmpty()) { + J.ClassDeclaration enclosing = getCursor().firstEnclosing(J.ClassDeclaration.class); + AccessLevel accessLevel = LombokUtils.getAccessLevel(method); + doAfterVisit(new JavaIsoVisitor() { + @Override + public J.ClassDeclaration visitClassDeclaration(J.ClassDeclaration classDecl, ExecutionContext ctx) { + if (TypeUtils.isOfType(classDecl.getType(), enclosing.getType())) { + String template = "@NoArgsConstructor" + (accessLevel == AccessLevel.PUBLIC ? + "" : "(access = AccessLevel." + accessLevel.name() + ")"); + maybeAddImport("lombok.NoArgsConstructor"); + return JavaTemplate.builder(template) + .imports("lombok.*") + .javaParser(JavaParser.fromJavaVersion().classpath("lombok")) + .build() + .apply(getCursor(), classDecl.getCoordinates().addAnnotation(comparing(J.Annotation::getSimpleName))); + } + return super.visitClassDeclaration(classDecl, ctx); + } + }); return null; } return super.visitMethodDeclaration(method, ctx); } - }; } - } diff --git a/src/test/java/org/openrewrite/java/migrate/lombok/UseNoArgsConstructorTest.java b/src/test/java/org/openrewrite/java/migrate/lombok/UseNoArgsConstructorTest.java index e450708c4..413c44588 100644 --- a/src/test/java/org/openrewrite/java/migrate/lombok/UseNoArgsConstructorTest.java +++ b/src/test/java/org/openrewrite/java/migrate/lombok/UseNoArgsConstructorTest.java @@ -27,8 +27,7 @@ class UseNoArgsConstructorTest implements RewriteTest { @Override public void defaults(RecipeSpec spec) { - spec.recipe(new UseNoArgsConstructor()) - .parser(JavaParser.fromJavaVersion().logCompilationWarningsAndErrors(true)); + spec.recipe(new UseNoArgsConstructor()); } @DocumentExample @@ -44,7 +43,7 @@ public A() {} """ import lombok.NoArgsConstructor; - @NoArgsConstructor() + @NoArgsConstructor class A { } """ @@ -55,6 +54,7 @@ class A { @Test void keepNonEmptyPublicConstructor() { rewriteRun( + //language=java java( """ class A { @@ -73,6 +73,7 @@ public A() { @Test void replaceEmptyProtectedConstructor() { rewriteRun( + //language=java java( """ class A { @@ -93,6 +94,7 @@ class A { @Test void replaceEmptyPrivateConstructor() { rewriteRun( + //language=java java( """ class A { @@ -113,6 +115,7 @@ class A { @Test void replaceEmptyPackageConstructor() { rewriteRun( + //language=java java( """ class A { From 5bc0325436c026fe327c3415da888ce0ee15024a Mon Sep 17 00:00:00 2001 From: Tim te Beek Date: Sun, 5 Jan 2025 19:11:05 +0100 Subject: [PATCH 4/5] Add missing AccessLevel imports --- .../openrewrite/java/migrate/lombok/UseNoArgsConstructor.java | 1 + .../java/migrate/lombok/UseNoArgsConstructorTest.java | 3 +++ 2 files changed, 4 insertions(+) diff --git a/src/main/java/org/openrewrite/java/migrate/lombok/UseNoArgsConstructor.java b/src/main/java/org/openrewrite/java/migrate/lombok/UseNoArgsConstructor.java index 9130db7d6..630041410 100644 --- a/src/main/java/org/openrewrite/java/migrate/lombok/UseNoArgsConstructor.java +++ b/src/main/java/org/openrewrite/java/migrate/lombok/UseNoArgsConstructor.java @@ -62,6 +62,7 @@ public J.ClassDeclaration visitClassDeclaration(J.ClassDeclaration classDecl, Ex if (TypeUtils.isOfType(classDecl.getType(), enclosing.getType())) { String template = "@NoArgsConstructor" + (accessLevel == AccessLevel.PUBLIC ? "" : "(access = AccessLevel." + accessLevel.name() + ")"); + maybeAddImport("lombok.AccessLevel"); maybeAddImport("lombok.NoArgsConstructor"); return JavaTemplate.builder(template) .imports("lombok.*") diff --git a/src/test/java/org/openrewrite/java/migrate/lombok/UseNoArgsConstructorTest.java b/src/test/java/org/openrewrite/java/migrate/lombok/UseNoArgsConstructorTest.java index 413c44588..d3aca34f1 100644 --- a/src/test/java/org/openrewrite/java/migrate/lombok/UseNoArgsConstructorTest.java +++ b/src/test/java/org/openrewrite/java/migrate/lombok/UseNoArgsConstructorTest.java @@ -81,6 +81,7 @@ protected A() {} } """, """ + import lombok.AccessLevel; import lombok.NoArgsConstructor; @NoArgsConstructor(access = AccessLevel.PROTECTED) @@ -102,6 +103,7 @@ private A() {} } """, """ + import lombok.AccessLevel; import lombok.NoArgsConstructor; @NoArgsConstructor(access = AccessLevel.PRIVATE) @@ -123,6 +125,7 @@ class A { } """, """ + import lombok.AccessLevel; import lombok.NoArgsConstructor; @NoArgsConstructor(access = AccessLevel.PACKAGE) From 50c2375a420c64918a731bbe8965c1f700ecd0b0 Mon Sep 17 00:00:00 2001 From: Tim te Beek Date: Sun, 5 Jan 2025 19:15:06 +0100 Subject: [PATCH 5/5] Remove unused import --- .../java/migrate/lombok/UseNoArgsConstructorTest.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/test/java/org/openrewrite/java/migrate/lombok/UseNoArgsConstructorTest.java b/src/test/java/org/openrewrite/java/migrate/lombok/UseNoArgsConstructorTest.java index d3aca34f1..92ec7a702 100644 --- a/src/test/java/org/openrewrite/java/migrate/lombok/UseNoArgsConstructorTest.java +++ b/src/test/java/org/openrewrite/java/migrate/lombok/UseNoArgsConstructorTest.java @@ -17,7 +17,6 @@ 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;