You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

OpenRewrite中引用Recipe应用类及解决测试IllegalStateException问题

OpenRewrite Recipe开发问题

问题描述

开发OpenRewrite Recipe时遇到两个问题:

  1. 如何引用Recipe所应用的类?
  2. 运行Recipe测试时抛出IllegalStateException:

java.lang.IllegalStateException: LST contains missing or invalid type information
Identifier->FieldAccess->MethodInvocation->NamedVariable->VariableDeclarations->Block->ClassDeclaration->CompilationUnit
/(Identifier type is missing or malformed)>/Test

推测原因是未像添加IString那样将Test类加入类路径,想知道是否有办法将Test添加到类路径,或通过其他方式避免Test.class抛出异常。

已尝试通过#{ }占位符替换为传入的类名来规避直接引用Test,相关代码如下:


测试代码

public class StringToIStringTest implements RewriteTest {
    @Override
    public void defaults(RecipeSpec spec) {
        spec.recipe(new StringToIString("Test")).parser(JavaParser.fromJavaVersion().logCompilationWarningsAndErrors(true).classpath("com.something.core.services"));
    }

    @Test
    void prerequisiteTest() {
        rewriteRun(
          //language=java
          java(
            """
              class Test {

              }
              """,
            """
              import com.something.core.services.IString;

              class Test {
                  private static final IString.Builder STRBLD = IString.builder(Test.class);
                  
              }
              """
          )
        );
    }
}

Recipe代码

public class StringToIString extends Recipe {

    @NonNull
    String fullyQualifiedClassName;

    @JsonCreator
    public StringToIString(@NonNull @JsonProperty("fullyQualifiedClassName") String fullyQualifiedClassName) {
        this.fullyQualifiedClassName = fullyQualifiedClassName;
    }

    @Override
    public TreeVisitor<?, ExecutionContext> getVisitor() {
        return new StringVisitor();
    }

    public class StringVisitor extends JavaIsoVisitor<ExecutionContext> {
        private final JavaTemplate stringBuilderTemplate = JavaTemplate.builder("private static final IString.Builder STRBLD = IString.builder(#{}.class);").imports("com.something.core.services.IString").javaParser(JavaParser.fromJavaVersion().classpath(fullyQualifiedClassName).classpath("com.something.core.services")).build();

        @Override
        public J.ClassDeclaration visitClassDeclaration(J.ClassDeclaration classDecl, ExecutionContext executionContext) {
            //check for right class
            if (classDecl.getType() == null || !classDecl.getType().getFullyQualifiedName().equals(fullyQualifiedClassName)) {
                return classDecl;
            }
            //check if STRBLD is present and add STRBLD
            boolean stringBuilderExists = classDecl.getBody().getStatements().stream().filter(statement -> statement instanceof J.VariableDeclarations).map(J.VariableDeclarations.class::cast).anyMatch(variableDeclarations -> variableDeclarations.toString().equals("private static final IString.Builder STRBLD = IString.builder(" +fullyQualifiedClassName + ".class)"));
            if (!stringBuilderExists) {
                classDecl = classDecl.withBody(stringBuilderTemplate.apply(new Cursor(getCursor(), classDecl.getBody()), classDecl.getBody().getCoordinates().lastStatement(), fullyQualifiedClassName));
                maybeAddImport("com.something.core.services.IString");
            }
            return classDecl;
        }
    }
}

解决方案

问题1:引用Recipe所应用的类

无需手动传入类名,直接通过visitClassDeclaration方法中的classDecl对象获取类型信息即可:

// 获取当前处理类的全限定名
String currentClassName = classDecl.getType().getFullyQualifiedName();

这样可以去掉构造函数中传入类名的逻辑,直接动态获取当前处理的类。

问题2:解决LST类型信息缺失异常

测试中的Test类是内联代码,不会被自动加入解析器的类路径,导致类型解析失败。可以通过以下两种方式解决:

方式1:将测试类所在路径加入解析器类路径

在测试类的defaults方法中,把测试类的类路径添加到JavaParser中:

@Override
public void defaults(RecipeSpec spec) {
    ClassLoader classLoader = getClass().getClassLoader();
    spec.recipe(new StringToIString("Test"))
        .parser(JavaParser.fromJavaVersion()
            .logCompilationWarningsAndErrors(true)
            .classpath("com.something.core.services")
            .classpath(classLoader.getResource("").getPath())); // 添加测试类所在路径
}

方式2:使用类型引用而非字符串拼接.class

修改JavaTemplate和应用逻辑,直接注入类字面量的语法树节点,避免解析器无法识别类型:

// 修改模板定义,去掉手动拼接的.class
private final JavaTemplate stringBuilderTemplate = JavaTemplate.builder("private static final IString.Builder STRBLD = IString.builder(#{});")
    .imports("com.something.core.services.IString")
    .javaParser(JavaParser.fromJavaVersion().classpath("com.something.core.services"))
    .build();

// 应用模板时传入类字面量节点
@Override
public J.ClassDeclaration visitClassDeclaration(J.ClassDeclaration classDecl, ExecutionContext executionContext) {
    if (classDecl.getType() == null) {
        return classDecl;
    }
    String currentClassName = classDecl.getType().getFullyQualifiedName();
    if (!currentClassName.equals(fullyQualifiedClassName)) {
        return classDecl;
    }

    // 检查STRBLD是否存在(优化判断逻辑)
    boolean stringBuilderExists = classDecl.getBody().getStatements().stream()
        .filter(J.VariableDeclarations.class::isInstance)
        .map(J.VariableDeclarations.class::cast)
        .anyMatch(vd -> vd.getVariables().stream()
            .anyMatch(var -> "STRBLD".equals(var.getName().getSimpleName())
                && var.getType() instanceof J.ParameterizedType paramType
                && "IString.Builder".equals(paramType.getType().getFullyQualifiedName())));

    if (!stringBuilderExists) {
        // 构造类字面量节点
        J.Ident classIdent = new J.Ident(null, classDecl.getType().getShortName());
        J.FieldAccess classLiteral = new J.FieldAccess(null, classIdent, "class");
        
        classDecl = classDecl.withBody(stringBuilderTemplate.apply(
            new Cursor(getCursor(), classDecl.getBody()),
            classDecl.getBody().getCoordinates().lastStatement(),
            classLiteral
        ));
        maybeAddImport("com.something.core.services.IString");
    }
    return classDecl;
}

额外优化:变量存在性判断逻辑

不要用toString()判断变量是否存在,容易因格式细节出错,改用类型和变量名匹配的方式,如上述代码所示。


内容的提问来源于stack exchange,提问作者user26677970

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.19 22:07:28