cargo_coupling/
temporal.rs

1//! Temporal Coupling detection through static analysis
2//!
3//! Temporal coupling occurs when components must be used in a specific order.
4//! This module detects potential temporal coupling through heuristic patterns:
5//!
6//! 1. **Paired Operations**: open/close, lock/unlock, begin/commit
7//! 2. **Lifecycle Methods**: init, setup, start, stop, cleanup
8//! 3. **State Dependencies**: Methods that check initialization state
9//! 4. **Rust-specific Patterns**: Drop trait, MutexGuard, async spawn/join
10//!
11//! Note: This is heuristic-based detection. Runtime order cannot be
12//! fully determined through static analysis.
13
14use std::collections::HashMap;
15
16/// Types of temporal coupling patterns
17#[derive(Debug, Clone, PartialEq, Eq, Hash)]
18pub enum TemporalPattern {
19    /// Paired operations that must be balanced (open/close, lock/unlock)
20    PairedOperation {
21        open_method: String,
22        close_method: String,
23    },
24    /// Lifecycle methods that suggest initialization order
25    LifecycleSequence {
26        phase: LifecyclePhase,
27        method_name: String,
28    },
29    /// State check suggesting temporal dependency
30    StateCheck {
31        check_method: String,
32        implied_prerequisite: String,
33    },
34    /// Rust-specific: Drop impl provides cleanup
35    RustDropImpl { type_name: String },
36    /// Rust-specific: Guard pattern (MutexGuard, RwLockGuard, etc.)
37    RustGuardPattern {
38        guard_type: String,
39        resource: String,
40    },
41    /// Rust-specific: Async spawn without join
42    RustAsyncSpawnWithoutJoin,
43    /// Rust-specific: Unsafe block with manual resource management
44    RustUnsafeManualResource { operation: String },
45    /// Rust-specific: Builder pattern detected
46    RustBuilderPattern {
47        type_name: String,
48        required_methods: Vec<String>,
49    },
50}
51
52/// Lifecycle phases for temporal ordering
53#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
54pub enum LifecyclePhase {
55    /// Phase 1: Construction/Creation
56    Create = 0,
57    /// Phase 2: Configuration
58    Configure = 1,
59    /// Phase 3: Initialization
60    Initialize = 2,
61    /// Phase 4: Start/Connect
62    Start = 3,
63    /// Phase 5: Running/Active
64    Active = 4,
65    /// Phase 6: Stop/Disconnect
66    Stop = 5,
67    /// Phase 7: Cleanup/Destroy
68    Cleanup = 6,
69}
70
71impl LifecyclePhase {
72    pub fn description(&self) -> &'static str {
73        match self {
74            LifecyclePhase::Create => "Object creation",
75            LifecyclePhase::Configure => "Configuration",
76            LifecyclePhase::Initialize => "Initialization",
77            LifecyclePhase::Start => "Start/Connect",
78            LifecyclePhase::Active => "Active operation",
79            LifecyclePhase::Stop => "Stop/Disconnect",
80            LifecyclePhase::Cleanup => "Cleanup/Destroy",
81        }
82    }
83}
84
85/// A detected temporal coupling instance
86#[derive(Debug, Clone)]
87pub struct TemporalCouplingInstance {
88    /// The pattern type
89    pub pattern: TemporalPattern,
90    /// Source module/file
91    pub source: String,
92    /// Severity (0.0 - 1.0)
93    pub severity: f64,
94    /// Description of the issue
95    pub description: String,
96    /// Suggested fix
97    pub suggestion: String,
98}
99
100/// Paired operation definition
101#[derive(Debug, Clone)]
102struct PairedOp {
103    open: &'static str,
104    close: &'static str,
105    severity: f64,
106}
107
108/// Statistics about temporal coupling
109#[derive(Debug, Clone, Default)]
110pub struct TemporalCouplingStats {
111    /// Paired operations found
112    pub paired_operations: HashMap<String, PairedOperationStats>,
113    /// Lifecycle methods by phase
114    pub lifecycle_methods: HashMap<LifecyclePhase, Vec<String>>,
115    /// State check patterns
116    pub state_checks: Vec<String>,
117    /// Total issues detected
118    pub total_issues: usize,
119    /// Rust-specific: Types with Drop impl
120    pub drop_impls: Vec<String>,
121    /// Rust-specific: Guard patterns used
122    pub guard_patterns: Vec<String>,
123    /// Rust-specific: Async spawn count
124    pub async_spawns: usize,
125    /// Rust-specific: Async join count
126    pub async_joins: usize,
127    /// Rust-specific: Unsafe blocks with allocation
128    pub unsafe_allocations: Vec<String>,
129    /// Rust-specific: Builder patterns detected
130    pub builder_patterns: Vec<String>,
131}
132
133/// Stats for a specific paired operation type
134#[derive(Debug, Clone, Default)]
135pub struct PairedOperationStats {
136    pub open_count: usize,
137    pub close_count: usize,
138    pub locations: Vec<String>,
139}
140
141/// Temporal coupling analyzer
142#[derive(Debug, Default)]
143pub struct TemporalAnalyzer {
144    /// Detected instances
145    pub instances: Vec<TemporalCouplingInstance>,
146    /// Statistics
147    pub stats: TemporalCouplingStats,
148    /// Current module being analyzed
149    current_module: String,
150    /// Method calls found (for paired operation tracking)
151    method_calls: HashMap<String, Vec<String>>,
152    /// Function definitions found
153    function_defs: Vec<(String, String)>, // (module, function_name)
154    /// Rust-specific: Drop impls found
155    drop_impls: Vec<(String, String)>, // (module, type_name)
156    /// Rust-specific: Guard usages
157    guard_usages: Vec<(String, String)>, // (module, guard_type)
158    /// Rust-specific: Async spawns
159    async_spawns: Vec<String>,
160    /// Rust-specific: Async joins
161    async_joins: Vec<String>,
162    /// Rust-specific: Unsafe allocations
163    unsafe_allocs: Vec<(String, String)>, // (module, operation)
164    /// Rust-specific: Builder pattern types
165    builder_types: Vec<(String, Vec<String>)>, // (type_name, builder_methods)
166}
167
168impl TemporalAnalyzer {
169    /// Known paired operations
170    const PAIRED_OPS: &'static [PairedOp] = &[
171        PairedOp {
172            open: "open",
173            close: "close",
174            severity: 0.8,
175        },
176        PairedOp {
177            open: "lock",
178            close: "unlock",
179            severity: 0.9,
180        },
181        PairedOp {
182            open: "acquire",
183            close: "release",
184            severity: 0.9,
185        },
186        PairedOp {
187            open: "begin",
188            close: "commit",
189            severity: 0.7,
190        },
191        PairedOp {
192            open: "begin",
193            close: "end",
194            severity: 0.6,
195        },
196        PairedOp {
197            open: "start",
198            close: "stop",
199            severity: 0.7,
200        },
201        PairedOp {
202            open: "connect",
203            close: "disconnect",
204            severity: 0.8,
205        },
206        PairedOp {
207            open: "enter",
208            close: "exit",
209            severity: 0.7,
210        },
211        PairedOp {
212            open: "push",
213            close: "pop",
214            severity: 0.5,
215        },
216        PairedOp {
217            open: "subscribe",
218            close: "unsubscribe",
219            severity: 0.6,
220        },
221        PairedOp {
222            open: "register",
223            close: "unregister",
224            severity: 0.6,
225        },
226        PairedOp {
227            open: "enable",
228            close: "disable",
229            severity: 0.5,
230        },
231        PairedOp {
232            open: "activate",
233            close: "deactivate",
234            severity: 0.6,
235        },
236        PairedOp {
237            open: "attach",
238            close: "detach",
239            severity: 0.6,
240        },
241        PairedOp {
242            open: "bind",
243            close: "unbind",
244            severity: 0.7,
245        },
246        PairedOp {
247            open: "mount",
248            close: "unmount",
249            severity: 0.8,
250        },
251        PairedOp {
252            open: "init",
253            close: "deinit",
254            severity: 0.7,
255        },
256        PairedOp {
257            open: "setup",
258            close: "teardown",
259            severity: 0.7,
260        },
261        PairedOp {
262            open: "create",
263            close: "destroy",
264            severity: 0.7,
265        },
266        PairedOp {
267            open: "alloc",
268            close: "free",
269            severity: 0.9,
270        },
271        PairedOp {
272            open: "malloc",
273            close: "free",
274            severity: 0.9,
275        },
276        PairedOp {
277            open: "borrow",
278            close: "return",
279            severity: 0.6,
280        },
281        PairedOp {
282            open: "checkout",
283            close: "checkin",
284            severity: 0.6,
285        },
286    ];
287
288    /// Lifecycle method patterns
289    const LIFECYCLE_PATTERNS: &'static [(LifecyclePhase, &'static [&'static str])] = &[
290        (
291            LifecyclePhase::Create,
292            &["new", "create", "build", "construct", "make"],
293        ),
294        (
295            LifecyclePhase::Configure,
296            &[
297                "configure",
298                "config",
299                "set_config",
300                "with_config",
301                "options",
302            ],
303        ),
304        (
305            LifecyclePhase::Initialize,
306            &["init", "initialize", "setup", "prepare", "bootstrap"],
307        ),
308        (
309            LifecyclePhase::Start,
310            &[
311                "start", "begin", "run", "launch", "open", "connect", "activate",
312            ],
313        ),
314        (
315            LifecyclePhase::Active,
316            &["process", "execute", "handle", "perform", "do_work"],
317        ),
318        (
319            LifecyclePhase::Stop,
320            &[
321                "stop",
322                "end",
323                "halt",
324                "pause",
325                "close",
326                "disconnect",
327                "deactivate",
328            ],
329        ),
330        (
331            LifecyclePhase::Cleanup,
332            &[
333                "cleanup", "clean", "dispose", "destroy", "drop", "finalize", "shutdown",
334                "teardown",
335            ],
336        ),
337    ];
338
339    /// State check patterns that imply temporal dependency
340    const STATE_CHECK_PATTERNS: &'static [(&'static str, &'static str)] = &[
341        ("is_initialized", "init/initialize"),
342        ("is_connected", "connect"),
343        ("is_open", "open"),
344        ("is_started", "start"),
345        ("is_ready", "init/prepare"),
346        ("is_running", "start/run"),
347        ("is_active", "activate/start"),
348        ("is_configured", "configure"),
349        ("is_setup", "setup"),
350        ("has_started", "start"),
351        ("was_initialized", "init"),
352        ("check_initialized", "init"),
353        ("ensure_initialized", "init"),
354        ("assert_initialized", "init"),
355        ("require_connection", "connect"),
356    ];
357
358    /// Rust-specific: Guard types that auto-release
359    const RUST_GUARD_TYPES: &'static [&'static str] = &[
360        "MutexGuard",
361        "RwLockReadGuard",
362        "RwLockWriteGuard",
363        "RefCell",
364        "Ref",
365        "RefMut",
366        "ScopedJoinHandle",
367        "Guard",
368        "ScopeGuard",
369        "Entered", // tracing span guard
370    ];
371
372    /// Rust-specific: Unsafe allocation patterns
373    const RUST_UNSAFE_ALLOC_PATTERNS: &'static [&'static str] = &[
374        "alloc",
375        "dealloc",
376        "realloc",
377        "Box::from_raw",
378        "Box::into_raw",
379        "Vec::from_raw_parts",
380        "String::from_raw_parts",
381        "ptr::read",
382        "ptr::write",
383        "ManuallyDrop",
384        "mem::forget",
385        "mem::transmute",
386    ];
387
388    /// Rust-specific: Async spawn patterns
389    const RUST_ASYNC_SPAWN_PATTERNS: &'static [&'static str] = &[
390        "spawn",
391        "spawn_blocking",
392        "spawn_local",
393        "task::spawn",
394        "tokio::spawn",
395        "async_std::spawn",
396        "rayon::spawn",
397    ];
398
399    /// Rust-specific: Async join patterns
400    const RUST_ASYNC_JOIN_PATTERNS: &'static [&'static str] =
401        &["join", "join_all", "await", "block_on", "JoinHandle"];
402
403    pub fn new() -> Self {
404        Self::default()
405    }
406
407    /// Set current module context
408    pub fn set_module(&mut self, module: String) {
409        self.current_module = module;
410    }
411
412    /// Record a method/function call
413    pub fn record_call(&mut self, method_name: &str) {
414        let name = method_name.to_lowercase();
415        self.method_calls
416            .entry(name)
417            .or_default()
418            .push(self.current_module.clone());
419    }
420
421    /// Record a function definition
422    pub fn record_function_def(&mut self, function_name: &str) {
423        self.function_defs
424            .push((self.current_module.clone(), function_name.to_lowercase()));
425    }
426
427    /// Record a Drop impl
428    pub fn record_drop_impl(&mut self, type_name: &str) {
429        self.drop_impls
430            .push((self.current_module.clone(), type_name.to_string()));
431    }
432
433    /// Record a guard usage
434    pub fn record_guard_usage(&mut self, guard_type: &str) {
435        self.guard_usages
436            .push((self.current_module.clone(), guard_type.to_string()));
437    }
438
439    /// Record an async spawn
440    pub fn record_async_spawn(&mut self, spawn_type: &str) {
441        self.async_spawns
442            .push(format!("{}::{}", self.current_module, spawn_type));
443    }
444
445    /// Record an async join
446    pub fn record_async_join(&mut self, join_type: &str) {
447        self.async_joins
448            .push(format!("{}::{}", self.current_module, join_type));
449    }
450
451    /// Record unsafe allocation
452    pub fn record_unsafe_alloc(&mut self, operation: &str) {
453        self.unsafe_allocs
454            .push((self.current_module.clone(), operation.to_string()));
455    }
456
457    /// Record builder pattern
458    pub fn record_builder_pattern(&mut self, type_name: &str, methods: Vec<String>) {
459        self.builder_types.push((type_name.to_string(), methods));
460    }
461
462    /// Analyze collected data for temporal coupling patterns
463    pub fn analyze(&mut self) {
464        self.detect_paired_operation_imbalance();
465        self.detect_lifecycle_patterns();
466        self.detect_state_checks();
467        self.detect_rust_patterns();
468    }
469
470    /// Detect imbalanced paired operations
471    fn detect_paired_operation_imbalance(&mut self) {
472        for paired in Self::PAIRED_OPS {
473            let open_calls = self
474                .method_calls
475                .get(paired.open)
476                .map(|v| v.len())
477                .unwrap_or(0);
478            let close_calls = self
479                .method_calls
480                .get(paired.close)
481                .map(|v| v.len())
482                .unwrap_or(0);
483
484            if open_calls > 0 || close_calls > 0 {
485                let stats = self
486                    .stats
487                    .paired_operations
488                    .entry(format!("{}/{}", paired.open, paired.close))
489                    .or_default();
490                stats.open_count = open_calls;
491                stats.close_count = close_calls;
492
493                // Record locations
494                if let Some(locs) = self.method_calls.get(paired.open) {
495                    stats.locations.extend(locs.iter().cloned());
496                }
497                if let Some(locs) = self.method_calls.get(paired.close) {
498                    stats.locations.extend(locs.iter().cloned());
499                }
500
501                // Detect imbalance
502                if open_calls != close_calls && open_calls > 0 && close_calls > 0 {
503                    let (description, suggestion) = if open_calls > close_calls {
504                        (
505                            format!(
506                                "More {}() calls ({}) than {}() calls ({})",
507                                paired.open, open_calls, paired.close, close_calls
508                            ),
509                            format!(
510                                "Ensure every {}() has a matching {}(). Consider using RAII pattern or Drop trait.",
511                                paired.open, paired.close
512                            ),
513                        )
514                    } else {
515                        (
516                            format!(
517                                "More {}() calls ({}) than {}() calls ({})",
518                                paired.close, close_calls, paired.open, open_calls
519                            ),
520                            format!(
521                                "Check if {}() is called without prior {}()",
522                                paired.close, paired.open
523                            ),
524                        )
525                    };
526
527                    self.instances.push(TemporalCouplingInstance {
528                        pattern: TemporalPattern::PairedOperation {
529                            open_method: paired.open.to_string(),
530                            close_method: paired.close.to_string(),
531                        },
532                        source: "project-wide".to_string(),
533                        severity: paired.severity,
534                        description,
535                        suggestion,
536                    });
537                    self.stats.total_issues += 1;
538                }
539            }
540        }
541    }
542
543    /// Detect lifecycle method patterns
544    fn detect_lifecycle_patterns(&mut self) {
545        let mut found_phases: Vec<(LifecyclePhase, String, String)> = Vec::new();
546
547        for (module, func_name) in &self.function_defs {
548            for (phase, patterns) in Self::LIFECYCLE_PATTERNS {
549                for pattern in *patterns {
550                    if func_name.contains(pattern) {
551                        found_phases.push((*phase, module.clone(), func_name.clone()));
552                        self.stats
553                            .lifecycle_methods
554                            .entry(*phase)
555                            .or_default()
556                            .push(format!("{}::{}", module, func_name));
557                        break;
558                    }
559                }
560            }
561        }
562
563        // Check for missing lifecycle phases (heuristic)
564        let has_init = self
565            .stats
566            .lifecycle_methods
567            .contains_key(&LifecyclePhase::Initialize);
568        let has_cleanup = self
569            .stats
570            .lifecycle_methods
571            .contains_key(&LifecyclePhase::Cleanup);
572        let has_start = self
573            .stats
574            .lifecycle_methods
575            .contains_key(&LifecyclePhase::Start);
576        let has_stop = self
577            .stats
578            .lifecycle_methods
579            .contains_key(&LifecyclePhase::Stop);
580
581        // Warn if init exists but no cleanup
582        if has_init && !has_cleanup {
583            self.instances.push(TemporalCouplingInstance {
584                pattern: TemporalPattern::LifecycleSequence {
585                    phase: LifecyclePhase::Initialize,
586                    method_name: "init*".to_string(),
587                },
588                source: "project-wide".to_string(),
589                severity: 0.5,
590                description: "Initialization methods found but no cleanup/teardown methods"
591                    .to_string(),
592                suggestion: "Consider adding cleanup methods to properly release resources"
593                    .to_string(),
594            });
595            self.stats.total_issues += 1;
596        }
597
598        // Warn if start exists but no stop
599        if has_start && !has_stop {
600            self.instances.push(TemporalCouplingInstance {
601                pattern: TemporalPattern::LifecycleSequence {
602                    phase: LifecyclePhase::Start,
603                    method_name: "start*".to_string(),
604                },
605                source: "project-wide".to_string(),
606                severity: 0.5,
607                description: "Start methods found but no stop methods".to_string(),
608                suggestion: "Consider adding stop/shutdown methods for graceful termination"
609                    .to_string(),
610            });
611            self.stats.total_issues += 1;
612        }
613    }
614
615    /// Detect state check patterns
616    fn detect_state_checks(&mut self) {
617        for (module, func_name) in &self.function_defs {
618            for (check_pattern, prerequisite) in Self::STATE_CHECK_PATTERNS {
619                if func_name.contains(check_pattern) {
620                    self.stats
621                        .state_checks
622                        .push(format!("{}::{}", module, func_name));
623
624                    self.instances.push(TemporalCouplingInstance {
625                        pattern: TemporalPattern::StateCheck {
626                            check_method: func_name.clone(),
627                            implied_prerequisite: prerequisite.to_string(),
628                        },
629                        source: module.clone(),
630                        severity: 0.4,
631                        description: format!(
632                            "State check '{}' implies temporal dependency on {}",
633                            func_name, prerequisite
634                        ),
635                        suggestion: "Document the required call order or use type-state pattern to enforce it at compile time".to_string(),
636                    });
637                    self.stats.total_issues += 1;
638                    break;
639                }
640            }
641        }
642    }
643
644    /// Detect Rust-specific temporal patterns
645    fn detect_rust_patterns(&mut self) {
646        // Record Drop impls (positive pattern - indicates RAII)
647        for (module, type_name) in &self.drop_impls {
648            self.stats
649                .drop_impls
650                .push(format!("{}::{}", module, type_name));
651        }
652
653        // Record guard usages (positive pattern - auto-release)
654        for (module, guard_type) in &self.guard_usages {
655            self.stats
656                .guard_patterns
657                .push(format!("{}::{}", module, guard_type));
658        }
659
660        // Detect async spawn/join imbalance
661        self.stats.async_spawns = self.async_spawns.len();
662        self.stats.async_joins = self.async_joins.len();
663
664        if self.stats.async_spawns > 0 && self.stats.async_joins == 0 {
665            self.instances.push(TemporalCouplingInstance {
666                pattern: TemporalPattern::RustAsyncSpawnWithoutJoin,
667                source: "project-wide".to_string(),
668                severity: 0.6,
669                description: format!(
670                    "Found {} async spawn(s) but no explicit join/await. Tasks may be orphaned.",
671                    self.stats.async_spawns
672                ),
673                suggestion: "Ensure spawned tasks are awaited or their JoinHandles are collected"
674                    .to_string(),
675            });
676            self.stats.total_issues += 1;
677        }
678
679        // Detect unsafe allocation patterns
680        for (module, operation) in &self.unsafe_allocs {
681            self.stats
682                .unsafe_allocations
683                .push(format!("{}::{}", module, operation));
684
685            // Check for allocation without deallocation
686            let has_dealloc = self.unsafe_allocs.iter().any(|(_, op)| {
687                op.contains("dealloc") || op.contains("free") || op.contains("drop")
688            });
689
690            if operation.contains("alloc") && !has_dealloc {
691                self.instances.push(TemporalCouplingInstance {
692                    pattern: TemporalPattern::RustUnsafeManualResource {
693                        operation: operation.clone(),
694                    },
695                    source: module.clone(),
696                    severity: 0.9,
697                    description: format!(
698                        "Unsafe allocation '{}' detected without corresponding deallocation",
699                        operation
700                    ),
701                    suggestion: "Ensure manual allocations have corresponding deallocations, or use safe wrappers".to_string(),
702                });
703                self.stats.total_issues += 1;
704            }
705        }
706
707        // Record builder patterns
708        for (type_name, methods) in &self.builder_types {
709            self.stats
710                .builder_patterns
711                .push(format!("{} ({})", type_name, methods.join(" -> ")));
712
713            if methods.len() >= 3 {
714                self.instances.push(TemporalCouplingInstance {
715                    pattern: TemporalPattern::RustBuilderPattern {
716                        type_name: type_name.clone(),
717                        required_methods: methods.clone(),
718                    },
719                    source: "project-wide".to_string(),
720                    severity: 0.3,
721                    description: format!(
722                        "Builder pattern for '{}' has {} methods that may require specific order",
723                        type_name,
724                        methods.len()
725                    ),
726                    suggestion:
727                        "Consider using type-state pattern to enforce build order at compile time"
728                            .to_string(),
729                });
730            }
731        }
732    }
733
734    /// Generate summary report
735    pub fn summary(&self) -> String {
736        let mut report = String::new();
737        report.push_str("## Temporal Coupling Analysis\n\n");
738
739        if self.instances.is_empty()
740            && self.stats.drop_impls.is_empty()
741            && self.stats.guard_patterns.is_empty()
742        {
743            report.push_str("No temporal coupling patterns detected.\n");
744            return report;
745        }
746
747        report.push_str(&format!(
748            "**Total Issues**: {}\n\n",
749            self.stats.total_issues
750        ));
751
752        // Rust-specific positive patterns (RAII)
753        if !self.stats.drop_impls.is_empty() || !self.stats.guard_patterns.is_empty() {
754            report.push_str("### Rust RAII Patterns (Positive)\n\n");
755            report.push_str("These patterns help prevent temporal coupling issues:\n\n");
756
757            if !self.stats.drop_impls.is_empty() {
758                report.push_str(&format!(
759                    "- **Drop implementations**: {} types with automatic cleanup\n",
760                    self.stats.drop_impls.len()
761                ));
762            }
763            if !self.stats.guard_patterns.is_empty() {
764                report.push_str(&format!(
765                    "- **Guard patterns**: {} auto-release guards used\n",
766                    self.stats.guard_patterns.len()
767                ));
768            }
769            report.push('\n');
770        }
771
772        // Paired operations
773        if !self.stats.paired_operations.is_empty() {
774            report.push_str("### Paired Operations\n\n");
775            report.push_str("| Operation | Open | Close | Status |\n");
776            report.push_str("|-----------|------|-------|--------|\n");
777
778            for (op, stats) in &self.stats.paired_operations {
779                let status = if stats.open_count == stats.close_count {
780                    "Balanced"
781                } else {
782                    "Imbalanced"
783                };
784                report.push_str(&format!(
785                    "| {} | {} | {} | {} |\n",
786                    op, stats.open_count, stats.close_count, status
787                ));
788            }
789            report.push('\n');
790        }
791
792        // Async spawn/join
793        if self.stats.async_spawns > 0 || self.stats.async_joins > 0 {
794            report.push_str("### Async Task Management\n\n");
795            report.push_str("| Metric | Count |\n");
796            report.push_str("|--------|-------|\n");
797            report.push_str(&format!("| Spawns | {} |\n", self.stats.async_spawns));
798            report.push_str(&format!("| Joins/Awaits | {} |\n", self.stats.async_joins));
799            if self.stats.async_spawns > self.stats.async_joins {
800                report.push_str("\n**Warning**: More spawns than joins detected.\n");
801            }
802            report.push('\n');
803        }
804
805        // Lifecycle methods
806        if !self.stats.lifecycle_methods.is_empty() {
807            report.push_str("### Lifecycle Methods\n\n");
808            report.push_str("| Phase | Methods |\n");
809            report.push_str("|-------|--------|\n");
810
811            let mut phases: Vec<_> = self.stats.lifecycle_methods.iter().collect();
812            phases.sort_by_key(|(phase, _)| **phase);
813
814            for (phase, methods) in phases {
815                let method_list = if methods.len() > 3 {
816                    format!("{}, ... ({} total)", methods[..3].join(", "), methods.len())
817                } else {
818                    methods.join(", ")
819                };
820                report.push_str(&format!("| {} | {} |\n", phase.description(), method_list));
821            }
822            report.push('\n');
823        }
824
825        // Unsafe allocations
826        if !self.stats.unsafe_allocations.is_empty() {
827            report.push_str("### Unsafe Manual Resource Management\n\n");
828            report.push_str(
829                "**Warning**: Manual memory management detected. Ensure proper cleanup.\n\n",
830            );
831            for alloc in &self.stats.unsafe_allocations {
832                report.push_str(&format!("- `{}`\n", alloc));
833            }
834            report.push('\n');
835        }
836
837        // High severity issues
838        let high_severity: Vec<_> = self
839            .instances
840            .iter()
841            .filter(|i| i.severity >= 0.6)
842            .collect();
843
844        if !high_severity.is_empty() {
845            report.push_str("### Issues Detected\n\n");
846            for instance in high_severity {
847                let severity_label = if instance.severity >= 0.8 {
848                    "Critical"
849                } else if instance.severity >= 0.6 {
850                    "High"
851                } else {
852                    "Medium"
853                };
854                report.push_str(&format!(
855                    "- **[{}]** {}\n",
856                    severity_label, instance.description
857                ));
858                report.push_str(&format!("  - Suggestion: {}\n", instance.suggestion));
859            }
860            report.push('\n');
861        }
862
863        report
864    }
865
866    /// Get high severity instances
867    pub fn high_severity_instances(&self) -> Vec<&TemporalCouplingInstance> {
868        self.instances
869            .iter()
870            .filter(|i| i.severity >= 0.6)
871            .collect()
872    }
873}
874
875/// Analyze source code for temporal patterns
876pub fn analyze_temporal_patterns(content: &str, module_name: &str) -> TemporalAnalyzer {
877    let mut analyzer = TemporalAnalyzer::new();
878    analyzer.set_module(module_name.to_string());
879
880    // Simple pattern matching for method calls and definitions
881    // This is a heuristic approach - not full AST parsing
882
883    // Detect function definitions
884    let fn_regex = regex_lite::Regex::new(r"fn\s+([a-z_][a-z0-9_]*)\s*[<(]").unwrap();
885    for cap in fn_regex.captures_iter(content) {
886        if let Some(name) = cap.get(1) {
887            analyzer.record_function_def(name.as_str());
888        }
889    }
890
891    // Detect method calls (simplified)
892    let method_regex = regex_lite::Regex::new(r"\.([a-z_][a-z0-9_]*)\s*\(").unwrap();
893    for cap in method_regex.captures_iter(content) {
894        if let Some(name) = cap.get(1) {
895            let name_str = name.as_str();
896            analyzer.record_call(name_str);
897
898            // Check for async spawn patterns
899            for spawn_pattern in TemporalAnalyzer::RUST_ASYNC_SPAWN_PATTERNS {
900                if name_str.contains(spawn_pattern) {
901                    analyzer.record_async_spawn(name_str);
902                    break;
903                }
904            }
905
906            // Check for async join patterns
907            for join_pattern in TemporalAnalyzer::RUST_ASYNC_JOIN_PATTERNS {
908                if name_str.contains(join_pattern) {
909                    analyzer.record_async_join(name_str);
910                    break;
911                }
912            }
913        }
914    }
915
916    // Detect function calls
917    let call_regex = regex_lite::Regex::new(r"([a-z_][a-z0-9_]*)\s*\(").unwrap();
918    for cap in call_regex.captures_iter(content) {
919        if let Some(name) = cap.get(1) {
920            let name_str = name.as_str();
921            // Skip common keywords
922            if ![
923                "if", "while", "for", "match", "fn", "let", "return", "Some", "None", "Ok", "Err",
924            ]
925            .contains(&name_str)
926            {
927                analyzer.record_call(name_str);
928            }
929        }
930    }
931
932    // Detect Drop impl
933    let drop_regex = regex_lite::Regex::new(r"impl\s+Drop\s+for\s+([A-Z][a-zA-Z0-9_]*)").unwrap();
934    for cap in drop_regex.captures_iter(content) {
935        if let Some(name) = cap.get(1) {
936            analyzer.record_drop_impl(name.as_str());
937        }
938    }
939
940    // Detect guard type usage
941    for guard_type in TemporalAnalyzer::RUST_GUARD_TYPES {
942        if content.contains(guard_type) {
943            analyzer.record_guard_usage(guard_type);
944        }
945    }
946
947    // Detect unsafe allocation patterns
948    for pattern in TemporalAnalyzer::RUST_UNSAFE_ALLOC_PATTERNS {
949        if content.contains(pattern) {
950            analyzer.record_unsafe_alloc(pattern);
951        }
952    }
953
954    analyzer
955}
956
957#[cfg(test)]
958mod tests {
959    use super::*;
960
961    #[test]
962    fn test_paired_operation_detection() {
963        let mut analyzer = TemporalAnalyzer::new();
964        analyzer.set_module("test".to_string());
965
966        // Simulate open calls
967        analyzer.record_call("open");
968        analyzer.record_call("open");
969        // Simulate close calls (fewer)
970        analyzer.record_call("close");
971
972        analyzer.analyze();
973
974        assert_eq!(
975            analyzer
976                .stats
977                .paired_operations
978                .get("open/close")
979                .unwrap()
980                .open_count,
981            2
982        );
983        assert_eq!(
984            analyzer
985                .stats
986                .paired_operations
987                .get("open/close")
988                .unwrap()
989                .close_count,
990            1
991        );
992        assert!(analyzer.stats.total_issues > 0);
993    }
994
995    #[test]
996    fn test_lifecycle_detection() {
997        let mut analyzer = TemporalAnalyzer::new();
998        analyzer.set_module("test".to_string());
999
1000        analyzer.record_function_def("initialize");
1001        analyzer.record_function_def("start_server");
1002        analyzer.record_function_def("process_request");
1003
1004        analyzer.analyze();
1005
1006        assert!(
1007            analyzer
1008                .stats
1009                .lifecycle_methods
1010                .contains_key(&LifecyclePhase::Initialize)
1011        );
1012        assert!(
1013            analyzer
1014                .stats
1015                .lifecycle_methods
1016                .contains_key(&LifecyclePhase::Start)
1017        );
1018    }
1019
1020    #[test]
1021    fn test_state_check_detection() {
1022        let mut analyzer = TemporalAnalyzer::new();
1023        analyzer.set_module("test".to_string());
1024
1025        analyzer.record_function_def("is_initialized");
1026        analyzer.record_function_def("check_connected");
1027
1028        analyzer.analyze();
1029
1030        assert!(!analyzer.stats.state_checks.is_empty());
1031    }
1032
1033    #[test]
1034    fn test_balanced_operations() {
1035        let mut analyzer = TemporalAnalyzer::new();
1036        analyzer.set_module("test".to_string());
1037
1038        analyzer.record_call("lock");
1039        analyzer.record_call("unlock");
1040        analyzer.record_call("lock");
1041        analyzer.record_call("unlock");
1042
1043        analyzer.analyze();
1044
1045        let stats = analyzer.stats.paired_operations.get("lock/unlock").unwrap();
1046        assert_eq!(stats.open_count, stats.close_count);
1047    }
1048
1049    #[test]
1050    fn test_analyze_temporal_patterns() {
1051        let code = r#"
1052            fn initialize(&mut self) {
1053                self.ready = true;
1054            }
1055
1056            fn process(&mut self) {
1057                if self.is_initialized() {
1058                    self.handle_request();
1059                }
1060            }
1061
1062            fn cleanup(&mut self) {
1063                self.close();
1064            }
1065        "#;
1066
1067        let analyzer = analyze_temporal_patterns(code, "test_module");
1068        assert!(!analyzer.function_defs.is_empty());
1069    }
1070}