Skip to main content

code_repo_wiki/ingest/parser/
mod.rs

1mod rust;
2mod typescript;
3mod python;
4mod go;
5mod javascript;
6mod csharp;
7mod java;
8
9use std::path::{Path, PathBuf};
10use anyhow::Result;
11use serde::{Deserialize, Serialize};
12use tree_sitter::{Language, Node, Parser};
13
14/// 全部注册语言处理器的扩展名集合(含前导点,与各处理器 extensions() 逐项一致)。
15/// 扫描器与文件监听共用此集合:只收可解析语言,非支持语言一律不进入管线。
16pub const SUPPORTED_EXTENSIONS: &[&str] = &[
17    ".rs", ".ts", ".tsx", ".py", ".go", ".js", ".jsx", ".mjs", ".cjs", ".cs", ".java",
18];
19
20/// 解析后的文件洞察,包含所有提取的实体和导入信息
21///
22/// Serialize/Deserialize 供增量管线的解析缓存使用(.state/insights_cache.json):
23/// 未变更文件直接反序列化复用,避免重复 tree-sitter 解析。
24#[derive(Debug, Clone, Serialize, Deserialize)]
25pub struct FileInsight {
26    pub path: PathBuf,
27    pub language: String,
28    pub entities: Vec<Entity>,
29    pub imports: Vec<ImportStmt>,
30    pub doc_comments: Vec<String>,
31    /// 文件的源代码文本,用于避免搜索索引构建时重复读盘
32    pub source: String,
33}
34
35/// 代码实体(struct / fn / trait / impl / enum / type / const / 等)
36#[derive(Debug, Clone, Serialize, Deserialize)]
37pub struct Entity {
38    pub name: String,
39    /// 实体类型:"struct", "fn", "trait", "impl", "enum", "type", "const", "mod", "class", "interface", "function"
40    pub kind: String,
41    /// 起始行号(1-based)
42    pub line_start: usize,
43    /// 结束行号(1-based)
44    pub line_end: usize,
45    pub doc_comment: Option<String>,
46    pub signature: Option<String>,
47    // 实体摘要字段已删除(v31):原 generate_entity_summaries 对每实体一次
48    // LLM 调用但字段零消费者——纯 token 浪费;未来如需实体级语义索引,
49    // 应在生成时预索引重建,而非逐个惰性调用。
50    /// 可见性修饰符("pub"/"pub(crate)"/"private"/"internal"/"export" 等);
51    /// 由解析出口的 fill_visibilities 按行级文本统一提取,缺失(默认可见性)
52    /// 为 None。serde(default) 兼容旧版 insights_cache 反序列化。
53    #[serde(default)]
54    pub visibility: Option<String>,
55}
56
57/// 导入语句
58#[derive(Debug, Clone, Serialize, Deserialize)]
59pub struct ImportStmt {
60    /// 导入的源模块(如 "std::collections::HashMap")
61    pub source: String,
62    /// 别名(如 use Foo as Bar 中的 "Bar")
63    pub alias: Option<String>,
64    /// 导入语句所在行号(1-based)
65    pub line: usize,
66}
67
68/// 语言处理器 trait — 每个语言实现此 trait
69///
70/// 必须 Send + Sync 以支持并行解析
71pub trait LanguageProcessor: Send + Sync {
72    fn name(&self) -> &'static str;
73    fn extensions(&self) -> &[&str];
74    fn parse(&self, source: &str, path: &Path) -> Result<FileInsight>;
75}
76
77/// kind 映射规则:tree-sitter 节点 kind → 实体 kind(语言差异点数据化)
78///
79/// 覆盖 7 语言 walk 中"纯 kind→kind 映射 + 可选签名提取"的分支,
80/// 由 `SharedProcessor::record_by_rule` 统一提取,输出与各语言原 match 分支逐字节一致。
81#[derive(Debug, Clone, Copy)]
82pub struct KindRule {
83    /// tree-sitter 节点 kind(如 "function_declaration")
84    pub node_kind: &'static str,
85    /// 映射后的实体 kind(如 "function")
86    pub entity_kind: &'static str,
87    /// 是否提取签名:取节点文本中首个分隔符前的部分
88    pub with_signature: bool,
89    /// 签名截断分隔符(Rust/Go/Java/C#/JS/TS 用 '{',Python 用 ':')
90    pub sig_delim: char,
91}
92
93impl KindRule {
94    /// 无签名提取的映射规则
95    pub const fn plain(node_kind: &'static str, entity_kind: &'static str) -> Self {
96        Self { node_kind, entity_kind, with_signature: false, sig_delim: '{' }
97    }
98    /// 带签名提取的映射规则(sig_delim 为签名截断分隔符)
99    pub const fn with_sig(node_kind: &'static str, entity_kind: &'static str, sig_delim: char) -> Self {
100        Self { node_kind, entity_kind, with_signature: true, sig_delim }
101    }
102}
103
104/// 共享的 tree-sitter 解析骨架 — 7 语言 walk/fallback 公共部分去重
105///
106/// 背景:原 7 个 parser 的 walk 头部(bytes/entities/imports 初始化、
107/// Parser 构造、set_language 失败→fallback、parse 返回 None→fallback、cursor 初始化)、
108/// walk 尾部(DFS 遍历)与 parse 方法体(FileInsight 组装)逐字重复约 20 行 × 7,
109/// 全部收敛到本 trait 的默认实现,每语言只保留差异点。
110///
111/// 差异点分三类承载:
112/// 1. grammar():tree-sitter 语法常量(原 LANGUAGE 一行差异)
113/// 2. kinds():纯 kind→kind 映射分支数据化(含签名提取规则),
114///    公共 walk 命中后走统一的 record_by_rule;命中规则与 handle_special
115///    的分支互斥(等价于原 match 每个 kind 恰好一个分支)
116/// 3. handle_special():无法数据化的分支钩子 — 动态 kind 判断
117///    (Go type_spec、JS variable_declarator 箭头函数)、子节点遍历
118///    (field_declaration)、导入语句文本解析(use_declaration / import_* / using_directive)
119///
120/// fallback() 保持每语言钩子:各语言正则降级差异极大(C# 是启发式代码、
121/// 导入解析规则各异),强行表化会引入比原代码更复杂的规则引擎,故不数据化;
122/// 统一的是触发契约 — set_language 失败或 parse 返回 None 时由 extract()
123/// 统一调用 fallback(),保证降级路径行为一致。
124///
125/// post_process():walk 结束后的文档注释关联钩子
126/// (Rust 的 /// 注释、Python 的 docstring),其余语言无操作。
127pub trait SharedProcessor: Sized {
128    /// 语言名,用于 name() 与 FileInsight.language(与现状一致,如 "Go")
129    fn language() -> &'static str;
130    /// tree-sitter 语法常量(语言差异点)
131    fn grammar() -> Language;
132    /// kind 映射表(差异点数据化):命中的节点由 record_by_rule 统一提取
133    fn kinds() -> &'static [KindRule];
134    /// 无法数据化的节点处理钩子(动态 kind / 子节点遍历 / 导入解析)
135    fn handle_special(node: Node, bytes: &[u8], entities: &mut Vec<Entity>, imports: &mut Vec<ImportStmt>);
136    /// tree-sitter 失败时的正则降级(差异点钩子,触发契约由 extract 统一)
137    fn fallback(source: &str) -> (Vec<Entity>, Vec<ImportStmt>);
138    /// walk 完成后的文档关联钩子(默认无操作)
139    fn post_process(_source: &str, _entities: &mut Vec<Entity>) {}
140
141    /// 统一 walk 入口:构造 parser → tree-sitter 失败触发 fallback → DFS 遍历
142    ///
143    /// 骨架与原各语言 walk 逐字一致:命中 kinds() 表走 record_by_rule,
144    /// 未命中走 handle_special(等价于原 match 分支的聚合)。
145    fn extract(source: &str) -> (Vec<Entity>, Vec<ImportStmt>) {
146        let bytes = source.as_bytes();
147        let mut entities = Vec::new();
148        let mut imports = Vec::new();
149
150        let mut parser = Parser::new();
151        if parser.set_language(&Self::grammar()).is_err() {
152            let (mut e, i) = Self::fallback(source);
153            fill_visibilities(source, &mut e);
154            return (e, i);
155        }
156        let tree = match parser.parse(source, None) {
157            Some(t) => t,
158            None => return Self::fallback(source),
159        };
160
161        let mut cursor = tree.walk();
162        if !cursor.goto_first_child() { return (entities, imports); }
163
164        'walk: loop {
165            let node = cursor.node();
166            match Self::kinds().iter().find(|r| r.node_kind == node.kind()) {
167                Some(rule) => Self::record_by_rule(node, bytes, rule, &mut entities),
168                None => Self::handle_special(node, bytes, &mut entities, &mut imports),
169            }
170            if cursor.goto_first_child() { continue; }
171            loop {
172                if cursor.goto_next_sibling() { continue 'walk; }
173                if !cursor.goto_parent() { break 'walk; }
174            }
175        }
176
177        Self::post_process(source, &mut entities);
178        fill_visibilities(source, &mut entities);
179        (entities, imports)
180    }
181
182    /// 按 kinds() 规则统一提取实体(与各语言原 match 分支输出一致)
183    fn record_by_rule(node: Node, bytes: &[u8], rule: &KindRule, entities: &mut Vec<Entity>) {
184        if let Some(name) = node.child_by_field_name("name").and_then(|n| n.utf8_text(bytes).ok()) {
185            let sig = if rule.with_signature {
186                node.utf8_text(bytes).ok()
187                    .and_then(|t| t.split(rule.sig_delim).next().map(|s| s.trim().to_string()))
188            } else { None };
189            entities.push(Entity {
190                name: name.to_string(), kind: rule.entity_kind.to_string(),
191                line_start: node.start_position().row + 1,                line_end: node.end_position().row + 1,
192                doc_comment: None, signature: sig, visibility: None,
193            });
194        }
195    }
196
197    /// 统一 FileInsight 组装(empty 早退 + extract),语言侧 parse 一行调用
198    fn parse_file(source: &str, path: &Path) -> Result<FileInsight> {        let language = Self::language();
199        if source.is_empty() {
200            return Ok(FileInsight { path: path.to_path_buf(), language: language.into(), entities: vec![], imports: vec![], doc_comments: vec![], source: source.to_string() });
201        }
202        let (entities, imports) = Self::extract(source);
203        Ok(FileInsight { path: path.to_path_buf(), language: language.into(), entities, imports, doc_comments: vec![], source: source.to_string() })
204    }
205}
206
207/// 从源码文本统一提取实体可见性(解析出口调用,覆盖 record_by_rule /
208/// handle_special / fallback 三条产出路径)
209///
210/// 可见性不在 tree-sitter 节点字段中(各语言语法差异),统一按行级文本
211/// 提取:从实体起始行向上回溯,跳过属性宏行(Rust `#[...]`、C# `[...]`)
212/// 与空行,取首个「修饰符 token」——命中显式可见性声明(Rust pub 系 /
213/// C# Java private·protected·internal / TS JS export)即返回原文;
214/// 未命中(默认可见性,如 Python 无修饰符、Go 大写导出语义)返回 None,
215/// api.md 标注省略。实体起始行即声明行(多行签名首行含可见性),
216/// 回溯只在属性宏场景发生(如 `#[derive]` 前置的 pub struct),
217/// 遇到任何非属性行即停止,不会扫到文件头。
218fn fill_visibilities(source: &str, entities: &mut Vec<Entity>) {
219    let lines: Vec<&str> = source.lines().collect();
220    for e in entities {
221        if e.visibility.is_some() {
222            continue;
223        }
224        let mut i = e.line_start.saturating_sub(1);
225        while let Some(line) = lines.get(i) {
226            let t = line.trim();
227            if t.is_empty() || t.starts_with('#') || t.starts_with('[') {
228                if i == 0 {
229                    break;
230                }
231                i -= 1;
232                continue;
233            }
234            let token = t.split_whitespace().next().unwrap_or("");
235            e.visibility = match token {
236                "pub" | "pub(crate)" | "pub(super)" | "private" | "protected" | "internal" | "export" => {
237                    Some(token.to_string())
238                }
239                _ => None,
240            };
241            break;
242        }
243    }
244}
245
246/// 解析器注册表 — 管理所有内置语言处理器
247pub struct ParserRegistry {
248    parsers: Vec<Box<dyn LanguageProcessor>>,
249}
250
251impl ParserRegistry {
252    /// 创建注册表并注册所有内置处理器
253    pub fn new() -> Self {
254        let mut reg = Self { parsers: Vec::new() };
255        reg.register(Box::new(rust::RustProcessor::new().unwrap()));
256        reg.register(Box::new(typescript::TypeScriptProcessor::new().unwrap()));
257        reg.register(Box::new(python::PythonProcessor::new().unwrap()));
258        reg.register(Box::new(go::GoProcessor::new().unwrap()));
259        reg.register(Box::new(javascript::JavaScriptProcessor::new().unwrap()));
260        reg.register(Box::new(csharp::CSharpProcessor::new().unwrap()));
261        reg.register(Box::new(java::JavaProcessor::new().unwrap()));
262        reg
263    }
264
265    /// 注册自定义处理器
266    pub fn register(&mut self, parser: Box<dyn LanguageProcessor>) {
267        self.parsers.push(parser);
268    }
269
270    /// 根据文件路径查找对应的处理器
271    pub fn get_for_file(&self, path: &Path) -> Option<&dyn LanguageProcessor> {
272        let ext = path.extension()?.to_str()?;
273        let ext_str = format!(".{}", ext);
274        self.parsers.iter().find(|p| p.extensions().contains(&ext_str.as_str())).map(|b| b.as_ref())
275    }
276}
277
278impl Default for ParserRegistry {
279    fn default() -> Self {
280        Self::new()
281    }
282}
283
284#[cfg(test)]
285mod tests {
286    use super::*;
287
288    fn entity(name: &str, start: usize) -> Entity {
289        Entity {
290            name: name.into(),
291            kind: "function".into(),
292            line_start: start,
293            line_end: start,
294            doc_comment: None,
295            signature: None,
296            visibility: None,
297        }
298    }
299
300    #[test]
301    fn test_fill_visibilities_extracts_modifiers() {
302        // 显式修饰符:Rust pub / C# private 按行首 token 提取
303        let src = "pub fn a() {}\n\nprivate int x;\n";
304        let mut es = vec![entity("a", 1), entity("x", 3)];
305        fill_visibilities(src, &mut es);
306        assert_eq!(es[0].visibility.as_deref(), Some("pub"));
307        assert_eq!(es[1].visibility.as_deref(), Some("private"));
308    }
309
310    #[test]
311    fn test_fill_visibilities_skips_attribute_lines() {
312        // 属性宏行(Rust #[derive] / C# [SerializeField])不携带可见性,
313        // 回溯到其上方的 pub / private 声明行
314        let src = "#[derive(Debug)]\npub struct Foo;\n\n[SerializeField]\nprivate float speed;\n";
315        let mut es = vec![entity("Foo", 2), entity("speed", 5)];
316        fill_visibilities(src, &mut es);
317        assert_eq!(es[0].visibility.as_deref(), Some("pub"));
318        assert_eq!(es[1].visibility.as_deref(), Some("private"));
319    }
320
321    #[test]
322    fn test_fill_visibilities_none_without_modifier() {
323        // 无修饰符语言(Python 默认可见性 / Go 大写导出语义)→ None,
324        // api.md 渲染省略可见性标注
325        let src = "def run():\n    pass\n\nfunc Run() {}\n";
326        let mut es = vec![entity("run", 1), entity("Run", 4)];
327        fill_visibilities(src, &mut es);
328        assert!(es[0].visibility.is_none());
329        assert!(es[1].visibility.is_none());
330    }
331
332    #[test]
333    fn test_fill_visibilities_keeps_pub_crate_variant() {
334        // pub(crate)/pub(super) 为完整 token,原样保留
335        let src = "pub(crate) fn internal() {}\npub(super) fn child() {}\n";
336        let mut es = vec![entity("internal", 1), entity("child", 2)];
337        fill_visibilities(src, &mut es);
338        assert_eq!(es[0].visibility.as_deref(), Some("pub(crate)"));
339        assert_eq!(es[1].visibility.as_deref(), Some("pub(super)"));
340    }
341}