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 #[serde(default)]
513 pub all_exports: Vec<String>,
514 pub functions: Vec<FunctionInfo>,
516 pub classes: Vec<ClassInfo>,
518}
519
520#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
526pub struct ResourceInfo {
527 pub name: String,
529 pub resource_type: String,
531 pub line: u32,
533 pub closed: bool,
535}
536
537#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
539pub struct LeakInfo {
540 pub resource: String,
542 pub line: u32,
544 pub paths: Option<Vec<String>>,
546}
547
548#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
550pub struct DoubleCloseInfo {
551 pub resource: String,
553 pub first_close: u32,
555 pub second_close: u32,
557}
558
559#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
561pub struct UseAfterCloseInfo {
562 pub resource: String,
564 pub close_line: u32,
566 pub use_line: u32,
568}
569
570#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
572pub struct ContextSuggestion {
573 pub resource: String,
575 pub suggestion: String,
577}
578
579#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
581pub struct ResourceConstraint {
582 pub rule: String,
584 pub context: String,
586 pub confidence: f64,
588}
589
590#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
592pub struct ResourceSummary {
593 pub resources_detected: u32,
595 pub leaks_found: u32,
597 pub double_closes_found: u32,
599 pub use_after_closes_found: u32,
601}
602
603#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
605pub struct ResourceReport {
606 pub file: String,
608 pub language: String,
610 pub function: Option<String>,
612 pub resources: Vec<ResourceInfo>,
614 pub leaks: Vec<LeakInfo>,
616 pub double_closes: Vec<DoubleCloseInfo>,
618 pub use_after_closes: Vec<UseAfterCloseInfo>,
620 pub suggestions: Vec<ContextSuggestion>,
622 pub constraints: Vec<ResourceConstraint>,
624 pub summary: ResourceSummary,
626 pub analysis_time_ms: u64,
628}
629
630#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
636pub struct Precondition {
637 pub param: String,
639 pub expression: Option<String>,
641 pub description: Option<String>,
643 pub type_hint: Option<String>,
645 pub source: ConditionSource,
647}
648
649#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
651pub struct Postcondition {
652 pub expression: Option<String>,
654 pub description: Option<String>,
656 pub type_hint: Option<String>,
658}
659
660#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
662pub struct ExceptionInfo {
663 pub exception_type: String,
665 pub description: Option<String>,
667}
668
669#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
671pub struct YieldInfo {
672 pub type_hint: Option<String>,
674 pub description: Option<String>,
676}
677
678#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
680pub struct SideEffect {
681 pub effect_type: EffectType,
683 pub target: Option<String>,
685}
686
687#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
689pub struct FunctionBehavior {
690 pub function_name: String,
692 pub file_path: String,
694 pub line: u32,
696 pub purity_classification: String,
698 pub is_generator: bool,
700 pub is_async: bool,
702 pub preconditions: Vec<Precondition>,
704 pub postconditions: Vec<Postcondition>,
706 pub exceptions: Vec<ExceptionInfo>,
708 pub yields: Vec<YieldInfo>,
710 pub side_effects: Vec<SideEffect>,
712}
713
714#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
716pub struct ClassInvariant {
717 pub expression: String,
719}
720
721#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
723pub struct ClassBehavior {
724 pub class_name: String,
726 pub invariants: Vec<ClassInvariant>,
728 pub methods: Vec<FunctionBehavior>,
730}
731
732#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
734pub struct BehavioralReport {
735 pub file_path: String,
737 pub docstring_style: DocstringStyle,
739 pub has_icontract: bool,
741 pub has_deal: bool,
743 pub functions: Vec<FunctionBehavior>,
745 pub classes: Vec<ClassBehavior>,
747}
748
749#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
755pub struct VariableMutability {
756 pub name: String,
758 pub mutable: bool,
760 pub reassignments: u32,
762 pub mutations: u32,
764}
765
766#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
768pub struct ParameterMutability {
769 pub name: String,
771 pub mutated: bool,
773 pub mutation_sites: Vec<u32>,
775}
776
777#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
779pub struct CollectionMutation {
780 pub variable: String,
782 pub operation: String,
784 pub line: u32,
786}
787
788#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
790pub struct FunctionMutability {
791 pub name: String,
793 pub variables: Vec<VariableMutability>,
795 pub parameters: Vec<ParameterMutability>,
797 pub collection_mutations: Vec<CollectionMutation>,
799}
800
801#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
803pub struct FieldMutability {
804 pub name: String,
806 pub mutable: bool,
808 pub init_only: bool,
810}
811
812#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
814pub struct ClassMutability {
815 pub name: String,
817 pub fields: Vec<FieldMutability>,
819}
820
821#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
823pub struct MutabilitySummary {
824 pub functions_analyzed: u32,
826 pub classes_analyzed: u32,
828 pub total_variables: u32,
830 pub mutable_variables: u32,
832 pub immutable_variables: u32,
834 pub immutable_pct: f64,
836 pub parameters_analyzed: u32,
838 pub mutated_parameters: u32,
840 pub unmutated_pct: f64,
842 pub fields_analyzed: u32,
844 pub mutable_fields: u32,
846 pub constraints_generated: u32,
848}
849
850impl Default for MutabilitySummary {
851 fn default() -> Self {
852 Self {
853 functions_analyzed: 0,
854 classes_analyzed: 0,
855 total_variables: 0,
856 mutable_variables: 0,
857 immutable_variables: 0,
858 immutable_pct: 0.0,
859 parameters_analyzed: 0,
860 mutated_parameters: 0,
861 unmutated_pct: 0.0,
862 fields_analyzed: 0,
863 mutable_fields: 0,
864 constraints_generated: 0,
865 }
866 }
867}
868
869#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
871pub struct MutabilityReport {
872 pub file: String,
874 pub language: String,
876 pub functions: Vec<FunctionMutability>,
878 pub classes: Vec<ClassMutability>,
880 pub summary: MutabilitySummary,
882 pub analysis_time_ms: u64,
884}
885
886#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, ValueEnum)]
892pub enum OutputFormat {
893 #[default]
895 Json,
896
897 Text,
899}
900
901impl std::fmt::Display for OutputFormat {
902 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
903 match self {
904 Self::Json => write!(f, "json"),
905 Self::Text => write!(f, "text"),
906 }
907 }
908}
909
910#[cfg(test)]
915mod tests {
916 use super::*;
917
918 #[test]
923 fn test_confidence_enum_serialization() {
924 let json = serde_json::to_string(&Confidence::High).unwrap();
925 assert_eq!(json, r#""high""#);
926
927 let json = serde_json::to_string(&Confidence::Medium).unwrap();
928 assert_eq!(json, r#""medium""#);
929
930 let json = serde_json::to_string(&Confidence::Low).unwrap();
931 assert_eq!(json, r#""low""#);
932 }
933
934 #[test]
935 fn test_confidence_enum_deserialization() {
936 let high: Confidence = serde_json::from_str(r#""high""#).unwrap();
937 assert_eq!(high, Confidence::High);
938
939 let medium: Confidence = serde_json::from_str(r#""medium""#).unwrap();
940 assert_eq!(medium, Confidence::Medium);
941
942 let low: Confidence = serde_json::from_str(r#""low""#).unwrap();
943 assert_eq!(low, Confidence::Low);
944 }
945
946 #[test]
947 fn test_confidence_display() {
948 assert_eq!(Confidence::High.to_string(), "high");
949 assert_eq!(Confidence::Medium.to_string(), "medium");
950 assert_eq!(Confidence::Low.to_string(), "low");
951 }
952
953 #[test]
954 fn test_confidence_default() {
955 assert_eq!(Confidence::default(), Confidence::Medium);
956 }
957
958 #[test]
963 fn test_docstring_style_serialization() {
964 let json = serde_json::to_string(&DocstringStyle::Google).unwrap();
965 assert_eq!(json, r#""google""#);
966
967 let json = serde_json::to_string(&DocstringStyle::Numpy).unwrap();
968 assert_eq!(json, r#""numpy""#);
969
970 let json = serde_json::to_string(&DocstringStyle::Sphinx).unwrap();
971 assert_eq!(json, r#""sphinx""#);
972
973 let json = serde_json::to_string(&DocstringStyle::Plain).unwrap();
974 assert_eq!(json, r#""plain""#);
975 }
976
977 #[test]
978 fn test_docstring_style_display() {
979 assert_eq!(DocstringStyle::Google.to_string(), "google");
980 assert_eq!(DocstringStyle::Numpy.to_string(), "numpy");
981 assert_eq!(DocstringStyle::Sphinx.to_string(), "sphinx");
982 assert_eq!(DocstringStyle::Plain.to_string(), "plain");
983 }
984
985 #[test]
990 fn test_effect_type_serialization() {
991 let json = serde_json::to_string(&EffectType::Io).unwrap();
992 assert_eq!(json, r#""io""#);
993
994 let json = serde_json::to_string(&EffectType::GlobalWrite).unwrap();
995 assert_eq!(json, r#""global_write""#);
996
997 let json = serde_json::to_string(&EffectType::AttributeWrite).unwrap();
998 assert_eq!(json, r#""attribute_write""#);
999
1000 let json = serde_json::to_string(&EffectType::CollectionModify).unwrap();
1001 assert_eq!(json, r#""collection_modify""#);
1002
1003 let json = serde_json::to_string(&EffectType::Call).unwrap();
1004 assert_eq!(json, r#""call""#);
1005 }
1006
1007 #[test]
1012 fn test_condition_source_serialization() {
1013 let json = serde_json::to_string(&ConditionSource::Guard).unwrap();
1014 assert_eq!(json, r#""guard""#);
1015
1016 let json = serde_json::to_string(&ConditionSource::Docstring).unwrap();
1017 assert_eq!(json, r#""docstring""#);
1018
1019 let json = serde_json::to_string(&ConditionSource::TypeHint).unwrap();
1020 assert_eq!(json, r#""type_hint""#);
1021
1022 let json = serde_json::to_string(&ConditionSource::Assertion).unwrap();
1023 assert_eq!(json, r#""assertion""#);
1024
1025 let json = serde_json::to_string(&ConditionSource::Icontract).unwrap();
1026 assert_eq!(json, r#""icontract""#);
1027
1028 let json = serde_json::to_string(&ConditionSource::Deal).unwrap();
1029 assert_eq!(json, r#""deal""#);
1030 }
1031
1032 #[test]
1037 fn test_cohesion_verdict_from_lcom4() {
1038 assert_eq!(CohesionVerdict::from_lcom4(0), CohesionVerdict::Cohesive);
1039 assert_eq!(CohesionVerdict::from_lcom4(1), CohesionVerdict::Cohesive);
1040 assert_eq!(
1041 CohesionVerdict::from_lcom4(2),
1042 CohesionVerdict::SplitCandidate
1043 );
1044 assert_eq!(
1045 CohesionVerdict::from_lcom4(5),
1046 CohesionVerdict::SplitCandidate
1047 );
1048 }
1049
1050 #[test]
1055 fn test_coupling_verdict_from_score() {
1056 assert_eq!(CouplingVerdict::from_score(0.0), CouplingVerdict::Low);
1057 assert_eq!(CouplingVerdict::from_score(0.1), CouplingVerdict::Low);
1058 assert_eq!(CouplingVerdict::from_score(0.2), CouplingVerdict::Moderate);
1059 assert_eq!(CouplingVerdict::from_score(0.3), CouplingVerdict::Moderate);
1060 assert_eq!(CouplingVerdict::from_score(0.4), CouplingVerdict::High);
1061 assert_eq!(CouplingVerdict::from_score(0.5), CouplingVerdict::High);
1062 assert_eq!(CouplingVerdict::from_score(0.6), CouplingVerdict::VeryHigh);
1063 assert_eq!(CouplingVerdict::from_score(1.0), CouplingVerdict::VeryHigh);
1064 }
1065
1066 #[test]
1071 fn test_class_cohesion_serialization() {
1072 let cohesion = ClassCohesion {
1073 class_name: "MyClass".to_string(),
1074 file_path: "test.py".to_string(),
1075 line: 10,
1076 lcom4: 2,
1077 method_count: 4,
1078 field_count: 3,
1079 verdict: CohesionVerdict::SplitCandidate,
1080 split_suggestion: Some("Consider splitting".to_string()),
1081 components: vec![ComponentInfo {
1082 methods: vec!["method1".to_string()],
1083 fields: vec!["field1".to_string()],
1084 }],
1085 };
1086
1087 let json = serde_json::to_string(&cohesion).unwrap();
1088 let parsed: ClassCohesion = serde_json::from_str(&json).unwrap();
1089 assert_eq!(parsed.class_name, "MyClass");
1090 assert_eq!(parsed.lcom4, 2);
1091 }
1092
1093 #[test]
1094 fn test_coupling_report_serialization() {
1095 let report = CouplingReport {
1096 path_a: "module_a.py".to_string(),
1097 path_b: "module_b.py".to_string(),
1098 a_to_b: CrossCalls {
1099 calls: vec![CrossCall {
1100 caller: "func_a".to_string(),
1101 callee: "func_b".to_string(),
1102 line: 10,
1103 }],
1104 count: 1,
1105 },
1106 b_to_a: CrossCalls::default(),
1107 total_calls: 1,
1108 coupling_score: 0.1,
1109 verdict: CouplingVerdict::Low,
1110 };
1111
1112 let json = serde_json::to_string(&report).unwrap();
1113 let parsed: CouplingReport = serde_json::from_str(&json).unwrap();
1114 assert_eq!(parsed.coupling_score, 0.1);
1115 }
1116
1117 #[test]
1118 fn test_purity_report_serialization() {
1119 let report = PurityReport {
1120 files: vec![FilePurityReport {
1121 source_file: "test.py".to_string(),
1122 functions: vec![FunctionPurity {
1123 name: "pure_func".to_string(),
1124 classification: "pure".to_string(),
1125 effects: vec![],
1126 confidence: Confidence::High,
1127 }],
1128 pure_count: 1,
1129 }],
1130 total_functions: 1,
1131 total_pure: 1,
1132 };
1133
1134 let json = serde_json::to_string(&report).unwrap();
1135 let parsed: PurityReport = serde_json::from_str(&json).unwrap();
1136 assert_eq!(parsed.total_pure, 1);
1137 }
1138
1139 #[test]
1140 fn test_temporal_report_serialization() {
1141 let report = TemporalReport {
1142 constraints: vec![TemporalConstraint {
1143 before: "open".to_string(),
1144 after: "close".to_string(),
1145 support: 5,
1146 confidence: 0.9,
1147 examples: vec![TemporalExample {
1148 file: "test.py".to_string(),
1149 line: 10,
1150 }],
1151 }],
1152 trigrams: vec![],
1153 metadata: TemporalMetadata::default(),
1154 };
1155
1156 let json = serde_json::to_string(&report).unwrap();
1157 let parsed: TemporalReport = serde_json::from_str(&json).unwrap();
1158 assert_eq!(parsed.constraints[0].before, "open");
1159 }
1160
1161 #[test]
1162 fn test_interface_info_serialization() {
1163 let info = InterfaceInfo {
1164 file: "test.py".to_string(),
1165 all_exports: vec!["func1".to_string()],
1166 functions: vec![FunctionInfo {
1167 name: "func1".to_string(),
1168 signature: "def func1(x: int) -> str".to_string(),
1169 docstring: Some("A function".to_string()),
1170 lineno: 5,
1171 is_async: false,
1172 }],
1173 classes: vec![],
1174 };
1175
1176 let json = serde_json::to_string(&info).unwrap();
1177 let parsed: InterfaceInfo = serde_json::from_str(&json).unwrap();
1178 assert_eq!(parsed.functions[0].name, "func1");
1179 }
1180
1181 #[test]
1182 fn test_resource_report_serialization() {
1183 let report = ResourceReport {
1184 file: "test.py".to_string(),
1185 language: "python".to_string(),
1186 function: Some("process".to_string()),
1187 resources: vec![ResourceInfo {
1188 name: "f".to_string(),
1189 resource_type: "file".to_string(),
1190 line: 10,
1191 closed: true,
1192 }],
1193 leaks: vec![],
1194 double_closes: vec![],
1195 use_after_closes: vec![],
1196 suggestions: vec![],
1197 constraints: vec![],
1198 summary: ResourceSummary::default(),
1199 analysis_time_ms: 50,
1200 };
1201
1202 let json = serde_json::to_string(&report).unwrap();
1203 let parsed: ResourceReport = serde_json::from_str(&json).unwrap();
1204 assert_eq!(parsed.resources[0].name, "f");
1205 }
1206
1207 #[test]
1208 fn test_behavioral_report_serialization() {
1209 let report = BehavioralReport {
1210 file_path: "test.py".to_string(),
1211 docstring_style: DocstringStyle::Google,
1212 has_icontract: false,
1213 has_deal: false,
1214 functions: vec![FunctionBehavior {
1215 function_name: "validate".to_string(),
1216 file_path: "test.py".to_string(),
1217 line: 10,
1218 purity_classification: "pure".to_string(),
1219 is_generator: false,
1220 is_async: false,
1221 preconditions: vec![Precondition {
1222 param: "x".to_string(),
1223 expression: Some("x > 0".to_string()),
1224 description: None,
1225 type_hint: Some("int".to_string()),
1226 source: ConditionSource::Guard,
1227 }],
1228 postconditions: vec![],
1229 exceptions: vec![],
1230 yields: vec![],
1231 side_effects: vec![],
1232 }],
1233 classes: vec![],
1234 };
1235
1236 let json = serde_json::to_string(&report).unwrap();
1237 let parsed: BehavioralReport = serde_json::from_str(&json).unwrap();
1238 assert_eq!(parsed.functions[0].function_name, "validate");
1239 }
1240
1241 #[test]
1242 fn test_mutability_report_serialization() {
1243 let report = MutabilityReport {
1244 file: "test.py".to_string(),
1245 language: "python".to_string(),
1246 functions: vec![FunctionMutability {
1247 name: "process".to_string(),
1248 variables: vec![VariableMutability {
1249 name: "count".to_string(),
1250 mutable: true,
1251 reassignments: 3,
1252 mutations: 0,
1253 }],
1254 parameters: vec![],
1255 collection_mutations: vec![],
1256 }],
1257 classes: vec![],
1258 summary: MutabilitySummary::default(),
1259 analysis_time_ms: 30,
1260 };
1261
1262 let json = serde_json::to_string(&report).unwrap();
1263 let parsed: MutabilityReport = serde_json::from_str(&json).unwrap();
1264 assert_eq!(parsed.functions[0].name, "process");
1265 }
1266
1267 #[test]
1272 fn test_output_format_display() {
1273 assert_eq!(OutputFormat::Json.to_string(), "json");
1274 assert_eq!(OutputFormat::Text.to_string(), "text");
1275 }
1276
1277 #[test]
1278 fn test_output_format_default() {
1279 assert_eq!(OutputFormat::default(), OutputFormat::Json);
1280 }
1281}