ferrous_forge/ai_analyzer/
context.rs

1use super::types::{CodeContext, ErrorHandlingStyle};
2
3/// Extract code context around a violation line
4pub fn extract_code_context(line_number: usize, content: &str) -> CodeContext {
5    let lines: Vec<&str> = content.lines().collect();
6    let context_start = line_number.saturating_sub(10);
7    let context_end = (line_number + 10).min(lines.len());
8
9    let surrounding_code = lines[context_start..context_end]
10        .iter()
11        .map(|s| s.to_string())
12        .collect();
13
14    let imports = extract_imports(content);
15    let (function_name, function_signature, return_type) =
16        extract_function_info(&lines, line_number);
17    let is_async = function_signature
18        .as_ref()
19        .map(|s| s.contains("async"))
20        .unwrap_or(false);
21    let is_generic = function_signature
22        .as_ref()
23        .map(|s| s.contains('<'))
24        .unwrap_or(false);
25    let trait_impl = detect_trait_impl(content, line_number);
26    let error_handling_style = detect_error_handling_style(&imports, content);
27
28    CodeContext {
29        function_name,
30        function_signature,
31        return_type,
32        is_async,
33        is_generic,
34        trait_impl,
35        surrounding_code,
36        imports,
37        error_handling_style,
38    }
39}
40
41fn extract_imports(content: &str) -> Vec<String> {
42    content
43        .lines()
44        .filter(|line| line.trim().starts_with("use "))
45        .map(|s| s.to_string())
46        .collect()
47}
48
49fn extract_function_info(
50    lines: &[&str],
51    line_number: usize,
52) -> (Option<String>, Option<String>, Option<String>) {
53    for i in (0..line_number).rev() {
54        if let Some(line) = lines.get(i) {
55            if line.contains("fn ") && !line.trim().starts_with("//") {
56                let signature = line.trim().to_string();
57                let name = signature
58                    .split("fn ")
59                    .nth(1)
60                    .and_then(|s| s.split('(').next())
61                    .map(|s| s.trim().to_string());
62                let return_type = if signature.contains("->") {
63                    signature.split("->").nth(1).map(|s| s.trim().to_string())
64                } else {
65                    None
66                };
67                return (name, Some(signature), return_type);
68            }
69        }
70    }
71    (None, None, None)
72}
73
74/// Detect the error handling style used in the code
75pub fn detect_error_handling_style(imports: &[String], content: &str) -> ErrorHandlingStyle {
76    if imports.iter().any(|i| i.contains("anyhow")) || content.contains("anyhow::Result") {
77        ErrorHandlingStyle::AnyhowResult
78    } else if content.contains("Result<") && !content.contains("std::result::Result") {
79        ErrorHandlingStyle::CustomResult
80    } else if content.contains("Result<") {
81        ErrorHandlingStyle::StdResult
82    } else if content.contains("Option<") {
83        ErrorHandlingStyle::OptionBased
84    } else if content.contains("panic!") || content.contains(".unwrap()") {
85        ErrorHandlingStyle::Panic
86    } else {
87        ErrorHandlingStyle::Unknown
88    }
89}
90
91fn detect_trait_impl(content: &str, line: usize) -> Option<String> {
92    let lines: Vec<&str> = content.lines().collect();
93    for i in (0..line.min(lines.len())).rev() {
94        if lines[i].contains("impl") && lines[i].contains("for") {
95            return Some(lines[i].trim().to_string());
96        }
97    }
98    None
99}