From 128786b226ec377811f3e59f1ef3bebb042eb2e6 Mon Sep 17 00:00:00 2001 From: Tim te Beek Date: Sat, 23 Nov 2024 13:34:20 +0100 Subject: [PATCH] Add option to override Generated annotation with Jakarta (#116) --- build.gradle.kts | 1 + .../processor/RefasterTemplateProcessor.java | 148 ++++++++++-------- .../RefasterTemplateProcessorTest.java | 30 +++- 3 files changed, 109 insertions(+), 70 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index 875dbae2..2fe8948c 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -92,6 +92,7 @@ dependencies { // Skip `2.1.0-alpha0` for now over "class file has wrong version 55.0, should be 52.0" testImplementation("org.slf4j:slf4j-api:2.0.+") testImplementation("com.google.testing.compile:compile-testing:latest.release") + testImplementation("jakarta.annotation:jakarta.annotation-api:2.+") testImplementation("org.junit.jupiter:junit-jupiter-api:latest.release") testImplementation("org.junit.jupiter:junit-jupiter-params:latest.release") diff --git a/src/main/java/org/openrewrite/java/template/processor/RefasterTemplateProcessor.java b/src/main/java/org/openrewrite/java/template/processor/RefasterTemplateProcessor.java index b1b67f36..83f01148 100644 --- a/src/main/java/org/openrewrite/java/template/processor/RefasterTemplateProcessor.java +++ b/src/main/java/org/openrewrite/java/template/processor/RefasterTemplateProcessor.java @@ -243,79 +243,91 @@ public void visitClassDef(JCTree.JCClassDecl classDecl) { if (classDecl.sym != null && classDecl.sym.getNestingKind() == NestingKind.TOP_LEVEL && !recipes.isEmpty()) { boolean outerClassRequired = descriptor == null; - try { - Symbol.PackageSymbol pkg = classDecl.sym.packge(); - String inputOuterFQN = outerClassRequired ? classDecl.sym.fullname.toString() : descriptor.classDecl.sym.fullname.toString(); - String className = inputOuterFQN + (outerClassRequired ? "Recipes" : "Recipe"); - JavaFileObject builderFile = processingEnv.getFiler().createSourceFile(className); - try (Writer out = new BufferedWriter(builderFile.openWriter())) { - if (!pkg.isUnnamed()) { - out.write("package " + pkg.fullname + ";\n"); - out.write("\n"); - } - out.write("import org.jspecify.annotations.NullMarked;\n"); - out.write("import org.openrewrite.ExecutionContext;\n"); - out.write("import org.openrewrite.Preconditions;\n"); - out.write("import org.openrewrite.Recipe;\n"); - out.write("import org.openrewrite.TreeVisitor;\n"); - out.write("import org.openrewrite.java.JavaParser;\n"); - out.write("import org.openrewrite.java.JavaTemplate;\n"); - out.write("import org.openrewrite.java.JavaVisitor;\n"); - out.write("import org.openrewrite.java.search.*;\n"); - out.write("import org.openrewrite.java.template.Primitive;\n"); - out.write("import org.openrewrite.java.template.function.*;\n"); - out.write("import org.openrewrite.java.template.internal.AbstractRefasterJavaVisitor;\n"); - out.write("import org.openrewrite.java.tree.*;\n"); - if (anySearchRecipe) { - out.write("import org.openrewrite.marker.SearchResult;\n"); - } - out.write("\n"); - out.write("import javax.annotation.Generated;\n"); - out.write("import java.util.*;\n"); - out.write("\n"); - out.write("import static org.openrewrite.java.template.internal.AbstractRefasterJavaVisitor.EmbeddingOption.*;\n"); + writeRecipeClass(classDecl, outerClassRequired, descriptor); + } + } - out.write("\n"); + private void writeRecipeClass(JCTree.JCClassDecl classDecl, boolean outerClassRequired, RuleDescriptor descriptor) { + try { + Symbol.PackageSymbol pkg = classDecl.sym.packge(); + String inputOuterFQN = outerClassRequired ? classDecl.sym.fullname.toString() : descriptor.classDecl.sym.fullname.toString(); + String className = inputOuterFQN + (outerClassRequired ? "Recipes" : "Recipe"); + JavaFileObject builderFile = processingEnv.getFiler().createSourceFile(className); + + // Pass in `-Arewrite.generatedAnnotation=jakarta.annotation.Generated` to override the default + String generatedAnnotation = processingEnv.getOptions().get("rewrite.generatedAnnotation"); + if (generatedAnnotation == null) { + generatedAnnotation = "javax.annotation.Generated"; + } - if (outerClassRequired) { - out.write("/**\n * OpenRewrite recipes created for Refaster template {@code " + inputOuterFQN + "}.\n */\n"); - String outerClassName = className.substring(className.lastIndexOf('.') + 1); - out.write("@SuppressWarnings(\"all\")\n"); - out.write("@Generated(\"" + GENERATOR_NAME + "\")\n"); - out.write("public class " + outerClassName + " extends Recipe {\n"); - out.write(" /**\n"); - out.write(" * Instantiates a new instance.\n"); - out.write(" */\n"); - out.write(" public " + outerClassName + "() {}\n\n"); - out.write(recipeDescriptor(classDecl, - String.format("`%s` Refaster recipes", inputOuterFQN.substring(inputOuterFQN.lastIndexOf('.') + 1)), - String.format("Refaster template recipes for `%s`.", inputOuterFQN))); - String recipesAsList = recipes.keySet().stream() - .map(r -> " new " + r.substring(r.lastIndexOf('.') + 1) + "()") - .collect(joining(",\n")); - out.write( - " @Override\n" + - " public List getRecipeList() {\n" + - " return Arrays.asList(\n" + - recipesAsList + '\n' + - " );\n" + - " }\n\n"); - - for (String r : recipes.values()) { - out.write(r.replaceAll("(?m)^(.+)$", " $1")); - out.write('\n'); - } - out.write("}\n"); - } else { - for (String r : recipes.values()) { - out.write(r); - out.write('\n'); - } + try (Writer out = new BufferedWriter(builderFile.openWriter())) { + if (!pkg.isUnnamed()) { + out.write("package " + pkg.fullname + ";\n"); + out.write("\n"); + } + out.write("import org.jspecify.annotations.NullMarked;\n"); + out.write("import org.openrewrite.ExecutionContext;\n"); + out.write("import org.openrewrite.Preconditions;\n"); + out.write("import org.openrewrite.Recipe;\n"); + out.write("import org.openrewrite.TreeVisitor;\n"); + out.write("import org.openrewrite.java.JavaParser;\n"); + out.write("import org.openrewrite.java.JavaTemplate;\n"); + out.write("import org.openrewrite.java.JavaVisitor;\n"); + out.write("import org.openrewrite.java.search.*;\n"); + out.write("import org.openrewrite.java.template.Primitive;\n"); + out.write("import org.openrewrite.java.template.function.*;\n"); + out.write("import org.openrewrite.java.template.internal.AbstractRefasterJavaVisitor;\n"); + out.write("import org.openrewrite.java.tree.*;\n"); + if (anySearchRecipe) { + out.write("import org.openrewrite.marker.SearchResult;\n"); + } + out.write("\n"); + + out.write("import " + generatedAnnotation + ";\n"); + out.write("import java.util.*;\n"); + out.write("\n"); + out.write("import static org.openrewrite.java.template.internal.AbstractRefasterJavaVisitor.EmbeddingOption.*;\n"); + + out.write("\n"); + + if (outerClassRequired) { + out.write("/**\n * OpenRewrite recipes created for Refaster template {@code " + inputOuterFQN + "}.\n */\n"); + String outerClassName = className.substring(className.lastIndexOf('.') + 1); + out.write("@SuppressWarnings(\"all\")\n"); + out.write("@Generated(\"" + GENERATOR_NAME + "\")\n"); + out.write("public class " + outerClassName + " extends Recipe {\n"); + out.write(" /**\n"); + out.write(" * Instantiates a new instance.\n"); + out.write(" */\n"); + out.write(" public " + outerClassName + "() {}\n\n"); + out.write(recipeDescriptor(classDecl, + String.format("`%s` Refaster recipes", inputOuterFQN.substring(inputOuterFQN.lastIndexOf('.') + 1)), + String.format("Refaster template recipes for `%s`.", inputOuterFQN))); + String recipesAsList = recipes.keySet().stream() + .map(r -> " new " + r.substring(r.lastIndexOf('.') + 1) + "()") + .collect(joining(",\n")); + out.write( + " @Override\n" + + " public List getRecipeList() {\n" + + " return Arrays.asList(\n" + + recipesAsList + '\n' + + " );\n" + + " }\n\n"); + + for (String r : recipes.values()) { + out.write(r.replaceAll("(?m)^(.+)$", " $1")); + out.write('\n'); + } + out.write("}\n"); + } else { + for (String r : recipes.values()) { + out.write(r); + out.write('\n'); } } - } catch (IOException e) { - throw new RuntimeException(e); } + } catch (IOException e) { + throw new RuntimeException(e); } } diff --git a/src/test/java/org/openrewrite/java/template/RefasterTemplateProcessorTest.java b/src/test/java/org/openrewrite/java/template/RefasterTemplateProcessorTest.java index d08a7606..b4d48199 100644 --- a/src/test/java/org/openrewrite/java/template/RefasterTemplateProcessorTest.java +++ b/src/test/java/org/openrewrite/java/template/RefasterTemplateProcessorTest.java @@ -19,6 +19,7 @@ import com.google.errorprone.refaster.annotation.BeforeTemplate; import com.google.testing.compile.Compilation; import com.google.testing.compile.JavaFileObjects; +import jakarta.annotation.Generated; import org.intellij.lang.annotations.Language; import org.jspecify.annotations.NullMarked; import org.junit.jupiter.api.Test; @@ -30,10 +31,14 @@ import javax.tools.JavaFileObject; import java.io.File; import java.net.URL; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; import java.util.Arrays; import static com.google.testing.compile.CompilationSubject.assertThat; import static com.google.testing.compile.Compiler.javac; +import static java.util.Objects.requireNonNull; import static org.junit.jupiter.api.Assertions.assertEquals; class RefasterTemplateProcessorTest { @@ -108,6 +113,25 @@ void stringIsEmptyPredicate() { assertEquals(0, compilation.generatedSourceFiles().size(), "Not yet supported"); } + @Test + void jakartaGeneratedAnnotationOverride() throws Exception { + // As per https://github.com/google/compile-testing/blob/v0.21.0/src/main/java/com/google/testing/compile/package-info.java#L53-L55 + Compilation compilation = compile( + JavaFileObjects.forResource("refaster/UseStringIsEmpty.java"), + new RefasterTemplateProcessor(), + "-Arewrite.generatedAnnotation=jakarta.annotation.Generated"); + assertThat(compilation).succeeded(); + assertThat(compilation).hadNoteCount(0); + + // Replace import in reference output file and compare with what's generated + Path path = Paths.get(requireNonNull(getClass().getResource("/refaster/UseStringIsEmptyRecipe.java")).toURI()); + String source = new String(Files.readAllBytes(path)) + .replace("javax.annotation.Generated", "jakarta.annotation.Generated"); + assertThat(compilation) + .generatedSourceFile("foo/UseStringIsEmptyRecipe") + .hasSourceEquivalentTo(JavaFileObjects.forSourceString("refaster.UseStringIsEmptyRecipe", source)); + } + private static Compilation compileResource(String resourceName) { return compileResource(resourceName, new RefasterTemplateProcessor()); } @@ -127,7 +151,7 @@ static Compilation compileSource(String fqn, @Language("java") String source, Ty return compile(JavaFileObjects.forSourceString(fqn, source), processor); } - static Compilation compile(JavaFileObject javaFileObject, TypeAwareProcessor processor) { + static Compilation compile(JavaFileObject javaFileObject, TypeAwareProcessor processor, Object... options) { return javac() .withProcessors(processor) .withClasspath(Arrays.asList( @@ -138,8 +162,10 @@ static Compilation compile(JavaFileObject javaFileObject, TypeAwareProcessor pro fileForClass(org.openrewrite.java.JavaTemplate.class), fileForClass(org.slf4j.Logger.class), fileForClass(Primitive.class), - fileForClass(NullMarked.class) + fileForClass(NullMarked.class), + fileForClass(Generated.class) )) + .withOptions(options) .compile(javaFileObject); }