Skip to main content

tldr_core/patterns/
async_patterns.rs

1//! Async/concurrency pattern detection
2//!
3//! Detects async patterns:
4//! - async/await keywords
5//! - Go goroutines (go keyword)
6//! - Tokio runtime usage
7//! - Sync primitives (mutex, channel, semaphore)
8
9use super::signals::PatternSignals;
10use crate::types::AsyncPattern;
11
12/// Convert signals to async pattern
13pub fn signals_to_pattern(signals: &PatternSignals, evidence_limit: usize) -> Option<AsyncPattern> {
14    let async_patterns = &signals.async_patterns;
15
16    if !async_patterns.has_signals() {
17        return None;
18    }
19
20    let concurrency_confidence = async_patterns.calculate_confidence();
21
22    // Detect patterns
23    let mut patterns = Vec::new();
24
25    if !async_patterns.async_await.is_empty() {
26        patterns.push("async_await".to_string());
27    }
28
29    if !async_patterns.goroutines.is_empty() {
30        patterns.push("goroutines".to_string());
31    }
32
33    if !async_patterns.tokio_usage.is_empty() {
34        patterns.push("tokio".to_string());
35    }
36
37    if !async_patterns.thread_spawns.is_empty() {
38        patterns.push("thread_spawn".to_string());
39    }
40
41    // Collect sync primitives
42    let mut sync_primitives: Vec<String> = async_patterns
43        .sync_primitives
44        .iter()
45        .map(|(name, _)| name.clone())
46        .collect();
47    sync_primitives.sort();
48    sync_primitives.dedup();
49
50    // Collect evidence (limited)
51    let mut evidence = Vec::new();
52    evidence.extend(
53        async_patterns
54            .async_await
55            .iter()
56            .take(evidence_limit)
57            .cloned(),
58    );
59    evidence.extend(
60        async_patterns
61            .goroutines
62            .iter()
63            .take(evidence_limit)
64            .cloned(),
65    );
66    evidence.extend(
67        async_patterns
68            .tokio_usage
69            .iter()
70            .take(evidence_limit)
71            .cloned(),
72    );
73    evidence.extend(
74        async_patterns
75            .sync_primitives
76            .iter()
77            .take(evidence_limit)
78            .map(|(_, e)| e.clone()),
79    );
80    evidence.truncate(evidence_limit);
81
82    Some(AsyncPattern {
83        concurrency_confidence,
84        patterns,
85        sync_primitives,
86        evidence,
87    })
88}
89
90#[cfg(test)]
91mod tests {
92    use super::*;
93    use crate::types::Evidence;
94
95    #[test]
96    fn test_no_signals_returns_none() {
97        let signals = PatternSignals::default();
98        assert!(signals_to_pattern(&signals, 3).is_none());
99    }
100
101    #[test]
102    fn test_async_await_detected() {
103        let mut signals = PatternSignals::default();
104        signals.async_patterns.async_await.push(Evidence::new(
105            "service.py",
106            10,
107            "async def fetch_data():",
108        ));
109
110        let pattern = signals_to_pattern(&signals, 3).unwrap();
111        assert!(pattern.patterns.contains(&"async_await".to_string()));
112    }
113
114    #[test]
115    fn test_goroutines_detected() {
116        let mut signals = PatternSignals::default();
117        signals.async_patterns.goroutines.push(Evidence::new(
118            "main.go",
119            15,
120            "go handleRequest(req)",
121        ));
122
123        let pattern = signals_to_pattern(&signals, 3).unwrap();
124        assert!(pattern.patterns.contains(&"goroutines".to_string()));
125    }
126
127    #[test]
128    fn test_tokio_detected() {
129        let mut signals = PatternSignals::default();
130        signals.async_patterns.tokio_usage.push(Evidence::new(
131            "main.rs",
132            5,
133            "use tokio::runtime::Runtime;",
134        ));
135
136        let pattern = signals_to_pattern(&signals, 3).unwrap();
137        assert!(pattern.patterns.contains(&"tokio".to_string()));
138    }
139
140    #[test]
141    fn test_sync_primitives_detected() {
142        let mut signals = PatternSignals::default();
143        signals.async_patterns.sync_primitives.push((
144            "mutex".to_string(),
145            Evidence::new("lib.rs", 10, "let lock = Mutex::new(0);"),
146        ));
147        signals.async_patterns.sync_primitives.push((
148            "channel".to_string(),
149            Evidence::new("lib.rs", 15, "let (tx, rx) = mpsc::channel();"),
150        ));
151
152        let pattern = signals_to_pattern(&signals, 3).unwrap();
153        assert!(pattern.sync_primitives.contains(&"mutex".to_string()));
154        assert!(pattern.sync_primitives.contains(&"channel".to_string()));
155    }
156}