Skip to content

Commit

Permalink
Add ability to name refaster rules with
Browse files Browse the repository at this point in the history
  • Loading branch information
jkschneider committed Sep 30, 2023
1 parent a5bba99 commit 0b0b6cd
Show file tree
Hide file tree
Showing 5 changed files with 73 additions and 49 deletions.
17 changes: 17 additions & 0 deletions src/main/java/org/openrewrite/java/template/RecipeDescriptor.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package org.openrewrite.java.template;

import org.intellij.lang.annotations.Language;

import java.lang.annotation.ElementType;
import java.lang.annotation.Target;

@Target(ElementType.TYPE)
public @interface RecipeDescriptor {
@Language("markdown")
String name();

@Language("markdown")
String description();

String[] tags() default {};
}
Original file line number Diff line number Diff line change
Expand Up @@ -128,13 +128,8 @@ public void visitClassDef(JCTree.JCClassDecl classDecl) {

processingEnv.getMessager().printMessage(Kind.NOTE, "Generating template for " + descriptor.classDecl.getSimpleName());

String templateName = classDecl.sym.fullname.toString().substring(classDecl.sym.packge().fullname.length() + 1);
String templateFqn = classDecl.sym.fullname.toString() + "Recipe";
String templateCode = copy.toString().trim();
String displayName = cu.docComments.getComment(classDecl) != null ? cu.docComments.getComment(classDecl).getText().trim() : "Refaster template `" + templateName + '`';
if (displayName.endsWith(".")) {
displayName = displayName.substring(0, displayName.length() - 1);
}

for (JCTree.JCMethodDecl template : descriptor.beforeTemplates) {
for (Symbol anImport : ImportDetector.imports(template)) {
Expand Down Expand Up @@ -192,16 +187,10 @@ public void visitClassDef(JCTree.JCClassDecl classDecl) {
recipe.append("@NonNullApi\n");
recipe.append(modifiers).append("class ").append(recipeName).append(" extends Recipe {\n");
recipe.append("\n");
recipe.append(" @Override\n");
recipe.append(" public String getDisplayName() {\n");
recipe.append(" return \"").append(escape(displayName)).append("\";\n");
recipe.append(" }\n");
recipe.append("\n");
recipe.append(" @Override\n");
recipe.append(" public String getDescription() {\n");
recipe.append(" return \"Recipe created for the following Refaster template:\\n```java\\n").append(escape(templateCode)).append("\\n```\\n.\";\n");
recipe.append(" }\n");
recipe.append("\n");
recipe.append(recipeDescriptor(classDecl,
"Refaster template `" + classDecl.sym.fullname.toString().substring(classDecl.sym.packge().fullname.length() + 1) + '`',
"Recipe created for the following Refaster template:\\n```java\\n" + escape(templateCode) + "\\n```\\n."
));
recipe.append(" @Override\n");
recipe.append(" public TreeVisitor<?, ExecutionContext> getVisitor() {\n");
recipe.append(" JavaVisitor<ExecutionContext> javaVisitor = new AbstractRefasterJavaVisitor() {\n");
Expand Down Expand Up @@ -362,19 +351,9 @@ public void visitClassDef(JCTree.JCClassDecl classDecl) {
if (outerClassRequired) {
String outerClassName = className.substring(className.lastIndexOf('.') + 1);
out.write("public final class " + outerClassName + " extends Recipe {\n");

String simpleInputOuterFQN = inputOuterFQN.substring(inputOuterFQN.lastIndexOf('.') + 1);
out.write("\n" +
" @Override\n" +
" public String getDisplayName() {\n" +
" return \"`" + simpleInputOuterFQN + "` Refaster recipes\";\n" +
" }\n" +
"\n" +
" @Override\n" +
" public String getDescription() {\n" +
" return \"Refaster template recipes for `" + inputOuterFQN + "`.\";\n" +
" }\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(Collectors.joining(",\n"));
Expand Down Expand Up @@ -404,6 +383,39 @@ public void visitClassDef(JCTree.JCClassDecl classDecl) {
}
}

private String recipeDescriptor(JCTree.JCClassDecl classDecl, String defaultDisplayName, String defaultDescription) {
String displayName = defaultDisplayName;
String description = defaultDescription;
for (JCTree.JCAnnotation annotation : classDecl.getModifiers().getAnnotations()) {
if (annotation.type.toString().equals("org.openrewrite.java.template.RecipeDescriptor")) {
for (JCTree.JCExpression argExpr : annotation.getArguments()) {
JCTree.JCAssign arg = (JCTree.JCAssign) argExpr;
switch (arg.lhs.toString()) {
case "name":
displayName = ((JCTree.JCLiteral) arg.rhs).getValue().toString();
break;
case "description":
description = ((JCTree.JCLiteral) arg.rhs).getValue().toString();
break;
}
}
break;
}
}

return "\n" +
" @Override\n" +
" public String getDisplayName() {\n" +
" return \"" + displayName + "\";\n" +
" }\n" +
"\n" +
" @Override\n" +
" public String getDescription() {\n" +
" return \"" + description + "\";\n" +
" }\n" +
"\n";
}

private void maybeRemoveImport(Map<JCTree.JCMethodDecl, Set<String>> imports, Set<String> beforeImports, Set<String> afterImports, StringBuilder recipe) {
for (String anImport : imports.values().stream().flatMap(Set::stream).collect(Collectors.toSet())) {
if (anImport.startsWith("java.lang.")) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@
import com.google.errorprone.refaster.annotation.BeforeTemplate;
import com.google.testing.compile.Compilation;
import com.google.testing.compile.JavaFileObjects;
import org.jetbrains.annotations.NotNull;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
import org.openrewrite.java.template.processor.RefasterTemplateProcessor;
Expand Down Expand Up @@ -72,7 +71,6 @@ void nestedRecipes(String recipeName) {
.hasSourceEquivalentTo(JavaFileObjects.forResource("refaster/" + recipeName + "Recipes.java"));
}

@NotNull
static Collection<File> classpath() {
return Arrays.asList(
fileForClass(BeforeTemplate.class),
Expand Down
11 changes: 11 additions & 0 deletions src/test/resources/refaster/Matching.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,20 @@
import org.openrewrite.java.template.Matches;
import org.openrewrite.java.template.MethodInvocationMatcher;
import org.openrewrite.java.template.NotMatches;
import org.openrewrite.java.template.RecipeDescriptor;

@RecipeDescriptor(
name = "Static analysis",
description = "A set of static analysis recipes.",
tags = "sast"
)
public class Matching {

@RecipeDescriptor(
name = "Use String length comparison",
description = "Use String#length() == 0 instead of String#isEmpty().",
tags = "sast"
)
public static class StringIsEmpty {
@BeforeTemplate
boolean before(int i, @NotMatches(MethodInvocationMatcher.class) String s) {
Expand Down
24 changes: 5 additions & 19 deletions src/test/resources/refaster/MatchingRecipes.java
Original file line number Diff line number Diff line change
@@ -1,18 +1,3 @@
/*
* Copyright 2023 the original author or authors.
* <p>
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* https://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package foo;

import org.openrewrite.ExecutionContext;
Expand All @@ -39,12 +24,12 @@ public final class MatchingRecipes extends Recipe {

@Override
public String getDisplayName() {
return "`Matching` Refaster recipes";
return "Static analysis";
}

@Override
public String getDescription() {
return "Refaster template recipes for `foo.Matching`.";
return "A set of static analysis recipes.";
}

@Override
Expand All @@ -57,14 +42,15 @@ public List<Recipe> getRecipeList() {
@NonNullApi
public static class StringIsEmptyRecipe extends Recipe {


@Override
public String getDisplayName() {
return "Refaster template `Matching.StringIsEmpty`";
return "Use String length comparison";
}

@Override
public String getDescription() {
return "Recipe created for the following Refaster template:\n```java\npublic static class StringIsEmpty {\n \n @BeforeTemplate()\n boolean before(int i, @NotMatches(value = MethodInvocationMatcher.class)\n String s) {\n return s.substring(i).isEmpty();\n }\n \n @BeforeTemplate()\n boolean before2(int i, @Matches(value = MethodInvocationMatcher.class)\n String s) {\n return s.substring(i).isEmpty();\n }\n \n @AfterTemplate()\n boolean after(String s) {\n return s != null && s.length() == 0;\n }\n}\n```\n.";
return "Use String#length() == 0 instead of String#isEmpty().";
}

@Override
Expand Down

0 comments on commit 0b0b6cd

Please sign in to comment.