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/// Coupling analysis between two modules.
321#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
322pub struct CouplingReport {
323    /// Path to first module
324    pub path_a: String,
325    /// Path to second module
326    pub path_b: String,
327    /// Calls from A to B
328    pub a_to_b: CrossCalls,
329    /// Calls from B to A
330    pub b_to_a: CrossCalls,
331    /// Total cross-module calls
332    pub total_calls: u32,
333    /// Coupling score (0.0-1.0)
334    pub coupling_score: f64,
335    /// Verdict based on score
336    pub verdict: CouplingVerdict,
337}
338
339// =============================================================================
340// Purity Types
341// =============================================================================
342
343/// Purity analysis result for a single function.
344#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
345pub struct FunctionPurity {
346    /// Function name
347    pub name: String,
348    /// Purity classification: "pure", "impure", or "unknown"
349    pub classification: String,
350    /// List of detected effects (empty if pure)
351    pub effects: Vec<String>,
352    /// Confidence level of the analysis
353    pub confidence: Confidence,
354}
355
356/// Purity report for a single file.
357#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
358pub struct FilePurityReport {
359    /// Source file path
360    pub source_file: String,
361    /// Purity results per function
362    pub functions: Vec<FunctionPurity>,
363    /// Count of pure functions
364    pub pure_count: u32,
365}
366
367/// Full purity report (may include multiple files for directory analysis).
368#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
369pub struct PurityReport {
370    /// Per-file reports
371    pub files: Vec<FilePurityReport>,
372    /// Total functions analyzed
373    pub total_functions: u32,
374    /// Total pure functions
375    pub total_pure: u32,
376}
377
378// =============================================================================
379// Temporal Types
380// =============================================================================
381
382/// Example location for a temporal constraint.
383#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
384pub struct TemporalExample {
385    /// File where the pattern was observed
386    pub file: String,
387    /// Line number
388    pub line: u32,
389}
390
391/// A temporal constraint (before -> after sequence).
392#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
393pub struct TemporalConstraint {
394    /// Method that must come first
395    pub before: String,
396    /// Method that must come after
397    pub after: String,
398    /// Number of times this pattern was observed
399    pub support: u32,
400    /// Confidence (support / total sequences containing 'before')
401    pub confidence: f64,
402    /// Example locations where this pattern appears
403    pub examples: Vec<TemporalExample>,
404}
405
406/// A trigram (3-method sequence).
407#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
408pub struct Trigram {
409    /// The 3-method sequence
410    pub sequence: [String; 3],
411    /// Number of observations
412    pub support: u32,
413    /// Confidence score
414    pub confidence: f64,
415}
416
417/// Metadata about temporal mining.
418#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
419pub struct TemporalMetadata {
420    /// Number of files analyzed
421    pub files_analyzed: u32,
422    /// Total sequences extracted
423    pub sequences_extracted: u32,
424    /// Minimum support threshold used
425    pub min_support: u32,
426    /// Minimum confidence threshold used
427    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/// Full temporal constraint report.
442#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
443pub struct TemporalReport {
444    /// Discovered temporal constraints
445    pub constraints: Vec<TemporalConstraint>,
446    /// Discovered trigrams (if requested)
447    pub trigrams: Vec<Trigram>,
448    /// Analysis metadata
449    pub metadata: TemporalMetadata,
450}
451
452// =============================================================================
453// Interface Types
454// =============================================================================
455
456/// Information about a public function.
457#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
458pub struct FunctionInfo {
459    /// Function name
460    pub name: String,
461    /// Full signature (e.g., "def foo(x: int, y: str) -> bool")
462    pub signature: String,
463    /// Docstring if present
464    pub docstring: Option<String>,
465    /// Line number of definition
466    pub lineno: u32,
467    /// Whether the function is async
468    pub is_async: bool,
469}
470
471/// Information about a public method within a class.
472#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
473pub struct MethodInfo {
474    /// Method name
475    pub name: String,
476    /// Full signature
477    pub signature: String,
478    /// Whether the method is async
479    pub is_async: bool,
480}
481
482/// Information about a public class.
483#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
484pub struct ClassInfo {
485    /// Class name
486    pub name: String,
487    /// Line number of definition
488    pub lineno: u32,
489    /// Base classes
490    pub bases: Vec<String>,
491    /// Public methods
492    pub methods: Vec<MethodInfo>,
493    /// Count of private methods (for completeness)
494    pub private_method_count: u32,
495}
496
497/// Interface (public API) for a single file.
498#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
499pub struct InterfaceInfo {
500    /// File path
501    pub file: String,
502    /// Contents of __all__ if defined
503    pub all_exports: Option<Vec<String>>,
504    /// Public functions
505    pub functions: Vec<FunctionInfo>,
506    /// Public classes
507    pub classes: Vec<ClassInfo>,
508}
509
510// =============================================================================
511// Resource Types
512// =============================================================================
513
514/// Information about a detected resource.
515#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
516pub struct ResourceInfo {
517    /// Variable name holding the resource
518    pub name: String,
519    /// Type of resource (e.g., "file", "socket", "connection")
520    pub resource_type: String,
521    /// Line where resource was created/opened
522    pub line: u32,
523    /// Whether the resource is properly closed
524    pub closed: bool,
525}
526
527/// Information about a potential resource leak.
528#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
529pub struct LeakInfo {
530    /// Resource that may be leaked
531    pub resource: String,
532    /// Line where resource was created
533    pub line: u32,
534    /// Paths to the leak (if --show-paths enabled)
535    pub paths: Option<Vec<String>>,
536}
537
538/// Information about a double-close issue.
539#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
540pub struct DoubleCloseInfo {
541    /// Resource being closed twice
542    pub resource: String,
543    /// Line of first close
544    pub first_close: u32,
545    /// Line of second close
546    pub second_close: u32,
547}
548
549/// Information about use-after-close issue.
550#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
551pub struct UseAfterCloseInfo {
552    /// Resource being used after close
553    pub resource: String,
554    /// Line where resource was closed
555    pub close_line: u32,
556    /// Line where resource is used after close
557    pub use_line: u32,
558}
559
560/// Suggestion for using context manager.
561#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
562pub struct ContextSuggestion {
563    /// Resource that should use context manager
564    pub resource: String,
565    /// Suggested code pattern
566    pub suggestion: String,
567}
568
569/// LLM-ready constraint from resource analysis.
570#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
571pub struct ResourceConstraint {
572    /// The constraint rule
573    pub rule: String,
574    /// Context where it applies
575    pub context: String,
576    /// Confidence level
577    pub confidence: f64,
578}
579
580/// Summary of resource analysis.
581#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
582pub struct ResourceSummary {
583    /// Total resources detected
584    pub resources_detected: u32,
585    /// Number of leaks found
586    pub leaks_found: u32,
587    /// Number of double-close issues
588    pub double_closes_found: u32,
589    /// Number of use-after-close issues
590    pub use_after_closes_found: u32,
591}
592
593/// Full resource analysis report.
594#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
595pub struct ResourceReport {
596    /// File analyzed
597    pub file: String,
598    /// Language
599    pub language: String,
600    /// Function analyzed (if specific function requested)
601    pub function: Option<String>,
602    /// Detected resources
603    pub resources: Vec<ResourceInfo>,
604    /// Potential leaks
605    pub leaks: Vec<LeakInfo>,
606    /// Double-close issues
607    pub double_closes: Vec<DoubleCloseInfo>,
608    /// Use-after-close issues
609    pub use_after_closes: Vec<UseAfterCloseInfo>,
610    /// Context manager suggestions
611    pub suggestions: Vec<ContextSuggestion>,
612    /// LLM constraints (if --constraints enabled)
613    pub constraints: Vec<ResourceConstraint>,
614    /// Summary statistics
615    pub summary: ResourceSummary,
616    /// Analysis time in milliseconds
617    pub analysis_time_ms: u64,
618}
619
620// =============================================================================
621// Behavioral Types
622// =============================================================================
623
624/// A precondition on a function parameter.
625#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
626pub struct Precondition {
627    /// Parameter name
628    pub param: String,
629    /// Constraint expression (e.g., "x > 0")
630    pub expression: Option<String>,
631    /// Human-readable description from docstring
632    pub description: Option<String>,
633    /// Type hint if present
634    pub type_hint: Option<String>,
635    /// Source of this condition
636    pub source: ConditionSource,
637}
638
639/// A postcondition on function return.
640#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
641pub struct Postcondition {
642    /// Constraint expression
643    pub expression: Option<String>,
644    /// Human-readable description
645    pub description: Option<String>,
646    /// Return type hint
647    pub type_hint: Option<String>,
648}
649
650/// Information about an exception the function may raise.
651#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
652pub struct ExceptionInfo {
653    /// Exception type (e.g., "ValueError")
654    pub exception_type: String,
655    /// Description of when it's raised
656    pub description: Option<String>,
657}
658
659/// Information about yield values (for generators).
660#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
661pub struct YieldInfo {
662    /// Type hint for yielded values
663    pub type_hint: Option<String>,
664    /// Description of yielded values
665    pub description: Option<String>,
666}
667
668/// Side effect detected in function.
669#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
670pub struct SideEffect {
671    /// Type of effect
672    pub effect_type: EffectType,
673    /// Target of the effect (e.g., "self.count", "global_var")
674    pub target: Option<String>,
675}
676
677/// Behavioral analysis for a single function.
678#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
679pub struct FunctionBehavior {
680    /// Function name
681    pub function_name: String,
682    /// File path
683    pub file_path: String,
684    /// Line number of function definition
685    pub line: u32,
686    /// Purity classification: "pure", "impure", or "unknown"
687    pub purity_classification: String,
688    /// Whether it's a generator
689    pub is_generator: bool,
690    /// Whether it's an async function
691    pub is_async: bool,
692    /// Preconditions on parameters
693    pub preconditions: Vec<Precondition>,
694    /// Postconditions on return
695    pub postconditions: Vec<Postcondition>,
696    /// Exceptions that may be raised
697    pub exceptions: Vec<ExceptionInfo>,
698    /// Yield information (if generator)
699    pub yields: Vec<YieldInfo>,
700    /// Detected side effects
701    pub side_effects: Vec<SideEffect>,
702}
703
704/// Class invariant.
705#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
706pub struct ClassInvariant {
707    /// Invariant expression
708    pub expression: String,
709}
710
711/// Behavioral analysis for a class.
712#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
713pub struct ClassBehavior {
714    /// Class name
715    pub class_name: String,
716    /// Class invariants
717    pub invariants: Vec<ClassInvariant>,
718    /// Method behaviors
719    pub methods: Vec<FunctionBehavior>,
720}
721
722/// Full behavioral analysis report.
723#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
724pub struct BehavioralReport {
725    /// File analyzed
726    pub file_path: String,
727    /// Detected docstring style
728    pub docstring_style: DocstringStyle,
729    /// Whether icontract library is used
730    pub has_icontract: bool,
731    /// Whether deal library is used
732    pub has_deal: bool,
733    /// Function behaviors
734    pub functions: Vec<FunctionBehavior>,
735    /// Class behaviors
736    pub classes: Vec<ClassBehavior>,
737}
738
739// =============================================================================
740// Mutability Types
741// =============================================================================
742
743/// Mutability information for a variable.
744#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
745pub struct VariableMutability {
746    /// Variable name
747    pub name: String,
748    /// Whether the variable is ever reassigned
749    pub mutable: bool,
750    /// Number of reassignments
751    pub reassignments: u32,
752    /// Number of in-place mutations
753    pub mutations: u32,
754}
755
756/// Mutability information for a function parameter.
757#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
758pub struct ParameterMutability {
759    /// Parameter name
760    pub name: String,
761    /// Whether the parameter is mutated
762    pub mutated: bool,
763    /// Lines where mutation occurs
764    pub mutation_sites: Vec<u32>,
765}
766
767/// Collection mutation detected.
768#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
769pub struct CollectionMutation {
770    /// Variable being mutated
771    pub variable: String,
772    /// Operation (e.g., "append", "update", "pop")
773    pub operation: String,
774    /// Line number
775    pub line: u32,
776}
777
778/// Mutability analysis for a function.
779#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
780pub struct FunctionMutability {
781    /// Function name
782    pub name: String,
783    /// Variable mutability info
784    pub variables: Vec<VariableMutability>,
785    /// Parameter mutability info
786    pub parameters: Vec<ParameterMutability>,
787    /// Collection mutations
788    pub collection_mutations: Vec<CollectionMutation>,
789}
790
791/// Field mutability for a class.
792#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
793pub struct FieldMutability {
794    /// Field name
795    pub name: String,
796    /// Whether the field is mutable after __init__
797    pub mutable: bool,
798    /// Whether the field is only set in __init__
799    pub init_only: bool,
800}
801
802/// Mutability analysis for a class.
803#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
804pub struct ClassMutability {
805    /// Class name
806    pub name: String,
807    /// Field mutability info
808    pub fields: Vec<FieldMutability>,
809}
810
811/// Summary of mutability analysis.
812#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
813pub struct MutabilitySummary {
814    /// Functions analyzed
815    pub functions_analyzed: u32,
816    /// Classes analyzed
817    pub classes_analyzed: u32,
818    /// Total variables
819    pub total_variables: u32,
820    /// Mutable variables
821    pub mutable_variables: u32,
822    /// Immutable variables
823    pub immutable_variables: u32,
824    /// Percentage of immutable variables
825    pub immutable_pct: f64,
826    /// Parameters analyzed
827    pub parameters_analyzed: u32,
828    /// Mutated parameters
829    pub mutated_parameters: u32,
830    /// Percentage of unmutated parameters
831    pub unmutated_pct: f64,
832    /// Fields analyzed
833    pub fields_analyzed: u32,
834    /// Mutable fields
835    pub mutable_fields: u32,
836    /// Constraints generated (if --constraints)
837    pub constraints_generated: u32,
838}
839
840impl Default for MutabilitySummary {
841    fn default() -> Self {
842        Self {
843            functions_analyzed: 0,
844            classes_analyzed: 0,
845            total_variables: 0,
846            mutable_variables: 0,
847            immutable_variables: 0,
848            immutable_pct: 0.0,
849            parameters_analyzed: 0,
850            mutated_parameters: 0,
851            unmutated_pct: 0.0,
852            fields_analyzed: 0,
853            mutable_fields: 0,
854            constraints_generated: 0,
855        }
856    }
857}
858
859/// Full mutability report.
860#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
861pub struct MutabilityReport {
862    /// File analyzed
863    pub file: String,
864    /// Language
865    pub language: String,
866    /// Function mutability results
867    pub functions: Vec<FunctionMutability>,
868    /// Class mutability results
869    pub classes: Vec<ClassMutability>,
870    /// Summary statistics
871    pub summary: MutabilitySummary,
872    /// Analysis time in milliseconds
873    pub analysis_time_ms: u64,
874}
875
876// =============================================================================
877// Output Format
878// =============================================================================
879
880/// Output format for command results.
881#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, ValueEnum)]
882pub enum OutputFormat {
883    /// JSON output (default)
884    #[default]
885    Json,
886
887    /// Human-readable text output
888    Text,
889}
890
891impl std::fmt::Display for OutputFormat {
892    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
893        match self {
894            Self::Json => write!(f, "json"),
895            Self::Text => write!(f, "text"),
896        }
897    }
898}
899
900// =============================================================================
901// Tests
902// =============================================================================
903
904#[cfg(test)]
905mod tests {
906    use super::*;
907
908    // -------------------------------------------------------------------------
909    // Confidence Tests
910    // -------------------------------------------------------------------------
911
912    #[test]
913    fn test_confidence_enum_serialization() {
914        let json = serde_json::to_string(&Confidence::High).unwrap();
915        assert_eq!(json, r#""high""#);
916
917        let json = serde_json::to_string(&Confidence::Medium).unwrap();
918        assert_eq!(json, r#""medium""#);
919
920        let json = serde_json::to_string(&Confidence::Low).unwrap();
921        assert_eq!(json, r#""low""#);
922    }
923
924    #[test]
925    fn test_confidence_enum_deserialization() {
926        let high: Confidence = serde_json::from_str(r#""high""#).unwrap();
927        assert_eq!(high, Confidence::High);
928
929        let medium: Confidence = serde_json::from_str(r#""medium""#).unwrap();
930        assert_eq!(medium, Confidence::Medium);
931
932        let low: Confidence = serde_json::from_str(r#""low""#).unwrap();
933        assert_eq!(low, Confidence::Low);
934    }
935
936    #[test]
937    fn test_confidence_display() {
938        assert_eq!(Confidence::High.to_string(), "high");
939        assert_eq!(Confidence::Medium.to_string(), "medium");
940        assert_eq!(Confidence::Low.to_string(), "low");
941    }
942
943    #[test]
944    fn test_confidence_default() {
945        assert_eq!(Confidence::default(), Confidence::Medium);
946    }
947
948    // -------------------------------------------------------------------------
949    // DocstringStyle Tests
950    // -------------------------------------------------------------------------
951
952    #[test]
953    fn test_docstring_style_serialization() {
954        let json = serde_json::to_string(&DocstringStyle::Google).unwrap();
955        assert_eq!(json, r#""google""#);
956
957        let json = serde_json::to_string(&DocstringStyle::Numpy).unwrap();
958        assert_eq!(json, r#""numpy""#);
959
960        let json = serde_json::to_string(&DocstringStyle::Sphinx).unwrap();
961        assert_eq!(json, r#""sphinx""#);
962
963        let json = serde_json::to_string(&DocstringStyle::Plain).unwrap();
964        assert_eq!(json, r#""plain""#);
965    }
966
967    #[test]
968    fn test_docstring_style_display() {
969        assert_eq!(DocstringStyle::Google.to_string(), "google");
970        assert_eq!(DocstringStyle::Numpy.to_string(), "numpy");
971        assert_eq!(DocstringStyle::Sphinx.to_string(), "sphinx");
972        assert_eq!(DocstringStyle::Plain.to_string(), "plain");
973    }
974
975    // -------------------------------------------------------------------------
976    // EffectType Tests
977    // -------------------------------------------------------------------------
978
979    #[test]
980    fn test_effect_type_serialization() {
981        let json = serde_json::to_string(&EffectType::Io).unwrap();
982        assert_eq!(json, r#""io""#);
983
984        let json = serde_json::to_string(&EffectType::GlobalWrite).unwrap();
985        assert_eq!(json, r#""global_write""#);
986
987        let json = serde_json::to_string(&EffectType::AttributeWrite).unwrap();
988        assert_eq!(json, r#""attribute_write""#);
989
990        let json = serde_json::to_string(&EffectType::CollectionModify).unwrap();
991        assert_eq!(json, r#""collection_modify""#);
992
993        let json = serde_json::to_string(&EffectType::Call).unwrap();
994        assert_eq!(json, r#""call""#);
995    }
996
997    // -------------------------------------------------------------------------
998    // ConditionSource Tests
999    // -------------------------------------------------------------------------
1000
1001    #[test]
1002    fn test_condition_source_serialization() {
1003        let json = serde_json::to_string(&ConditionSource::Guard).unwrap();
1004        assert_eq!(json, r#""guard""#);
1005
1006        let json = serde_json::to_string(&ConditionSource::Docstring).unwrap();
1007        assert_eq!(json, r#""docstring""#);
1008
1009        let json = serde_json::to_string(&ConditionSource::TypeHint).unwrap();
1010        assert_eq!(json, r#""type_hint""#);
1011
1012        let json = serde_json::to_string(&ConditionSource::Assertion).unwrap();
1013        assert_eq!(json, r#""assertion""#);
1014
1015        let json = serde_json::to_string(&ConditionSource::Icontract).unwrap();
1016        assert_eq!(json, r#""icontract""#);
1017
1018        let json = serde_json::to_string(&ConditionSource::Deal).unwrap();
1019        assert_eq!(json, r#""deal""#);
1020    }
1021
1022    // -------------------------------------------------------------------------
1023    // CohesionVerdict Tests
1024    // -------------------------------------------------------------------------
1025
1026    #[test]
1027    fn test_cohesion_verdict_from_lcom4() {
1028        assert_eq!(CohesionVerdict::from_lcom4(0), CohesionVerdict::Cohesive);
1029        assert_eq!(CohesionVerdict::from_lcom4(1), CohesionVerdict::Cohesive);
1030        assert_eq!(
1031            CohesionVerdict::from_lcom4(2),
1032            CohesionVerdict::SplitCandidate
1033        );
1034        assert_eq!(
1035            CohesionVerdict::from_lcom4(5),
1036            CohesionVerdict::SplitCandidate
1037        );
1038    }
1039
1040    // -------------------------------------------------------------------------
1041    // CouplingVerdict Tests
1042    // -------------------------------------------------------------------------
1043
1044    #[test]
1045    fn test_coupling_verdict_from_score() {
1046        assert_eq!(CouplingVerdict::from_score(0.0), CouplingVerdict::Low);
1047        assert_eq!(CouplingVerdict::from_score(0.1), CouplingVerdict::Low);
1048        assert_eq!(CouplingVerdict::from_score(0.2), CouplingVerdict::Moderate);
1049        assert_eq!(CouplingVerdict::from_score(0.3), CouplingVerdict::Moderate);
1050        assert_eq!(CouplingVerdict::from_score(0.4), CouplingVerdict::High);
1051        assert_eq!(CouplingVerdict::from_score(0.5), CouplingVerdict::High);
1052        assert_eq!(CouplingVerdict::from_score(0.6), CouplingVerdict::VeryHigh);
1053        assert_eq!(CouplingVerdict::from_score(1.0), CouplingVerdict::VeryHigh);
1054    }
1055
1056    // -------------------------------------------------------------------------
1057    // Report Serialization Tests
1058    // -------------------------------------------------------------------------
1059
1060    #[test]
1061    fn test_class_cohesion_serialization() {
1062        let cohesion = ClassCohesion {
1063            class_name: "MyClass".to_string(),
1064            file_path: "test.py".to_string(),
1065            line: 10,
1066            lcom4: 2,
1067            method_count: 4,
1068            field_count: 3,
1069            verdict: CohesionVerdict::SplitCandidate,
1070            split_suggestion: Some("Consider splitting".to_string()),
1071            components: vec![ComponentInfo {
1072                methods: vec!["method1".to_string()],
1073                fields: vec!["field1".to_string()],
1074            }],
1075        };
1076
1077        let json = serde_json::to_string(&cohesion).unwrap();
1078        let parsed: ClassCohesion = serde_json::from_str(&json).unwrap();
1079        assert_eq!(parsed.class_name, "MyClass");
1080        assert_eq!(parsed.lcom4, 2);
1081    }
1082
1083    #[test]
1084    fn test_coupling_report_serialization() {
1085        let report = CouplingReport {
1086            path_a: "module_a.py".to_string(),
1087            path_b: "module_b.py".to_string(),
1088            a_to_b: CrossCalls {
1089                calls: vec![CrossCall {
1090                    caller: "func_a".to_string(),
1091                    callee: "func_b".to_string(),
1092                    line: 10,
1093                }],
1094                count: 1,
1095            },
1096            b_to_a: CrossCalls::default(),
1097            total_calls: 1,
1098            coupling_score: 0.1,
1099            verdict: CouplingVerdict::Low,
1100        };
1101
1102        let json = serde_json::to_string(&report).unwrap();
1103        let parsed: CouplingReport = serde_json::from_str(&json).unwrap();
1104        assert_eq!(parsed.coupling_score, 0.1);
1105    }
1106
1107    #[test]
1108    fn test_purity_report_serialization() {
1109        let report = PurityReport {
1110            files: vec![FilePurityReport {
1111                source_file: "test.py".to_string(),
1112                functions: vec![FunctionPurity {
1113                    name: "pure_func".to_string(),
1114                    classification: "pure".to_string(),
1115                    effects: vec![],
1116                    confidence: Confidence::High,
1117                }],
1118                pure_count: 1,
1119            }],
1120            total_functions: 1,
1121            total_pure: 1,
1122        };
1123
1124        let json = serde_json::to_string(&report).unwrap();
1125        let parsed: PurityReport = serde_json::from_str(&json).unwrap();
1126        assert_eq!(parsed.total_pure, 1);
1127    }
1128
1129    #[test]
1130    fn test_temporal_report_serialization() {
1131        let report = TemporalReport {
1132            constraints: vec![TemporalConstraint {
1133                before: "open".to_string(),
1134                after: "close".to_string(),
1135                support: 5,
1136                confidence: 0.9,
1137                examples: vec![TemporalExample {
1138                    file: "test.py".to_string(),
1139                    line: 10,
1140                }],
1141            }],
1142            trigrams: vec![],
1143            metadata: TemporalMetadata::default(),
1144        };
1145
1146        let json = serde_json::to_string(&report).unwrap();
1147        let parsed: TemporalReport = serde_json::from_str(&json).unwrap();
1148        assert_eq!(parsed.constraints[0].before, "open");
1149    }
1150
1151    #[test]
1152    fn test_interface_info_serialization() {
1153        let info = InterfaceInfo {
1154            file: "test.py".to_string(),
1155            all_exports: Some(vec!["func1".to_string()]),
1156            functions: vec![FunctionInfo {
1157                name: "func1".to_string(),
1158                signature: "def func1(x: int) -> str".to_string(),
1159                docstring: Some("A function".to_string()),
1160                lineno: 5,
1161                is_async: false,
1162            }],
1163            classes: vec![],
1164        };
1165
1166        let json = serde_json::to_string(&info).unwrap();
1167        let parsed: InterfaceInfo = serde_json::from_str(&json).unwrap();
1168        assert_eq!(parsed.functions[0].name, "func1");
1169    }
1170
1171    #[test]
1172    fn test_resource_report_serialization() {
1173        let report = ResourceReport {
1174            file: "test.py".to_string(),
1175            language: "python".to_string(),
1176            function: Some("process".to_string()),
1177            resources: vec![ResourceInfo {
1178                name: "f".to_string(),
1179                resource_type: "file".to_string(),
1180                line: 10,
1181                closed: true,
1182            }],
1183            leaks: vec![],
1184            double_closes: vec![],
1185            use_after_closes: vec![],
1186            suggestions: vec![],
1187            constraints: vec![],
1188            summary: ResourceSummary::default(),
1189            analysis_time_ms: 50,
1190        };
1191
1192        let json = serde_json::to_string(&report).unwrap();
1193        let parsed: ResourceReport = serde_json::from_str(&json).unwrap();
1194        assert_eq!(parsed.resources[0].name, "f");
1195    }
1196
1197    #[test]
1198    fn test_behavioral_report_serialization() {
1199        let report = BehavioralReport {
1200            file_path: "test.py".to_string(),
1201            docstring_style: DocstringStyle::Google,
1202            has_icontract: false,
1203            has_deal: false,
1204            functions: vec![FunctionBehavior {
1205                function_name: "validate".to_string(),
1206                file_path: "test.py".to_string(),
1207                line: 10,
1208                purity_classification: "pure".to_string(),
1209                is_generator: false,
1210                is_async: false,
1211                preconditions: vec![Precondition {
1212                    param: "x".to_string(),
1213                    expression: Some("x > 0".to_string()),
1214                    description: None,
1215                    type_hint: Some("int".to_string()),
1216                    source: ConditionSource::Guard,
1217                }],
1218                postconditions: vec![],
1219                exceptions: vec![],
1220                yields: vec![],
1221                side_effects: vec![],
1222            }],
1223            classes: vec![],
1224        };
1225
1226        let json = serde_json::to_string(&report).unwrap();
1227        let parsed: BehavioralReport = serde_json::from_str(&json).unwrap();
1228        assert_eq!(parsed.functions[0].function_name, "validate");
1229    }
1230
1231    #[test]
1232    fn test_mutability_report_serialization() {
1233        let report = MutabilityReport {
1234            file: "test.py".to_string(),
1235            language: "python".to_string(),
1236            functions: vec![FunctionMutability {
1237                name: "process".to_string(),
1238                variables: vec![VariableMutability {
1239                    name: "count".to_string(),
1240                    mutable: true,
1241                    reassignments: 3,
1242                    mutations: 0,
1243                }],
1244                parameters: vec![],
1245                collection_mutations: vec![],
1246            }],
1247            classes: vec![],
1248            summary: MutabilitySummary::default(),
1249            analysis_time_ms: 30,
1250        };
1251
1252        let json = serde_json::to_string(&report).unwrap();
1253        let parsed: MutabilityReport = serde_json::from_str(&json).unwrap();
1254        assert_eq!(parsed.functions[0].name, "process");
1255    }
1256
1257    // -------------------------------------------------------------------------
1258    // OutputFormat Tests
1259    // -------------------------------------------------------------------------
1260
1261    #[test]
1262    fn test_output_format_display() {
1263        assert_eq!(OutputFormat::Json.to_string(), "json");
1264        assert_eq!(OutputFormat::Text.to_string(), "text");
1265    }
1266
1267    #[test]
1268    fn test_output_format_default() {
1269        assert_eq!(OutputFormat::default(), OutputFormat::Json);
1270    }
1271}