1use clap::ValueEnum;
18use serde::{Deserialize, Serialize};
19
20#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
30#[serde(rename_all = "snake_case")]
31pub enum Confidence {
32 High,
34 #[default]
36 Medium,
37 Low,
39}
40
41impl std::fmt::Display for Confidence {
42 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
43 match self {
44 Self::High => write!(f, "high"),
45 Self::Medium => write!(f, "medium"),
46 Self::Low => write!(f, "low"),
47 }
48 }
49}
50
51#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
59#[serde(rename_all = "snake_case")]
60pub enum DocstringStyle {
61 Google,
63 Numpy,
65 Sphinx,
67 #[default]
69 Plain,
70}
71
72impl std::fmt::Display for DocstringStyle {
73 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
74 match self {
75 Self::Google => write!(f, "google"),
76 Self::Numpy => write!(f, "numpy"),
77 Self::Sphinx => write!(f, "sphinx"),
78 Self::Plain => write!(f, "plain"),
79 }
80 }
81}
82
83#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
91#[serde(rename_all = "snake_case")]
92pub enum EffectType {
93 Io,
95 GlobalWrite,
97 AttributeWrite,
99 CollectionModify,
101 Call,
103 UnknownCall,
105}
106
107impl std::fmt::Display for EffectType {
108 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
109 match self {
110 Self::Io => write!(f, "io"),
111 Self::GlobalWrite => write!(f, "global_write"),
112 Self::AttributeWrite => write!(f, "attribute_write"),
113 Self::CollectionModify => write!(f, "collection_modify"),
114 Self::Call => write!(f, "call"),
115 Self::UnknownCall => write!(f, "unknown_call"),
116 }
117 }
118}
119
120#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
128#[serde(rename_all = "snake_case")]
129pub enum ConditionSource {
130 Guard,
132 Docstring,
134 TypeHint,
136 Assertion,
138 Icontract,
140 Deal,
142}
143
144impl std::fmt::Display for ConditionSource {
145 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
146 match self {
147 Self::Guard => write!(f, "guard"),
148 Self::Docstring => write!(f, "docstring"),
149 Self::TypeHint => write!(f, "type_hint"),
150 Self::Assertion => write!(f, "assertion"),
151 Self::Icontract => write!(f, "icontract"),
152 Self::Deal => write!(f, "deal"),
153 }
154 }
155}
156
157#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
163#[serde(rename_all = "snake_case")]
164pub enum CohesionVerdict {
165 Cohesive,
167 SplitCandidate,
169}
170
171impl CohesionVerdict {
172 pub fn from_lcom4(lcom4: u32) -> Self {
174 if lcom4 <= 1 {
175 Self::Cohesive
176 } else {
177 Self::SplitCandidate
178 }
179 }
180}
181
182impl std::fmt::Display for CohesionVerdict {
183 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
184 match self {
185 Self::Cohesive => write!(f, "cohesive"),
186 Self::SplitCandidate => write!(f, "split_candidate"),
187 }
188 }
189}
190
191#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
193pub struct ComponentInfo {
194 pub methods: Vec<String>,
196 pub fields: Vec<String>,
198}
199
200#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
202pub struct ClassCohesion {
203 pub class_name: String,
205 pub file_path: String,
207 pub line: u32,
209 pub lcom4: u32,
211 pub method_count: u32,
213 pub field_count: u32,
215 pub verdict: CohesionVerdict,
217 pub split_suggestion: Option<String>,
219 pub components: Vec<ComponentInfo>,
221}
222
223#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
225pub struct CohesionSummary {
226 pub total_classes: u32,
228 pub cohesive: u32,
230 pub split_candidates: u32,
232 pub avg_lcom4: f64,
234}
235
236impl Default for CohesionSummary {
237 fn default() -> Self {
238 Self {
239 total_classes: 0,
240 cohesive: 0,
241 split_candidates: 0,
242 avg_lcom4: 0.0,
243 }
244 }
245}
246
247#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
249pub struct CohesionReport {
250 pub classes: Vec<ClassCohesion>,
252 pub summary: CohesionSummary,
254}
255
256#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
262#[serde(rename_all = "snake_case")]
263pub enum CouplingVerdict {
264 Low,
266 Moderate,
268 High,
270 VeryHigh,
272}
273
274impl CouplingVerdict {
275 pub fn from_score(score: f64) -> Self {
277 if score < 0.2 {
278 Self::Low
279 } else if score < 0.4 {
280 Self::Moderate
281 } else if score < 0.6 {
282 Self::High
283 } else {
284 Self::VeryHigh
285 }
286 }
287}
288
289impl std::fmt::Display for CouplingVerdict {
290 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
291 match self {
292 Self::Low => write!(f, "low"),
293 Self::Moderate => write!(f, "moderate"),
294 Self::High => write!(f, "high"),
295 Self::VeryHigh => write!(f, "very_high"),
296 }
297 }
298}
299
300#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
302pub struct CrossCall {
303 pub caller: String,
305 pub callee: String,
307 pub line: u32,
309}
310
311#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
313pub struct CrossCalls {
314 pub calls: Vec<CrossCall>,
316 pub count: u32,
318}
319
320#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
322pub struct CouplingReport {
323 pub path_a: String,
325 pub path_b: String,
327 pub a_to_b: CrossCalls,
329 pub b_to_a: CrossCalls,
331 pub total_calls: u32,
333 pub coupling_score: f64,
335 pub verdict: CouplingVerdict,
337}
338
339#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
345pub struct FunctionPurity {
346 pub name: String,
348 pub classification: String,
350 pub effects: Vec<String>,
352 pub confidence: Confidence,
354}
355
356#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
358pub struct FilePurityReport {
359 pub source_file: String,
361 pub functions: Vec<FunctionPurity>,
363 pub pure_count: u32,
365}
366
367#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
369pub struct PurityReport {
370 pub files: Vec<FilePurityReport>,
372 pub total_functions: u32,
374 pub total_pure: u32,
376}
377
378#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
384pub struct TemporalExample {
385 pub file: String,
387 pub line: u32,
389}
390
391#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
393pub struct TemporalConstraint {
394 pub before: String,
396 pub after: String,
398 pub support: u32,
400 pub confidence: f64,
402 pub examples: Vec<TemporalExample>,
404}
405
406#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
408pub struct Trigram {
409 pub sequence: [String; 3],
411 pub support: u32,
413 pub confidence: f64,
415}
416
417#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
419pub struct TemporalMetadata {
420 pub files_analyzed: u32,
422 pub sequences_extracted: u32,
424 pub min_support: u32,
426 pub min_confidence: f64,
428}
429
430impl Default for TemporalMetadata {
431 fn default() -> Self {
432 Self {
433 files_analyzed: 0,
434 sequences_extracted: 0,
435 min_support: 2,
436 min_confidence: 0.5,
437 }
438 }
439}
440
441#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
443pub struct TemporalReport {
444 pub constraints: Vec<TemporalConstraint>,
446 pub trigrams: Vec<Trigram>,
448 pub metadata: TemporalMetadata,
450}
451
452#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
458pub struct FunctionInfo {
459 pub name: String,
461 pub signature: String,
463 pub docstring: Option<String>,
465 pub lineno: u32,
467 pub is_async: bool,
469}
470
471#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
473pub struct MethodInfo {
474 pub name: String,
476 pub signature: String,
478 pub is_async: bool,
480}
481
482#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
484pub struct ClassInfo {
485 pub name: String,
487 pub lineno: u32,
489 pub bases: Vec<String>,
491 pub methods: Vec<MethodInfo>,
493 pub private_method_count: u32,
495}
496
497#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
499pub struct InterfaceInfo {
500 pub file: String,
502 pub all_exports: Option<Vec<String>>,
504 pub functions: Vec<FunctionInfo>,
506 pub classes: Vec<ClassInfo>,
508}
509
510#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
516pub struct ResourceInfo {
517 pub name: String,
519 pub resource_type: String,
521 pub line: u32,
523 pub closed: bool,
525}
526
527#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
529pub struct LeakInfo {
530 pub resource: String,
532 pub line: u32,
534 pub paths: Option<Vec<String>>,
536}
537
538#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
540pub struct DoubleCloseInfo {
541 pub resource: String,
543 pub first_close: u32,
545 pub second_close: u32,
547}
548
549#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
551pub struct UseAfterCloseInfo {
552 pub resource: String,
554 pub close_line: u32,
556 pub use_line: u32,
558}
559
560#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
562pub struct ContextSuggestion {
563 pub resource: String,
565 pub suggestion: String,
567}
568
569#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
571pub struct ResourceConstraint {
572 pub rule: String,
574 pub context: String,
576 pub confidence: f64,
578}
579
580#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
582pub struct ResourceSummary {
583 pub resources_detected: u32,
585 pub leaks_found: u32,
587 pub double_closes_found: u32,
589 pub use_after_closes_found: u32,
591}
592
593#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
595pub struct ResourceReport {
596 pub file: String,
598 pub language: String,
600 pub function: Option<String>,
602 pub resources: Vec<ResourceInfo>,
604 pub leaks: Vec<LeakInfo>,
606 pub double_closes: Vec<DoubleCloseInfo>,
608 pub use_after_closes: Vec<UseAfterCloseInfo>,
610 pub suggestions: Vec<ContextSuggestion>,
612 pub constraints: Vec<ResourceConstraint>,
614 pub summary: ResourceSummary,
616 pub analysis_time_ms: u64,
618}
619
620#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
626pub struct Precondition {
627 pub param: String,
629 pub expression: Option<String>,
631 pub description: Option<String>,
633 pub type_hint: Option<String>,
635 pub source: ConditionSource,
637}
638
639#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
641pub struct Postcondition {
642 pub expression: Option<String>,
644 pub description: Option<String>,
646 pub type_hint: Option<String>,
648}
649
650#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
652pub struct ExceptionInfo {
653 pub exception_type: String,
655 pub description: Option<String>,
657}
658
659#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
661pub struct YieldInfo {
662 pub type_hint: Option<String>,
664 pub description: Option<String>,
666}
667
668#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
670pub struct SideEffect {
671 pub effect_type: EffectType,
673 pub target: Option<String>,
675}
676
677#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
679pub struct FunctionBehavior {
680 pub function_name: String,
682 pub file_path: String,
684 pub line: u32,
686 pub purity_classification: String,
688 pub is_generator: bool,
690 pub is_async: bool,
692 pub preconditions: Vec<Precondition>,
694 pub postconditions: Vec<Postcondition>,
696 pub exceptions: Vec<ExceptionInfo>,
698 pub yields: Vec<YieldInfo>,
700 pub side_effects: Vec<SideEffect>,
702}
703
704#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
706pub struct ClassInvariant {
707 pub expression: String,
709}
710
711#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
713pub struct ClassBehavior {
714 pub class_name: String,
716 pub invariants: Vec<ClassInvariant>,
718 pub methods: Vec<FunctionBehavior>,
720}
721
722#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
724pub struct BehavioralReport {
725 pub file_path: String,
727 pub docstring_style: DocstringStyle,
729 pub has_icontract: bool,
731 pub has_deal: bool,
733 pub functions: Vec<FunctionBehavior>,
735 pub classes: Vec<ClassBehavior>,
737}
738
739#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
745pub struct VariableMutability {
746 pub name: String,
748 pub mutable: bool,
750 pub reassignments: u32,
752 pub mutations: u32,
754}
755
756#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
758pub struct ParameterMutability {
759 pub name: String,
761 pub mutated: bool,
763 pub mutation_sites: Vec<u32>,
765}
766
767#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
769pub struct CollectionMutation {
770 pub variable: String,
772 pub operation: String,
774 pub line: u32,
776}
777
778#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
780pub struct FunctionMutability {
781 pub name: String,
783 pub variables: Vec<VariableMutability>,
785 pub parameters: Vec<ParameterMutability>,
787 pub collection_mutations: Vec<CollectionMutation>,
789}
790
791#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
793pub struct FieldMutability {
794 pub name: String,
796 pub mutable: bool,
798 pub init_only: bool,
800}
801
802#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
804pub struct ClassMutability {
805 pub name: String,
807 pub fields: Vec<FieldMutability>,
809}
810
811#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
813pub struct MutabilitySummary {
814 pub functions_analyzed: u32,
816 pub classes_analyzed: u32,
818 pub total_variables: u32,
820 pub mutable_variables: u32,
822 pub immutable_variables: u32,
824 pub immutable_pct: f64,
826 pub parameters_analyzed: u32,
828 pub mutated_parameters: u32,
830 pub unmutated_pct: f64,
832 pub fields_analyzed: u32,
834 pub mutable_fields: u32,
836 pub constraints_generated: u32,
838}
839
840impl Default for MutabilitySummary {
841 fn default() -> Self {
842 Self {
843 functions_analyzed: 0,
844 classes_analyzed: 0,
845 total_variables: 0,
846 mutable_variables: 0,
847 immutable_variables: 0,
848 immutable_pct: 0.0,
849 parameters_analyzed: 0,
850 mutated_parameters: 0,
851 unmutated_pct: 0.0,
852 fields_analyzed: 0,
853 mutable_fields: 0,
854 constraints_generated: 0,
855 }
856 }
857}
858
859#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
861pub struct MutabilityReport {
862 pub file: String,
864 pub language: String,
866 pub functions: Vec<FunctionMutability>,
868 pub classes: Vec<ClassMutability>,
870 pub summary: MutabilitySummary,
872 pub analysis_time_ms: u64,
874}
875
876#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, ValueEnum)]
882pub enum OutputFormat {
883 #[default]
885 Json,
886
887 Text,
889}
890
891impl std::fmt::Display for OutputFormat {
892 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
893 match self {
894 Self::Json => write!(f, "json"),
895 Self::Text => write!(f, "text"),
896 }
897 }
898}
899
900#[cfg(test)]
905mod tests {
906 use super::*;
907
908 #[test]
913 fn test_confidence_enum_serialization() {
914 let json = serde_json::to_string(&Confidence::High).unwrap();
915 assert_eq!(json, r#""high""#);
916
917 let json = serde_json::to_string(&Confidence::Medium).unwrap();
918 assert_eq!(json, r#""medium""#);
919
920 let json = serde_json::to_string(&Confidence::Low).unwrap();
921 assert_eq!(json, r#""low""#);
922 }
923
924 #[test]
925 fn test_confidence_enum_deserialization() {
926 let high: Confidence = serde_json::from_str(r#""high""#).unwrap();
927 assert_eq!(high, Confidence::High);
928
929 let medium: Confidence = serde_json::from_str(r#""medium""#).unwrap();
930 assert_eq!(medium, Confidence::Medium);
931
932 let low: Confidence = serde_json::from_str(r#""low""#).unwrap();
933 assert_eq!(low, Confidence::Low);
934 }
935
936 #[test]
937 fn test_confidence_display() {
938 assert_eq!(Confidence::High.to_string(), "high");
939 assert_eq!(Confidence::Medium.to_string(), "medium");
940 assert_eq!(Confidence::Low.to_string(), "low");
941 }
942
943 #[test]
944 fn test_confidence_default() {
945 assert_eq!(Confidence::default(), Confidence::Medium);
946 }
947
948 #[test]
953 fn test_docstring_style_serialization() {
954 let json = serde_json::to_string(&DocstringStyle::Google).unwrap();
955 assert_eq!(json, r#""google""#);
956
957 let json = serde_json::to_string(&DocstringStyle::Numpy).unwrap();
958 assert_eq!(json, r#""numpy""#);
959
960 let json = serde_json::to_string(&DocstringStyle::Sphinx).unwrap();
961 assert_eq!(json, r#""sphinx""#);
962
963 let json = serde_json::to_string(&DocstringStyle::Plain).unwrap();
964 assert_eq!(json, r#""plain""#);
965 }
966
967 #[test]
968 fn test_docstring_style_display() {
969 assert_eq!(DocstringStyle::Google.to_string(), "google");
970 assert_eq!(DocstringStyle::Numpy.to_string(), "numpy");
971 assert_eq!(DocstringStyle::Sphinx.to_string(), "sphinx");
972 assert_eq!(DocstringStyle::Plain.to_string(), "plain");
973 }
974
975 #[test]
980 fn test_effect_type_serialization() {
981 let json = serde_json::to_string(&EffectType::Io).unwrap();
982 assert_eq!(json, r#""io""#);
983
984 let json = serde_json::to_string(&EffectType::GlobalWrite).unwrap();
985 assert_eq!(json, r#""global_write""#);
986
987 let json = serde_json::to_string(&EffectType::AttributeWrite).unwrap();
988 assert_eq!(json, r#""attribute_write""#);
989
990 let json = serde_json::to_string(&EffectType::CollectionModify).unwrap();
991 assert_eq!(json, r#""collection_modify""#);
992
993 let json = serde_json::to_string(&EffectType::Call).unwrap();
994 assert_eq!(json, r#""call""#);
995 }
996
997 #[test]
1002 fn test_condition_source_serialization() {
1003 let json = serde_json::to_string(&ConditionSource::Guard).unwrap();
1004 assert_eq!(json, r#""guard""#);
1005
1006 let json = serde_json::to_string(&ConditionSource::Docstring).unwrap();
1007 assert_eq!(json, r#""docstring""#);
1008
1009 let json = serde_json::to_string(&ConditionSource::TypeHint).unwrap();
1010 assert_eq!(json, r#""type_hint""#);
1011
1012 let json = serde_json::to_string(&ConditionSource::Assertion).unwrap();
1013 assert_eq!(json, r#""assertion""#);
1014
1015 let json = serde_json::to_string(&ConditionSource::Icontract).unwrap();
1016 assert_eq!(json, r#""icontract""#);
1017
1018 let json = serde_json::to_string(&ConditionSource::Deal).unwrap();
1019 assert_eq!(json, r#""deal""#);
1020 }
1021
1022 #[test]
1027 fn test_cohesion_verdict_from_lcom4() {
1028 assert_eq!(CohesionVerdict::from_lcom4(0), CohesionVerdict::Cohesive);
1029 assert_eq!(CohesionVerdict::from_lcom4(1), CohesionVerdict::Cohesive);
1030 assert_eq!(
1031 CohesionVerdict::from_lcom4(2),
1032 CohesionVerdict::SplitCandidate
1033 );
1034 assert_eq!(
1035 CohesionVerdict::from_lcom4(5),
1036 CohesionVerdict::SplitCandidate
1037 );
1038 }
1039
1040 #[test]
1045 fn test_coupling_verdict_from_score() {
1046 assert_eq!(CouplingVerdict::from_score(0.0), CouplingVerdict::Low);
1047 assert_eq!(CouplingVerdict::from_score(0.1), CouplingVerdict::Low);
1048 assert_eq!(CouplingVerdict::from_score(0.2), CouplingVerdict::Moderate);
1049 assert_eq!(CouplingVerdict::from_score(0.3), CouplingVerdict::Moderate);
1050 assert_eq!(CouplingVerdict::from_score(0.4), CouplingVerdict::High);
1051 assert_eq!(CouplingVerdict::from_score(0.5), CouplingVerdict::High);
1052 assert_eq!(CouplingVerdict::from_score(0.6), CouplingVerdict::VeryHigh);
1053 assert_eq!(CouplingVerdict::from_score(1.0), CouplingVerdict::VeryHigh);
1054 }
1055
1056 #[test]
1061 fn test_class_cohesion_serialization() {
1062 let cohesion = ClassCohesion {
1063 class_name: "MyClass".to_string(),
1064 file_path: "test.py".to_string(),
1065 line: 10,
1066 lcom4: 2,
1067 method_count: 4,
1068 field_count: 3,
1069 verdict: CohesionVerdict::SplitCandidate,
1070 split_suggestion: Some("Consider splitting".to_string()),
1071 components: vec![ComponentInfo {
1072 methods: vec!["method1".to_string()],
1073 fields: vec!["field1".to_string()],
1074 }],
1075 };
1076
1077 let json = serde_json::to_string(&cohesion).unwrap();
1078 let parsed: ClassCohesion = serde_json::from_str(&json).unwrap();
1079 assert_eq!(parsed.class_name, "MyClass");
1080 assert_eq!(parsed.lcom4, 2);
1081 }
1082
1083 #[test]
1084 fn test_coupling_report_serialization() {
1085 let report = CouplingReport {
1086 path_a: "module_a.py".to_string(),
1087 path_b: "module_b.py".to_string(),
1088 a_to_b: CrossCalls {
1089 calls: vec![CrossCall {
1090 caller: "func_a".to_string(),
1091 callee: "func_b".to_string(),
1092 line: 10,
1093 }],
1094 count: 1,
1095 },
1096 b_to_a: CrossCalls::default(),
1097 total_calls: 1,
1098 coupling_score: 0.1,
1099 verdict: CouplingVerdict::Low,
1100 };
1101
1102 let json = serde_json::to_string(&report).unwrap();
1103 let parsed: CouplingReport = serde_json::from_str(&json).unwrap();
1104 assert_eq!(parsed.coupling_score, 0.1);
1105 }
1106
1107 #[test]
1108 fn test_purity_report_serialization() {
1109 let report = PurityReport {
1110 files: vec![FilePurityReport {
1111 source_file: "test.py".to_string(),
1112 functions: vec![FunctionPurity {
1113 name: "pure_func".to_string(),
1114 classification: "pure".to_string(),
1115 effects: vec![],
1116 confidence: Confidence::High,
1117 }],
1118 pure_count: 1,
1119 }],
1120 total_functions: 1,
1121 total_pure: 1,
1122 };
1123
1124 let json = serde_json::to_string(&report).unwrap();
1125 let parsed: PurityReport = serde_json::from_str(&json).unwrap();
1126 assert_eq!(parsed.total_pure, 1);
1127 }
1128
1129 #[test]
1130 fn test_temporal_report_serialization() {
1131 let report = TemporalReport {
1132 constraints: vec![TemporalConstraint {
1133 before: "open".to_string(),
1134 after: "close".to_string(),
1135 support: 5,
1136 confidence: 0.9,
1137 examples: vec![TemporalExample {
1138 file: "test.py".to_string(),
1139 line: 10,
1140 }],
1141 }],
1142 trigrams: vec![],
1143 metadata: TemporalMetadata::default(),
1144 };
1145
1146 let json = serde_json::to_string(&report).unwrap();
1147 let parsed: TemporalReport = serde_json::from_str(&json).unwrap();
1148 assert_eq!(parsed.constraints[0].before, "open");
1149 }
1150
1151 #[test]
1152 fn test_interface_info_serialization() {
1153 let info = InterfaceInfo {
1154 file: "test.py".to_string(),
1155 all_exports: Some(vec!["func1".to_string()]),
1156 functions: vec![FunctionInfo {
1157 name: "func1".to_string(),
1158 signature: "def func1(x: int) -> str".to_string(),
1159 docstring: Some("A function".to_string()),
1160 lineno: 5,
1161 is_async: false,
1162 }],
1163 classes: vec![],
1164 };
1165
1166 let json = serde_json::to_string(&info).unwrap();
1167 let parsed: InterfaceInfo = serde_json::from_str(&json).unwrap();
1168 assert_eq!(parsed.functions[0].name, "func1");
1169 }
1170
1171 #[test]
1172 fn test_resource_report_serialization() {
1173 let report = ResourceReport {
1174 file: "test.py".to_string(),
1175 language: "python".to_string(),
1176 function: Some("process".to_string()),
1177 resources: vec![ResourceInfo {
1178 name: "f".to_string(),
1179 resource_type: "file".to_string(),
1180 line: 10,
1181 closed: true,
1182 }],
1183 leaks: vec![],
1184 double_closes: vec![],
1185 use_after_closes: vec![],
1186 suggestions: vec![],
1187 constraints: vec![],
1188 summary: ResourceSummary::default(),
1189 analysis_time_ms: 50,
1190 };
1191
1192 let json = serde_json::to_string(&report).unwrap();
1193 let parsed: ResourceReport = serde_json::from_str(&json).unwrap();
1194 assert_eq!(parsed.resources[0].name, "f");
1195 }
1196
1197 #[test]
1198 fn test_behavioral_report_serialization() {
1199 let report = BehavioralReport {
1200 file_path: "test.py".to_string(),
1201 docstring_style: DocstringStyle::Google,
1202 has_icontract: false,
1203 has_deal: false,
1204 functions: vec![FunctionBehavior {
1205 function_name: "validate".to_string(),
1206 file_path: "test.py".to_string(),
1207 line: 10,
1208 purity_classification: "pure".to_string(),
1209 is_generator: false,
1210 is_async: false,
1211 preconditions: vec![Precondition {
1212 param: "x".to_string(),
1213 expression: Some("x > 0".to_string()),
1214 description: None,
1215 type_hint: Some("int".to_string()),
1216 source: ConditionSource::Guard,
1217 }],
1218 postconditions: vec![],
1219 exceptions: vec![],
1220 yields: vec![],
1221 side_effects: vec![],
1222 }],
1223 classes: vec![],
1224 };
1225
1226 let json = serde_json::to_string(&report).unwrap();
1227 let parsed: BehavioralReport = serde_json::from_str(&json).unwrap();
1228 assert_eq!(parsed.functions[0].function_name, "validate");
1229 }
1230
1231 #[test]
1232 fn test_mutability_report_serialization() {
1233 let report = MutabilityReport {
1234 file: "test.py".to_string(),
1235 language: "python".to_string(),
1236 functions: vec![FunctionMutability {
1237 name: "process".to_string(),
1238 variables: vec![VariableMutability {
1239 name: "count".to_string(),
1240 mutable: true,
1241 reassignments: 3,
1242 mutations: 0,
1243 }],
1244 parameters: vec![],
1245 collection_mutations: vec![],
1246 }],
1247 classes: vec![],
1248 summary: MutabilitySummary::default(),
1249 analysis_time_ms: 30,
1250 };
1251
1252 let json = serde_json::to_string(&report).unwrap();
1253 let parsed: MutabilityReport = serde_json::from_str(&json).unwrap();
1254 assert_eq!(parsed.functions[0].name, "process");
1255 }
1256
1257 #[test]
1262 fn test_output_format_display() {
1263 assert_eq!(OutputFormat::Json.to_string(), "json");
1264 assert_eq!(OutputFormat::Text.to_string(), "text");
1265 }
1266
1267 #[test]
1268 fn test_output_format_default() {
1269 assert_eq!(OutputFormat::default(), OutputFormat::Json);
1270 }
1271}