Skip to main content

forgekit_core/analysis/
mod.rs

1//! Analysis module - Combined analysis operations
2//!
3//! This module provides composite operations using graph, search, cfg, and edit modules.
4
5use crate::cfg::CfgModule;
6use crate::edit::EditModule;
7use crate::error::Result;
8use crate::graph::GraphModule;
9use crate::search::SearchModule;
10use crate::types::Symbol;
11use std::sync::Arc;
12use std::time::Instant;
13
14pub mod complexity;
15pub mod dead_code;
16pub mod diff;
17pub mod impact;
18pub mod modules;
19
20pub use complexity::{ComplexityMetrics, RiskLevel};
21pub use dead_code::{DeadCodeAnalyzer, DeadSymbol};
22pub use diff::{
23    DeleteOperation, Diff, EditOperation, ErrorResult, InsertOperation, RenameOperation,
24};
25pub use impact::{CallChain, CrossReferences, ImpactAnalysis, ImpactData, ReferenceChain};
26pub use modules::{ModuleAnalyzer, ModuleDependencyGraph, ModuleInfo};
27
28/// Analysis module for combined operations.
29pub struct AnalysisModule {
30    graph: GraphModule,
31    search: SearchModule,
32    cfg: CfgModule,
33    edit: EditModule,
34}
35
36/// Performance benchmark results.
37#[derive(Debug, Clone)]
38pub struct BenchmarkResults {
39    /// Time to perform impact analysis
40    pub impact_analysis_ms: f64,
41    /// Time to find dead code
42    pub dead_code_ms: f64,
43    /// Time to compute reference chain
44    pub reference_chain_ms: f64,
45    /// Time to compute call chain
46    pub call_chain_ms: f64,
47    /// Total benchmark time
48    pub total_ms: f64,
49}
50
51/// Result of applying an edit operation.
52#[derive(Debug, Clone, PartialEq, Eq)]
53pub enum ApplyResult {
54    /// Operation was applied successfully
55    Applied,
56    /// Operation always returns an error
57    AlwaysError,
58    /// Operation is pending verification
59    Pending,
60    /// Operation failed with reason
61    Failed(String),
62}
63
64/// Module dependency.
65#[derive(Debug, Clone)]
66pub struct ModuleDependency {
67    /// Source module
68    pub from: String,
69    /// Target module
70    pub to: String,
71}
72
73impl AnalysisModule {
74    /// Create a new AnalysisModule.
75    pub fn new(graph: GraphModule, cfg: CfgModule, edit: EditModule, search: SearchModule) -> Self {
76        Self {
77            graph,
78            search,
79            cfg,
80            edit,
81        }
82    }
83
84    /// Get the graph module
85    pub fn graph(&self) -> &GraphModule {
86        &self.graph
87    }
88
89    /// Get the search module
90    pub fn search(&self) -> &SearchModule {
91        &self.search
92    }
93
94    /// Get the CFG module
95    pub fn cfg(&self) -> &CfgModule {
96        &self.cfg
97    }
98
99    /// Get the edit module
100    pub fn edit(&self) -> &EditModule {
101        &self.edit
102    }
103
104    /// Analyze the impact of changing a symbol.
105    ///
106    /// Returns detailed impact data including references, calls, and impact score.
107    pub async fn impact_analysis(&self, symbol: &str) -> Result<ImpactData> {
108        let start = Instant::now();
109
110        // Get all callers
111        let callers = self.graph.callers_of(symbol).await.unwrap_or_default();
112
113        // Get all references
114        let refs = self.graph.references(symbol).await.unwrap_or_default();
115
116        // Also search for the symbol to get its metadata
117        let _symbol_info = self
118            .graph
119            .find_symbol(symbol)
120            .await
121            .unwrap_or_default()
122            .into_iter()
123            .next();
124
125        // Compute impact score based on reference and call counts
126        let ref_count = refs.len();
127        let call_count = callers.len();
128        let impact_score = ref_count + call_count * 2; // Calls weigh more
129
130        tracing::debug!(
131            "Impact analysis for '{}' completed in {:?}",
132            symbol,
133            start.elapsed()
134        );
135
136        Ok(ImpactData {
137            symbol: symbol.to_string(),
138            ref_count,
139            call_count,
140            referenced_by: callers
141                .into_iter()
142                .map(|r| {
143                    let name: Arc<str> = Arc::from(r.from_name.unwrap_or_default());
144                    Symbol {
145                        id: r.from,
146                        name: name.clone(),
147                        fully_qualified_name: name,
148                        kind: crate::types::SymbolKind::Function,
149                        language: crate::types::Language::Unknown("unknown".to_string()),
150                        location: r.location,
151                        parent_id: None,
152                        metadata: serde_json::Value::Null,
153                    }
154                })
155                .collect(),
156            references: refs
157                .into_iter()
158                .map(|r| {
159                    let name: Arc<str> = Arc::from(r.to_name.unwrap_or_default());
160                    Symbol {
161                        id: r.to,
162                        name: name.clone(),
163                        fully_qualified_name: name,
164                        kind: crate::types::SymbolKind::Function,
165                        language: crate::types::Language::Unknown("unknown".to_string()),
166                        location: r.location,
167                        parent_id: None,
168                        metadata: serde_json::Value::Null,
169                    }
170                })
171                .collect(),
172            impact_score,
173        })
174    }
175
176    /// Analyze the impact of changing a symbol.
177    ///
178    /// Returns all symbols that would be affected by modifying the given symbol.
179    pub async fn analyze_impact(&self, symbol_name: &str) -> Result<ImpactAnalysis> {
180        let impact = self.impact_analysis(symbol_name).await?;
181        Ok(ImpactAnalysis {
182            affected_symbols: impact.referenced_by,
183            call_sites: impact.call_count,
184        })
185    }
186
187    /// Find dead code in the codebase.
188    ///
189    /// Returns symbols that are defined but never called/referenced.
190    pub async fn dead_code_detection(&self) -> Result<Vec<Symbol>> {
191        let start = Instant::now();
192
193        let db_path = self.graph.store().db_path().join("graph.db");
194
195        // Check if database exists first
196        if !db_path.exists() {
197            tracing::debug!(
198                "No graph database found at {:?}, returning empty dead code list",
199                db_path
200            );
201            return Ok(Vec::new());
202        }
203
204        let analyzer = dead_code::DeadCodeAnalyzer::new(&db_path);
205
206        match analyzer.find_dead_code() {
207            Ok(dead_symbols) => {
208                let result: Vec<Symbol> = dead_symbols.into_iter().map(Into::into).collect();
209                tracing::debug!(
210                    "Dead code detection found {} symbols in {:?}",
211                    result.len(),
212                    start.elapsed()
213                );
214                Ok(result)
215            }
216            Err(e) => {
217                tracing::warn!("Dead code detection failed: {}", e);
218                // Return empty list on error rather than failing
219                Ok(Vec::new())
220            }
221        }
222    }
223
224    /// Perform deep impact analysis with k-hop traversal.
225    ///
226    /// Returns all symbols within k hops of the target symbol.
227    pub async fn deep_impact_analysis(
228        &self,
229        symbol_name: &str,
230        depth: u32,
231    ) -> Result<Vec<crate::graph::ImpactedSymbol>> {
232        self.graph.impact_analysis(symbol_name, Some(depth)).await
233    }
234
235    /// Find dead code in the codebase.
236    ///
237    /// Returns symbols that are defined but never called/referenced.
238    pub async fn find_dead_code(&self) -> Result<Vec<Symbol>> {
239        self.dead_code_detection().await
240    }
241
242    /// Trace the reference chain from a symbol.
243    ///
244    /// Returns an ordered list showing how symbols reference each other.
245    pub async fn reference_chain(&self, symbol: &str) -> Result<Vec<Symbol>> {
246        let start = Instant::now();
247
248        let refs = self.graph.references(symbol).await?;
249
250        let chain: Vec<Symbol> = refs
251            .into_iter()
252            .map(|r| {
253                let name: Arc<str> = Arc::from(r.to_name.unwrap_or_default());
254                Symbol {
255                    id: r.from,
256                    name: name.clone(),
257                    fully_qualified_name: name,
258                    kind: crate::types::SymbolKind::Function,
259                    language: crate::types::Language::Unknown("unknown".to_string()),
260                    location: r.location,
261                    parent_id: None,
262                    metadata: serde_json::Value::Null,
263                }
264            })
265            .collect();
266
267        tracing::debug!(
268            "Reference chain for '{}' has {} symbols, found in {:?}",
269            symbol,
270            chain.len(),
271            start.elapsed()
272        );
273        Ok(chain)
274    }
275
276    /// Trace all callers to a function.
277    ///
278    /// Returns an ordered list of calling symbols.
279    pub async fn call_chain(&self, symbol: &str) -> Result<Vec<Symbol>> {
280        let start = Instant::now();
281
282        let callers = self.graph.callers_of(symbol).await?;
283
284        let chain: Vec<Symbol> = callers
285            .into_iter()
286            .map(|r| {
287                let name: Arc<str> = Arc::from(r.from_name.unwrap_or_default());
288                Symbol {
289                    id: r.from,
290                    name: name.clone(),
291                    fully_qualified_name: name,
292                    kind: crate::types::SymbolKind::Function,
293                    language: crate::types::Language::Unknown("unknown".to_string()),
294                    location: r.location,
295                    parent_id: None,
296                    metadata: serde_json::Value::Null,
297                }
298            })
299            .collect();
300
301        tracing::debug!(
302            "Call chain for '{}' has {} symbols, found in {:?}",
303            symbol,
304            chain.len(),
305            start.elapsed()
306        );
307        Ok(chain)
308    }
309
310    /// Run performance benchmarks for key operations.
311    ///
312    /// Returns timing data for each operation type.
313    pub async fn benchmarks(&self) -> Result<BenchmarkResults> {
314        let total_start = Instant::now();
315
316        // Benchmark impact analysis
317        let impact_start = Instant::now();
318        let _ = self.impact_analysis("test_symbol").await;
319        let impact_analysis_ms = impact_start.elapsed().as_secs_f64() * 1000.0;
320
321        // Benchmark dead code detection
322        let dead_start = Instant::now();
323        let _ = self.dead_code_detection().await;
324        let dead_code_ms = dead_start.elapsed().as_secs_f64() * 1000.0;
325
326        // Benchmark reference chain
327        let ref_start = Instant::now();
328        let _ = self.reference_chain("test_symbol").await;
329        let reference_chain_ms = ref_start.elapsed().as_secs_f64() * 1000.0;
330
331        // Benchmark call chain
332        let call_start = Instant::now();
333        let _ = self.call_chain("test_symbol").await;
334        let call_chain_ms = call_start.elapsed().as_secs_f64() * 1000.0;
335
336        let total_ms = total_start.elapsed().as_secs_f64() * 1000.0;
337
338        Ok(BenchmarkResults {
339            impact_analysis_ms,
340            dead_code_ms,
341            reference_chain_ms,
342            call_chain_ms,
343            total_ms,
344        })
345    }
346
347    /// Calculate complexity metrics for a function.
348    ///
349    /// Looks up the symbol's source code and analyzes it for complexity.
350    pub async fn complexity_metrics(&self, symbol_name: &str) -> Result<ComplexityMetrics> {
351        // Try to find the symbol's source and analyze it
352        let symbols = self
353            .graph
354            .find_symbol(symbol_name)
355            .await
356            .unwrap_or_default();
357        if let Some(sym) = symbols.first() {
358            let full_path = self
359                .graph
360                .store()
361                .codebase_path
362                .join(&sym.location.file_path);
363            if let Ok(content) = tokio::fs::read_to_string(&full_path).await {
364                // Extract the function body from byte span
365                let start = sym.location.byte_start as usize;
366                let end = sym.location.byte_end as usize;
367                if end <= content.len() {
368                    let source = &content[start..end];
369                    return Ok(self.analyze_source_complexity(source));
370                }
371            }
372        }
373
374        // Fallback: minimal metrics
375        Ok(ComplexityMetrics {
376            cyclomatic_complexity: 1,
377            lines_of_code: 1,
378            max_nesting_depth: 0,
379            decision_points: 0,
380        })
381    }
382
383    /// Calculate complexity from source code directly.
384    pub fn analyze_source_complexity(&self, source: &str) -> ComplexityMetrics {
385        let metrics = complexity::analyze_source_complexity(source);
386        ComplexityMetrics {
387            cyclomatic_complexity: metrics.cyclomatic_complexity,
388            lines_of_code: metrics.lines_of_code,
389            max_nesting_depth: metrics.max_nesting_depth,
390            decision_points: metrics.decision_points,
391        }
392    }
393
394    /// Get cross-references for a symbol.
395    ///
396    /// Returns both callers (incoming) and callees (outgoing).
397    pub async fn cross_references(&self, symbol_name: &str) -> Result<CrossReferences> {
398        let caller_refs = self.graph.callers_of(symbol_name).await?;
399        let callee_refs = self.graph.references(symbol_name).await?;
400
401        let callers = caller_refs
402            .iter()
403            .map(|r| {
404                let name: Arc<str> = Arc::from(r.from_name.clone().unwrap_or_default());
405                Symbol {
406                    id: r.from,
407                    name: name.clone(),
408                    fully_qualified_name: name,
409                    kind: crate::types::SymbolKind::Function,
410                    language: crate::types::Language::Unknown(String::new()),
411                    location: r.location.clone(),
412                    parent_id: None,
413                    metadata: serde_json::Value::Null,
414                }
415            })
416            .collect();
417
418        let callees = callee_refs
419            .iter()
420            .map(|r| {
421                let name: Arc<str> = Arc::from(r.to_name.clone().unwrap_or_default());
422                Symbol {
423                    id: r.to,
424                    name: name.clone(),
425                    fully_qualified_name: name,
426                    kind: crate::types::SymbolKind::Function,
427                    language: crate::types::Language::Unknown(String::new()),
428                    location: r.location.clone(),
429                    parent_id: None,
430                    metadata: serde_json::Value::Null,
431                }
432            })
433            .collect();
434
435        Ok(CrossReferences { callers, callees })
436    }
437
438    /// Analyze module dependencies.
439    ///
440    /// Returns dependencies between modules in the codebase.
441    pub async fn module_dependencies(&self) -> Result<Vec<ModuleDependency>> {
442        let db_path = self.graph.store().db_path();
443        let analyzer = ModuleAnalyzer::new(db_path);
444
445        let graph = analyzer.analyze_dependencies()?;
446
447        let mut deps = Vec::new();
448        for (from, tos) in graph.dependencies {
449            for to in tos {
450                deps.push(ModuleDependency {
451                    from: from.clone(),
452                    to,
453                });
454            }
455        }
456
457        Ok(deps)
458    }
459
460    /// Find circular dependencies between modules.
461    pub async fn find_dependency_cycles(&self) -> Result<Vec<Vec<String>>> {
462        let db_path = self.graph.store().db_path();
463        let analyzer = ModuleAnalyzer::new(db_path);
464        analyzer.find_cycles()
465    }
466}
467
468#[cfg(test)]
469mod tests {
470    use super::*;
471    use crate::storage::BackendKind;
472
473    #[tokio::test]
474    async fn test_impact_data_creation() {
475        let impact = ImpactData {
476            symbol: "test_function".to_string(),
477            ref_count: 5,
478            call_count: 3,
479            referenced_by: vec![],
480            references: vec![],
481            impact_score: 11,
482        };
483        assert_eq!(impact.symbol, "test_function");
484        assert_eq!(impact.ref_count, 5);
485        assert_eq!(impact.call_count, 3);
486        assert_eq!(impact.impact_score, 11);
487    }
488
489    #[tokio::test]
490    async fn test_impact_analysis() {
491        let temp_dir = tempfile::tempdir().unwrap();
492        let store = std::sync::Arc::new(
493            crate::storage::UnifiedGraphStore::open(temp_dir.path(), BackendKind::SQLite)
494                .await
495                .unwrap(),
496        );
497        let graph = GraphModule::new(Arc::clone(&store));
498        let search = SearchModule::new(Arc::clone(&store));
499        let cfg = CfgModule::new(Arc::clone(&store));
500        let edit = EditModule::new(store);
501
502        let analysis = AnalysisModule::new(graph, cfg, edit, search);
503        let impact = analysis.impact_analysis("nonexistent").await.unwrap();
504
505        assert_eq!(impact.symbol, "nonexistent");
506        assert_eq!(impact.ref_count, 0);
507        assert_eq!(impact.call_count, 0);
508    }
509
510    #[tokio::test]
511    async fn test_dead_code_detection() {
512        let temp_dir = tempfile::tempdir().unwrap();
513        let store = std::sync::Arc::new(
514            crate::storage::UnifiedGraphStore::open(temp_dir.path(), BackendKind::SQLite)
515                .await
516                .unwrap(),
517        );
518        let graph = GraphModule::new(Arc::clone(&store));
519        let search = SearchModule::new(Arc::clone(&store));
520        let cfg = CfgModule::new(Arc::clone(&store));
521        let edit = EditModule::new(store);
522
523        let analysis = AnalysisModule::new(graph, cfg, edit, search);
524        let dead_code = analysis.dead_code_detection().await.unwrap();
525
526        // Empty database should return no dead code
527        assert!(dead_code.is_empty());
528    }
529
530    #[tokio::test]
531    async fn test_reference_chain() {
532        let temp_dir = tempfile::tempdir().unwrap();
533        let store = std::sync::Arc::new(
534            crate::storage::UnifiedGraphStore::open(temp_dir.path(), BackendKind::SQLite)
535                .await
536                .unwrap(),
537        );
538        let graph = GraphModule::new(Arc::clone(&store));
539        let search = SearchModule::new(Arc::clone(&store));
540        let cfg = CfgModule::new(Arc::clone(&store));
541        let edit = EditModule::new(store);
542
543        let analysis = AnalysisModule::new(graph, cfg, edit, search);
544        let chain = analysis.reference_chain("test_symbol").await.unwrap();
545
546        // Should return empty chain for non-existent symbol
547        assert!(chain.is_empty());
548    }
549
550    #[tokio::test]
551    async fn test_call_chain() {
552        let temp_dir = tempfile::tempdir().unwrap();
553        let store = std::sync::Arc::new(
554            crate::storage::UnifiedGraphStore::open(temp_dir.path(), BackendKind::SQLite)
555                .await
556                .unwrap(),
557        );
558        let graph = GraphModule::new(Arc::clone(&store));
559        let search = SearchModule::new(Arc::clone(&store));
560        let cfg = CfgModule::new(Arc::clone(&store));
561        let edit = EditModule::new(store);
562
563        let analysis = AnalysisModule::new(graph, cfg, edit, search);
564        let chain = analysis.call_chain("test_function").await.unwrap();
565
566        // Should return empty chain for non-existent function
567        assert!(chain.is_empty());
568    }
569
570    #[tokio::test]
571    async fn test_benchmarks() {
572        let temp_dir = tempfile::tempdir().unwrap();
573        let store = std::sync::Arc::new(
574            crate::storage::UnifiedGraphStore::open(temp_dir.path(), BackendKind::SQLite)
575                .await
576                .unwrap(),
577        );
578        let graph = GraphModule::new(Arc::clone(&store));
579        let search = SearchModule::new(Arc::clone(&store));
580        let cfg = CfgModule::new(Arc::clone(&store));
581        let edit = EditModule::new(store);
582
583        let analysis = AnalysisModule::new(graph, cfg, edit, search);
584        let benchmarks = analysis.benchmarks().await.unwrap();
585
586        // All timings should be non-negative
587        assert!(benchmarks.impact_analysis_ms >= 0.0);
588        assert!(benchmarks.dead_code_ms >= 0.0);
589        assert!(benchmarks.reference_chain_ms >= 0.0);
590        assert!(benchmarks.call_chain_ms >= 0.0);
591        assert!(benchmarks.total_ms >= 0.0);
592    }
593
594    #[tokio::test]
595    async fn test_analyze_impact_backward_compat() {
596        let temp_dir = tempfile::tempdir().unwrap();
597        let store = std::sync::Arc::new(
598            crate::storage::UnifiedGraphStore::open(temp_dir.path(), BackendKind::SQLite)
599                .await
600                .unwrap(),
601        );
602        let graph = GraphModule::new(Arc::clone(&store));
603        let search = SearchModule::new(Arc::clone(&store));
604        let cfg = CfgModule::new(Arc::clone(&store));
605        let edit = EditModule::new(store);
606
607        let analysis = AnalysisModule::new(graph, cfg, edit, search);
608        let impact = analysis.analyze_impact("test").await.unwrap();
609
610        // Backward compatible API
611        assert_eq!(impact.call_sites, 0);
612        assert!(impact.affected_symbols.is_empty());
613    }
614
615    #[tokio::test]
616    async fn test_find_dead_code_backward_compat() {
617        let temp_dir = tempfile::tempdir().unwrap();
618        let store = std::sync::Arc::new(
619            crate::storage::UnifiedGraphStore::open(temp_dir.path(), BackendKind::SQLite)
620                .await
621                .unwrap(),
622        );
623        let graph = GraphModule::new(Arc::clone(&store));
624        let search = SearchModule::new(Arc::clone(&store));
625        let cfg = CfgModule::new(Arc::clone(&store));
626        let edit = EditModule::new(store);
627
628        let analysis = AnalysisModule::new(graph, cfg, edit, search);
629        let dead_code = analysis.find_dead_code().await.unwrap();
630
631        // Empty database should return no dead code
632        assert!(dead_code.is_empty());
633    }
634
635    #[test]
636    fn test_impact_analysis_creation() {
637        let impact = ImpactAnalysis {
638            affected_symbols: vec![],
639            call_sites: 0,
640        };
641        assert!(impact.affected_symbols.is_empty());
642        assert_eq!(impact.call_sites, 0);
643    }
644
645    #[test]
646    fn test_cross_references_creation() {
647        let xrefs = CrossReferences {
648            callers: vec![],
649            callees: vec![],
650        };
651        assert!(xrefs.callers.is_empty());
652        assert!(xrefs.callees.is_empty());
653    }
654
655    #[test]
656    fn test_complexity_metrics_creation() {
657        let metrics = ComplexityMetrics {
658            cyclomatic_complexity: 5,
659            lines_of_code: 100,
660            max_nesting_depth: 3,
661            decision_points: 4,
662        };
663        assert_eq!(metrics.cyclomatic_complexity, 5);
664        assert_eq!(metrics.lines_of_code, 100);
665        assert_eq!(metrics.max_nesting_depth, 3);
666        assert_eq!(metrics.decision_points, 4);
667    }
668
669    #[test]
670    fn test_module_dependency_creation() {
671        let dep = ModuleDependency {
672            from: "mod_a".to_string(),
673            to: "mod_b".to_string(),
674        };
675        assert_eq!(dep.from, "mod_a");
676        assert_eq!(dep.to, "mod_b");
677    }
678
679    // End-to-end integration tests
680
681    #[tokio::test]
682    async fn test_cross_module_error_handling() {
683        let temp_dir = tempfile::tempdir().unwrap();
684        let store = std::sync::Arc::new(
685            crate::storage::UnifiedGraphStore::open(temp_dir.path(), BackendKind::SQLite)
686                .await
687                .unwrap(),
688        );
689        let graph = GraphModule::new(Arc::clone(&store));
690        let search = SearchModule::new(Arc::clone(&store));
691        let cfg = CfgModule::new(Arc::clone(&store));
692        let edit = EditModule::new(store);
693
694        let analysis = AnalysisModule::new(graph, cfg, edit, search);
695
696        // Test that errors from graph module propagate correctly
697        let result = analysis.graph().find_symbol("").await;
698        assert!(result.is_ok()); // Empty query returns empty, not error
699
700        // Test that search module handles valid patterns
701        let search_result = analysis.search().pattern_search("test").await;
702        assert!(search_result.is_ok());
703
704        // Test semantic search
705        let semantic_result = analysis.search().semantic_search("test").await;
706        assert!(semantic_result.is_ok());
707    }
708
709    #[tokio::test]
710    async fn test_deep_impact_analysis_integration() {
711        let temp_dir = tempfile::tempdir().unwrap();
712        let store = std::sync::Arc::new(
713            crate::storage::UnifiedGraphStore::open(temp_dir.path(), BackendKind::SQLite)
714                .await
715                .unwrap(),
716        );
717        let graph = GraphModule::new(Arc::clone(&store));
718        let search = SearchModule::new(Arc::clone(&store));
719        let cfg = CfgModule::new(Arc::clone(&store));
720        let edit = EditModule::new(store);
721
722        let analysis = AnalysisModule::new(graph, cfg, edit, search);
723
724        // Test k-hop impact analysis
725        let impacted = analysis.deep_impact_analysis("test", 2).await.unwrap();
726        assert!(impacted.is_empty()); // No symbols in empty database
727    }
728
729    #[tokio::test]
730    async fn test_module_dependencies_integration() {
731        let temp_dir = tempfile::tempdir().unwrap();
732        let store = std::sync::Arc::new(
733            crate::storage::UnifiedGraphStore::open(temp_dir.path(), BackendKind::SQLite)
734                .await
735                .unwrap(),
736        );
737        let graph = GraphModule::new(Arc::clone(&store));
738        let search = SearchModule::new(Arc::clone(&store));
739        let cfg = CfgModule::new(Arc::clone(&store));
740        let edit = EditModule::new(store);
741
742        let analysis = AnalysisModule::new(graph, cfg, edit, search);
743
744        // Test module dependency analysis
745        let deps = analysis.module_dependencies().await.unwrap();
746        assert!(deps.is_empty()); // No dependencies in empty database
747
748        // Test circular dependency detection
749        let cycles = analysis.find_dependency_cycles().await.unwrap();
750        assert!(cycles.is_empty());
751    }
752
753    #[tokio::test]
754    async fn test_complexity_metrics_integration() {
755        let temp_dir = tempfile::tempdir().unwrap();
756        let store = std::sync::Arc::new(
757            crate::storage::UnifiedGraphStore::open(temp_dir.path(), BackendKind::SQLite)
758                .await
759                .unwrap(),
760        );
761        let graph = GraphModule::new(Arc::clone(&store));
762        let search = SearchModule::new(Arc::clone(&store));
763        let cfg = CfgModule::new(Arc::clone(&store));
764        let edit = EditModule::new(store);
765
766        let analysis = AnalysisModule::new(graph, cfg, edit, search);
767
768        // Test complexity metrics
769        let metrics = analysis.complexity_metrics("test_function").await.unwrap();
770        assert_eq!(metrics.cyclomatic_complexity, 1);
771        assert_eq!(metrics.decision_points, 0);
772
773        // Test source complexity analysis
774        let source = r#"
775            fn example(x: i32) -> i32 {
776                if x > 0 {
777                    return x * 2;
778                }
779                x
780            }
781        "#;
782        let source_metrics = analysis.analyze_source_complexity(source);
783        assert!(source_metrics.cyclomatic_complexity >= 1);
784    }
785
786    #[tokio::test]
787    async fn test_cross_references_integration() {
788        let temp_dir = tempfile::tempdir().unwrap();
789        let store = std::sync::Arc::new(
790            crate::storage::UnifiedGraphStore::open(temp_dir.path(), BackendKind::SQLite)
791                .await
792                .unwrap(),
793        );
794        let graph = GraphModule::new(Arc::clone(&store));
795        let search = SearchModule::new(Arc::clone(&store));
796        let cfg = CfgModule::new(Arc::clone(&store));
797        let edit = EditModule::new(store);
798
799        let analysis = AnalysisModule::new(graph, cfg, edit, search);
800
801        // Test cross-references
802        let xrefs = analysis.cross_references("test").await.unwrap();
803        assert!(xrefs.callers.is_empty());
804        assert!(xrefs.callees.is_empty());
805    }
806
807    #[tokio::test]
808    async fn test_performance_benchmarks_integration() {
809        let temp_dir = tempfile::tempdir().unwrap();
810        let store = std::sync::Arc::new(
811            crate::storage::UnifiedGraphStore::open(temp_dir.path(), BackendKind::SQLite)
812                .await
813                .unwrap(),
814        );
815        let graph = GraphModule::new(Arc::clone(&store));
816        let search = SearchModule::new(Arc::clone(&store));
817        let cfg = CfgModule::new(Arc::clone(&store));
818        let edit = EditModule::new(store);
819
820        let analysis = AnalysisModule::new(graph, cfg, edit, search);
821
822        // Test performance benchmarks
823        let benchmarks = analysis.benchmarks().await.unwrap();
824
825        // Verify all benchmarks completed
826        assert!(benchmarks.impact_analysis_ms >= 0.0);
827        assert!(benchmarks.dead_code_ms >= 0.0);
828        assert!(benchmarks.reference_chain_ms >= 0.0);
829        assert!(benchmarks.call_chain_ms >= 0.0);
830        assert!(benchmarks.total_ms >= 0.0);
831
832        // Total should be sum of components (approximately)
833        let sum = benchmarks.impact_analysis_ms
834            + benchmarks.dead_code_ms
835            + benchmarks.reference_chain_ms
836            + benchmarks.call_chain_ms;
837        assert!(benchmarks.total_ms >= sum * 0.9); // Allow for timing overhead
838    }
839}