架构

cha 是一个 Rust workspace,一共七个 crate。依赖方向写死了:cha-core 不依赖 cha-parser(它只通过 cha-core/src/plugin.rs 里的 trait 间接接触 cha-parser 的产物),cha-cli 依赖 cha-corecha-plugin-sdk 谁都不依赖。这个方向不能反。

Crate 关系

flowchart TB
    xtask["xtask<br/><i>CI / 发版自动化</i>"]
    cli["cha-cli<br/><i>二进制</i>"]
    core["cha-core<br/><i>分析核心</i>"]
    lsp["cha-lsp<br/><i>LSP server</i>"]
    parser["cha-parser<br/><i>tree-sitter 封装</i>"]
    sdk["cha-plugin-sdk<br/><i>guest 侧,不依赖 host</i>"]

    xtask -.-> cli
    xtask -.-> core
    cli --> core
    lsp --> core
    parser --> core
    sdk -. WASM .-> core

    classDef host fill:#e8f5e9,stroke:#2e7d32,color:#1b5e20;
    classDef tool fill:#fff8e1,stroke:#f57f17,color:#5d4037;
    classDef guest fill:#e3f2fd,stroke:#1565c0,color:#0d47a1,stroke-dasharray:5 3;
    class core,cli,lsp,parser host;
    class xtask tool;
    class sdk guest;
Crate位置职责
cha-corecha-core/Plugin trait、Finding / SourceModel / SymbolIndex 数据模型、registry、reporter(terminal / JSON / SARIF / HTML / LLM)、WASM runtime、两层缓存。
cha-parsercha-parser/Python、TypeScript / TSX、Rust、Go、C、C++ 的 tree-sitter parser。产出 SourceModelSymbolIndex
cha-clicha-cli/CLI 二进制。子命令在 命令行参考 全列出来了。
cha-lspcha-lsp/LSP server 库 + 入口。诊断、code action、code lens、hover、inlay hint、semantic token、workspace diagnostics。
cha-plugin-sdkcha-plugin-sdk/Guest 侧库 + plugin! 宏。编译目标 wasm32-wasip2。不依赖 cha-core
xtaskxtask/cargo xtask 自动化:citestlintanalyzebumpreleasepublishdocgen-clidocs-checki18n-check
vscode-chavscode-cha/VS Code 扩展。第一次启动时自动下载匹配版本的 cha 二进制。

数据流

flowchart LR
    src["源文件"]
    parser["cha-parser"]
    model[("SourceModel")]
    cfg["config TOML"]
    analyze["Plugin::analyze"]
    findings["Vec&lt;Finding&gt;"]
    cache[("L1 内存 + L2 bincode")]

    src --> parser --> model
    model --> analyze
    cfg --> analyze
    analyze --> findings
    model --> cache
    cache -.命中?.-> analyze

    classDef store fill:#fff3e0,stroke:#e65100,color:#bf360c;
    classDef proc fill:#e8f5e9,stroke:#2e7d32,color:#1b5e20;
    class model,cache store;
    class parser,analyze proc;

SourceModel 是统一的中间格式。每个插件拿到的都是同一份 &AnalysisContext { file, model, config }。每个文件只解析一次,结果按缓存 key 哈希后在所有插件间共享。

WASM 插件多一跳:cha-core::wasm 里的 host adapter 把 AnalysisInputAnalysisContext 的一个子集——只保留能跨 WASM 边界传递的字段,定义在 wit/cha-plugin.wit 里)序列化送过去;guest 侧的 cha-plugin-sdk 把它反序列化成 Rust 类型给插件用。

Plugin trait

内置检测器实现 cha_core::Plugin

#![allow(unused)]
fn main() {
pub trait Plugin: Send + Sync {
    fn name(&self) -> &str;
    fn smells(&self) -> Vec<String>;
    fn description(&self) -> &str;
    fn analyze(&self, ctx: &AnalysisContext) -> Vec<Finding>;
}
}

WASM 插件实现 cha_plugin_sdk::PluginImpl —— 跟 Plugin 形状对应的另一个 trait,只是返回 String 不返回 &str(WIT 不支持借用类型)。cha-core::wasm 的 host bridge 让 PluginImpl 实现可以跟原生 Plugin 一起进同一个 registry。

缓存

两层,都在 cha-core::cache 里:

  • L1:进程内的 DashMap<PathBuf, CachedResult>。生命周期 = 一次 cha analyze
  • L2.cha/cache/ 下的 bincode 文件。缓存 key 是 (文件 mtime, 文件大小, 插件集合 hash, config hash)。mtime 没变就直接跳过解析。

插件集合 hash 包含已安装的 .wasm 文件 —— 装新插件 / 重装插件会自动作废这个插件碰过的缓存。

想扩什么动哪里

想做的事改哪里
加一条内置 smellcha-core/src/plugins/ 加文件 + 在 cha-core/src/registry.rs 注册
支持新语言cha-parser/src/<lang>.rs + 在 cha-parser/src/lib.rs 里映射
加一条 CLI 子命令cha-cli/src/<subcommand>.rs + 在 cha-cli/src/main.rs 里接进来
给 WASM 插件暴露新能力wit/cha-plugin.wit、重生成 binding、在 cha-core/src/wasm.rs 里实现 host adapter、再在 cha-plugin-sdk/src/lib.rs 里暴露
加 LSP 能力cha-lsp/src/lib.rs

See also