1use anyhow::Result;
4use rig::tool::Tool;
5use serde::{Deserialize, Serialize};
6use std::path::Path;
7
8#[derive(Debug, Clone)]
10pub struct CodeAnalyzerTool {
11 project_root: std::path::PathBuf,
12}
13
14#[derive(Debug, Deserialize)]
16pub struct CodeAnalyzerArgs {
17 pub action: String, pub file_path: String,
19 pub language: Option<String>,
20 pub function_name: Option<String>,
21}
22
23#[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#[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#[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#[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#[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 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, parameters: Vec::new(), return_type: None, 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 }; 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 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 }; 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 if trimmed.contains(" => ") {
267 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 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 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 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}