Skip to main content

tldr_cli/commands/patterns/
types.rs

1//! Shared types for Pattern Analysis commands
2//!
3//! This module defines all data types used across the patterns analysis
4//! commands. Types are designed for JSON serialization with serde.
5//!
6//! # Commands Using These Types
7//!
8//! - `cohesion`: LCOM4 class cohesion analysis
9//! - `coupling`: Cross-module coupling analysis
10//! - `interface`: Public API extraction
11//! - `purity`: Function purity/effect analysis
12//! - `temporal`: Temporal constraint mining
13//! - `behavioral`: Pre/postcondition extraction
14//! - `mutability`: Variable/parameter mutation tracking
15//! - `resources`: Resource lifecycle analysis
16
17use clap::ValueEnum;
18use serde::{Deserialize, Serialize};
19
20// =============================================================================
21// Confidence Level
22// =============================================================================
23
24/// Confidence level for inferred patterns and analysis results.
25///
26/// # Serialization
27///
28/// Serializes to snake_case: `"high"`, `"medium"`, `"low"`
29#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
30#[serde(rename_all = "snake_case")]
31pub enum Confidence {
32    /// High confidence - direct code evidence
33    High,
34    /// Medium confidence - inferred from patterns
35    #[default]
36    Medium,
37    /// Low confidence - heuristic or partial evidence
38    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// =============================================================================
52// Docstring Style
53// =============================================================================
54
55/// Documentation style detected in source code.
56///
57/// Used by behavioral analysis to parse docstrings correctly.
58#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
59#[serde(rename_all = "snake_case")]
60pub enum DocstringStyle {
61    /// Google-style docstrings (Args:, Returns:, Raises:)
62    Google,
63    /// NumPy-style docstrings (Parameters, Returns sections)
64    Numpy,
65    /// Sphinx/reST style docstrings (:param:, :returns:, :raises:)
66    Sphinx,
67    /// Plain docstrings without structured sections
68    #[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// =============================================================================
84// Effect Type
85// =============================================================================
86
87/// Type of side effect detected in code.
88///
89/// Used by purity and behavioral analysis.
90#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
91#[serde(rename_all = "snake_case")]
92pub enum EffectType {
93    /// I/O operations (file, network, console)
94    Io,
95    /// Writing to global variables
96    GlobalWrite,
97    /// Writing to object attributes (self.x = ...)
98    AttributeWrite,
99    /// Modifying collections in place (list.append, dict.update)
100    CollectionModify,
101    /// Calling functions with potential side effects
102    Call,
103    /// Calling unknown/unresolved functions (purity cannot be determined)
104    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// =============================================================================
121// Condition Source
122// =============================================================================
123
124/// Source of a pre/postcondition constraint.
125///
126/// Tracks where a constraint was extracted from.
127#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
128#[serde(rename_all = "snake_case")]
129pub enum ConditionSource {
130    /// Guard clause (if x < 0: raise ValueError)
131    Guard,
132    /// Docstring description
133    Docstring,
134    /// Type hint annotation
135    TypeHint,
136    /// Assert statement
137    Assertion,
138    /// icontract decorator (@require, @ensure)
139    Icontract,
140    /// deal library decorator
141    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// =============================================================================
158// Cohesion Types
159// =============================================================================
160
161/// Verdict for cohesion analysis.
162#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
163#[serde(rename_all = "snake_case")]
164pub enum CohesionVerdict {
165    /// Class is cohesive (LCOM4 = 1)
166    Cohesive,
167    /// Class could be split (LCOM4 > 1)
168    SplitCandidate,
169}
170
171impl CohesionVerdict {
172    /// Determine verdict from LCOM4 value.
173    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/// Information about a connected component in LCOM4 analysis.
192#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
193pub struct ComponentInfo {
194    /// Methods in this component
195    pub methods: Vec<String>,
196    /// Fields accessed by this component
197    pub fields: Vec<String>,
198}
199
200/// Cohesion analysis result for a single class.
201#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
202pub struct ClassCohesion {
203    /// Class name
204    pub class_name: String,
205    /// File path where class is defined
206    pub file_path: String,
207    /// Line number of class definition
208    pub line: u32,
209    /// LCOM4 value (1 = cohesive, >1 = split candidate)
210    pub lcom4: u32,
211    /// Number of methods analyzed
212    pub method_count: u32,
213    /// Number of fields detected
214    pub field_count: u32,
215    /// Verdict based on LCOM4 value
216    pub verdict: CohesionVerdict,
217    /// Suggestion for splitting if LCOM4 > 1
218    pub split_suggestion: Option<String>,
219    /// Connected components (if LCOM4 > 1)
220    pub components: Vec<ComponentInfo>,
221}
222
223/// Summary of cohesion analysis across multiple classes.
224#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
225pub struct CohesionSummary {
226    /// Total classes analyzed
227    pub total_classes: u32,
228    /// Number of cohesive classes
229    pub cohesive: u32,
230    /// Number of split candidates
231    pub split_candidates: u32,
232    /// Average LCOM4 value
233    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/// Full report from cohesion analysis.
248#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
249pub struct CohesionReport {
250    /// Cohesion results per class
251    pub classes: Vec<ClassCohesion>,
252    /// Summary statistics
253    pub summary: CohesionSummary,
254}
255
256// =============================================================================
257// Coupling Types
258// =============================================================================
259
260/// Coupling verdict based on score.
261#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
262#[serde(rename_all = "snake_case")]
263pub enum CouplingVerdict {
264    /// Low coupling (0.0-0.2)
265    Low,
266    /// Moderate coupling (0.2-0.4)
267    Moderate,
268    /// High coupling (0.4-0.6)
269    High,
270    /// Very high coupling (0.6-1.0)
271    VeryHigh,
272}
273
274impl CouplingVerdict {
275    /// Determine verdict from coupling score.
276    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/// A single cross-module function call.
301#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
302pub struct CrossCall {
303    /// Function making the call
304    pub caller: String,
305    /// Function being called
306    pub callee: String,
307    /// Line number of the call
308    pub line: u32,
309}
310
311/// Calls from one module to another.
312#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
313pub struct CrossCalls {
314    /// Individual call sites
315    pub calls: Vec<CrossCall>,
316    /// Total count of calls
317    pub count: u32,
318}
319
320
321/// Coupling analysis between two modules.
322#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
323pub struct CouplingReport {
324    /// Path to first module
325    pub path_a: String,
326    /// Path to second module
327    pub path_b: String,
328    /// Calls from A to B
329    pub a_to_b: CrossCalls,
330    /// Calls from B to A
331    pub b_to_a: CrossCalls,
332    /// Total cross-module calls
333    pub total_calls: u32,
334    /// Coupling score (0.0-1.0)
335    pub coupling_score: f64,
336    /// Verdict based on score
337    pub verdict: CouplingVerdict,
338}
339
340// =============================================================================
341// Purity Types
342// =============================================================================
343
344/// Purity analysis result for a single function.
345#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
346pub struct FunctionPurity {
347    /// Function name
348    pub name: String,
349    /// Purity classification: "pure", "impure", or "unknown"
350    pub classification: String,
351    /// List of detected effects (empty if pure)
352    pub effects: Vec<String>,
353    /// Confidence level of the analysis
354    pub confidence: Confidence,
355}
356
357/// Purity report for a single file.
358#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
359pub struct FilePurityReport {
360    /// Source file path
361    pub source_file: String,
362    /// Purity results per function
363    pub functions: Vec<FunctionPurity>,
364    /// Count of pure functions
365    pub pure_count: u32,
366}
367
368/// Full purity report (may include multiple files for directory analysis).
369#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
370pub struct PurityReport {
371    /// Per-file reports
372    pub files: Vec<FilePurityReport>,
373    /// Total functions analyzed
374    pub total_functions: u32,
375    /// Total pure functions
376    pub total_pure: u32,
377}
378
379// =============================================================================
380// Temporal Types
381// =============================================================================
382
383/// Example location for a temporal constraint.
384#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
385pub struct TemporalExample {
386    /// File where the pattern was observed
387    pub file: String,
388    /// Line number
389    pub line: u32,
390}
391
392/// A temporal constraint (before -> after sequence).
393#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
394pub struct TemporalConstraint {
395    /// Method that must come first
396    pub before: String,
397    /// Method that must come after
398    pub after: String,
399    /// Number of times this pattern was observed
400    pub support: u32,
401    /// Confidence (support / total sequences containing 'before')
402    pub confidence: f64,
403    /// Example locations where this pattern appears
404    pub examples: Vec<TemporalExample>,
405}
406
407/// A trigram (3-method sequence).
408#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
409pub struct Trigram {
410    /// The 3-method sequence
411    pub sequence: [String; 3],
412    /// Number of observations
413    pub support: u32,
414    /// Confidence score
415    pub confidence: f64,
416}
417
418/// Metadata about temporal mining.
419#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
420pub struct TemporalMetadata {
421    /// Number of files analyzed
422    pub files_analyzed: u32,
423    /// Total sequences extracted
424    pub sequences_extracted: u32,
425    /// Minimum support threshold used
426    pub min_support: u32,
427    /// Minimum confidence threshold used
428    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/// Full temporal constraint report.
443#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
444pub struct TemporalReport {
445    /// Discovered temporal constraints
446    pub constraints: Vec<TemporalConstraint>,
447    /// Discovered trigrams (if requested)
448    pub trigrams: Vec<Trigram>,
449    /// Analysis metadata
450    pub metadata: TemporalMetadata,
451}
452
453// =============================================================================
454// Interface Types
455// =============================================================================
456
457/// Information about a public function.
458#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
459pub struct FunctionInfo {
460    /// Function name
461    pub name: String,
462    /// Full signature (e.g., "def foo(x: int, y: str) -> bool")
463    pub signature: String,
464    /// Docstring if present
465    pub docstring: Option<String>,
466    /// Line number of definition
467    pub lineno: u32,
468    /// Whether the function is async
469    pub is_async: bool,
470}
471
472/// Information about a public method within a class.
473#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
474pub struct MethodInfo {
475    /// Method name
476    pub name: String,
477    /// Full signature
478    pub signature: String,
479    /// Whether the method is async
480    pub is_async: bool,
481}
482
483/// Information about a public class.
484#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
485pub struct ClassInfo {
486    /// Class name
487    pub name: String,
488    /// Line number of definition
489    pub lineno: u32,
490    /// Base classes
491    pub bases: Vec<String>,
492    /// Public methods
493    pub methods: Vec<MethodInfo>,
494    /// Count of private methods (for completeness)
495    pub private_method_count: u32,
496}
497
498/// Interface (public API) for a single file.
499#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
500pub struct InterfaceInfo {
501    /// File path
502    pub file: String,
503    /// Contents of __all__ if defined
504    pub all_exports: Option<Vec<String>>,
505    /// Public functions
506    pub functions: Vec<FunctionInfo>,
507    /// Public classes
508    pub classes: Vec<ClassInfo>,
509}
510
511// =============================================================================
512// Resource Types
513// =============================================================================
514
515/// Information about a detected resource.
516#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
517pub struct ResourceInfo {
518    /// Variable name holding the resource
519    pub name: String,
520    /// Type of resource (e.g., "file", "socket", "connection")
521    pub resource_type: String,
522    /// Line where resource was created/opened
523    pub line: u32,
524    /// Whether the resource is properly closed
525    pub closed: bool,
526}
527
528/// Information about a potential resource leak.
529#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
530pub struct LeakInfo {
531    /// Resource that may be leaked
532    pub resource: String,
533    /// Line where resource was created
534    pub line: u32,
535    /// Paths to the leak (if --show-paths enabled)
536    pub paths: Option<Vec<String>>,
537}
538
539/// Information about a double-close issue.
540#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
541pub struct DoubleCloseInfo {
542    /// Resource being closed twice
543    pub resource: String,
544    /// Line of first close
545    pub first_close: u32,
546    /// Line of second close
547    pub second_close: u32,
548}
549
550/// Information about use-after-close issue.
551#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
552pub struct UseAfterCloseInfo {
553    /// Resource being used after close
554    pub resource: String,
555    /// Line where resource was closed
556    pub close_line: u32,
557    /// Line where resource is used after close
558    pub use_line: u32,
559}
560
561/// Suggestion for using context manager.
562#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
563pub struct ContextSuggestion {
564    /// Resource that should use context manager
565    pub resource: String,
566    /// Suggested code pattern
567    pub suggestion: String,
568}
569
570/// LLM-ready constraint from resource analysis.
571#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
572pub struct ResourceConstraint {
573    /// The constraint rule
574    pub rule: String,
575    /// Context where it applies
576    pub context: String,
577    /// Confidence level
578    pub confidence: f64,
579}
580
581/// Summary of resource analysis.
582#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
583pub struct ResourceSummary {
584    /// Total resources detected
585    pub resources_detected: u32,
586    /// Number of leaks found
587    pub leaks_found: u32,
588    /// Number of double-close issues
589    pub double_closes_found: u32,
590    /// Number of use-after-close issues
591    pub use_after_closes_found: u32,
592}
593
594
595/// Full resource analysis report.
596#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
597pub struct ResourceReport {
598    /// File analyzed
599    pub file: String,
600    /// Language
601    pub language: String,
602    /// Function analyzed (if specific function requested)
603    pub function: Option<String>,
604    /// Detected resources
605    pub resources: Vec<ResourceInfo>,
606    /// Potential leaks
607    pub leaks: Vec<LeakInfo>,
608    /// Double-close issues
609    pub double_closes: Vec<DoubleCloseInfo>,
610    /// Use-after-close issues
611    pub use_after_closes: Vec<UseAfterCloseInfo>,
612    /// Context manager suggestions
613    pub suggestions: Vec<ContextSuggestion>,
614    /// LLM constraints (if --constraints enabled)
615    pub constraints: Vec<ResourceConstraint>,
616    /// Summary statistics
617    pub summary: ResourceSummary,
618    /// Analysis time in milliseconds
619    pub analysis_time_ms: u64,
620}
621
622// =============================================================================
623// Behavioral Types
624// =============================================================================
625
626/// A precondition on a function parameter.
627#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
628pub struct Precondition {
629    /// Parameter name
630    pub param: String,
631    /// Constraint expression (e.g., "x > 0")
632    pub expression: Option<String>,
633    /// Human-readable description from docstring
634    pub description: Option<String>,
635    /// Type hint if present
636    pub type_hint: Option<String>,
637    /// Source of this condition
638    pub source: ConditionSource,
639}
640
641/// A postcondition on function return.
642#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
643pub struct Postcondition {
644    /// Constraint expression
645    pub expression: Option<String>,
646    /// Human-readable description
647    pub description: Option<String>,
648    /// Return type hint
649    pub type_hint: Option<String>,
650}
651
652/// Information about an exception the function may raise.
653#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
654pub struct ExceptionInfo {
655    /// Exception type (e.g., "ValueError")
656    pub exception_type: String,
657    /// Description of when it's raised
658    pub description: Option<String>,
659}
660
661/// Information about yield values (for generators).
662#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
663pub struct YieldInfo {
664    /// Type hint for yielded values
665    pub type_hint: Option<String>,
666    /// Description of yielded values
667    pub description: Option<String>,
668}
669
670/// Side effect detected in function.
671#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
672pub struct SideEffect {
673    /// Type of effect
674    pub effect_type: EffectType,
675    /// Target of the effect (e.g., "self.count", "global_var")
676    pub target: Option<String>,
677}
678
679/// Behavioral analysis for a single function.
680#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
681pub struct FunctionBehavior {
682    /// Function name
683    pub function_name: String,
684    /// File path
685    pub file_path: String,
686    /// Line number of function definition
687    pub line: u32,
688    /// Purity classification: "pure", "impure", or "unknown"
689    pub purity_classification: String,
690    /// Whether it's a generator
691    pub is_generator: bool,
692    /// Whether it's an async function
693    pub is_async: bool,
694    /// Preconditions on parameters
695    pub preconditions: Vec<Precondition>,
696    /// Postconditions on return
697    pub postconditions: Vec<Postcondition>,
698    /// Exceptions that may be raised
699    pub exceptions: Vec<ExceptionInfo>,
700    /// Yield information (if generator)
701    pub yields: Vec<YieldInfo>,
702    /// Detected side effects
703    pub side_effects: Vec<SideEffect>,
704}
705
706/// Class invariant.
707#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
708pub struct ClassInvariant {
709    /// Invariant expression
710    pub expression: String,
711}
712
713/// Behavioral analysis for a class.
714#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
715pub struct ClassBehavior {
716    /// Class name
717    pub class_name: String,
718    /// Class invariants
719    pub invariants: Vec<ClassInvariant>,
720    /// Method behaviors
721    pub methods: Vec<FunctionBehavior>,
722}
723
724/// Full behavioral analysis report.
725#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
726pub struct BehavioralReport {
727    /// File analyzed
728    pub file_path: String,
729    /// Detected docstring style
730    pub docstring_style: DocstringStyle,
731    /// Whether icontract library is used
732    pub has_icontract: bool,
733    /// Whether deal library is used
734    pub has_deal: bool,
735    /// Function behaviors
736    pub functions: Vec<FunctionBehavior>,
737    /// Class behaviors
738    pub classes: Vec<ClassBehavior>,
739}
740
741// =============================================================================
742// Mutability Types
743// =============================================================================
744
745/// Mutability information for a variable.
746#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
747pub struct VariableMutability {
748    /// Variable name
749    pub name: String,
750    /// Whether the variable is ever reassigned
751    pub mutable: bool,
752    /// Number of reassignments
753    pub reassignments: u32,
754    /// Number of in-place mutations
755    pub mutations: u32,
756}
757
758/// Mutability information for a function parameter.
759#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
760pub struct ParameterMutability {
761    /// Parameter name
762    pub name: String,
763    /// Whether the parameter is mutated
764    pub mutated: bool,
765    /// Lines where mutation occurs
766    pub mutation_sites: Vec<u32>,
767}
768
769/// Collection mutation detected.
770#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
771pub struct CollectionMutation {
772    /// Variable being mutated
773    pub variable: String,
774    /// Operation (e.g., "append", "update", "pop")
775    pub operation: String,
776    /// Line number
777    pub line: u32,
778}
779
780/// Mutability analysis for a function.
781#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
782pub struct FunctionMutability {
783    /// Function name
784    pub name: String,
785    /// Variable mutability info
786    pub variables: Vec<VariableMutability>,
787    /// Parameter mutability info
788    pub parameters: Vec<ParameterMutability>,
789    /// Collection mutations
790    pub collection_mutations: Vec<CollectionMutation>,
791}
792
793/// Field mutability for a class.
794#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
795pub struct FieldMutability {
796    /// Field name
797    pub name: String,
798    /// Whether the field is mutable after __init__
799    pub mutable: bool,
800    /// Whether the field is only set in __init__
801    pub init_only: bool,
802}
803
804/// Mutability analysis for a class.
805#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
806pub struct ClassMutability {
807    /// Class name
808    pub name: String,
809    /// Field mutability info
810    pub fields: Vec<FieldMutability>,
811}
812
813/// Summary of mutability analysis.
814#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
815pub struct MutabilitySummary {
816    /// Functions analyzed
817    pub functions_analyzed: u32,
818    /// Classes analyzed
819    pub classes_analyzed: u32,
820    /// Total variables
821    pub total_variables: u32,
822    /// Mutable variables
823    pub mutable_variables: u32,
824    /// Immutable variables
825    pub immutable_variables: u32,
826    /// Percentage of immutable variables
827    pub immutable_pct: f64,
828    /// Parameters analyzed
829    pub parameters_analyzed: u32,
830    /// Mutated parameters
831    pub mutated_parameters: u32,
832    /// Percentage of unmutated parameters
833    pub unmutated_pct: f64,
834    /// Fields analyzed
835    pub fields_analyzed: u32,
836    /// Mutable fields
837    pub mutable_fields: u32,
838    /// Constraints generated (if --constraints)
839    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/// Full mutability report.
862#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
863pub struct MutabilityReport {
864    /// File analyzed
865    pub file: String,
866    /// Language
867    pub language: String,
868    /// Function mutability results
869    pub functions: Vec<FunctionMutability>,
870    /// Class mutability results
871    pub classes: Vec<ClassMutability>,
872    /// Summary statistics
873    pub summary: MutabilitySummary,
874    /// Analysis time in milliseconds
875    pub analysis_time_ms: u64,
876}
877
878// =============================================================================
879// Output Format
880// =============================================================================
881
882/// Output format for command results.
883#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, ValueEnum)]
884pub enum OutputFormat {
885    /// JSON output (default)
886    #[default]
887    Json,
888
889    /// Human-readable text output
890    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// =============================================================================
903// Tests
904// =============================================================================
905
906#[cfg(test)]
907mod tests {
908    use super::*;
909
910    // -------------------------------------------------------------------------
911    // Confidence Tests
912    // -------------------------------------------------------------------------
913
914    #[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    // -------------------------------------------------------------------------
951    // DocstringStyle Tests
952    // -------------------------------------------------------------------------
953
954    #[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    // -------------------------------------------------------------------------
978    // EffectType Tests
979    // -------------------------------------------------------------------------
980
981    #[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    // -------------------------------------------------------------------------
1000    // ConditionSource Tests
1001    // -------------------------------------------------------------------------
1002
1003    #[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    // -------------------------------------------------------------------------
1025    // CohesionVerdict Tests
1026    // -------------------------------------------------------------------------
1027
1028    #[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    // -------------------------------------------------------------------------
1043    // CouplingVerdict Tests
1044    // -------------------------------------------------------------------------
1045
1046    #[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    // -------------------------------------------------------------------------
1059    // Report Serialization Tests
1060    // -------------------------------------------------------------------------
1061
1062    #[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    // -------------------------------------------------------------------------
1260    // OutputFormat Tests
1261    // -------------------------------------------------------------------------
1262
1263    #[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}