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 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}