debtmap/analyzers/
mod.rs

1use crate::core::{ast::Ast, FileMetrics, FunctionMetrics};
2use crate::priority::file_metrics::FileDebtMetrics;
3use anyhow::Result;
4use std::path::Path;
5
6pub mod batch;
7pub mod call_graph;
8pub mod call_graph_integration;
9pub mod closure_analyzer;
10pub mod context_aware;
11pub mod custom_macro_analyzer;
12pub mod effects;
13pub mod enhanced_analyzer;
14pub mod file_analyzer;
15pub mod function_registry;
16pub mod implementations;
17pub mod io_detector;
18pub mod macro_definition_collector;
19pub mod purity_detector;
20pub mod rust;
21pub mod rust_call_graph;
22pub mod rust_complexity_calculation;
23pub mod rust_constructor_detector;
24pub mod rust_data_flow_analyzer;
25pub mod rust_enum_converter_detector;
26pub mod scope_tracker;
27pub mod signature_extractor;
28pub mod state_field_detector;
29pub mod state_machine_pattern_detector;
30pub mod test_detector;
31pub mod trait_implementation_tracker;
32pub mod trait_resolver;
33pub mod traits;
34pub mod type_registry;
35pub mod type_tracker;
36pub mod validation_pattern_detector;
37
38pub use enhanced_analyzer::{AnalysisResult, EnhancedAnalyzer};
39
40pub trait Analyzer: Send + Sync {
41    fn parse(&self, content: &str, path: std::path::PathBuf) -> Result<Ast>;
42    fn analyze(&self, ast: &Ast) -> FileMetrics;
43    fn language(&self) -> crate::core::Language;
44}
45
46pub trait FileAnalyzer {
47    fn analyze_file(&self, path: &Path, content: &str) -> Result<FileDebtMetrics>;
48    fn aggregate_functions(&self, functions: &[FunctionMetrics]) -> FileDebtMetrics;
49}
50
51pub fn analyze_file(
52    content: String,
53    path: std::path::PathBuf,
54    analyzer: &dyn Analyzer,
55) -> Result<FileMetrics> {
56    analyzer
57        .parse(&content, path.clone())
58        .map(transform_ast)
59        .map(|ast| analyzer.analyze(&ast))
60        .map(apply_filters)
61}
62
63fn transform_ast(ast: Ast) -> Ast {
64    ast.transform(|a| a)
65}
66
67fn apply_filters(metrics: FileMetrics) -> FileMetrics {
68    metrics
69}
70
71type Parser = Box<dyn Fn(&str) -> Result<Ast>>;
72type Transformer = Box<dyn Fn(Ast) -> Ast>;
73type Calculator = Box<dyn Fn(&Ast) -> FileMetrics>;
74
75pub fn compose_analyzers(
76    parsers: Vec<Parser>,
77    transformers: Vec<Transformer>,
78    calculators: Vec<Calculator>,
79) -> impl Fn(&str) -> Result<FileMetrics> {
80    move |content: &str| {
81        let ast = parsers[0](content)?;
82        let transformed = transformers.iter().fold(ast, |acc, f| f(acc));
83        Ok(calculators[0](&transformed))
84    }
85}
86
87pub fn get_analyzer(language: crate::core::Language) -> Box<dyn Analyzer> {
88    use crate::core::Language;
89
90    type AnalyzerFactory = fn() -> Box<dyn Analyzer>;
91
92    static ANALYZER_MAP: &[(Language, AnalyzerFactory)] = &[(Language::Rust, || {
93        let mut analyzer = rust::RustAnalyzer::new();
94        // Check environment variable for functional analysis (spec 111)
95        if std::env::var("DEBTMAP_FUNCTIONAL_ANALYSIS").is_ok() {
96            analyzer = analyzer.with_functional_analysis(true);
97        }
98        Box::new(analyzer)
99    })];
100
101    ANALYZER_MAP
102        .iter()
103        .find(|(lang, _)| *lang == language)
104        .map(|(_, factory)| factory())
105        .unwrap_or_else(|| Box::new(NullAnalyzer))
106}
107
108pub fn get_analyzer_with_context(
109    language: crate::core::Language,
110    context_aware: bool,
111) -> Box<dyn Analyzer> {
112    let base_analyzer = get_analyzer(language);
113
114    if context_aware {
115        Box::new(context_aware::ContextAwareAnalyzer::new(base_analyzer))
116    } else {
117        base_analyzer
118    }
119}
120
121struct NullAnalyzer;
122
123impl Analyzer for NullAnalyzer {
124    fn parse(&self, _content: &str, _path: std::path::PathBuf) -> Result<Ast> {
125        Ok(Ast::Unknown)
126    }
127
128    fn analyze(&self, _ast: &Ast) -> FileMetrics {
129        FileMetrics {
130            path: std::path::PathBuf::new(),
131            language: crate::core::Language::Unknown,
132            complexity: crate::core::ComplexityMetrics::default(),
133            debt_items: vec![],
134            dependencies: vec![],
135            duplications: vec![],
136            module_scope: None,
137            classes: None,
138        }
139    }
140
141    fn language(&self) -> crate::core::Language {
142        crate::core::Language::Unknown
143    }
144}
145
146#[cfg(test)]
147mod tests {
148    use super::*;
149    use std::path::PathBuf;
150
151    #[test]
152    fn test_transform_ast() {
153        let ast = Ast::Unknown;
154        let result = transform_ast(ast);
155        assert!(matches!(result, Ast::Unknown));
156    }
157
158    #[test]
159    fn test_apply_filters() {
160        let metrics = FileMetrics {
161            path: PathBuf::from("test.rs"),
162            language: crate::core::Language::Rust,
163            complexity: crate::core::ComplexityMetrics::default(),
164            debt_items: vec![],
165            dependencies: vec![],
166            duplications: vec![],
167            module_scope: None,
168            classes: None,
169        };
170        let result = apply_filters(metrics.clone());
171        assert_eq!(result.path, metrics.path);
172        assert_eq!(result.language, metrics.language);
173    }
174
175    #[test]
176    fn test_get_analyzer_rust() {
177        let analyzer = get_analyzer(crate::core::Language::Rust);
178        assert_eq!(analyzer.language(), crate::core::Language::Rust);
179    }
180
181    #[test]
182    fn test_get_analyzer_unknown() {
183        let analyzer = get_analyzer(crate::core::Language::Unknown);
184        assert_eq!(analyzer.language(), crate::core::Language::Unknown);
185    }
186
187    #[test]
188    fn test_null_analyzer_parse() {
189        let analyzer = NullAnalyzer;
190        let result = analyzer
191            .parse("test content", PathBuf::from("test.txt"))
192            .unwrap();
193        assert!(matches!(result, Ast::Unknown));
194    }
195
196    #[test]
197    fn test_null_analyzer_analyze() {
198        let analyzer = NullAnalyzer;
199        let ast = Ast::Unknown;
200        let metrics = analyzer.analyze(&ast);
201        assert_eq!(metrics.path, PathBuf::new());
202        assert_eq!(metrics.language, crate::core::Language::Unknown);
203        assert_eq!(metrics.complexity.functions.len(), 0);
204        assert_eq!(metrics.debt_items.len(), 0);
205        assert_eq!(metrics.dependencies.len(), 0);
206        assert_eq!(metrics.duplications.len(), 0);
207    }
208
209    #[test]
210    fn test_null_analyzer_language() {
211        let analyzer = NullAnalyzer;
212        assert_eq!(analyzer.language(), crate::core::Language::Unknown);
213    }
214
215    #[test]
216    fn test_analyze_file() {
217        let analyzer = NullAnalyzer;
218        let content = String::from("test content");
219        let path = PathBuf::from("test.txt");
220        let result = analyze_file(content, path.clone(), &analyzer).unwrap();
221        assert_eq!(result.language, crate::core::Language::Unknown);
222    }
223
224    #[test]
225    fn test_compose_analyzers() {
226        let parsers: Vec<Parser> = vec![Box::new(|_| Ok(Ast::Unknown))];
227        let transformers: Vec<Transformer> = vec![Box::new(|ast| ast)];
228        let calculators: Vec<Calculator> = vec![Box::new(|_| FileMetrics {
229            path: PathBuf::from("test.rs"),
230            language: crate::core::Language::Rust,
231            complexity: crate::core::ComplexityMetrics::default(),
232            debt_items: vec![],
233            dependencies: vec![],
234            duplications: vec![],
235            module_scope: None,
236            classes: None,
237        })];
238
239        let analyzer = compose_analyzers(parsers, transformers, calculators);
240        let result = analyzer("test content").unwrap();
241        assert_eq!(result.path, PathBuf::from("test.rs"));
242        assert_eq!(result.language, crate::core::Language::Rust);
243    }
244}