Skip to content

Commit

Permalink
Add option to override Generated annotation with Jakarta (#116)
Browse files Browse the repository at this point in the history
  • Loading branch information
timtebeek authored Nov 23, 2024
1 parent 02a1a94 commit 128786b
Show file tree
Hide file tree
Showing 3 changed files with 109 additions and 70 deletions.
1 change: 1 addition & 0 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<Recipe> 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<Recipe> 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);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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 {
Expand Down Expand Up @@ -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());
}
Expand All @@ -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(
Expand All @@ -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);
}

Expand Down

0 comments on commit 128786b

Please sign in to comment.