What is the smallest, simplest way to reproduce the problem?
import org.jspecify.annotations.Nullable;
import org.openrewrite.ExecutionContext;
import org.openrewrite.NlsRewrite.Description;
import org.openrewrite.NlsRewrite.DisplayName;
import org.openrewrite.Recipe;
import org.openrewrite.TreeVisitor;
import org.openrewrite.java.JavaIsoVisitor;
import org.openrewrite.java.RemoveAnnotation;
import org.openrewrite.java.TypeNameMatcher;
import org.openrewrite.java.tree.J;
import org.openrewrite.java.tree.J.Annotation;
import org.openrewrite.java.tree.J.ClassDeclaration;
import org.openrewrite.java.tree.J.CompilationUnit;
import org.openrewrite.java.tree.J.Identifier;
import org.openrewrite.java.tree.J.MethodInvocation;
import org.openrewrite.java.tree.JavaType;
public class RemoveUnusedSlf4jAnnotationRecipe extends Recipe {
private static final String LOMBOK_SLF4J_FQCN = "lombok.extern.slf4j.Slf4j";
public RemoveUnusedSlf4jAnnotationRecipe() {
}
static @Nullable Annotation findAnnotation(J j, TypeNameMatcher matcher) {
if (j instanceof ClassDeclaration classDecl) {
return classDecl.getLeadingAnnotations()
.stream()
.filter(annotation -> {
var type = annotation.getAnnotationType().getType();
return type != null && type.isAssignableFrom(matcher);
})
.findFirst()
.orElse(null);
}
return null;
}
@Override
public @DisplayName String getDisplayName() {
return "Removes unused @Slf4j annotation";
}
@Override
public @Description String getDescription() {
return "Removes unused @Slf4j annotation.";
}
@Override
public TreeVisitor<?, ExecutionContext> getVisitor() {
return new RemoveUnusedSlf4jAnnotationVisitor();
}
public static class RemoveUnusedSlf4jAnnotationVisitor extends JavaIsoVisitor<ExecutionContext> {
private static final String USED_KEY = "ee.trigon.openrewrite.recipe.RemoveUnusedSlf4jAnnotationRecipe.used";
private static boolean isLogger(Identifier ident) {
return ident.getType() instanceof JavaType.Class type && type.getFullyQualifiedName().equals("org.slf4j.Logger");
}
private static boolean isLoggerMethodName(MethodInvocation method) {
return switch (method.getSimpleName()) {
case "trace", "debug", "info", "warn", "error" -> true;
default -> false;
};
}
private static boolean hasNotNestedClassDeclaration(ClassDeclaration classDecl) {
return classDecl.getBody().getStatements().stream().noneMatch(statement -> statement instanceof ClassDeclaration);
}
@Override
public CompilationUnit visitCompilationUnit(CompilationUnit cu, ExecutionContext executionContext) {
cu = super.visitCompilationUnit(cu, executionContext);
if (getCursor().pollMessage(USED_KEY) == null) {
cu = new RemoveAnnotation(LOMBOK_SLF4J_FQCN).getVisitor().visitCompilationUnit(cu, executionContext);
maybeRemoveImport(LOMBOK_SLF4J_FQCN);
}
return cu;
}
@Override
public ClassDeclaration visitClassDeclaration(ClassDeclaration classDecl, ExecutionContext executionContext) {
var matcher = TypeNameMatcher.fromPattern(LOMBOK_SLF4J_FQCN);
var slf4jAnnotation = findAnnotation(classDecl, matcher);
if (slf4jAnnotation == null && hasNotNestedClassDeclaration(classDecl)) {
return classDecl;
}
return super.visitClassDeclaration(classDecl, executionContext);
}
@Override
public MethodInvocation visitMethodInvocation(MethodInvocation method, ExecutionContext executionContext) {
var cursor = getCursor().dropParentUntil(o -> o instanceof CompilationUnit);
// if usage was found previously do not visit
if (cursor.getMessage(USED_KEY) != null) {
return method;
}
if (method.getSelect() instanceof Identifier ident
&& ident.getSimpleName().equals("log")
&& (isLogger(ident) || (ident.getType() instanceof JavaType.Unknown && isLoggerMethodName(method)))) {
cursor.putMessage(USED_KEY, true);
return method;
}
return super.visitMethodInvocation(method, executionContext);
}
}
}
What did you expect to see?
I expect to this test to pass.
import static org.openrewrite.java.Assertions.java;
import org.junit.jupiter.api.Test;
import org.openrewrite.test.RecipeSpec;
import org.openrewrite.test.RewriteTest;
class RemoveUnusedSlf4jAnnotationRecipeTest implements RewriteTest {
@Override
public void defaults(RecipeSpec spec) {
spec.recipe(new RemoveUnusedSlf4jAnnotationRecipe());
}
@Test
void shouldRemove() {
rewriteRun(
java(
"""
import lombok.extern.slf4j.Slf4j;
@Slf4j
class FooBar {
}
""",
"""
class FooBar {
}
"""
)
);
}
}
What is the full stack trace of any errors you encountered?
java.lang.AssertionError: Failed to run recipe at Cursor{ClassDeclaration->root}
at org.openrewrite.test.RewriteTest.lambda$defaultExecutionContext$14(RewriteTest.java:720)
at org.openrewrite.scheduling.RecipeRunCycle.handleError(RecipeRunCycle.java:749)
at org.openrewrite.scheduling.RecipeRunCycle.lambda$editSource$10(RecipeRunCycle.java:409)
at org.openrewrite.scheduling.RecipeStack.reduce(RecipeStack.java:72)
at org.openrewrite.scheduling.RecipeRunCycle.editSource(RecipeRunCycle.java:287)
at org.openrewrite.scheduling.RecipeRunCycle.lambda$editSources$8(RecipeRunCycle.java:261)
at org.openrewrite.internal.InMemoryLargeSourceSet.lambda$edit$0(InMemoryLargeSourceSet.java:86)
at org.openrewrite.internal.ListUtils.map(ListUtils.java:245)
at org.openrewrite.internal.ListUtils.map(ListUtils.java:269)
at org.openrewrite.internal.InMemoryLargeSourceSet.edit(InMemoryLargeSourceSet.java:85)
at org.openrewrite.scheduling.RecipeRunCycle.editSources(RecipeRunCycle.java:261)
at org.openrewrite.RecipeScheduler.runRecipeCycles(RecipeScheduler.java:108)
at org.openrewrite.RecipeScheduler.scheduleRun(RecipeScheduler.java:63)
at org.openrewrite.Recipe.run(Recipe.java:516)
at org.openrewrite.test.RewriteRunner.run(RewriteRunner.java:51)
at org.openrewrite.test.RewriteTest.rewriteRun(RewriteTest.java:453)
at org.openrewrite.test.RewriteTest.rewriteRun(RewriteTest.java:159)
at org.openrewrite.test.RewriteTest.rewriteRun(RewriteTest.java:154)
at RemoveUnusedSlf4jAnnotationRecipeTest.shouldRemove(RemoveUnusedSlf4jAnnotationRecipeTest.java:18)
at java.base/java.lang.reflect.Method.invoke(Method.java:565)
at java.base/java.util.ArrayList.forEach(ArrayList.java:1604)
at java.base/java.util.ArrayList.forEach(ArrayList.java:1604)
Caused by: org.openrewrite.internal.RecipeRunException: java.lang.IllegalStateException: Expected to find enclosing JavaSourceFile
at org.openrewrite.TreeVisitor.visit(TreeVisitor.java:269)
at org.openrewrite.TreeVisitor.visitAndCast(TreeVisitor.java:328)
at org.openrewrite.java.JavaVisitor.lambda$visitCompilationUnit$9(JavaVisitor.java:520)
at org.openrewrite.internal.ListUtils.map(ListUtils.java:245)
at org.openrewrite.internal.ListUtils.map(ListUtils.java:269)
at org.openrewrite.java.JavaVisitor.visitCompilationUnit(JavaVisitor.java:520)
at org.openrewrite.java.JavaIsoVisitor.visitCompilationUnit(JavaIsoVisitor.java:113)
at RemoveUnusedSlf4jAnnotationRecipe$RemoveUnusedSlf4jAnnotationVisitor.visitCompilationUnit(RemoveUnusedSlf4jAnnotationRecipe.java:81)
at RemoveUnusedSlf4jAnnotationRecipe$RemoveUnusedSlf4jAnnotationVisitor.visitCompilationUnit(RemoveUnusedSlf4jAnnotationRecipe.java:58)
at org.openrewrite.java.tree.J$CompilationUnit.acceptJava(J.java:1664)
at org.openrewrite.java.tree.J.accept(J.java:55)
at org.openrewrite.TreeVisitor.visit(TreeVisitor.java:242)
at org.openrewrite.TreeVisitor.visit(TreeVisitor.java:154)
at org.openrewrite.scheduling.RecipeRunCycle.lambda$editSource$9(RecipeRunCycle.java:386)
at org.openrewrite.table.RecipeRunStats$PhaseTimer.recordTimed(RecipeRunStats.java:141)
at org.openrewrite.table.RecipeRunStats$RecipeTimers.recordEdit(RecipeRunStats.java:129)
at org.openrewrite.table.RecipeRunStats.recordEdit(RecipeRunStats.java:60)
at org.openrewrite.scheduling.RecipeRunCycle.lambda$editSource$10(RecipeRunCycle.java:382)
... 19 more
Caused by: java.lang.IllegalStateException: Expected to find enclosing JavaSourceFile
at org.openrewrite.Cursor.firstEnclosingOrThrow(Cursor.java:182)
at org.openrewrite.java.JavaVisitor.maybeRemoveBlankLines(JavaVisitor.java:126)
at org.openrewrite.java.JavaVisitor.maybeRemoveBlankLines(JavaVisitor.java:116)
at org.openrewrite.java.RemoveAnnotationVisitor.visitClassDeclaration(RemoveAnnotationVisitor.java:63)
at org.openrewrite.java.RemoveAnnotationVisitor.visitClassDeclaration(RemoveAnnotationVisitor.java:29)
at org.openrewrite.java.tree.J$ClassDeclaration.acceptJava(J.java:1382)
at org.openrewrite.java.tree.J.accept(J.java:55)
at org.openrewrite.TreeVisitor.visit(TreeVisitor.java:242)
... 36 more
What is the smallest, simplest way to reproduce the problem?
What did you expect to see?
I expect to this test to pass.
What is the full stack trace of any errors you encountered?
Are you interested in contributing a fix to OpenRewrite?