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

如何使用kotlin-compiler-embeddable分析外部Kotlin项目?

使用kotlin-compiler-embeddable编译源码并访问BindingContext

1. 依赖配置(Gradle)

添加必要依赖到构建脚本:

dependencies {
    implementation "org.jetbrains.kotlin:kotlin-compiler-embeddable:1.9.20"
    implementation "org.jetbrains.kotlin:kotlin-stdlib:1.9.20"
}

2. 完整代码示例

以下代码会编译一组Kotlin源文件,获取BindingContext,并解析目标类中参数类型的全限定名及PSI类表示:

import org.jetbrains.kotlin.cli.common.CLICompiler
import org.jetbrains.kotlin.cli.common.arguments.K2JVMCompilerArguments
import org.jetbrains.kotlin.cli.common.messages.MessageCollector
import org.jetbrains.kotlin.cli.jvm.K2JVMCompiler
import org.jetbrains.kotlin.config.Services
import org.jetbrains.kotlin.descriptors.ClassDescriptor
import org.jetbrains.kotlin.descriptors.ValueParameterDescriptor
import org.jetbrains.kotlin.psi.KtClass
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.psi.KtParameter
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.DescriptorToSourceUtils
import java.io.File

fun main() {
    // 配置编译参数
    val args = K2JVMCompilerArguments().apply {
        // 替换为你的项目源文件路径
        freeArgs = listOf(
            "src/main/kotlin/ShapeService.kt",
            "src/main/kotlin/ShapeEndpoint.kt"
        )
        // 配置类路径:包含Kotlin标准库、项目依赖JAR/类文件路径
        classpath = listOf(
            System.getProperty("java.class.path"),
            // 有外部依赖时添加对应JAR路径
            "libs/external-dependency.jar"
        ).joinToString(File.pathSeparator)
    }

    // 初始化编译器
    val compiler: CLICompiler<K2JVMCompilerArguments> = K2JVMCompiler()
    val services = Services.EMPTY
    val messageCollector = MessageCollector.NONE

    // 执行编译并获取结果
    val result = compiler.compile(args, services, messageCollector)
    val bindingContext = result.bindingContext ?: error("无法获取BindingContext")
    val psiFiles = result.psiFiles ?: error("无法获取PSI文件")

    // 遍历PSI文件,解析目标类参数类型
    psiFiles.forEach { psiFile ->
        if (psiFile is KtFile) {
            psiFile.declarations.forEach { declaration ->
                if (declaration is KtClass && declaration.name == "ShapeEndpoint") {
                    declaration.primaryConstructor?.valueParameters?.forEach { param ->
                        if (param.name == "service") {
                            resolveParameterInfo(param, bindingContext)
                        }
                    }
                }
            }
        }
    }
}

/**
 * 解析参数的类型全限定名及对应的PSI类
 */
fun resolveParameterInfo(param: KtParameter, bindingContext: BindingContext) {
    val paramDescriptor = bindingContext[BindingContext.VALUE_PARAMETER, param] as? ValueParameterDescriptor
    paramDescriptor?.type?.constructor?.declarationDescriptor?.let { descriptor ->
        if (descriptor is ClassDescriptor) {
            // 获取类型全限定名
            val fqn = descriptor.qualifiedName?.asString()
            println("参数${param.name}的类型全限定名:$fqn")
            
            // 获取对应的PSI类表示
            val psiClass = DescriptorToSourceUtils.descriptorToDeclaration(descriptor) as? KtClass
            psiClass?.let {
                println("对应的PSI类:${it.name},所在文件:${it.containingFile.name}")
            }
        }
    }
}

3. 关键注意事项

  • 类路径配置:必须确保classpath包含所有依赖类(Kotlin标准库、项目自身类、外部依赖JAR),否则编译器无法解析引用类型,BindingContext会缺失有效语义信息。
  • BindingContext作用:它是语义分析的核心载体,关联了PSI元素与对应的语义描述符,通过它才能从PSI参数映射到真实的类型信息。
  • 多文件处理:编译时传入所有相关源文件,才能让编译器建立完整的类依赖关系,单个文件编译无法解析跨文件的类型引用。

4. 示例输出

针对代码class ShapeEndpoint(service: ShapeService),运行代码后会输出类似内容:

参数service的类型全限定名:com.example.ShapeService
对应的PSI类:ShapeService,所在文件:ShapeService.kt

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.31 17:55:42