Skip to content

Unable to invoke RemoveAnnotation recepie within other recepie. #8130

Description

@nstdio

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

Are you interested in contributing a fix to OpenRewrite?

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    Status
    No status

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions