1 方式

1.1基于starter
1.1.1 https://github.com/moderneinc/rewrite-recipe-starter
1.2使用gradle或者maven从头开始
1.2.1https://docs.openrewrite.org/authoring-recipes/recipe-development-environment
1.2.2重要的是复制pom.xml内容,没有提供maven 特殊模板

2 java 版本定义

2.1建议Bytecode level 1.8,非强制

3 Types of recipes

3.1 Declarative recipes 就是写一个rewrite.yml
3.1.1测试要注意相关类在classpath中

public void defaults(RecipeSpec spec) {
        spec.recipeFromResources("com.yourorg.UseApacheStringUtils")
          // Notice how we only pass in `spring-core` as the classpath, but not `commons-lang3`.
          // That's because we only need dependencies to compile the before code blocks, not the after code blocks.
          .parser(JavaParser.fromJavaVersion().classpath("spring-core"));
    }

3.2Refaster template recipes
3.2.1中等复杂
3.2.2使用 Refaster templates
3.3Imperative recipes
3.3.1基于代码加Lossless Semantic Trees (LST) 来实现复杂recipe
3.3.2LST 类型

  • java
  • YAML

4Unit Test

4.1使用RewriteTest 类
4.1.1一次可以传入多个Assertions 对象, 不仅仅是java 函数,只要是返回SourceSpecs的就可以
4.1.2比如package org.openrewrite.yaml;

public static SourceSpecs yaml(@Language("yml") @Nullable String before, @Language("yml") @Nullable String after,
                             Consumer<SourceSpec<Yaml.Documents>> spec) {
        SourceSpec<Yaml.Documents> yaml = new SourceSpec<>(Yaml.Documents.class, null, YamlParser.builder(), before, s -> after);
        spec.accept(yaml);
        return yaml
    }

4.2注意java()定义

public static SourceSpecs java(@Language("java") @Nullable String before, @Language("java") @Nullable String after,
                                   Consumer<SourceSpec<J.CompilationUnit>> spec) {
        SourceSpec<J.CompilationUnit> java = new SourceSpec<>(J.CompilationUnit.class, null, javaParser, before,
                Assertions::validateTypes,
                Assertions::customizeExecutionContext).after(s -> after);
        acceptSpec(spec, java);
        return java
    }

4.2.2example

java(
                // The Java source file before the recipe is run:
                """
                    package com.UPPERCASE.CamelCase;
                    class FooBar {}
                """,
                // The expected Java source file after the recipe is run:
                """
                    package com.uppercase.camelcase;
                    class FooBar {}
                """,
                // An optional callback that can be used after the recipe has been
                // executed to assert additional conditions on the resulting source file:
                spec -> spec.afterRecipe(cu -> assertThat(PathUtils.equalIgnoringSeparators(cu.getSourcePath(), Paths.get("com/uppercase/camelcase/FooBar.java"))).isTrue()))
        );

4.3其它类型定义-public class Assertions
4.3.1mavenProject
4.3.2srcMainJava

return dir("src/main/java", spec, javaSources);

4.3.3srcMainResources

 return dir("src/main/resources", spec, resources);

4.3.4validateTypes
4.4Specifying Java versions
4.4.1需要引入rewrite-migrate-java

<dependency>
    <groupId>org.openrewrite.recipe</groupId>
    <artifactId>rewrite-migrate-java</artifactId>
     <scope>runtime</scope>
</dependency>

4.4.2code

import static org.openrewrite.java.Assertions.java import org.openrewrite.java.migrate.lang.UseTextBlocks


    @Override
    public void defaults(RecipeSpec spec) {
    
        
    spec.recipe(new UseTextBlocks())
         .allSources(s -> s.markers(Assertions.javaVersion(17)));

javaVersion 实现代码

public static JavaVersion javaVersion(int version) {
        return javaVersions.computeIfAbsent(version, v ->
                new JavaVersion(Tree.randomId(), "openjdk", "adoptopenjdk",
                        Integer.toString(v), Integer.toString(v)));
    }

4.4.3specify the version on an individual test

 @Test
    void noChangeOnJava17() {
        //language=java
        rewriteRun(Assertions.version(java("""
            import java.util.zip.ZipFile;

            class FooBar extends ZipFile {
                FooBar(){
                    super("");
                }
                public void test() {
                    finalize();
                }
            }
            """), 17));
    }

5重要类

5.1JavaTemplate
生成java 代码块的template 类

 private final JavaTemplate helloTemplate =
                JavaTemplate.builder( "public String hello() { return \"Hello from #{}!\"; }")
                        .build();

apply方法
apply(Cursor scope, JavaCoordinates coordinates, Object… parameters) :生成代码并加入到指定scope的指定coordinates,入参: scope, 要加入的范围(parent),A cursor is linked path of LST elements that can be used to traverse down the tree towards the root.

  • coordinates: 加入的位置
  • parameters: template填充参数

example:

helloTemplate.apply(new Cursor(getCursor(), classDecl.getBody()),
                    classDecl.getBody().getCoordinates().lastStatement(),
                    fullyQualifiedClassName )

5.2JavaIsoVisitor

  • 每个recipe都要通过getVisitor 返回一个TreeVisitor的子类,JavaIsoVisitor是常用的子类

  • 根据需要search和修改的LST类型(类,method、invoke、field等) override JavaIsoVisitor的方法
    5.3Recipe 基类
    5.4JavaVisitor

  • JavaIsoVisitor的父类,里面是很多更基础/底层的方法

  • 在Preconditions.check 使用比较多

6创建template recipes

6.1底层使用com.google.errorprone

  • Error Prone是谷歌开源的一个 Java 编译插件,可以在编译时进行静态分析、bug
    检测,或者对可能的优化提出建议。插件中包括了超过 500 个预定义的bug检查,并且允许第三方和自定义插件
  • 可以根据规则修改代码
  • 参考url
    • https://juejin.cn/post/7158726654926258190
    • https://errorprone.info/bugpatterns
    • https://errorprone.info/docs/refaster

6.2使用@RecipeDescriptor ,@BeforeTemplate,@AfterTemplate等annotation 实现
6.3可以在一个java 内设置多个template class
6.3.1编译后生产一个list

@Override
    public List<Recipe> getRecipeList() {
        return Arrays.asList(
                new SimplifyTernaryTrueFalseRecipe(),
                new SimplifyTernaryFalseTrueRecipe()
        );
    }

以下为生成的代码

public class SimplifyTernary {

    @RecipeDescriptor(
            name = "Replace `booleanExpression ? true : false` with `booleanExpression`",
            description = "Replace ternary expressions like `booleanExpression ? true : false` with `booleanExpression`."
    )
    public static class SimplifyTernaryTrueFalse {

        @BeforeTemplate
        boolean before(boolean expr) {
            return expr ? true : false;
        }

        @AfterTemplate
        boolean after(boolean expr) {
            return expr;
        }
    }

    @RecipeDescriptor(
            name = "Replace `booleanExpression ? false : true` with `!booleanExpression`",
            description = "Replace ternary expressions like `booleanExpression ? false : true` with `!booleanExpression`."
    )
    public static class SimplifyTernaryFalseTrue {

        @BeforeTemplate
        boolean before(boolean expr) {
            return expr ? false : true;
        }

        @AfterTemplate
        boolean after(boolean expr) {
            return !(expr);
        }
    }
}

6.4注意class 通过annotation后自动增加了recipe后缀
6.4.1

6.4.2class 定义

// Making your recipe immutable helps make them idempotent and eliminates categories of possible bugs.
// Configuring your recipe in this way also guarantees that basic validation of parameters will be done for you by rewrite.
// Also note: All recipes must be serializable. This is verified by RewriteTest.rewriteRun() in your tests.
@Value
public class SayHelloRecipe extends Recipe {
    @Option(displayName = "Fully Qualified Class Name",
            description = "A fully qualified class name indicating which class to add a hello() method to.",
            example = "com.yourorg.FooBar")

6.4.3测试执行

class StringIsEmptyRecipeTest implements RewriteTest {

@Override
    public void defaults(RecipeSpec spec) {
        spec.recipe(new StringIsEmptyRecipe());
    }

6.4.4增加输出可以看到代码

<build>
<plugins>
<!--<plugin>-->
<!--<artifactId>maven-surefire-plugin</artifactId>-->
<!--</plugin>-->


    <plugin>
        <groupId>org.codehaus.mojo</groupId>
        <artifactId>build-helper-maven-plugin</artifactId>
        <executions>
            <execution>
                <id>add-source</id>
                <phase>generate-sources</phase>
                <goals>
                    <goal>add-source</goal>
                </goals>
                <configuration>
                    <sources>
                        <source>${basedir}/target/generated-sources/annotations</source>
                    </sources>
                </configuration>
            </execution>
        </executions>
    </plugin>
    

6.5使用template修改method内容
6.6缺点
没有在Preconditions.check 的annotation,目前是在process过程中根据beforetemplate里面的method和identification来生成内容

  • 可以通过生产代码后修改代码取代template 原始类来实现
  • 生成代码在class RecipeWriter
private
   static @Nullable Precondition
   generatePreconditions(List<TemplateDescriptor> beforeTemplates) {

7约定和最佳实践

7.1如果不清楚是否有伤害(隐患),那么不设计为recipe
7.2如果可以通过declarative实现,就通过declarative实现
7.3处理好traversal/visitor: 准确比性能更重要
7.4使用好preconditions

  • 通过precondition 控制范围
  • 可以使用 Preconditions.and(), Preconditions.or(), and Preconditions.not() 构造准确和复杂的检查

7.5recipe必须是幂等和不可变的-idempotent and immutable
7.6注意null处理
7.7java日期格式不应使用 Week Year (“YYYY“)
7.8使用 JavaTemplate 而不是手工构造java 代码block
7.9Recipe.causesAnotherCycle() 可能带来openrewrite执行多次cycle,但一个recipe 应该尽量在一次cycle执行: 多次执行是由于有些复杂场景下recipe之间有依赖
7.10如果可能的话,通常应避免在访问者之间传递状态: 如果需要传递,尽可能使用cursor messaging 而不是execution context messaging
7.11记住可能处于一个多project的环境下

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐