synwire_agent/dataflow/
tracer.rs1#[non_exhaustive]
5pub struct DataflowOrigin {
6 pub file: String,
8 pub line: u32,
10 pub kind: String,
12 pub snippet: String,
14}
15
16#[non_exhaustive]
18pub struct DataflowHop {
19 pub origin: DataflowOrigin,
21 pub depth: u32,
23}
24
25pub struct DataflowTracer {
30 pub max_hops: u32,
32}
33
34impl DataflowTracer {
35 pub const fn new(max_hops: u32) -> Self {
37 Self { max_hops }
38 }
39
40 pub fn trace(&self, source: &str, variable: &str, file: &str) -> Vec<DataflowHop> {
54 let mut hops = Vec::new();
55 let assign_pattern1 = format!("{variable} =");
56 let assign_pattern2 = format!("{variable}=");
57 let let_pattern = format!("let {variable}");
58
59 for (line_idx, line) in source.lines().enumerate() {
60 if line.contains(&assign_pattern1)
61 || line.contains(&assign_pattern2)
62 || line.contains(&let_pattern)
63 {
64 let kind = if line.contains("let ") {
65 "definition".to_owned()
66 } else {
67 "assignment".to_owned()
68 };
69 hops.push(DataflowHop {
70 origin: DataflowOrigin {
71 file: file.to_owned(),
72 line: u32::try_from(line_idx + 1).unwrap_or(u32::MAX),
73 kind,
74 snippet: line.trim().to_owned(),
75 },
76 depth: 0,
77 });
78 }
79 if hops.len() >= self.max_hops as usize {
80 break;
81 }
82 }
83 hops
84 }
85}
86
87#[cfg(test)]
88mod tests {
89 use super::*;
90
91 #[test]
92 fn dataflow_finds_assignment() {
93 let tracer = DataflowTracer::new(5);
94 let source = "let x = 5;\nlet y = x + 1;\nx = compute();\n";
95 let hops = tracer.trace(source, "x", "test.rs");
96 assert!(!hops.is_empty());
97 assert!(hops.iter().any(|h| h.origin.kind == "definition"));
98 }
99
100 #[test]
101 fn dataflow_respects_max_hops() {
102 let tracer = DataflowTracer::new(2);
103 let source = "x = 1;\nx = 2;\nx = 3;\nx = 4;\n";
104 let hops = tracer.trace(source, "x", "test.rs");
105 assert!(hops.len() <= 2);
106 }
107
108 #[test]
109 fn dataflow_no_match_returns_empty() {
110 let tracer = DataflowTracer::new(5);
111 let source = "let y = 10;\nz = 20;\n";
112 let hops = tracer.trace(source, "nonexistent_var", "test.rs");
113 assert!(hops.is_empty());
114 }
115}