ricecoder_refactoring/impact/
graph.rs1use std::collections::{HashMap, HashSet, VecDeque};
4
5#[allow(dead_code)]
7#[derive(Debug, Clone, PartialEq, Eq, Hash)]
8pub struct Symbol {
9 pub name: String,
11 pub file: String,
13 pub symbol_type: SymbolType,
15}
16
17#[allow(dead_code)]
19#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
20pub enum SymbolType {
21 Function,
23 Struct,
25 Enum,
27 Trait,
29 Module,
31 Variable,
33 TypeAlias,
35 Macro,
37 Other,
39}
40
41#[allow(dead_code)]
43#[derive(Debug, Clone, PartialEq, Eq)]
44pub struct Dependency {
45 pub from: Symbol,
47 pub to: Symbol,
49 pub dep_type: DependencyType,
51}
52
53#[allow(dead_code)]
55#[derive(Debug, Clone, Copy, PartialEq, Eq)]
56pub enum DependencyType {
57 Direct,
59 TraitImpl,
61 TypeRef,
63 Import,
65 Generic,
67}
68
69#[derive(Debug, Clone)]
71pub struct DependencyGraph {
72 symbols: HashMap<String, Symbol>,
74 dependencies: Vec<Dependency>,
76 adjacency: HashMap<String, Vec<String>>,
78 reverse_adjacency: HashMap<String, Vec<String>>,
80}
81
82impl DependencyGraph {
83 pub fn new() -> Self {
85 Self {
86 symbols: HashMap::new(),
87 dependencies: Vec::new(),
88 adjacency: HashMap::new(),
89 reverse_adjacency: HashMap::new(),
90 }
91 }
92
93 pub fn add_symbol(&mut self, symbol: Symbol) {
95 let key = format!("{}:{}", symbol.name, symbol.file);
96 self.symbols.insert(key, symbol);
97 }
98
99 pub fn add_dependency(&mut self, dependency: Dependency) {
101 let from_key = format!("{}:{}", dependency.from.name, dependency.from.file);
102 let to_key = format!("{}:{}", dependency.to.name, dependency.to.file);
103
104 self.add_symbol(dependency.from.clone());
106 self.add_symbol(dependency.to.clone());
107
108 self.adjacency
110 .entry(from_key.clone())
111 .or_default()
112 .push(to_key.clone());
113
114 self.reverse_adjacency
116 .entry(to_key)
117 .or_default()
118 .push(from_key);
119
120 self.dependencies.push(dependency);
121 }
122
123 pub fn get_direct_dependents(&self, symbol_key: &str) -> Vec<String> {
126 let dependents = self.reverse_adjacency
127 .get(symbol_key)
128 .cloned()
129 .unwrap_or_default();
130
131 dependents.iter().map(|key| {
133 if let Some(colon_pos) = key.find(':') {
134 key[..colon_pos].to_string()
135 } else {
136 key.clone()
137 }
138 }).collect()
139 }
140
141 pub fn get_direct_dependencies(&self, symbol_key: &str) -> Vec<String> {
144 let dependencies = self.adjacency
145 .get(symbol_key)
146 .cloned()
147 .unwrap_or_default();
148
149 dependencies.iter().map(|key| {
151 if let Some(colon_pos) = key.find(':') {
152 key[..colon_pos].to_string()
153 } else {
154 key.clone()
155 }
156 }).collect()
157 }
158
159 pub fn get_transitive_dependents(&self, symbol_key: &str) -> HashSet<String> {
163 let mut affected_keys = HashSet::new();
164 let mut queue = VecDeque::new();
165
166 if let Some(dependents) = self.reverse_adjacency.get(symbol_key) {
168 for dependent in dependents {
169 queue.push_back(dependent.clone());
170 affected_keys.insert(dependent.clone());
171 }
172 }
173
174 while let Some(current) = queue.pop_front() {
176 if let Some(dependents) = self.reverse_adjacency.get(¤t) {
177 for dependent in dependents {
178 if !affected_keys.contains(dependent) {
179 affected_keys.insert(dependent.clone());
180 queue.push_back(dependent.clone());
181 }
182 }
183 }
184 }
185
186 affected_keys.iter().map(|key| {
188 if let Some(colon_pos) = key.find(':') {
189 key[..colon_pos].to_string()
190 } else {
191 key.clone()
192 }
193 }).collect()
194 }
195
196 pub fn get_transitive_dependencies(&self, symbol_name: &str) -> HashSet<String> {
198 let mut dependencies = HashSet::new();
199 let mut queue = VecDeque::new();
200
201 if let Some(deps) = self.adjacency.get(symbol_name) {
203 for dep in deps {
204 queue.push_back(dep.clone());
205 dependencies.insert(dep.clone());
206 }
207 }
208
209 while let Some(current) = queue.pop_front() {
211 if let Some(deps) = self.adjacency.get(¤t) {
212 for dep in deps {
213 if !dependencies.contains(dep) {
214 dependencies.insert(dep.clone());
215 queue.push_back(dep.clone());
216 }
217 }
218 }
219 }
220
221 dependencies
222 }
223
224 pub fn find_circular_dependencies(&self) -> Vec<Vec<String>> {
226 let mut cycles = Vec::new();
227 let mut visited = HashSet::new();
228 let mut rec_stack = HashSet::new();
229
230 for symbol_name in self.symbols.keys() {
231 if !visited.contains(symbol_name) {
232 self.dfs_cycles(symbol_name, &mut visited, &mut rec_stack, &mut cycles, Vec::new());
233 }
234 }
235
236 cycles
237 }
238
239 fn dfs_cycles(
241 &self,
242 node: &str,
243 visited: &mut HashSet<String>,
244 rec_stack: &mut HashSet<String>,
245 cycles: &mut Vec<Vec<String>>,
246 mut path: Vec<String>,
247 ) {
248 visited.insert(node.to_string());
249 rec_stack.insert(node.to_string());
250 path.push(node.to_string());
251
252 if let Some(neighbors) = self.adjacency.get(node) {
253 for neighbor in neighbors {
254 if !visited.contains(neighbor) {
255 self.dfs_cycles(neighbor, visited, rec_stack, cycles, path.clone());
256 } else if rec_stack.contains(neighbor) {
257 if let Some(start_idx) = path.iter().position(|x| x == neighbor) {
259 let cycle: Vec<String> = path[start_idx..].to_vec();
260 if !cycles.contains(&cycle) {
261 cycles.push(cycle);
262 }
263 }
264 }
265 }
266 }
267
268 rec_stack.remove(node);
269 }
270
271 pub fn get_symbols(&self) -> Vec<Symbol> {
273 self.symbols.values().cloned().collect()
274 }
275
276 pub fn get_dependencies(&self) -> &[Dependency] {
278 &self.dependencies
279 }
280
281 pub fn symbol_count(&self) -> usize {
283 self.symbols.len()
284 }
285
286 pub fn dependency_count(&self) -> usize {
288 self.dependencies.len()
289 }
290}
291
292impl Default for DependencyGraph {
293 fn default() -> Self {
294 Self::new()
295 }
296}
297
298#[cfg(test)]
299mod tests {
300 use super::*;
301
302 #[test]
303 fn test_add_symbol() {
304 let mut graph = DependencyGraph::new();
305 let symbol = Symbol {
306 name: "foo".to_string(),
307 file: "main.rs".to_string(),
308 symbol_type: SymbolType::Function,
309 };
310
311 graph.add_symbol(symbol.clone());
312 assert_eq!(graph.symbol_count(), 1);
313 }
314
315 #[test]
316 fn test_add_dependency() {
317 let mut graph = DependencyGraph::new();
318 let from = Symbol {
319 name: "foo".to_string(),
320 file: "main.rs".to_string(),
321 symbol_type: SymbolType::Function,
322 };
323 let to = Symbol {
324 name: "bar".to_string(),
325 file: "main.rs".to_string(),
326 symbol_type: SymbolType::Function,
327 };
328
329 let dep = Dependency {
330 from,
331 to,
332 dep_type: DependencyType::Direct,
333 };
334
335 graph.add_dependency(dep);
336 assert_eq!(graph.symbol_count(), 2);
337 assert_eq!(graph.dependency_count(), 1);
338 }
339
340 #[test]
341 fn test_get_direct_dependents() {
342 let mut graph = DependencyGraph::new();
343 let foo = Symbol {
344 name: "foo".to_string(),
345 file: "main.rs".to_string(),
346 symbol_type: SymbolType::Function,
347 };
348 let bar = Symbol {
349 name: "bar".to_string(),
350 file: "main.rs".to_string(),
351 symbol_type: SymbolType::Function,
352 };
353
354 graph.add_dependency(Dependency {
355 from: bar.clone(),
356 to: foo.clone(),
357 dep_type: DependencyType::Direct,
358 });
359
360 let dependents = graph.get_direct_dependents("foo:main.rs");
361 assert_eq!(dependents.len(), 1);
362 assert_eq!(dependents[0], "bar");
363 }
364
365 #[test]
366 fn test_transitive_dependents() {
367 let mut graph = DependencyGraph::new();
368 let a = Symbol {
369 name: "a".to_string(),
370 file: "main.rs".to_string(),
371 symbol_type: SymbolType::Function,
372 };
373 let b = Symbol {
374 name: "b".to_string(),
375 file: "main.rs".to_string(),
376 symbol_type: SymbolType::Function,
377 };
378 let c = Symbol {
379 name: "c".to_string(),
380 file: "main.rs".to_string(),
381 symbol_type: SymbolType::Function,
382 };
383
384 graph.add_dependency(Dependency {
386 from: b.clone(),
387 to: a.clone(),
388 dep_type: DependencyType::Direct,
389 });
390 graph.add_dependency(Dependency {
391 from: c.clone(),
392 to: b.clone(),
393 dep_type: DependencyType::Direct,
394 });
395
396 let affected = graph.get_transitive_dependents("a:main.rs");
397 assert_eq!(affected.len(), 2);
398 assert!(affected.contains("b"));
399 assert!(affected.contains("c"));
400 }
401
402 #[test]
403 fn test_circular_dependencies() {
404 let mut graph = DependencyGraph::new();
405 let a = Symbol {
406 name: "a".to_string(),
407 file: "main.rs".to_string(),
408 symbol_type: SymbolType::Function,
409 };
410 let b = Symbol {
411 name: "b".to_string(),
412 file: "main.rs".to_string(),
413 symbol_type: SymbolType::Function,
414 };
415
416 graph.add_dependency(Dependency {
418 from: a.clone(),
419 to: b.clone(),
420 dep_type: DependencyType::Direct,
421 });
422 graph.add_dependency(Dependency {
423 from: b.clone(),
424 to: a.clone(),
425 dep_type: DependencyType::Direct,
426 });
427
428 let cycles = graph.find_circular_dependencies();
429 assert!(!cycles.is_empty());
430 }
431}