deepwiki_rs/react/tools/
code_analyzer.rs

1//! 代码分析工具
2
3use anyhow::Result;
4use rig::tool::Tool;
5use serde::{Deserialize, Serialize};
6use std::path::Path;
7
8/// 代码分析工具
9#[derive(Debug, Clone)]
10pub struct CodeAnalyzerTool {
11    project_root: std::path::PathBuf,
12}
13
14/// 代码分析参数
15#[derive(Debug, Deserialize)]
16pub struct CodeAnalyzerArgs {
17    pub action: String, // "analyze_file", "extract_dependencies", "find_functions"
18    pub file_path: String,
19    pub language: Option<String>,
20    pub function_name: Option<String>,
21}
22
23/// 代码分析结果
24#[derive(Debug, Serialize, Default)]
25pub struct CodeAnalyzerResult {
26    pub file_path: String,
27    pub language: String,
28    pub functions: Vec<FunctionInfo>,
29    pub imports: Vec<ImportInfo>,
30    pub classes: Vec<ClassInfo>,
31    pub dependencies: Vec<DependencyInfo>,
32    pub complexity_score: f64,
33    pub insights: Vec<String>,
34}
35
36/// 函数信息
37#[derive(Debug, Serialize, Clone)]
38pub struct FunctionInfo {
39    pub name: String,
40    pub line_start: usize,
41    pub line_end: usize,
42    pub parameters: Vec<String>,
43    pub return_type: Option<String>,
44    pub visibility: String,
45    pub is_async: bool,
46}
47
48/// 导入信息
49#[derive(Debug, Serialize, Clone)]
50pub struct ImportInfo {
51    pub module: String,
52    pub items: Vec<String>,
53    pub alias: Option<String>,
54    pub is_external: bool,
55}
56
57/// 类信息
58#[derive(Debug, Serialize, Clone)]
59pub struct ClassInfo {
60    pub name: String,
61    pub line_start: usize,
62    pub line_end: usize,
63    pub methods: Vec<String>,
64    pub fields: Vec<String>,
65    pub inheritance: Vec<String>,
66}
67
68/// 依赖信息
69#[derive(Debug, Serialize, Clone)]
70pub struct DependencyInfo {
71    pub name: String,
72    pub dependency_type: String,
73    pub version: Option<String>,
74    pub source: String,
75}
76
77impl CodeAnalyzerTool {
78    pub fn new(project_root: std::path::PathBuf) -> Self {
79        Self { project_root }
80    }
81
82    async fn analyze_file(&self, args: &CodeAnalyzerArgs) -> Result<CodeAnalyzerResult> {
83        let file_path = self.project_root.join(&args.file_path);
84
85        if !file_path.exists() {
86            return Ok(CodeAnalyzerResult {
87                insights: vec![format!("文件不存在: {}", args.file_path)],
88                file_path: args.file_path.clone(),
89                ..Default::default()
90            });
91        }
92
93        if crate::utils::fs::is_binary_file_path(&file_path) {
94            return Ok(CodeAnalyzerResult {
95                insights: vec![format!("无法分析二进制文件: {}", args.file_path)],
96                file_path: args.file_path.clone(),
97                ..Default::default()
98            });
99        }
100
101        let content = tokio::fs::read_to_string(&file_path).await?;
102        let language = args
103            .language
104            .clone()
105            .unwrap_or_else(|| self.detect_language(&file_path));
106
107        let functions = self.extract_functions(&content, &language);
108        let imports = self.extract_imports(&content, &language);
109        let classes = self.extract_classes(&content, &language);
110        let dependencies = self.extract_dependencies(&content, &language);
111        let complexity_score = self.calculate_complexity(&content, &language);
112        let insights = self.generate_insights(&functions, &imports, &classes, &language);
113
114        Ok(CodeAnalyzerResult {
115            file_path: args.file_path.clone(),
116            language,
117            functions,
118            imports,
119            classes,
120            dependencies,
121            complexity_score,
122            insights,
123        })
124    }
125
126    fn detect_language(&self, file_path: &Path) -> String {
127        if let Some(ext) = file_path.extension().and_then(|e| e.to_str()) {
128            match ext {
129                "rs" => "rust".to_string(),
130                "py" => "python".to_string(),
131                "js" => "javascript".to_string(),
132                "ts" => "typescript".to_string(),
133                "java" => "java".to_string(),
134                "cpp" | "cc" | "cxx" => "cpp".to_string(),
135                "c" => "c".to_string(),
136                "go" => "go".to_string(),
137                _ => "unknown".to_string(),
138            }
139        } else {
140            "unknown".to_string()
141        }
142    }
143
144    fn extract_functions(&self, content: &str, language: &str) -> Vec<FunctionInfo> {
145        let mut functions = Vec::new();
146
147        match language {
148            "rust" => {
149                for (line_num, line) in content.lines().enumerate() {
150                    if let Some(func_info) = self.parse_rust_function(line, line_num + 1) {
151                        functions.push(func_info);
152                    }
153                }
154            }
155            "python" => {
156                for (line_num, line) in content.lines().enumerate() {
157                    if let Some(func_info) = self.parse_python_function(line, line_num + 1) {
158                        functions.push(func_info);
159                    }
160                }
161            }
162            "javascript" | "typescript" => {
163                for (line_num, line) in content.lines().enumerate() {
164                    if let Some(func_info) = self.parse_js_function(line, line_num + 1) {
165                        functions.push(func_info);
166                    }
167                }
168            }
169            _ => {}
170        }
171
172        functions
173    }
174
175    fn parse_rust_function(&self, line: &str, line_num: usize) -> Option<FunctionInfo> {
176        let trimmed = line.trim();
177        if trimmed.starts_with("fn ")
178            || trimmed.starts_with("pub fn ")
179            || trimmed.starts_with("async fn ")
180            || trimmed.starts_with("pub async fn ")
181        {
182            let is_async = trimmed.contains("async");
183            let visibility = if trimmed.starts_with("pub") {
184                "public"
185            } else {
186                "private"
187            }
188            .to_string();
189
190            // 简单的函数名提取
191            if let Some(name_start) = trimmed.find("fn ") {
192                let after_fn = &trimmed[name_start + 3..];
193                if let Some(paren_pos) = after_fn.find('(') {
194                    let name = after_fn[..paren_pos].trim().to_string();
195                    return Some(FunctionInfo {
196                        name,
197                        line_start: line_num,
198                        line_end: line_num,     // 简化处理
199                        parameters: Vec::new(), // 简化处理
200                        return_type: None,      // 简化处理
201                        visibility,
202                        is_async,
203                    });
204                }
205            }
206        }
207        None
208    }
209
210    fn parse_python_function(&self, line: &str, line_num: usize) -> Option<FunctionInfo> {
211        let trimmed = line.trim();
212        if trimmed.starts_with("def ") || trimmed.starts_with("async def ") {
213            let is_async = trimmed.starts_with("async");
214            let def_start = if is_async { 10 } else { 4 }; // "async def " or "def "
215
216            if let Some(paren_pos) = trimmed.find('(') {
217                if paren_pos > def_start {
218                    let name = trimmed[def_start..paren_pos].trim().to_string();
219                    let visibility = if name.starts_with('_') {
220                        "private"
221                    } else {
222                        "public"
223                    }
224                    .to_string();
225
226                    return Some(FunctionInfo {
227                        name,
228                        line_start: line_num,
229                        line_end: line_num,
230                        parameters: Vec::new(),
231                        return_type: None,
232                        visibility,
233                        is_async,
234                    });
235                }
236            }
237        }
238        None
239    }
240
241    fn parse_js_function(&self, line: &str, line_num: usize) -> Option<FunctionInfo> {
242        let trimmed = line.trim();
243
244        // function declaration
245        if trimmed.starts_with("function ") || trimmed.starts_with("async function ") {
246            let is_async = trimmed.starts_with("async");
247            let func_start = if is_async { 15 } else { 9 }; // "async function " or "function "
248
249            if let Some(paren_pos) = trimmed.find('(') {
250                if paren_pos > func_start {
251                    let name = trimmed[func_start..paren_pos].trim().to_string();
252                    return Some(FunctionInfo {
253                        name,
254                        line_start: line_num,
255                        line_end: line_num,
256                        parameters: Vec::new(),
257                        return_type: None,
258                        visibility: "public".to_string(),
259                        is_async,
260                    });
261                }
262            }
263        }
264
265        // arrow function
266        if trimmed.contains(" => ") {
267            // 简化的箭头函数检测
268            if let Some(arrow_pos) = trimmed.find(" => ") {
269                let before_arrow = &trimmed[..arrow_pos];
270                if let Some(eq_pos) = before_arrow.rfind('=') {
271                    let name_part = &before_arrow[..eq_pos].trim();
272                    if let Some(name) = name_part.split_whitespace().last() {
273                        return Some(FunctionInfo {
274                            name: name.to_string(),
275                            line_start: line_num,
276                            line_end: line_num,
277                            parameters: Vec::new(),
278                            return_type: None,
279                            visibility: "public".to_string(),
280                            is_async: trimmed.contains("async"),
281                        });
282                    }
283                }
284            }
285        }
286
287        None
288    }
289
290    fn extract_imports(&self, content: &str, language: &str) -> Vec<ImportInfo> {
291        let mut imports = Vec::new();
292
293        match language {
294            "rust" => {
295                for line in content.lines() {
296                    if let Some(import_info) = self.parse_rust_import(line) {
297                        imports.push(import_info);
298                    }
299                }
300            }
301            "python" => {
302                for line in content.lines() {
303                    if let Some(import_info) = self.parse_python_import(line) {
304                        imports.push(import_info);
305                    }
306                }
307            }
308            "javascript" | "typescript" => {
309                for line in content.lines() {
310                    if let Some(import_info) = self.parse_js_import(line) {
311                        imports.push(import_info);
312                    }
313                }
314            }
315            _ => {}
316        }
317
318        imports
319    }
320
321    fn parse_rust_import(&self, line: &str) -> Option<ImportInfo> {
322        let trimmed = line.trim();
323        if trimmed.starts_with("use ") {
324            let use_part = &trimmed[4..];
325            if let Some(semicolon_pos) = use_part.find(';') {
326                let import_path = use_part[..semicolon_pos].trim();
327                let is_external = !import_path.starts_with("crate")
328                    && !import_path.starts_with("super")
329                    && !import_path.starts_with("self");
330
331                return Some(ImportInfo {
332                    module: import_path.to_string(),
333                    items: Vec::new(),
334                    alias: None,
335                    is_external,
336                });
337            }
338        }
339        None
340    }
341
342    fn parse_python_import(&self, line: &str) -> Option<ImportInfo> {
343        let trimmed = line.trim();
344        if trimmed.starts_with("import ") {
345            let import_part = &trimmed[7..];
346            let module = import_part.split_whitespace().next()?.to_string();
347            let is_external = !module.starts_with('.');
348
349            return Some(ImportInfo {
350                module,
351                items: Vec::new(),
352                alias: None,
353                is_external,
354            });
355        } else if trimmed.starts_with("from ") {
356            if let Some(import_pos) = trimmed.find(" import ") {
357                let module = trimmed[5..import_pos].trim().to_string();
358                let is_external = !module.starts_with('.');
359
360                return Some(ImportInfo {
361                    module,
362                    items: Vec::new(),
363                    alias: None,
364                    is_external,
365                });
366            }
367        }
368        None
369    }
370
371    fn parse_js_import(&self, line: &str) -> Option<ImportInfo> {
372        let trimmed = line.trim();
373        if trimmed.starts_with("import ") {
374            // 简化的import解析
375            if let Some(from_pos) = trimmed.find(" from ") {
376                let module_part = &trimmed[from_pos + 6..];
377                let module = module_part
378                    .trim_matches(|c| c == '"' || c == '\'' || c == ';')
379                    .to_string();
380                let is_external = !module.starts_with("./") && !module.starts_with("../");
381
382                return Some(ImportInfo {
383                    module,
384                    items: Vec::new(),
385                    alias: None,
386                    is_external,
387                });
388            }
389        }
390        None
391    }
392
393    fn extract_classes(&self, content: &str, language: &str) -> Vec<ClassInfo> {
394        let mut classes = Vec::new();
395
396        match language {
397            "python" => {
398                for (line_num, line) in content.lines().enumerate() {
399                    if let Some(class_info) = self.parse_python_class(line, line_num + 1) {
400                        classes.push(class_info);
401                    }
402                }
403            }
404            "javascript" | "typescript" => {
405                for (line_num, line) in content.lines().enumerate() {
406                    if let Some(class_info) = self.parse_js_class(line, line_num + 1) {
407                        classes.push(class_info);
408                    }
409                }
410            }
411            _ => {}
412        }
413
414        classes
415    }
416
417    fn parse_python_class(&self, line: &str, line_num: usize) -> Option<ClassInfo> {
418        let trimmed = line.trim();
419        if trimmed.starts_with("class ") {
420            if let Some(colon_pos) = trimmed.find(':') {
421                let class_part = &trimmed[6..colon_pos];
422                let name = if let Some(paren_pos) = class_part.find('(') {
423                    class_part[..paren_pos].trim().to_string()
424                } else {
425                    class_part.trim().to_string()
426                };
427
428                return Some(ClassInfo {
429                    name,
430                    line_start: line_num,
431                    line_end: line_num,
432                    methods: Vec::new(),
433                    fields: Vec::new(),
434                    inheritance: Vec::new(),
435                });
436            }
437        }
438        None
439    }
440
441    fn parse_js_class(&self, line: &str, line_num: usize) -> Option<ClassInfo> {
442        let trimmed = line.trim();
443        if trimmed.starts_with("class ") {
444            let class_part = &trimmed[6..];
445            let name = if let Some(space_pos) = class_part.find(' ') {
446                class_part[..space_pos].trim().to_string()
447            } else if let Some(brace_pos) = class_part.find('{') {
448                class_part[..brace_pos].trim().to_string()
449            } else {
450                class_part.trim().to_string()
451            };
452
453            return Some(ClassInfo {
454                name,
455                line_start: line_num,
456                line_end: line_num,
457                methods: Vec::new(),
458                fields: Vec::new(),
459                inheritance: Vec::new(),
460            });
461        }
462        None
463    }
464
465    fn extract_dependencies(&self, _content: &str, _language: &str) -> Vec<DependencyInfo> {
466        // 简化实现,实际应该解析配置文件
467        Vec::new()
468    }
469
470    fn calculate_complexity(&self, content: &str, _language: &str) -> f64 {
471        let lines = content.lines().count();
472        let complexity_keywords = ["if", "else", "for", "while", "match", "switch", "case"];
473        let complexity_count = content
474            .split_whitespace()
475            .filter(|word| complexity_keywords.contains(word))
476            .count();
477
478        (complexity_count as f64 / lines.max(1) as f64) * 100.0
479    }
480
481    fn generate_insights(
482        &self,
483        functions: &[FunctionInfo],
484        imports: &[ImportInfo],
485        classes: &[ClassInfo],
486        language: &str,
487    ) -> Vec<String> {
488        let mut insights = Vec::new();
489
490        insights.push(format!("检测到 {} 编程语言", language));
491        insights.push(format!("发现 {} 个函数", functions.len()));
492        insights.push(format!("发现 {} 个导入", imports.len()));
493        insights.push(format!("发现 {} 个类", classes.len()));
494
495        let async_functions = functions.iter().filter(|f| f.is_async).count();
496        if async_functions > 0 {
497            insights.push(format!("包含 {} 个异步函数", async_functions));
498        }
499
500        let external_imports = imports.iter().filter(|i| i.is_external).count();
501        if external_imports > 0 {
502            insights.push(format!("使用了 {} 个外部依赖", external_imports));
503        }
504
505        if functions.len() > 20 {
506            insights.push("函数数量较多,可能是核心模块".to_string());
507        }
508
509        insights
510    }
511
512    async fn extract_dependencies_action(&self, args: &CodeAnalyzerArgs) -> Result<CodeAnalyzerResult> {
513        let file_path = self.project_root.join(&args.file_path);
514
515        if !file_path.exists() {
516            return Ok(CodeAnalyzerResult {
517                insights: vec![format!("文件不存在: {}", args.file_path)],
518                file_path: args.file_path.clone(),
519                ..Default::default()
520            });
521        }
522
523        if crate::utils::fs::is_binary_file_path(&file_path) {
524            return Ok(CodeAnalyzerResult {
525                insights: vec![format!("无法分析二进制文件: {}", args.file_path)],
526                file_path: args.file_path.clone(),
527                ..Default::default()
528            });
529        }
530
531        let content = tokio::fs::read_to_string(&file_path).await?;
532        let language = args
533            .language
534            .clone()
535            .unwrap_or_else(|| self.detect_language(&file_path));
536
537        let dependencies = self.extract_dependencies(&content, &language);
538        let imports = self.extract_imports(&content, &language);
539
540        let insights = vec![
541            format!("提取到 {} 个依赖项", dependencies.len()),
542            format!("提取到 {} 个导入语句", imports.len()),
543        ];
544
545        Ok(CodeAnalyzerResult {
546            file_path: args.file_path.clone(),
547            language,
548            functions: Vec::new(),
549            imports,
550            classes: Vec::new(),
551            dependencies,
552            complexity_score: 0.0,
553            insights,
554        })
555    }
556
557    async fn find_functions_action(&self, args: &CodeAnalyzerArgs) -> Result<CodeAnalyzerResult> {
558        let file_path = self.project_root.join(&args.file_path);
559
560        if !file_path.exists() {
561            return Ok(CodeAnalyzerResult {
562                insights: vec![format!("文件不存在: {}", args.file_path)],
563                file_path: args.file_path.clone(),
564                ..Default::default()
565            });
566        }
567
568        if crate::utils::fs::is_binary_file_path(&file_path) {
569            return Ok(CodeAnalyzerResult {
570                insights: vec![format!("无法分析二进制文件: {}", args.file_path)],
571                file_path: args.file_path.clone(),
572                ..Default::default()
573            });
574        }
575
576        let content = tokio::fs::read_to_string(&file_path).await?;
577        let language = args
578            .language
579            .clone()
580            .unwrap_or_else(|| self.detect_language(&file_path));
581
582        let mut functions = self.extract_functions(&content, &language);
583
584        // 如果指定了函数名,则过滤结果
585        if let Some(function_name) = &args.function_name {
586            functions.retain(|f| f.name.contains(function_name));
587        }
588
589        let insights = if let Some(function_name) = &args.function_name {
590            vec![
591                format!("搜索函数名包含 '{}' 的函数", function_name),
592                format!("找到 {} 个匹配的函数", functions.len()),
593            ]
594        } else {
595            vec![format!("找到 {} 个函数", functions.len())]
596        };
597
598        Ok(CodeAnalyzerResult {
599            file_path: args.file_path.clone(),
600            language,
601            functions,
602            imports: Vec::new(),
603            classes: Vec::new(),
604            dependencies: Vec::new(),
605            complexity_score: 0.0,
606            insights,
607        })
608    }
609}
610
611#[derive(Debug, thiserror::Error)]
612#[error("code analyzer tool error")]
613pub struct CodeAnalyzerToolError;
614
615impl Tool for CodeAnalyzerTool {
616    const NAME: &'static str = "code_analyzer";
617
618    type Error = CodeAnalyzerToolError;
619    type Args = CodeAnalyzerArgs;
620    type Output = CodeAnalyzerResult;
621
622    async fn definition(&self, _prompt: String) -> rig::completion::ToolDefinition {
623        rig::completion::ToolDefinition {
624            name: Self::NAME.to_string(),
625            description:
626                "分析代码文件,提取函数、类、导入等信息,计算复杂度并生成洞察。支持多种编程语言包括Rust、Python、JavaScript/TypeScript、Java、C/C++、Go等。"
627                    .to_string(),
628            parameters: serde_json::json!({
629                "type": "object",
630                "properties": {
631                    "action": {
632                        "type": "string",
633                        "enum": ["analyze_file", "extract_dependencies", "find_functions"],
634                        "description": "要执行的操作类型:analyze_file(分析文件), extract_dependencies(提取依赖), find_functions(查找函数)"
635                    },
636                    "file_path": {
637                        "type": "string",
638                        "description": "要分析的文件路径(相对于项目根目录)"
639                    },
640                    "language": {
641                        "type": "string",
642                        "description": "编程语言类型(可选,如果不指定会自动检测)"
643                    },
644                    "function_name": {
645                        "type": "string",
646                        "description": "要查找的特定函数名(用于find_functions操作)"
647                    }
648                },
649                "required": ["action", "file_path"]
650            }),
651        }
652    }
653
654    async fn call(&self, args: Self::Args) -> Result<Self::Output, Self::Error> {
655        match args.action.as_str() {
656            "analyze_file" => self
657                .analyze_file(&args)
658                .await
659                .map_err(|_e| CodeAnalyzerToolError),
660            "extract_dependencies" => self
661                .extract_dependencies_action(&args)
662                .await
663                .map_err(|_e| CodeAnalyzerToolError),
664            "find_functions" => self
665                .find_functions_action(&args)
666                .await
667                .map_err(|_e| CodeAnalyzerToolError),
668            _ => Err(CodeAnalyzerToolError),
669        }
670    }
671}