1use std::collections::{HashMap, HashSet};
6use std::path::Path;
7
8use super::types::{DependencyEdge, DependencyGraph, DependencyType, ModuleNode};
9
10struct ResolutionConfig {
12 extensions: Vec<&'static str>,
13 index_files: Vec<&'static str>,
14}
15
16fn get_resolution_config(language: &str) -> ResolutionConfig {
17 match language {
18 "typescript" => ResolutionConfig {
19 extensions: vec![".ts", ".tsx", ".d.ts", ".js", ".jsx", ""],
20 index_files: vec!["index.ts", "index.tsx", "index.js", "index.jsx"],
21 },
22 "javascript" => ResolutionConfig {
23 extensions: vec![".js", ".jsx", ".mjs", ".cjs", ""],
24 index_files: vec!["index.js", "index.jsx", "index.mjs"],
25 },
26 "python" => ResolutionConfig {
27 extensions: vec![".py", ""],
28 index_files: vec!["__init__.py"],
29 },
30 _ => ResolutionConfig {
31 extensions: vec![""],
32 index_files: vec![],
33 },
34 }
35}
36
37pub struct DependencyAnalyzer {
39 module_index: HashMap<String, ModuleNode>,
40 module_ids: HashSet<String>,
41}
42
43impl DependencyAnalyzer {
44 pub fn new() -> Self {
45 Self {
46 module_index: HashMap::new(),
47 module_ids: HashSet::new(),
48 }
49 }
50
51 pub fn analyze_dependencies(&mut self, modules: &[ModuleNode]) -> DependencyGraph {
53 self.build_module_index(modules);
54 let mut edges = Vec::new();
55
56 for module in modules {
57 self.analyze_module_dependencies(module, &mut edges);
58 }
59
60 DependencyGraph { edges }
61 }
62
63 fn build_module_index(&mut self, modules: &[ModuleNode]) {
65 self.module_index.clear();
66 self.module_ids.clear();
67
68 for module in modules {
69 self.module_index.insert(module.id.clone(), module.clone());
70 self.module_ids.insert(module.id.clone());
71
72 if let Some(pos) = module.id.rfind('.') {
74 let without_ext = module.id.get(..pos).unwrap_or(&module.id);
75 self.module_index
76 .insert(without_ext.to_string(), module.clone());
77 }
78 }
79 }
80
81 fn analyze_module_dependencies(&self, module: &ModuleNode, edges: &mut Vec<DependencyEdge>) {
83 for imp in &module.imports {
84 if let Some(target_id) =
85 self.resolve_import_target(&imp.source, &module.id, &module.language)
86 {
87 edges.push(DependencyEdge {
88 source: module.id.clone(),
89 target: target_id,
90 edge_type: if imp.is_dynamic {
91 DependencyType::Dynamic
92 } else {
93 DependencyType::Import
94 },
95 symbols: imp.symbols.clone(),
96 is_type_only: self.is_type_only_import(imp),
97 });
98 }
99 }
100 }
101
102 fn resolve_import_target(&self, source: &str, current_id: &str, lang: &str) -> Option<String> {
104 if !source.starts_with('.') && !source.starts_with('/') {
106 return None;
107 }
108
109 let current_dir = Path::new(current_id).parent()?.to_str()?;
110 let target_path = self.normalize_path(&format!("{}/{}", current_dir, source));
111 let config = get_resolution_config(lang);
112
113 for ext in &config.extensions {
115 let candidate = format!("{}{}", target_path, ext);
116 if self.module_ids.contains(&candidate) {
117 return Some(candidate);
118 }
119 }
120
121 for index_file in &config.index_files {
123 let candidate = format!("{}/{}", target_path, index_file);
124 if self.module_ids.contains(&candidate) {
125 return Some(candidate);
126 }
127 }
128
129 None
130 }
131
132 fn normalize_path(&self, p: &str) -> String {
134 let parts: Vec<&str> = p.split('/').collect();
135 let mut result = Vec::new();
136
137 for part in parts {
138 match part {
139 ".." => {
140 result.pop();
141 }
142 "." | "" => {}
143 _ => result.push(part),
144 }
145 }
146
147 result.join("/")
148 }
149
150 fn is_type_only_import(&self, imp: &super::types::ImportInfo) -> bool {
152 imp.symbols.iter().any(|s| s.starts_with("type "))
153 }
154
155 pub fn detect_circular_dependencies(&self, graph: &DependencyGraph) -> Vec<Vec<String>> {
157 let mut cycles = Vec::new();
158 let mut adjacency: HashMap<String, Vec<String>> = HashMap::new();
159
160 for edge in &graph.edges {
161 adjacency
162 .entry(edge.source.clone())
163 .or_default()
164 .push(edge.target.clone());
165 }
166
167 let mut visited = HashSet::new();
168 let mut rec_stack = HashSet::new();
169 let mut path = Vec::new();
170
171 for node in adjacency.keys() {
172 if !visited.contains(node) {
173 self.dfs_cycle(
174 node,
175 &adjacency,
176 &mut visited,
177 &mut rec_stack,
178 &mut path,
179 &mut cycles,
180 );
181 }
182 }
183
184 cycles
185 }
186
187 fn dfs_cycle(
188 &self,
189 node: &str,
190 adj: &HashMap<String, Vec<String>>,
191 visited: &mut HashSet<String>,
192 rec_stack: &mut HashSet<String>,
193 path: &mut Vec<String>,
194 cycles: &mut Vec<Vec<String>>,
195 ) {
196 visited.insert(node.to_string());
197 rec_stack.insert(node.to_string());
198 path.push(node.to_string());
199
200 if let Some(neighbors) = adj.get(node) {
201 for neighbor in neighbors {
202 if !visited.contains(neighbor) {
203 self.dfs_cycle(neighbor, adj, visited, rec_stack, path, cycles);
204 } else if rec_stack.contains(neighbor) {
205 if let Some(start) = path.iter().position(|x| x == neighbor) {
206 let mut cycle: Vec<String> = path[start..].to_vec();
207 cycle.push(neighbor.clone());
208 cycles.push(cycle);
209 }
210 }
211 }
212 }
213
214 path.pop();
215 rec_stack.remove(node);
216 }
217
218 pub fn get_dependency_stats(&self, graph: &DependencyGraph) -> DependencyStats {
220 let mut dependent_count: HashMap<String, usize> = HashMap::new();
221 let mut depended_count: HashMap<String, usize> = HashMap::new();
222 let mut type_only = 0;
223 let mut dynamic = 0;
224
225 for edge in &graph.edges {
226 *dependent_count.entry(edge.source.clone()).or_insert(0) += 1;
227 *depended_count.entry(edge.target.clone()).or_insert(0) += 1;
228 if edge.is_type_only {
229 type_only += 1;
230 }
231 if edge.edge_type == DependencyType::Dynamic {
232 dynamic += 1;
233 }
234 }
235
236 let mut most_dependent: Vec<_> = dependent_count.into_iter().collect();
237 most_dependent.sort_by(|a, b| b.1.cmp(&a.1));
238
239 let mut most_depended: Vec<_> = depended_count.into_iter().collect();
240 most_depended.sort_by(|a, b| b.1.cmp(&a.1));
241
242 DependencyStats {
243 total_edges: graph.edges.len(),
244 type_only_deps: type_only,
245 dynamic_deps: dynamic,
246 most_dependent: most_dependent.into_iter().take(10).collect(),
247 most_depended: most_depended.into_iter().take(10).collect(),
248 }
249 }
250}
251
252impl Default for DependencyAnalyzer {
253 fn default() -> Self {
254 Self::new()
255 }
256}
257
258#[derive(Debug, Clone, Default)]
260pub struct DependencyStats {
261 pub total_edges: usize,
262 pub type_only_deps: usize,
263 pub dynamic_deps: usize,
264 pub most_dependent: Vec<(String, usize)>,
265 pub most_depended: Vec<(String, usize)>,
266}
267
268pub fn analyze_dependencies(modules: &[ModuleNode]) -> DependencyGraph {
270 let mut analyzer = DependencyAnalyzer::new();
271 analyzer.analyze_dependencies(modules)
272}