1use recoco::utils::fingerprint::{Fingerprint, Fingerprinter};
11use serde::{Deserialize, Serialize};
12use std::path::{Path, PathBuf};
13use thread_utilities::RapidSet;
14
15#[derive(Debug, Clone)]
32pub struct AnalysisDefFingerprint {
33 pub source_files: RapidSet<PathBuf>,
36
37 pub fingerprint: Fingerprint,
40
41 pub last_analyzed: Option<i64>,
44}
45
46#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
66pub struct DependencyEdge {
67 pub from: PathBuf,
69
70 pub to: PathBuf,
72
73 pub dep_type: DependencyType,
75
76 pub symbol: Option<SymbolDependency>,
79}
80
81#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
85pub enum DependencyType {
86 Import,
88
89 Export,
91
92 Macro,
94
95 Type,
97
98 Trait,
100}
101
102#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
107pub enum DependencyStrength {
108 Strong,
110
111 Weak,
113}
114
115#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
134pub struct SymbolDependency {
135 pub from_symbol: String,
137
138 pub to_symbol: String,
140
141 pub kind: SymbolKind,
143
144 pub strength: DependencyStrength,
146}
147
148#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
150pub enum SymbolKind {
151 Function,
153
154 Class,
156
157 Interface,
159
160 TypeAlias,
162
163 Constant,
165
166 Enum,
168
169 Module,
171
172 Macro,
174}
175
176impl AnalysisDefFingerprint {
179 pub fn new(content: &[u8]) -> Self {
197 let mut fingerprinter = Fingerprinter::default();
198 fingerprinter.write_raw_bytes(content);
199 Self {
200 source_files: thread_utilities::get_set(),
201 fingerprint: fingerprinter.into_fingerprint(),
202 last_analyzed: None,
203 }
204 }
205
206 pub fn with_sources(content: &[u8], source_files: RapidSet<PathBuf>) -> Self {
228 let mut fingerprinter = Fingerprinter::default();
229 fingerprinter.write_raw_bytes(content);
230 Self {
231 source_files,
232 fingerprint: fingerprinter.into_fingerprint(),
233 last_analyzed: None,
234 }
235 }
236
237 pub fn update_fingerprint(&self, content: &[u8]) -> Self {
257 let mut fingerprinter = Fingerprinter::default();
258 fingerprinter.write_raw_bytes(content);
259 Self {
260 source_files: self.source_files.clone(),
261 fingerprint: fingerprinter.into_fingerprint(),
262 last_analyzed: None,
263 }
264 }
265
266 pub fn content_matches(&self, content: &[u8]) -> bool {
285 let mut fingerprinter = Fingerprinter::default();
286 fingerprinter.write_raw_bytes(content);
287 let other = fingerprinter.into_fingerprint();
288 self.fingerprint.as_slice() == other.as_slice()
289 }
290
291 pub fn add_source_file(&mut self, path: PathBuf) {
297 self.source_files.insert(path);
298 }
299
300 pub fn remove_source_file(&mut self, path: &Path) -> bool {
310 self.source_files.remove(path)
311 }
312
313 pub fn set_last_analyzed(&mut self, timestamp: i64) {
319 self.last_analyzed = Some(timestamp);
320 }
321
322 pub fn source_file_count(&self) -> usize {
324 self.source_files.len()
325 }
326
327 pub fn fingerprint(&self) -> &Fingerprint {
329 &self.fingerprint
330 }
331}
332
333impl DependencyEdge {
334 pub fn new(from: PathBuf, to: PathBuf, dep_type: DependencyType) -> Self {
356 Self {
357 from,
358 to,
359 dep_type,
360 symbol: None,
361 }
362 }
363
364 pub fn with_symbol(
373 from: PathBuf,
374 to: PathBuf,
375 dep_type: DependencyType,
376 symbol: SymbolDependency,
377 ) -> Self {
378 Self {
379 from,
380 to,
381 dep_type,
382 symbol: Some(symbol),
383 }
384 }
385
386 pub fn effective_strength(&self) -> DependencyStrength {
392 if let Some(ref sym) = self.symbol {
393 return sym.strength;
394 }
395 match self.dep_type {
396 DependencyType::Import | DependencyType::Trait | DependencyType::Macro => {
397 DependencyStrength::Strong
398 }
399 DependencyType::Export | DependencyType::Type => DependencyStrength::Weak,
400 }
401 }
402}
403
404impl std::fmt::Display for DependencyType {
405 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
406 match self {
407 Self::Import => write!(f, "import"),
408 Self::Export => write!(f, "export"),
409 Self::Macro => write!(f, "macro"),
410 Self::Type => write!(f, "type"),
411 Self::Trait => write!(f, "trait"),
412 }
413 }
414}
415
416impl std::fmt::Display for DependencyStrength {
417 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
418 match self {
419 Self::Strong => write!(f, "strong"),
420 Self::Weak => write!(f, "weak"),
421 }
422 }
423}
424
425impl std::fmt::Display for SymbolKind {
426 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
427 match self {
428 Self::Function => write!(f, "function"),
429 Self::Class => write!(f, "class"),
430 Self::Interface => write!(f, "interface"),
431 Self::TypeAlias => write!(f, "type_alias"),
432 Self::Constant => write!(f, "constant"),
433 Self::Enum => write!(f, "enum"),
434 Self::Module => write!(f, "module"),
435 Self::Macro => write!(f, "macro"),
436 }
437 }
438}
439
440#[cfg(test)]
443mod tests {
444 use super::*;
445
446 #[test]
449 fn test_fingerprint_new_creates_valid_fingerprint() {
450 let content = b"fn main() { println!(\"hello\"); }";
451 let fp = AnalysisDefFingerprint::new(content);
452
453 assert_eq!(fp.fingerprint.as_slice().len(), 16);
455 assert!(fp.source_files.is_empty());
457 assert!(fp.last_analyzed.is_none());
459 }
460
461 #[test]
462 fn test_fingerprint_content_matches_same_content() {
463 let content = b"use std::collections::HashMap;";
464 let fp = AnalysisDefFingerprint::new(content);
465 assert!(fp.content_matches(content));
466 }
467
468 #[test]
469 fn test_fingerprint_content_does_not_match_different_content() {
470 let fp = AnalysisDefFingerprint::new(b"original content");
471 assert!(!fp.content_matches(b"modified content"));
472 }
473
474 #[test]
475 fn test_fingerprint_deterministic() {
476 let content = b"deterministic test content";
477 let fp1 = AnalysisDefFingerprint::new(content);
478 let fp2 = AnalysisDefFingerprint::new(content);
479 assert_eq!(fp1.fingerprint.as_slice(), fp2.fingerprint.as_slice());
480 }
481
482 #[test]
483 fn test_fingerprint_different_content_different_hash() {
484 let fp1 = AnalysisDefFingerprint::new(b"content A");
485 let fp2 = AnalysisDefFingerprint::new(b"content B");
486 assert_ne!(fp1.fingerprint.as_slice(), fp2.fingerprint.as_slice());
487 }
488
489 #[test]
490 fn test_fingerprint_empty_content() {
491 let fp = AnalysisDefFingerprint::new(b"");
492 assert_eq!(fp.fingerprint.as_slice().len(), 16);
493 assert!(fp.content_matches(b""));
494 assert!(!fp.content_matches(b"non-empty"));
495 }
496
497 #[test]
498 fn test_fingerprint_with_sources() {
499 let sources: RapidSet<PathBuf> = [
500 PathBuf::from("src/utils.rs"),
501 PathBuf::from("src/config.rs"),
502 ]
503 .into_iter()
504 .collect();
505 let fp = AnalysisDefFingerprint::with_sources(b"content", sources.clone());
506 assert_eq!(fp.source_files, sources);
507 assert!(fp.content_matches(b"content"));
508 }
509
510 #[test]
511 fn test_fingerprint_update_changes_hash() {
512 let fp = AnalysisDefFingerprint::new(b"old content");
513 let updated = fp.update_fingerprint(b"new content");
514
515 assert_ne!(
516 fp.fingerprint.as_slice(),
517 updated.fingerprint.as_slice(),
518 "Updated fingerprint should differ from original"
519 );
520 assert!(updated.content_matches(b"new content"));
521 assert!(!updated.content_matches(b"old content"));
522 }
523
524 #[test]
525 fn test_fingerprint_update_preserves_source_files() {
526 let sources: RapidSet<PathBuf> = [PathBuf::from("dep.rs")].into_iter().collect();
527 let fp = AnalysisDefFingerprint::with_sources(b"old", sources.clone());
528 let updated = fp.update_fingerprint(b"new");
529 assert_eq!(updated.source_files, sources);
530 }
531
532 #[test]
533 fn test_fingerprint_update_resets_timestamp() {
534 let mut fp = AnalysisDefFingerprint::new(b"content");
535 fp.set_last_analyzed(1000000);
536 let updated = fp.update_fingerprint(b"new content");
537 assert!(
538 updated.last_analyzed.is_none(),
539 "Updated fingerprint should reset timestamp"
540 );
541 }
542
543 #[test]
544 fn test_fingerprint_add_source_file() {
545 let mut fp = AnalysisDefFingerprint::new(b"content");
546 assert_eq!(fp.source_file_count(), 0);
547
548 fp.add_source_file(PathBuf::from("a.rs"));
549 assert_eq!(fp.source_file_count(), 1);
550
551 fp.add_source_file(PathBuf::from("b.rs"));
552 assert_eq!(fp.source_file_count(), 2);
553
554 fp.add_source_file(PathBuf::from("a.rs"));
556 assert_eq!(fp.source_file_count(), 2);
557 }
558
559 #[test]
560 fn test_fingerprint_remove_source_file() {
561 let mut fp = AnalysisDefFingerprint::with_sources(
562 b"content",
563 [PathBuf::from("a.rs"), PathBuf::from("b.rs")]
564 .into_iter()
565 .collect::<RapidSet<PathBuf>>(),
566 );
567
568 assert!(fp.remove_source_file(Path::new("a.rs")));
569 assert_eq!(fp.source_file_count(), 1);
570
571 assert!(!fp.remove_source_file(Path::new("c.rs")));
573 assert_eq!(fp.source_file_count(), 1);
574 }
575
576 #[test]
577 fn test_fingerprint_set_last_analyzed() {
578 let mut fp = AnalysisDefFingerprint::new(b"content");
579 assert!(fp.last_analyzed.is_none());
580
581 fp.set_last_analyzed(1_706_400_000_000_000); assert_eq!(fp.last_analyzed, Some(1_706_400_000_000_000));
583 }
584
585 #[test]
586 fn test_fingerprint_accessor() {
587 let fp = AnalysisDefFingerprint::new(b"test");
588 let fingerprint_ref = fp.fingerprint();
589 assert_eq!(fingerprint_ref.as_slice().len(), 16);
590 }
591
592 #[test]
595 fn test_dependency_edge_new() {
596 let edge = DependencyEdge::new(
597 PathBuf::from("src/main.rs"),
598 PathBuf::from("src/utils.rs"),
599 DependencyType::Import,
600 );
601
602 assert_eq!(edge.from, PathBuf::from("src/main.rs"));
603 assert_eq!(edge.to, PathBuf::from("src/utils.rs"));
604 assert_eq!(edge.dep_type, DependencyType::Import);
605 assert!(edge.symbol.is_none());
606 }
607
608 #[test]
609 fn test_dependency_edge_with_symbol() {
610 let symbol = SymbolDependency {
611 from_symbol: "main".to_string(),
612 to_symbol: "parse_config".to_string(),
613 kind: SymbolKind::Function,
614 strength: DependencyStrength::Strong,
615 };
616
617 let edge = DependencyEdge::with_symbol(
618 PathBuf::from("main.rs"),
619 PathBuf::from("config.rs"),
620 DependencyType::Import,
621 symbol.clone(),
622 );
623
624 assert!(edge.symbol.is_some());
625 assert_eq!(edge.symbol.unwrap().to_symbol, "parse_config");
626 }
627
628 #[test]
629 fn test_dependency_edge_effective_strength_import() {
630 let edge = DependencyEdge::new(
631 PathBuf::from("a.rs"),
632 PathBuf::from("b.rs"),
633 DependencyType::Import,
634 );
635 assert_eq!(edge.effective_strength(), DependencyStrength::Strong);
636 }
637
638 #[test]
639 fn test_dependency_edge_effective_strength_export() {
640 let edge = DependencyEdge::new(
641 PathBuf::from("a.rs"),
642 PathBuf::from("b.rs"),
643 DependencyType::Export,
644 );
645 assert_eq!(edge.effective_strength(), DependencyStrength::Weak);
646 }
647
648 #[test]
649 fn test_dependency_edge_effective_strength_trait() {
650 let edge = DependencyEdge::new(
651 PathBuf::from("a.rs"),
652 PathBuf::from("b.rs"),
653 DependencyType::Trait,
654 );
655 assert_eq!(edge.effective_strength(), DependencyStrength::Strong);
656 }
657
658 #[test]
659 fn test_dependency_edge_effective_strength_macro() {
660 let edge = DependencyEdge::new(
661 PathBuf::from("a.rs"),
662 PathBuf::from("b.rs"),
663 DependencyType::Macro,
664 );
665 assert_eq!(edge.effective_strength(), DependencyStrength::Strong);
666 }
667
668 #[test]
669 fn test_dependency_edge_effective_strength_type() {
670 let edge = DependencyEdge::new(
671 PathBuf::from("a.rs"),
672 PathBuf::from("b.rs"),
673 DependencyType::Type,
674 );
675 assert_eq!(edge.effective_strength(), DependencyStrength::Weak);
676 }
677
678 #[test]
679 fn test_dependency_edge_symbol_overrides_strength() {
680 let symbol = SymbolDependency {
681 from_symbol: "a".to_string(),
682 to_symbol: "b".to_string(),
683 kind: SymbolKind::Function,
684 strength: DependencyStrength::Weak,
685 };
686
687 let edge = DependencyEdge::with_symbol(
689 PathBuf::from("a.rs"),
690 PathBuf::from("b.rs"),
691 DependencyType::Import,
692 symbol,
693 );
694 assert_eq!(edge.effective_strength(), DependencyStrength::Weak);
695 }
696
697 #[test]
698 fn test_dependency_edge_equality() {
699 let edge1 = DependencyEdge::new(
700 PathBuf::from("a.rs"),
701 PathBuf::from("b.rs"),
702 DependencyType::Import,
703 );
704 let edge2 = DependencyEdge::new(
705 PathBuf::from("a.rs"),
706 PathBuf::from("b.rs"),
707 DependencyType::Import,
708 );
709 assert_eq!(edge1, edge2);
710 }
711
712 #[test]
713 fn test_dependency_edge_inequality_different_type() {
714 let edge1 = DependencyEdge::new(
715 PathBuf::from("a.rs"),
716 PathBuf::from("b.rs"),
717 DependencyType::Import,
718 );
719 let edge2 = DependencyEdge::new(
720 PathBuf::from("a.rs"),
721 PathBuf::from("b.rs"),
722 DependencyType::Export,
723 );
724 assert_ne!(edge1, edge2);
725 }
726
727 #[test]
730 fn test_dependency_edge_serialization_roundtrip() {
731 let edge = DependencyEdge::new(
732 PathBuf::from("src/main.rs"),
733 PathBuf::from("src/lib.rs"),
734 DependencyType::Import,
735 );
736
737 let json = serde_json::to_string(&edge).expect("serialize");
738 let deserialized: DependencyEdge = serde_json::from_str(&json).expect("deserialize");
739
740 assert_eq!(edge, deserialized);
741 }
742
743 #[test]
744 fn test_dependency_edge_with_symbol_serialization_roundtrip() {
745 let symbol = SymbolDependency {
746 from_symbol: "handler".to_string(),
747 to_symbol: "Router".to_string(),
748 kind: SymbolKind::Class,
749 strength: DependencyStrength::Strong,
750 };
751
752 let edge = DependencyEdge::with_symbol(
753 PathBuf::from("api.rs"),
754 PathBuf::from("router.rs"),
755 DependencyType::Import,
756 symbol,
757 );
758
759 let json = serde_json::to_string(&edge).expect("serialize");
760 let deserialized: DependencyEdge = serde_json::from_str(&json).expect("deserialize");
761
762 assert_eq!(edge, deserialized);
763 }
764
765 #[test]
768 fn test_dependency_type_display() {
769 assert_eq!(format!("{}", DependencyType::Import), "import");
770 assert_eq!(format!("{}", DependencyType::Export), "export");
771 assert_eq!(format!("{}", DependencyType::Macro), "macro");
772 assert_eq!(format!("{}", DependencyType::Type), "type");
773 assert_eq!(format!("{}", DependencyType::Trait), "trait");
774 }
775
776 #[test]
777 fn test_dependency_strength_display() {
778 assert_eq!(format!("{}", DependencyStrength::Strong), "strong");
779 assert_eq!(format!("{}", DependencyStrength::Weak), "weak");
780 }
781
782 #[test]
783 fn test_symbol_kind_display() {
784 assert_eq!(format!("{}", SymbolKind::Function), "function");
785 assert_eq!(format!("{}", SymbolKind::Class), "class");
786 assert_eq!(format!("{}", SymbolKind::Interface), "interface");
787 assert_eq!(format!("{}", SymbolKind::TypeAlias), "type_alias");
788 assert_eq!(format!("{}", SymbolKind::Constant), "constant");
789 assert_eq!(format!("{}", SymbolKind::Enum), "enum");
790 assert_eq!(format!("{}", SymbolKind::Module), "module");
791 assert_eq!(format!("{}", SymbolKind::Macro), "macro");
792 }
793
794 #[test]
797 fn test_symbol_dependency_creation() {
798 let dep = SymbolDependency {
799 from_symbol: "parse".to_string(),
800 to_symbol: "Config".to_string(),
801 kind: SymbolKind::Class,
802 strength: DependencyStrength::Strong,
803 };
804
805 assert_eq!(dep.from_symbol, "parse");
806 assert_eq!(dep.to_symbol, "Config");
807 assert_eq!(dep.kind, SymbolKind::Class);
808 assert_eq!(dep.strength, DependencyStrength::Strong);
809 }
810
811 #[test]
812 fn test_symbol_dependency_serialization_roundtrip() {
813 let dep = SymbolDependency {
814 from_symbol: "main".to_string(),
815 to_symbol: "run_server".to_string(),
816 kind: SymbolKind::Function,
817 strength: DependencyStrength::Strong,
818 };
819
820 let json = serde_json::to_string(&dep).expect("serialize");
821 let deserialized: SymbolDependency = serde_json::from_str(&json).expect("deserialize");
822
823 assert_eq!(dep, deserialized);
824 }
825
826 #[test]
829 fn test_fingerprint_large_content() {
830 let large_content: Vec<u8> = (0..1_000_000).map(|i| (i % 256) as u8).collect();
832 let fp = AnalysisDefFingerprint::new(&large_content);
833 assert!(fp.content_matches(&large_content));
834
835 let mut modified = large_content.clone();
837 modified[500_000] = modified[500_000].wrapping_add(1);
838 assert!(!fp.content_matches(&modified));
839 }
840
841 #[test]
842 fn test_fingerprint_binary_content() {
843 let binary = vec![0u8, 1, 255, 128, 0, 0, 64, 32];
845 let fp = AnalysisDefFingerprint::new(&binary);
846 assert!(fp.content_matches(&binary));
847 }
848}