openrewrite 自定义recipe part 1
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.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的环境下
更多推荐





所有评论(0)