Skip to content

Commit

Permalink
feat: use lombok @NoArgsConstructor annotation (#645)
Browse files Browse the repository at this point in the history
* migrate recipe as-is

* Apply suggestions from code review

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>

* Minimize implementation

* Add missing AccessLevel imports

* Remove unused import

---------

Co-authored-by: Tim te Beek <[email protected]>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Tim te Beek <[email protected]>
  • Loading branch information
4 people authored Jan 5, 2025
1 parent f3baad0 commit 5b957bb
Show file tree
Hide file tree
Showing 2 changed files with 220 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
/*
* Copyright 2025 the original author or authors.
* <p>
* 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
* <p>
* https://docs.moderne.io/licensing/moderne-source-available-license
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.openrewrite.java.migrate.lombok;

import lombok.AccessLevel;
import lombok.EqualsAndHashCode;
import lombok.Value;
import org.jspecify.annotations.Nullable;
import org.openrewrite.ExecutionContext;
import org.openrewrite.Recipe;
import org.openrewrite.TreeVisitor;
import org.openrewrite.java.JavaIsoVisitor;
import org.openrewrite.java.JavaParser;
import org.openrewrite.java.JavaTemplate;
import org.openrewrite.java.tree.J;
import org.openrewrite.java.tree.TypeUtils;

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 `@NoArgsConstructor` annotation over explicitly written out constructors.";
}

@Override
public TreeVisitor<?, ExecutionContext> getVisitor() {
return new JavaIsoVisitor<ExecutionContext>() {
@Override
public J.@Nullable MethodDeclaration visitMethodDeclaration(J.MethodDeclaration method, ExecutionContext ctx) {
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<ExecutionContext>() {
@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.AccessLevel");
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);
}
};
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
/*
* Copyright 2024 the original author or authors.
* <p>
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* https://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.openrewrite.java.migrate.lombok;

import org.junit.jupiter.api.Test;
import org.openrewrite.DocumentExample;
import org.openrewrite.test.RecipeSpec;
import org.openrewrite.test.RewriteTest;

import static org.openrewrite.java.Assertions.java;

class UseNoArgsConstructorTest implements RewriteTest {

@Override
public void defaults(RecipeSpec spec) {
spec.recipe(new UseNoArgsConstructor());
}

@DocumentExample
@Test
void replaceEmptyPublicConstructor() {
rewriteRun(// language=java
java(
"""
class A {
public A() {}
}
""",
"""
import lombok.NoArgsConstructor;
@NoArgsConstructor
class A {
}
"""
)
);
}

@Test
void keepNonEmptyPublicConstructor() {
rewriteRun(
//language=java
java(
"""
class A {
int foo;
public A() {
foo = 7;
}
}
"""
)
);
}

@Test
void replaceEmptyProtectedConstructor() {
rewriteRun(
//language=java
java(
"""
class A {
protected A() {}
}
""",
"""
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
@NoArgsConstructor(access = AccessLevel.PROTECTED)
class A {
}
"""
)
);
}

@Test
void replaceEmptyPrivateConstructor() {
rewriteRun(
//language=java
java(
"""
class A {
private A() {}
}
""",
"""
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
@NoArgsConstructor(access = AccessLevel.PRIVATE)
class A {
}
"""
)
);
}

@Test
void replaceEmptyPackageConstructor() {
rewriteRun(
//language=java
java(
"""
class A {
A() {}
}
""",
"""
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
@NoArgsConstructor(access = AccessLevel.PACKAGE)
class A {
}
"""
)
);
}

}

0 comments on commit 5b957bb

Please sign in to comment.