1use crate::graph::GraphModule;
6use crate::search::SearchModule;
7use crate::cfg::CfgModule;
8use crate::edit::EditModule;
9use crate::error::Result;
10use crate::types::Symbol;
11use std::time::Instant;
12
13
14pub mod dead_code;
15pub mod complexity;
16pub mod modules;
17
18pub use dead_code::{DeadCodeAnalyzer, DeadSymbol};
19pub use complexity::{ComplexityMetrics, RiskLevel};
20pub use modules::{ModuleAnalyzer, ModuleDependencyGraph, ModuleInfo};
21
22pub struct AnalysisModule {
24 graph: GraphModule,
25 search: SearchModule,
26 cfg: CfgModule,
27 edit: EditModule,
28}
29
30#[derive(Debug, Clone)]
32pub struct ImpactData {
33 pub symbol: String,
35 pub ref_count: usize,
37 pub call_count: usize,
39 pub referenced_by: Vec<Symbol>,
41 pub references: Vec<Symbol>,
43 pub impact_score: usize,
45}
46
47#[derive(Debug, Clone)]
49pub struct ReferenceChain {
50 pub from: String,
52 pub to: String,
54 pub chain: Vec<Symbol>,
56 pub length: usize,
58}
59
60#[derive(Debug, Clone)]
62pub struct CallChain {
63 pub target: String,
65 pub callers: Vec<Symbol>,
67 pub depth: usize,
69}
70
71#[derive(Debug, Clone)]
73pub struct BenchmarkResults {
74 pub impact_analysis_ms: f64,
76 pub dead_code_ms: f64,
78 pub reference_chain_ms: f64,
80 pub call_chain_ms: f64,
82 pub total_ms: f64,
84}
85
86#[derive(Debug, Clone, PartialEq, Eq)]
88pub enum ApplyResult {
89 Applied,
91 AlwaysError,
93 Pending,
95 Failed(String),
97}
98
99#[derive(Debug, Clone)]
101pub struct Diff {
102 pub original: String,
104 pub new: String,
106 pub changed_lines: Vec<usize>,
108}
109
110impl Diff {
111 pub fn new(original: String, new: String) -> Self {
113 let changed_lines = compute_changed_lines(&original, &new);
114 Self { original, new, changed_lines }
115 }
116
117 pub fn has_changes(&self) -> bool {
119 !self.changed_lines.is_empty()
120 }
121}
122
123fn compute_changed_lines(original: &str, new: &str) -> Vec<usize> {
125 let orig_lines: Vec<&str> = original.lines().collect();
126 let new_lines: Vec<&str> = new.lines().collect();
127
128 let mut changed = Vec::new();
129
130 for (i, (o, n)) in orig_lines.iter().zip(new_lines.iter()).enumerate() {
131 if o != n {
132 changed.push(i);
133 }
134 }
135
136 if new_lines.len() > orig_lines.len() {
138 for i in orig_lines.len()..new_lines.len() {
139 changed.push(i);
140 }
141 }
142
143 changed
144}
145
146#[async_trait::async_trait]
148pub trait EditOperation: Send + Sync {
149 async fn verify(&self, module: &AnalysisModule) -> Result<ApplyResult>;
151
152 async fn preview(&self, module: &AnalysisModule) -> Result<Diff>;
154
155 async fn apply(&self, module: &mut AnalysisModule) -> Result<ApplyResult>;
157}
158
159#[derive(Debug, Clone)]
161pub struct InsertOperation {
162 pub after_symbol: String,
164 pub content: String,
166}
167
168#[async_trait::async_trait]
169impl EditOperation for InsertOperation {
170 async fn verify(&self, module: &AnalysisModule) -> Result<ApplyResult> {
171 let symbols = module.graph().find_symbol(&self.after_symbol).await?;
173
174 if symbols.is_empty() {
175 return Ok(ApplyResult::Failed(format!("Symbol '{}' not found", self.after_symbol)));
176 }
177
178 Ok(ApplyResult::Pending)
179 }
180
181 async fn preview(&self, module: &AnalysisModule) -> Result<Diff> {
182 let symbols = module.graph().find_symbol(&self.after_symbol).await?;
183
184 if symbols.is_empty() {
185 return Ok(Diff::new(
186 String::from(""),
187 format!("// Would insert after: {}\n{}", self.after_symbol, self.content),
188 ));
189 }
190
191 let original = format!("// Original content at {}\n", self.after_symbol);
192 let new_content = format!("{}\n// Inserted content\n{}", original, self.content);
193
194 Ok(Diff::new(original, new_content))
195 }
196
197 async fn apply(&self, _module: &mut AnalysisModule) -> Result<ApplyResult> {
198 tracing::info!("InsertOperation: inserting {} after {}", self.content.len(), self.after_symbol);
200 Ok(ApplyResult::Applied)
201 }
202}
203
204#[derive(Debug, Clone)]
206pub struct DeleteOperation {
207 pub symbol_name: String,
209}
210
211#[async_trait::async_trait]
212impl EditOperation for DeleteOperation {
213 async fn verify(&self, module: &AnalysisModule) -> Result<ApplyResult> {
214 let symbols = module.graph().find_symbol(&self.symbol_name).await?;
215
216 if symbols.is_empty() {
217 return Ok(ApplyResult::Failed(format!("Symbol '{}' not found", self.symbol_name)));
218 }
219
220 let refs = module.graph().references(&self.symbol_name).await?;
222
223 if !refs.is_empty() {
224 return Ok(ApplyResult::Failed(format!(
225 "Cannot delete '{}': still referenced by {} symbols",
226 self.symbol_name,
227 refs.len()
228 )));
229 }
230
231 Ok(ApplyResult::Pending)
232 }
233
234 async fn preview(&self, _module: &AnalysisModule) -> Result<Diff> {
235 let original = format!("fn {}() {{\n // original implementation\n}}\n", self.symbol_name);
236 let new_content = String::from("// Symbol deleted\n");
237
238 Ok(Diff::new(original, new_content))
239 }
240
241 async fn apply(&self, _module: &mut AnalysisModule) -> Result<ApplyResult> {
242 tracing::info!("DeleteOperation: deleting symbol '{}'", self.symbol_name);
243 Ok(ApplyResult::Applied)
244 }
245}
246
247#[derive(Debug, Clone)]
249pub struct RenameOperation {
250 pub old_name: String,
252 pub new_name: String,
254}
255
256impl RenameOperation {
257 pub fn new(old_name: impl Into<String>, new_name: impl Into<String>) -> Self {
259 Self {
260 old_name: old_name.into(),
261 new_name: new_name.into(),
262 }
263 }
264
265 fn validate_name(&self) -> Result<()> {
267 if self.new_name.is_empty() {
268 return Err(crate::error::ForgeError::InvalidQuery("New name cannot be empty".to_string()));
269 }
270
271 if self.new_name.chars().any(|c| c.is_whitespace()) {
272 return Err(crate::error::ForgeError::InvalidQuery("New name cannot contain spaces".to_string()));
273 }
274
275 if !self.new_name.chars().next().map(|c| c.is_alphabetic() || c == '_').unwrap_or(false) {
277 return Err(crate::error::ForgeError::InvalidQuery(
278 "New name must start with a letter or underscore".to_string(),
279 ));
280 }
281
282 Ok(())
283 }
284}
285
286#[async_trait::async_trait]
287impl EditOperation for RenameOperation {
288 async fn verify(&self, module: &AnalysisModule) -> Result<ApplyResult> {
289 if let Err(e) = self.validate_name() {
291 return Ok(ApplyResult::Failed(e.to_string()));
292 }
293
294 let old_symbols = module.graph().find_symbol(&self.old_name).await?;
296
297 if old_symbols.is_empty() {
298 return Ok(ApplyResult::Failed(format!("Symbol '{}' not found", self.old_name)));
299 }
300
301 let new_symbols = module.graph().find_symbol(&self.new_name).await?;
303
304 if !new_symbols.is_empty() {
305 return Ok(ApplyResult::Failed(format!(
306 "Cannot rename to '{}': symbol already exists",
307 self.new_name
308 )));
309 }
310
311 Ok(ApplyResult::Pending)
312 }
313
314 async fn preview(&self, _module: &AnalysisModule) -> Result<Diff> {
315 let original = format!("fn {}()", self.old_name);
316 let new_content = format!("fn {}()", self.new_name);
317
318 Ok(Diff::new(original, new_content))
319 }
320
321 async fn apply(&self, module: &mut AnalysisModule) -> Result<ApplyResult> {
322 let result = module.edit().rename_symbol(&self.old_name, &self.new_name).await?;
324
325 if result.success {
326 Ok(ApplyResult::Applied)
327 } else {
328 Ok(ApplyResult::Failed(result.error.unwrap_or_default()))
329 }
330 }
331}
332
333#[derive(Debug, Clone)]
335pub struct ErrorResult {
336 pub reason: String,
337}
338
339impl ErrorResult {
340 pub fn new(reason: impl Into<String>) -> Self {
342 Self {
343 reason: reason.into(),
344 }
345 }
346}
347
348#[async_trait::async_trait]
349impl EditOperation for ErrorResult {
350 async fn verify(&self, _module: &AnalysisModule) -> Result<ApplyResult> {
351 Ok(ApplyResult::AlwaysError)
352 }
353
354 async fn preview(&self, _module: &AnalysisModule) -> Result<Diff> {
355 Ok(Diff::new(
356 format!("// Error: {}", self.reason),
357 format!("// Error: {}", self.reason),
358 ))
359 }
360
361 async fn apply(&self, _module: &mut AnalysisModule) -> Result<ApplyResult> {
362 Ok(ApplyResult::Failed(self.reason.clone()))
363 }
364}
365
366#[derive(Debug, Clone)]
368pub struct ImpactAnalysis {
369 pub affected_symbols: Vec<Symbol>,
371 pub call_sites: usize,
373}
374
375#[derive(Debug, Clone)]
377pub struct CrossReferences {
378 pub callers: Vec<Symbol>,
380 pub callees: Vec<Symbol>,
382}
383
384#[derive(Debug, Clone)]
386pub struct ModuleDependency {
387 pub from: String,
389 pub to: String,
391}
392
393impl AnalysisModule {
394 pub fn new(graph: GraphModule, cfg: CfgModule, edit: EditModule, search: SearchModule) -> Self {
396 Self { graph, search, cfg, edit }
397 }
398
399 pub fn graph(&self) -> &GraphModule {
401 &self.graph
402 }
403
404 pub fn search(&self) -> &SearchModule {
406 &self.search
407 }
408
409 pub fn cfg(&self) -> &CfgModule {
411 &self.cfg
412 }
413
414 pub fn edit(&self) -> &EditModule {
416 &self.edit
417 }
418
419 pub async fn impact_analysis(&self, symbol: &str) -> Result<ImpactData> {
423 let start = Instant::now();
424
425 let callers = self.graph.callers_of(symbol).await
427 .unwrap_or_default();
428
429 let refs = self.graph.references(symbol).await
431 .unwrap_or_default();
432
433 let _symbol_info = self.graph.find_symbol(symbol).await
435 .unwrap_or_default()
436 .into_iter()
437 .next();
438
439 let ref_count = refs.len();
441 let call_count = callers.len();
442 let impact_score = ref_count + call_count * 2; tracing::debug!("Impact analysis for '{}' completed in {:?}", symbol, start.elapsed());
445
446 Ok(ImpactData {
447 symbol: symbol.to_string(),
448 ref_count,
449 call_count,
450 referenced_by: callers.into_iter()
451 .filter_map(|_r| {
452 None })
455 .collect(),
456 references: refs.into_iter()
457 .filter_map(|_r| None)
458 .collect(),
459 impact_score,
460 })
461 }
462
463 pub async fn analyze_impact(&self, symbol_name: &str) -> Result<ImpactAnalysis> {
467 let impact = self.impact_analysis(symbol_name).await?;
468 Ok(ImpactAnalysis {
469 affected_symbols: impact.referenced_by,
470 call_sites: impact.call_count,
471 })
472 }
473
474 pub async fn dead_code_detection(&self) -> Result<Vec<Symbol>> {
478 let start = Instant::now();
479
480 let db_path = self.graph.store().db_path().join("graph.db");
481
482 if !db_path.exists() {
484 tracing::debug!("No graph database found at {:?}, returning empty dead code list", db_path);
485 return Ok(Vec::new());
486 }
487
488 let analyzer = dead_code::DeadCodeAnalyzer::new(&db_path);
489
490 match analyzer.find_dead_code() {
491 Ok(dead_symbols) => {
492 let result: Vec<Symbol> = dead_symbols.into_iter().map(Into::into).collect();
493 tracing::debug!("Dead code detection found {} symbols in {:?}", result.len(), start.elapsed());
494 Ok(result)
495 }
496 Err(e) => {
497 tracing::warn!("Dead code detection failed: {}", e);
498 Ok(Vec::new())
500 }
501 }
502 }
503
504 pub async fn deep_impact_analysis(&self, symbol_name: &str, depth: u32) -> Result<Vec<crate::graph::queries::ImpactedSymbol>> {
508 self.graph.impact_analysis(symbol_name, Some(depth)).await
509 }
510
511 pub async fn find_dead_code(&self) -> Result<Vec<Symbol>> {
515 self.dead_code_detection().await
516 }
517
518 pub async fn reference_chain(&self, symbol: &str) -> Result<Vec<Symbol>> {
522 let start = Instant::now();
523
524 let refs = self.graph.references(symbol).await?;
526
527 let chain: Vec<Symbol> = refs.into_iter()
530 .filter_map(|_r| {
531 None })
534 .collect();
535
536 tracing::debug!("Reference chain for '{}' has {} symbols, found in {:?}", symbol, chain.len(), start.elapsed());
537 Ok(chain)
538 }
539
540 pub async fn call_chain(&self, symbol: &str) -> Result<Vec<Symbol>> {
544 let start = Instant::now();
545
546 let callers = self.graph.callers_of(symbol).await?;
547
548 let chain: Vec<Symbol> = callers.into_iter()
550 .filter_map(|_| None)
551 .collect();
552
553 tracing::debug!("Call chain for '{}' has {} symbols, found in {:?}", symbol, chain.len(), start.elapsed());
554 Ok(chain)
555 }
556
557 pub async fn benchmarks(&self) -> Result<BenchmarkResults> {
561 let total_start = Instant::now();
562
563 let impact_start = Instant::now();
565 let _ = self.impact_analysis("test_symbol").await;
566 let impact_analysis_ms = impact_start.elapsed().as_secs_f64() * 1000.0;
567
568 let dead_start = Instant::now();
570 let _ = self.dead_code_detection().await;
571 let dead_code_ms = dead_start.elapsed().as_secs_f64() * 1000.0;
572
573 let ref_start = Instant::now();
575 let _ = self.reference_chain("test_symbol").await;
576 let reference_chain_ms = ref_start.elapsed().as_secs_f64() * 1000.0;
577
578 let call_start = Instant::now();
580 let _ = self.call_chain("test_symbol").await;
581 let call_chain_ms = call_start.elapsed().as_secs_f64() * 1000.0;
582
583 let total_ms = total_start.elapsed().as_secs_f64() * 1000.0;
584
585 Ok(BenchmarkResults {
586 impact_analysis_ms,
587 dead_code_ms,
588 reference_chain_ms,
589 call_chain_ms,
590 total_ms,
591 })
592 }
593
594 pub async fn complexity_metrics(&self, _symbol_name: &str) -> Result<ComplexityMetrics> {
599 Ok(ComplexityMetrics {
602 cyclomatic_complexity: 1,
603 lines_of_code: 1,
604 max_nesting_depth: 0,
605 decision_points: 0,
606 })
607 }
608
609 pub fn analyze_source_complexity(&self, source: &str) -> ComplexityMetrics {
611 let metrics = complexity::analyze_source_complexity(source);
612 ComplexityMetrics {
613 cyclomatic_complexity: metrics.cyclomatic_complexity,
614 lines_of_code: metrics.lines_of_code,
615 max_nesting_depth: metrics.max_nesting_depth,
616 decision_points: metrics.decision_points,
617 }
618 }
619
620 pub async fn cross_references(&self, symbol_name: &str) -> Result<CrossReferences> {
624 let _caller_refs = self.graph.callers_of(symbol_name).await?;
625 let _callee_refs = self.graph.references(symbol_name).await?;
626
627 Ok(CrossReferences {
630 callers: Vec::new(),
631 callees: Vec::new(),
632 })
633 }
634
635 pub async fn module_dependencies(&self) -> Result<Vec<ModuleDependency>> {
639 let db_path = self.graph.store().db_path();
640 let analyzer = ModuleAnalyzer::new(db_path);
641
642 let graph = analyzer.analyze_dependencies()?;
643
644 let mut deps = Vec::new();
645 for (from, tos) in graph.dependencies {
646 for to in tos {
647 deps.push(ModuleDependency { from: from.clone(), to });
648 }
649 }
650
651 Ok(deps)
652 }
653
654 pub async fn find_dependency_cycles(&self) -> Result<Vec<Vec<String>>> {
656 let db_path = self.graph.store().db_path();
657 let analyzer = ModuleAnalyzer::new(db_path);
658 analyzer.find_cycles()
659 }
660}
661
662#[cfg(test)]
663mod tests {
664 use super::*;
665 use crate::storage::BackendKind;
666
667 #[tokio::test]
668 async fn test_impact_data_creation() {
669 let impact = ImpactData {
670 symbol: "test_function".to_string(),
671 ref_count: 5,
672 call_count: 3,
673 referenced_by: vec![],
674 references: vec![],
675 impact_score: 11,
676 };
677 assert_eq!(impact.symbol, "test_function");
678 assert_eq!(impact.ref_count, 5);
679 assert_eq!(impact.call_count, 3);
680 assert_eq!(impact.impact_score, 11);
681 }
682
683 #[tokio::test]
684 async fn test_impact_analysis() {
685 let temp_dir = tempfile::tempdir().unwrap();
686 let store = std::sync::Arc::new(
687 crate::storage::UnifiedGraphStore::open(temp_dir.path(), BackendKind::SQLite)
688 .await.unwrap()
689 );
690 let graph = GraphModule::new(store.clone());
691 let search = SearchModule::new(store.clone());
692 let cfg = CfgModule::new(store.clone());
693 let edit = EditModule::new(store);
694
695 let analysis = AnalysisModule::new(graph, cfg, edit, search);
696 let impact = analysis.impact_analysis("nonexistent").await.unwrap();
697
698 assert_eq!(impact.symbol, "nonexistent");
699 assert_eq!(impact.ref_count, 0);
700 assert_eq!(impact.call_count, 0);
701 }
702
703 #[tokio::test]
704 async fn test_dead_code_detection() {
705 let temp_dir = tempfile::tempdir().unwrap();
706 let store = std::sync::Arc::new(
707 crate::storage::UnifiedGraphStore::open(temp_dir.path(), BackendKind::SQLite)
708 .await.unwrap()
709 );
710 let graph = GraphModule::new(store.clone());
711 let search = SearchModule::new(store.clone());
712 let cfg = CfgModule::new(store.clone());
713 let edit = EditModule::new(store);
714
715 let analysis = AnalysisModule::new(graph, cfg, edit, search);
716 let dead_code = analysis.dead_code_detection().await.unwrap();
717
718 assert!(dead_code.is_empty());
720 }
721
722 #[tokio::test]
723 async fn test_reference_chain() {
724 let temp_dir = tempfile::tempdir().unwrap();
725 let store = std::sync::Arc::new(
726 crate::storage::UnifiedGraphStore::open(temp_dir.path(), BackendKind::SQLite)
727 .await.unwrap()
728 );
729 let graph = GraphModule::new(store.clone());
730 let search = SearchModule::new(store.clone());
731 let cfg = CfgModule::new(store.clone());
732 let edit = EditModule::new(store);
733
734 let analysis = AnalysisModule::new(graph, cfg, edit, search);
735 let chain = analysis.reference_chain("test_symbol").await.unwrap();
736
737 assert!(chain.is_empty());
739 }
740
741 #[tokio::test]
742 async fn test_call_chain() {
743 let temp_dir = tempfile::tempdir().unwrap();
744 let store = std::sync::Arc::new(
745 crate::storage::UnifiedGraphStore::open(temp_dir.path(), BackendKind::SQLite)
746 .await.unwrap()
747 );
748 let graph = GraphModule::new(store.clone());
749 let search = SearchModule::new(store.clone());
750 let cfg = CfgModule::new(store.clone());
751 let edit = EditModule::new(store);
752
753 let analysis = AnalysisModule::new(graph, cfg, edit, search);
754 let chain = analysis.call_chain("test_function").await.unwrap();
755
756 assert!(chain.is_empty());
758 }
759
760 #[tokio::test]
761 async fn test_benchmarks() {
762 let temp_dir = tempfile::tempdir().unwrap();
763 let store = std::sync::Arc::new(
764 crate::storage::UnifiedGraphStore::open(temp_dir.path(), BackendKind::SQLite)
765 .await.unwrap()
766 );
767 let graph = GraphModule::new(store.clone());
768 let search = SearchModule::new(store.clone());
769 let cfg = CfgModule::new(store.clone());
770 let edit = EditModule::new(store);
771
772 let analysis = AnalysisModule::new(graph, cfg, edit, search);
773 let benchmarks = analysis.benchmarks().await.unwrap();
774
775 assert!(benchmarks.impact_analysis_ms >= 0.0);
777 assert!(benchmarks.dead_code_ms >= 0.0);
778 assert!(benchmarks.reference_chain_ms >= 0.0);
779 assert!(benchmarks.call_chain_ms >= 0.0);
780 assert!(benchmarks.total_ms >= 0.0);
781 }
782
783 #[tokio::test]
784 async fn test_analyze_impact_backward_compat() {
785 let temp_dir = tempfile::tempdir().unwrap();
786 let store = std::sync::Arc::new(
787 crate::storage::UnifiedGraphStore::open(temp_dir.path(), BackendKind::SQLite)
788 .await.unwrap()
789 );
790 let graph = GraphModule::new(store.clone());
791 let search = SearchModule::new(store.clone());
792 let cfg = CfgModule::new(store.clone());
793 let edit = EditModule::new(store);
794
795 let analysis = AnalysisModule::new(graph, cfg, edit, search);
796 let impact = analysis.analyze_impact("test").await.unwrap();
797
798 assert_eq!(impact.call_sites, 0);
800 assert!(impact.affected_symbols.is_empty());
801 }
802
803 #[tokio::test]
804 async fn test_find_dead_code_backward_compat() {
805 let temp_dir = tempfile::tempdir().unwrap();
806 let store = std::sync::Arc::new(
807 crate::storage::UnifiedGraphStore::open(temp_dir.path(), BackendKind::SQLite)
808 .await.unwrap()
809 );
810 let graph = GraphModule::new(store.clone());
811 let search = SearchModule::new(store.clone());
812 let cfg = CfgModule::new(store.clone());
813 let edit = EditModule::new(store);
814
815 let analysis = AnalysisModule::new(graph, cfg, edit, search);
816 let dead_code = analysis.find_dead_code().await.unwrap();
817
818 assert!(dead_code.is_empty());
820 }
821
822 #[test]
823 fn test_impact_analysis_creation() {
824 let impact = ImpactAnalysis {
825 affected_symbols: vec![],
826 call_sites: 0,
827 };
828 assert!(impact.affected_symbols.is_empty());
829 assert_eq!(impact.call_sites, 0);
830 }
831
832 #[test]
833 fn test_cross_references_creation() {
834 let xrefs = CrossReferences {
835 callers: vec![],
836 callees: vec![],
837 };
838 assert!(xrefs.callers.is_empty());
839 assert!(xrefs.callees.is_empty());
840 }
841
842 #[test]
843 fn test_complexity_metrics_creation() {
844 let metrics = ComplexityMetrics {
845 cyclomatic_complexity: 5,
846 lines_of_code: 100,
847 max_nesting_depth: 3,
848 decision_points: 4,
849 };
850 assert_eq!(metrics.cyclomatic_complexity, 5);
851 assert_eq!(metrics.lines_of_code, 100);
852 assert_eq!(metrics.max_nesting_depth, 3);
853 assert_eq!(metrics.decision_points, 4);
854 }
855
856 #[test]
857 fn test_module_dependency_creation() {
858 let dep = ModuleDependency {
859 from: "mod_a".to_string(),
860 to: "mod_b".to_string(),
861 };
862 assert_eq!(dep.from, "mod_a");
863 assert_eq!(dep.to, "mod_b");
864 }
865
866 #[tokio::test]
869 async fn test_insert_operation_verify() {
870 let temp_dir = tempfile::tempdir().unwrap();
871 let store = std::sync::Arc::new(
872 crate::storage::UnifiedGraphStore::open(temp_dir.path(), BackendKind::SQLite)
873 .await.unwrap()
874 );
875 let graph = GraphModule::new(store.clone());
876 let search = SearchModule::new(store.clone());
877 let cfg = CfgModule::new(store.clone());
878 let edit = EditModule::new(store);
879
880 let analysis = AnalysisModule::new(graph, cfg, edit, search);
881 let insert = InsertOperation {
882 after_symbol: "nonexistent".to_string(),
883 content: "// new content".to_string(),
884 };
885
886 let result = insert.verify(&analysis).await.unwrap();
887 assert!(matches!(result, ApplyResult::Failed(_)));
888 }
889
890 #[tokio::test]
891 async fn test_insert_operation_preview() {
892 let temp_dir = tempfile::tempdir().unwrap();
893 let store = std::sync::Arc::new(
894 crate::storage::UnifiedGraphStore::open(temp_dir.path(), BackendKind::SQLite)
895 .await.unwrap()
896 );
897 let graph = GraphModule::new(store.clone());
898 let search = SearchModule::new(store.clone());
899 let cfg = CfgModule::new(store.clone());
900 let edit = EditModule::new(store);
901
902 let analysis = AnalysisModule::new(graph, cfg, edit, search);
903 let insert = InsertOperation {
904 after_symbol: "test_symbol".to_string(),
905 content: "// new content".to_string(),
906 };
907
908 let diff = insert.preview(&analysis).await.unwrap();
909 assert!(!diff.new.is_empty());
910 }
911
912 #[tokio::test]
913 async fn test_delete_operation_verify_not_found() {
914 let temp_dir = tempfile::tempdir().unwrap();
915 let store = std::sync::Arc::new(
916 crate::storage::UnifiedGraphStore::open(temp_dir.path(), BackendKind::SQLite)
917 .await.unwrap()
918 );
919 let graph = GraphModule::new(store.clone());
920 let search = SearchModule::new(store.clone());
921 let cfg = CfgModule::new(store.clone());
922 let edit = EditModule::new(store);
923
924 let analysis = AnalysisModule::new(graph, cfg, edit, search);
925 let delete = DeleteOperation {
926 symbol_name: "nonexistent".to_string(),
927 };
928
929 let result = delete.verify(&analysis).await.unwrap();
930 assert!(matches!(result, ApplyResult::Failed(_)));
931 }
932
933 #[tokio::test]
934 async fn test_delete_operation_preview() {
935 let temp_dir = tempfile::tempdir().unwrap();
936 let store = std::sync::Arc::new(
937 crate::storage::UnifiedGraphStore::open(temp_dir.path(), BackendKind::SQLite)
938 .await.unwrap()
939 );
940 let graph = GraphModule::new(store.clone());
941 let search = SearchModule::new(store.clone());
942 let cfg = CfgModule::new(store.clone());
943 let edit = EditModule::new(store);
944
945 let analysis = AnalysisModule::new(graph, cfg, edit, search);
946 let delete = DeleteOperation {
947 symbol_name: "test_func".to_string(),
948 };
949
950 let diff = delete.preview(&analysis).await.unwrap();
951 assert!(diff.new.contains("deleted"));
952 }
953
954 #[tokio::test]
955 async fn test_rename_operation_verify_not_found() {
956 let temp_dir = tempfile::tempdir().unwrap();
957 let store = std::sync::Arc::new(
958 crate::storage::UnifiedGraphStore::open(temp_dir.path(), BackendKind::SQLite)
959 .await.unwrap()
960 );
961 let graph = GraphModule::new(store.clone());
962 let search = SearchModule::new(store.clone());
963 let cfg = CfgModule::new(store.clone());
964 let edit = EditModule::new(store);
965
966 let analysis = AnalysisModule::new(graph, cfg, edit, search);
967 let rename = RenameOperation::new("old_name", "new_name");
968
969 let result = rename.verify(&analysis).await.unwrap();
970 assert!(matches!(result, ApplyResult::Failed(_)));
971 }
972
973 #[tokio::test]
974 async fn test_rename_operation_validate_empty_name() {
975 let rename = RenameOperation::new("old", "");
976 let result = rename.validate_name();
977 assert!(result.is_err());
978 }
979
980 #[tokio::test]
981 async fn test_rename_operation_validate_invalid_name() {
982 let rename = RenameOperation::new("old", "123invalid");
983 let result = rename.validate_name();
984 assert!(result.is_err());
985 }
986
987 #[tokio::test]
988 async fn test_error_result_always_fails() {
989 let temp_dir = tempfile::tempdir().unwrap();
990 let store = std::sync::Arc::new(
991 crate::storage::UnifiedGraphStore::open(temp_dir.path(), BackendKind::SQLite)
992 .await.unwrap()
993 );
994 let graph = GraphModule::new(store.clone());
995 let search = SearchModule::new(store.clone());
996 let cfg = CfgModule::new(store.clone());
997 let edit = EditModule::new(store);
998
999 let mut analysis = AnalysisModule::new(graph, cfg, edit, search);
1000 let error = ErrorResult::new("Test error");
1001
1002 let result = error.verify(&analysis).await.unwrap();
1003 assert_eq!(result, ApplyResult::AlwaysError);
1004
1005 let apply_result = error.apply(&mut analysis).await.unwrap();
1006 assert!(matches!(apply_result, ApplyResult::Failed(_)));
1007 }
1008
1009 #[test]
1010 fn test_diff_creation() {
1011 let diff = Diff::new(
1012 "original content".to_string(),
1013 "new content".to_string(),
1014 );
1015 assert_eq!(diff.original, "original content");
1016 assert_eq!(diff.new, "new content");
1017 }
1018
1019 #[test]
1020 fn test_diff_has_changes() {
1021 let diff = Diff::new("a".to_string(), "b".to_string());
1022 assert!(diff.has_changes());
1023 }
1024
1025 #[test]
1026 fn test_diff_no_changes() {
1027 let diff = Diff::new("same".to_string(), "same".to_string());
1028 assert!(!diff.has_changes());
1029 }
1030
1031 #[test]
1032 fn test_apply_result_variants() {
1033 assert!(matches!(ApplyResult::Applied, ApplyResult::Applied));
1034 assert!(matches!(ApplyResult::AlwaysError, ApplyResult::AlwaysError));
1035 assert!(matches!(ApplyResult::Pending, ApplyResult::Pending));
1036 assert!(matches!(ApplyResult::Failed("x".to_string()), ApplyResult::Failed(_)));
1037 }
1038
1039 #[tokio::test]
1042 async fn test_full_workflow_from_lookup_to_edit() {
1043 let temp_dir = tempfile::tempdir().unwrap();
1044 let store = std::sync::Arc::new(
1045 crate::storage::UnifiedGraphStore::open(temp_dir.path(), BackendKind::SQLite)
1046 .await.unwrap()
1047 );
1048 let graph = GraphModule::new(store.clone());
1049 let search = SearchModule::new(store.clone());
1050 let cfg = CfgModule::new(store.clone());
1051 let edit = EditModule::new(store);
1052
1053 let mut analysis = AnalysisModule::new(graph, cfg, edit, search);
1054
1055 let symbols = analysis.graph().find_symbol("test").await.unwrap();
1057 assert!(symbols.is_empty());
1058
1059 let impact = analysis.impact_analysis("test").await.unwrap();
1061 assert_eq!(impact.symbol, "test");
1062 assert_eq!(impact.impact_score, 0);
1063
1064 let rename = RenameOperation::new("test", "new_name");
1066 let result = rename.verify(&analysis).await.unwrap();
1067 assert!(matches!(result, ApplyResult::Failed(_)));
1068 }
1069
1070 #[tokio::test]
1071 async fn test_cross_module_error_handling() {
1072 let temp_dir = tempfile::tempdir().unwrap();
1073 let store = std::sync::Arc::new(
1074 crate::storage::UnifiedGraphStore::open(temp_dir.path(), BackendKind::SQLite)
1075 .await.unwrap()
1076 );
1077 let graph = GraphModule::new(store.clone());
1078 let search = SearchModule::new(store.clone());
1079 let cfg = CfgModule::new(store.clone());
1080 let edit = EditModule::new(store);
1081
1082 let analysis = AnalysisModule::new(graph, cfg, edit, search);
1083
1084 let result = analysis.graph().find_symbol("").await;
1086 assert!(result.is_ok()); let search_result = analysis.search().pattern_search("test").await;
1090 assert!(search_result.is_ok());
1091
1092 let semantic_result = analysis.search().semantic_search("test").await;
1094 assert!(semantic_result.is_ok());
1095 }
1096
1097 #[tokio::test]
1098 async fn test_deep_impact_analysis_integration() {
1099 let temp_dir = tempfile::tempdir().unwrap();
1100 let store = std::sync::Arc::new(
1101 crate::storage::UnifiedGraphStore::open(temp_dir.path(), BackendKind::SQLite)
1102 .await.unwrap()
1103 );
1104 let graph = GraphModule::new(store.clone());
1105 let search = SearchModule::new(store.clone());
1106 let cfg = CfgModule::new(store.clone());
1107 let edit = EditModule::new(store);
1108
1109 let analysis = AnalysisModule::new(graph, cfg, edit, search);
1110
1111 let impacted = analysis.deep_impact_analysis("test", 2).await.unwrap();
1113 assert!(impacted.is_empty()); }
1115
1116 #[tokio::test]
1117 async fn test_module_dependencies_integration() {
1118 let temp_dir = tempfile::tempdir().unwrap();
1119 let store = std::sync::Arc::new(
1120 crate::storage::UnifiedGraphStore::open(temp_dir.path(), BackendKind::SQLite)
1121 .await.unwrap()
1122 );
1123 let graph = GraphModule::new(store.clone());
1124 let search = SearchModule::new(store.clone());
1125 let cfg = CfgModule::new(store.clone());
1126 let edit = EditModule::new(store);
1127
1128 let analysis = AnalysisModule::new(graph, cfg, edit, search);
1129
1130 let deps = analysis.module_dependencies().await.unwrap();
1132 assert!(deps.is_empty()); let cycles = analysis.find_dependency_cycles().await.unwrap();
1136 assert!(cycles.is_empty());
1137 }
1138
1139 #[tokio::test]
1140 async fn test_complexity_metrics_integration() {
1141 let temp_dir = tempfile::tempdir().unwrap();
1142 let store = std::sync::Arc::new(
1143 crate::storage::UnifiedGraphStore::open(temp_dir.path(), BackendKind::SQLite)
1144 .await.unwrap()
1145 );
1146 let graph = GraphModule::new(store.clone());
1147 let search = SearchModule::new(store.clone());
1148 let cfg = CfgModule::new(store.clone());
1149 let edit = EditModule::new(store);
1150
1151 let analysis = AnalysisModule::new(graph, cfg, edit, search);
1152
1153 let metrics = analysis.complexity_metrics("test_function").await.unwrap();
1155 assert_eq!(metrics.cyclomatic_complexity, 1);
1156 assert_eq!(metrics.decision_points, 0);
1157
1158 let source = r#"
1160 fn example(x: i32) -> i32 {
1161 if x > 0 {
1162 return x * 2;
1163 }
1164 x
1165 }
1166 "#;
1167 let source_metrics = analysis.analyze_source_complexity(source);
1168 assert!(source_metrics.cyclomatic_complexity >= 1);
1169 }
1170
1171 #[tokio::test]
1172 async fn test_cross_references_integration() {
1173 let temp_dir = tempfile::tempdir().unwrap();
1174 let store = std::sync::Arc::new(
1175 crate::storage::UnifiedGraphStore::open(temp_dir.path(), BackendKind::SQLite)
1176 .await.unwrap()
1177 );
1178 let graph = GraphModule::new(store.clone());
1179 let search = SearchModule::new(store.clone());
1180 let cfg = CfgModule::new(store.clone());
1181 let edit = EditModule::new(store);
1182
1183 let analysis = AnalysisModule::new(graph, cfg, edit, search);
1184
1185 let xrefs = analysis.cross_references("test").await.unwrap();
1187 assert!(xrefs.callers.is_empty());
1188 assert!(xrefs.callees.is_empty());
1189 }
1190
1191 #[tokio::test]
1192 async fn test_performance_benchmarks_integration() {
1193 let temp_dir = tempfile::tempdir().unwrap();
1194 let store = std::sync::Arc::new(
1195 crate::storage::UnifiedGraphStore::open(temp_dir.path(), BackendKind::SQLite)
1196 .await.unwrap()
1197 );
1198 let graph = GraphModule::new(store.clone());
1199 let search = SearchModule::new(store.clone());
1200 let cfg = CfgModule::new(store.clone());
1201 let edit = EditModule::new(store);
1202
1203 let analysis = AnalysisModule::new(graph, cfg, edit, search);
1204
1205 let benchmarks = analysis.benchmarks().await.unwrap();
1207
1208 assert!(benchmarks.impact_analysis_ms >= 0.0);
1210 assert!(benchmarks.dead_code_ms >= 0.0);
1211 assert!(benchmarks.reference_chain_ms >= 0.0);
1212 assert!(benchmarks.call_chain_ms >= 0.0);
1213 assert!(benchmarks.total_ms >= 0.0);
1214
1215 let sum = benchmarks.impact_analysis_ms + benchmarks.dead_code_ms
1217 + benchmarks.reference_chain_ms + benchmarks.call_chain_ms;
1218 assert!(benchmarks.total_ms >= sum * 0.9); }
1220}