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
321#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
323pub struct CouplingReport {
324 pub path_a: String,
326 pub path_b: String,
328 pub a_to_b: CrossCalls,
330 pub b_to_a: CrossCalls,
332 pub total_calls: u32,
334 pub coupling_score: f64,
336 pub verdict: CouplingVerdict,
338}
339
340#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
346pub struct FunctionPurity {
347 pub name: String,
349 pub classification: String,
351 pub effects: Vec<String>,
353 pub confidence: Confidence,
355}
356
357#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
359pub struct FilePurityReport {
360 pub source_file: String,
362 pub functions: Vec<FunctionPurity>,
364 pub pure_count: u32,
366}
367
368#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
370pub struct PurityReport {
371 pub files: Vec<FilePurityReport>,
373 pub total_functions: u32,
375 pub total_pure: u32,
377}
378
379#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
385pub struct TemporalExample {
386 pub file: String,
388 pub line: u32,
390}
391
392#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
394pub struct TemporalConstraint {
395 pub before: String,
397 pub after: String,
399 pub support: u32,
401 pub confidence: f64,
403 pub examples: Vec<TemporalExample>,
405}
406
407#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
409pub struct Trigram {
410 pub sequence: [String; 3],
412 pub support: u32,
414 pub confidence: f64,
416}
417
418#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
420pub struct TemporalMetadata {
421 pub files_analyzed: u32,
423 pub sequences_extracted: u32,
425 pub min_support: u32,
427 pub min_confidence: f64,
429}
430
431impl Default for TemporalMetadata {
432 fn default() -> Self {
433 Self {
434 files_analyzed: 0,
435 sequences_extracted: 0,
436 min_support: 2,
437 min_confidence: 0.5,
438 }
439 }
440}
441
442#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
444pub struct TemporalReport {
445 pub constraints: Vec<TemporalConstraint>,
447 pub trigrams: Vec<Trigram>,
449 pub metadata: TemporalMetadata,
451}
452
453#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
459pub struct FunctionInfo {
460 pub name: String,
462 pub signature: String,
464 pub docstring: Option<String>,
466 pub lineno: u32,
468 pub is_async: bool,
470}
471
472#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
474pub struct MethodInfo {
475 pub name: String,
477 pub signature: String,
479 pub is_async: bool,
481}
482
483#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
485pub struct ClassInfo {
486 pub name: String,
488 pub lineno: u32,
490 pub bases: Vec<String>,
492 pub methods: Vec<MethodInfo>,
494 pub private_method_count: u32,
496}
497
498#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
500pub struct InterfaceInfo {
501 pub file: String,
503 pub all_exports: Option<Vec<String>>,
505 pub functions: Vec<FunctionInfo>,
507 pub classes: Vec<ClassInfo>,
509}
510
511#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
517pub struct ResourceInfo {
518 pub name: String,
520 pub resource_type: String,
522 pub line: u32,
524 pub closed: bool,
526}
527
528#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
530pub struct LeakInfo {
531 pub resource: String,
533 pub line: u32,
535 pub paths: Option<Vec<String>>,
537}
538
539#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
541pub struct DoubleCloseInfo {
542 pub resource: String,
544 pub first_close: u32,
546 pub second_close: u32,
548}
549
550#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
552pub struct UseAfterCloseInfo {
553 pub resource: String,
555 pub close_line: u32,
557 pub use_line: u32,
559}
560
561#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
563pub struct ContextSuggestion {
564 pub resource: String,
566 pub suggestion: String,
568}
569
570#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
572pub struct ResourceConstraint {
573 pub rule: String,
575 pub context: String,
577 pub confidence: f64,
579}
580
581#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
583pub struct ResourceSummary {
584 pub resources_detected: u32,
586 pub leaks_found: u32,
588 pub double_closes_found: u32,
590 pub use_after_closes_found: u32,
592}
593
594
595#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
597pub struct ResourceReport {
598 pub file: String,
600 pub language: String,
602 pub function: Option<String>,
604 pub resources: Vec<ResourceInfo>,
606 pub leaks: Vec<LeakInfo>,
608 pub double_closes: Vec<DoubleCloseInfo>,
610 pub use_after_closes: Vec<UseAfterCloseInfo>,
612 pub suggestions: Vec<ContextSuggestion>,
614 pub constraints: Vec<ResourceConstraint>,
616 pub summary: ResourceSummary,
618 pub analysis_time_ms: u64,
620}
621
622#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
628pub struct Precondition {
629 pub param: String,
631 pub expression: Option<String>,
633 pub description: Option<String>,
635 pub type_hint: Option<String>,
637 pub source: ConditionSource,
639}
640
641#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
643pub struct Postcondition {
644 pub expression: Option<String>,
646 pub description: Option<String>,
648 pub type_hint: Option<String>,
650}
651
652#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
654pub struct ExceptionInfo {
655 pub exception_type: String,
657 pub description: Option<String>,
659}
660
661#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
663pub struct YieldInfo {
664 pub type_hint: Option<String>,
666 pub description: Option<String>,
668}
669
670#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
672pub struct SideEffect {
673 pub effect_type: EffectType,
675 pub target: Option<String>,
677}
678
679#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
681pub struct FunctionBehavior {
682 pub function_name: String,
684 pub file_path: String,
686 pub line: u32,
688 pub purity_classification: String,
690 pub is_generator: bool,
692 pub is_async: bool,
694 pub preconditions: Vec<Precondition>,
696 pub postconditions: Vec<Postcondition>,
698 pub exceptions: Vec<ExceptionInfo>,
700 pub yields: Vec<YieldInfo>,
702 pub side_effects: Vec<SideEffect>,
704}
705
706#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
708pub struct ClassInvariant {
709 pub expression: String,
711}
712
713#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
715pub struct ClassBehavior {
716 pub class_name: String,
718 pub invariants: Vec<ClassInvariant>,
720 pub methods: Vec<FunctionBehavior>,
722}
723
724#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
726pub struct BehavioralReport {
727 pub file_path: String,
729 pub docstring_style: DocstringStyle,
731 pub has_icontract: bool,
733 pub has_deal: bool,
735 pub functions: Vec<FunctionBehavior>,
737 pub classes: Vec<ClassBehavior>,
739}
740
741#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
747pub struct VariableMutability {
748 pub name: String,
750 pub mutable: bool,
752 pub reassignments: u32,
754 pub mutations: u32,
756}
757
758#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
760pub struct ParameterMutability {
761 pub name: String,
763 pub mutated: bool,
765 pub mutation_sites: Vec<u32>,
767}
768
769#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
771pub struct CollectionMutation {
772 pub variable: String,
774 pub operation: String,
776 pub line: u32,
778}
779
780#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
782pub struct FunctionMutability {
783 pub name: String,
785 pub variables: Vec<VariableMutability>,
787 pub parameters: Vec<ParameterMutability>,
789 pub collection_mutations: Vec<CollectionMutation>,
791}
792
793#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
795pub struct FieldMutability {
796 pub name: String,
798 pub mutable: bool,
800 pub init_only: bool,
802}
803
804#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
806pub struct ClassMutability {
807 pub name: String,
809 pub fields: Vec<FieldMutability>,
811}
812
813#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
815pub struct MutabilitySummary {
816 pub functions_analyzed: u32,
818 pub classes_analyzed: u32,
820 pub total_variables: u32,
822 pub mutable_variables: u32,
824 pub immutable_variables: u32,
826 pub immutable_pct: f64,
828 pub parameters_analyzed: u32,
830 pub mutated_parameters: u32,
832 pub unmutated_pct: f64,
834 pub fields_analyzed: u32,
836 pub mutable_fields: u32,
838 pub constraints_generated: u32,
840}
841
842impl Default for MutabilitySummary {
843 fn default() -> Self {
844 Self {
845 functions_analyzed: 0,
846 classes_analyzed: 0,
847 total_variables: 0,
848 mutable_variables: 0,
849 immutable_variables: 0,
850 immutable_pct: 0.0,
851 parameters_analyzed: 0,
852 mutated_parameters: 0,
853 unmutated_pct: 0.0,
854 fields_analyzed: 0,
855 mutable_fields: 0,
856 constraints_generated: 0,
857 }
858 }
859}
860
861#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
863pub struct MutabilityReport {
864 pub file: String,
866 pub language: String,
868 pub functions: Vec<FunctionMutability>,
870 pub classes: Vec<ClassMutability>,
872 pub summary: MutabilitySummary,
874 pub analysis_time_ms: u64,
876}
877
878#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, ValueEnum)]
884pub enum OutputFormat {
885 #[default]
887 Json,
888
889 Text,
891}
892
893impl std::fmt::Display for OutputFormat {
894 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
895 match self {
896 Self::Json => write!(f, "json"),
897 Self::Text => write!(f, "text"),
898 }
899 }
900}
901
902#[cfg(test)]
907mod tests {
908 use super::*;
909
910 #[test]
915 fn test_confidence_enum_serialization() {
916 let json = serde_json::to_string(&Confidence::High).unwrap();
917 assert_eq!(json, r#""high""#);
918
919 let json = serde_json::to_string(&Confidence::Medium).unwrap();
920 assert_eq!(json, r#""medium""#);
921
922 let json = serde_json::to_string(&Confidence::Low).unwrap();
923 assert_eq!(json, r#""low""#);
924 }
925
926 #[test]
927 fn test_confidence_enum_deserialization() {
928 let high: Confidence = serde_json::from_str(r#""high""#).unwrap();
929 assert_eq!(high, Confidence::High);
930
931 let medium: Confidence = serde_json::from_str(r#""medium""#).unwrap();
932 assert_eq!(medium, Confidence::Medium);
933
934 let low: Confidence = serde_json::from_str(r#""low""#).unwrap();
935 assert_eq!(low, Confidence::Low);
936 }
937
938 #[test]
939 fn test_confidence_display() {
940 assert_eq!(Confidence::High.to_string(), "high");
941 assert_eq!(Confidence::Medium.to_string(), "medium");
942 assert_eq!(Confidence::Low.to_string(), "low");
943 }
944
945 #[test]
946 fn test_confidence_default() {
947 assert_eq!(Confidence::default(), Confidence::Medium);
948 }
949
950 #[test]
955 fn test_docstring_style_serialization() {
956 let json = serde_json::to_string(&DocstringStyle::Google).unwrap();
957 assert_eq!(json, r#""google""#);
958
959 let json = serde_json::to_string(&DocstringStyle::Numpy).unwrap();
960 assert_eq!(json, r#""numpy""#);
961
962 let json = serde_json::to_string(&DocstringStyle::Sphinx).unwrap();
963 assert_eq!(json, r#""sphinx""#);
964
965 let json = serde_json::to_string(&DocstringStyle::Plain).unwrap();
966 assert_eq!(json, r#""plain""#);
967 }
968
969 #[test]
970 fn test_docstring_style_display() {
971 assert_eq!(DocstringStyle::Google.to_string(), "google");
972 assert_eq!(DocstringStyle::Numpy.to_string(), "numpy");
973 assert_eq!(DocstringStyle::Sphinx.to_string(), "sphinx");
974 assert_eq!(DocstringStyle::Plain.to_string(), "plain");
975 }
976
977 #[test]
982 fn test_effect_type_serialization() {
983 let json = serde_json::to_string(&EffectType::Io).unwrap();
984 assert_eq!(json, r#""io""#);
985
986 let json = serde_json::to_string(&EffectType::GlobalWrite).unwrap();
987 assert_eq!(json, r#""global_write""#);
988
989 let json = serde_json::to_string(&EffectType::AttributeWrite).unwrap();
990 assert_eq!(json, r#""attribute_write""#);
991
992 let json = serde_json::to_string(&EffectType::CollectionModify).unwrap();
993 assert_eq!(json, r#""collection_modify""#);
994
995 let json = serde_json::to_string(&EffectType::Call).unwrap();
996 assert_eq!(json, r#""call""#);
997 }
998
999 #[test]
1004 fn test_condition_source_serialization() {
1005 let json = serde_json::to_string(&ConditionSource::Guard).unwrap();
1006 assert_eq!(json, r#""guard""#);
1007
1008 let json = serde_json::to_string(&ConditionSource::Docstring).unwrap();
1009 assert_eq!(json, r#""docstring""#);
1010
1011 let json = serde_json::to_string(&ConditionSource::TypeHint).unwrap();
1012 assert_eq!(json, r#""type_hint""#);
1013
1014 let json = serde_json::to_string(&ConditionSource::Assertion).unwrap();
1015 assert_eq!(json, r#""assertion""#);
1016
1017 let json = serde_json::to_string(&ConditionSource::Icontract).unwrap();
1018 assert_eq!(json, r#""icontract""#);
1019
1020 let json = serde_json::to_string(&ConditionSource::Deal).unwrap();
1021 assert_eq!(json, r#""deal""#);
1022 }
1023
1024 #[test]
1029 fn test_cohesion_verdict_from_lcom4() {
1030 assert_eq!(CohesionVerdict::from_lcom4(0), CohesionVerdict::Cohesive);
1031 assert_eq!(CohesionVerdict::from_lcom4(1), CohesionVerdict::Cohesive);
1032 assert_eq!(
1033 CohesionVerdict::from_lcom4(2),
1034 CohesionVerdict::SplitCandidate
1035 );
1036 assert_eq!(
1037 CohesionVerdict::from_lcom4(5),
1038 CohesionVerdict::SplitCandidate
1039 );
1040 }
1041
1042 #[test]
1047 fn test_coupling_verdict_from_score() {
1048 assert_eq!(CouplingVerdict::from_score(0.0), CouplingVerdict::Low);
1049 assert_eq!(CouplingVerdict::from_score(0.1), CouplingVerdict::Low);
1050 assert_eq!(CouplingVerdict::from_score(0.2), CouplingVerdict::Moderate);
1051 assert_eq!(CouplingVerdict::from_score(0.3), CouplingVerdict::Moderate);
1052 assert_eq!(CouplingVerdict::from_score(0.4), CouplingVerdict::High);
1053 assert_eq!(CouplingVerdict::from_score(0.5), CouplingVerdict::High);
1054 assert_eq!(CouplingVerdict::from_score(0.6), CouplingVerdict::VeryHigh);
1055 assert_eq!(CouplingVerdict::from_score(1.0), CouplingVerdict::VeryHigh);
1056 }
1057
1058 #[test]
1063 fn test_class_cohesion_serialization() {
1064 let cohesion = ClassCohesion {
1065 class_name: "MyClass".to_string(),
1066 file_path: "test.py".to_string(),
1067 line: 10,
1068 lcom4: 2,
1069 method_count: 4,
1070 field_count: 3,
1071 verdict: CohesionVerdict::SplitCandidate,
1072 split_suggestion: Some("Consider splitting".to_string()),
1073 components: vec![ComponentInfo {
1074 methods: vec!["method1".to_string()],
1075 fields: vec!["field1".to_string()],
1076 }],
1077 };
1078
1079 let json = serde_json::to_string(&cohesion).unwrap();
1080 let parsed: ClassCohesion = serde_json::from_str(&json).unwrap();
1081 assert_eq!(parsed.class_name, "MyClass");
1082 assert_eq!(parsed.lcom4, 2);
1083 }
1084
1085 #[test]
1086 fn test_coupling_report_serialization() {
1087 let report = CouplingReport {
1088 path_a: "module_a.py".to_string(),
1089 path_b: "module_b.py".to_string(),
1090 a_to_b: CrossCalls {
1091 calls: vec![CrossCall {
1092 caller: "func_a".to_string(),
1093 callee: "func_b".to_string(),
1094 line: 10,
1095 }],
1096 count: 1,
1097 },
1098 b_to_a: CrossCalls::default(),
1099 total_calls: 1,
1100 coupling_score: 0.1,
1101 verdict: CouplingVerdict::Low,
1102 };
1103
1104 let json = serde_json::to_string(&report).unwrap();
1105 let parsed: CouplingReport = serde_json::from_str(&json).unwrap();
1106 assert_eq!(parsed.coupling_score, 0.1);
1107 }
1108
1109 #[test]
1110 fn test_purity_report_serialization() {
1111 let report = PurityReport {
1112 files: vec![FilePurityReport {
1113 source_file: "test.py".to_string(),
1114 functions: vec![FunctionPurity {
1115 name: "pure_func".to_string(),
1116 classification: "pure".to_string(),
1117 effects: vec![],
1118 confidence: Confidence::High,
1119 }],
1120 pure_count: 1,
1121 }],
1122 total_functions: 1,
1123 total_pure: 1,
1124 };
1125
1126 let json = serde_json::to_string(&report).unwrap();
1127 let parsed: PurityReport = serde_json::from_str(&json).unwrap();
1128 assert_eq!(parsed.total_pure, 1);
1129 }
1130
1131 #[test]
1132 fn test_temporal_report_serialization() {
1133 let report = TemporalReport {
1134 constraints: vec![TemporalConstraint {
1135 before: "open".to_string(),
1136 after: "close".to_string(),
1137 support: 5,
1138 confidence: 0.9,
1139 examples: vec![TemporalExample {
1140 file: "test.py".to_string(),
1141 line: 10,
1142 }],
1143 }],
1144 trigrams: vec![],
1145 metadata: TemporalMetadata::default(),
1146 };
1147
1148 let json = serde_json::to_string(&report).unwrap();
1149 let parsed: TemporalReport = serde_json::from_str(&json).unwrap();
1150 assert_eq!(parsed.constraints[0].before, "open");
1151 }
1152
1153 #[test]
1154 fn test_interface_info_serialization() {
1155 let info = InterfaceInfo {
1156 file: "test.py".to_string(),
1157 all_exports: Some(vec!["func1".to_string()]),
1158 functions: vec![FunctionInfo {
1159 name: "func1".to_string(),
1160 signature: "def func1(x: int) -> str".to_string(),
1161 docstring: Some("A function".to_string()),
1162 lineno: 5,
1163 is_async: false,
1164 }],
1165 classes: vec![],
1166 };
1167
1168 let json = serde_json::to_string(&info).unwrap();
1169 let parsed: InterfaceInfo = serde_json::from_str(&json).unwrap();
1170 assert_eq!(parsed.functions[0].name, "func1");
1171 }
1172
1173 #[test]
1174 fn test_resource_report_serialization() {
1175 let report = ResourceReport {
1176 file: "test.py".to_string(),
1177 language: "python".to_string(),
1178 function: Some("process".to_string()),
1179 resources: vec![ResourceInfo {
1180 name: "f".to_string(),
1181 resource_type: "file".to_string(),
1182 line: 10,
1183 closed: true,
1184 }],
1185 leaks: vec![],
1186 double_closes: vec![],
1187 use_after_closes: vec![],
1188 suggestions: vec![],
1189 constraints: vec![],
1190 summary: ResourceSummary::default(),
1191 analysis_time_ms: 50,
1192 };
1193
1194 let json = serde_json::to_string(&report).unwrap();
1195 let parsed: ResourceReport = serde_json::from_str(&json).unwrap();
1196 assert_eq!(parsed.resources[0].name, "f");
1197 }
1198
1199 #[test]
1200 fn test_behavioral_report_serialization() {
1201 let report = BehavioralReport {
1202 file_path: "test.py".to_string(),
1203 docstring_style: DocstringStyle::Google,
1204 has_icontract: false,
1205 has_deal: false,
1206 functions: vec![FunctionBehavior {
1207 function_name: "validate".to_string(),
1208 file_path: "test.py".to_string(),
1209 line: 10,
1210 purity_classification: "pure".to_string(),
1211 is_generator: false,
1212 is_async: false,
1213 preconditions: vec![Precondition {
1214 param: "x".to_string(),
1215 expression: Some("x > 0".to_string()),
1216 description: None,
1217 type_hint: Some("int".to_string()),
1218 source: ConditionSource::Guard,
1219 }],
1220 postconditions: vec![],
1221 exceptions: vec![],
1222 yields: vec![],
1223 side_effects: vec![],
1224 }],
1225 classes: vec![],
1226 };
1227
1228 let json = serde_json::to_string(&report).unwrap();
1229 let parsed: BehavioralReport = serde_json::from_str(&json).unwrap();
1230 assert_eq!(parsed.functions[0].function_name, "validate");
1231 }
1232
1233 #[test]
1234 fn test_mutability_report_serialization() {
1235 let report = MutabilityReport {
1236 file: "test.py".to_string(),
1237 language: "python".to_string(),
1238 functions: vec![FunctionMutability {
1239 name: "process".to_string(),
1240 variables: vec![VariableMutability {
1241 name: "count".to_string(),
1242 mutable: true,
1243 reassignments: 3,
1244 mutations: 0,
1245 }],
1246 parameters: vec![],
1247 collection_mutations: vec![],
1248 }],
1249 classes: vec![],
1250 summary: MutabilitySummary::default(),
1251 analysis_time_ms: 30,
1252 };
1253
1254 let json = serde_json::to_string(&report).unwrap();
1255 let parsed: MutabilityReport = serde_json::from_str(&json).unwrap();
1256 assert_eq!(parsed.functions[0].name, "process");
1257 }
1258
1259 #[test]
1264 fn test_output_format_display() {
1265 assert_eq!(OutputFormat::Json.to_string(), "json");
1266 assert_eq!(OutputFormat::Text.to_string(), "text");
1267 }
1268
1269 #[test]
1270 fn test_output_format_default() {
1271 assert_eq!(OutputFormat::default(), OutputFormat::Json);
1272 }
1273}