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 workingquestionFurther information is requested

    Type

    No type

    Projects

    Status
    Done

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions