1use std::collections::{HashMap, HashSet};
2use std::path::Path;
3
4use crate::parser::{EdgeDef, EdgeKind, NodeDef, NodeKind};
5
6pub fn resolve(
7 nodes: &[NodeDef],
8 edges: &[EdgeDef],
9 _repo_root: &Path,
10) -> anyhow::Result<Vec<EdgeDef>> {
11 let mut resolved_edges: Vec<EdgeDef> = Vec::new();
12
13 let node_ids: HashSet<&str> = nodes.iter().map(|n| n.id.as_str()).collect();
15
16 let mut export_index: HashMap<String, Vec<String>> = HashMap::new();
18 for node in nodes {
19 export_index
20 .entry(node.name.clone())
21 .or_default()
22 .push(node.id.clone());
23 }
24
25 let mut file_paths: HashSet<String> = HashSet::new();
27 for node in nodes {
28 file_paths.insert(node.path.clone());
29 }
30
31 let known_file_ids: HashSet<String> =
33 file_paths.iter().map(|p| format!("file:{}", p)).collect();
34
35 for edge in edges {
37 match edge.kind {
38 EdgeKind::Imports => {
39 let dst_is_valid =
42 node_ids.contains(edge.dst.as_str()) || known_file_ids.contains(&edge.dst);
43
44 if dst_is_valid {
45 resolved_edges.push(EdgeDef {
46 src: edge.src.clone(),
47 dst: edge.dst.clone(),
48 kind: EdgeKind::Imports,
49 ..Default::default()
50 });
51 } else {
52 let import_target = edge.dst.trim_start_matches("file:");
54 let mut found = false;
55 for ext in &[".ts", ".tsx", ".js", ".jsx", ".py", ".rs"] {
56 let alt = format!("file:{}{}", import_target, ext);
57 if known_file_ids.contains(&alt) {
58 resolved_edges.push(EdgeDef {
59 src: edge.src.clone(),
60 dst: alt,
61 kind: EdgeKind::Imports,
62 ..Default::default()
63 });
64 found = true;
65 break;
66 }
67 }
68 if !found {
70 for index in &["/index.js", "/index.ts", "/index.jsx", "/index.tsx"] {
71 let alt = format!("file:{}{}", import_target, index);
72 if known_file_ids.contains(&alt) {
73 resolved_edges.push(EdgeDef {
74 src: edge.src.clone(),
75 dst: alt,
76 kind: EdgeKind::Imports,
77 ..Default::default()
78 });
79 found = true;
80 break;
81 }
82 }
83 }
84 if !found {
85 resolved_edges.push(edge.clone());
87 }
88 }
89 }
90 EdgeKind::Exports => {
91 if node_ids.contains(edge.dst.as_str()) {
93 resolved_edges.push(edge.clone());
94 } else {
95 tracing::debug!("Unresolved export edge: {} -> {}", edge.src, edge.dst);
97 resolved_edges.push(edge.clone());
98 }
99 }
100 EdgeKind::Calls | EdgeKind::Inherits => {
101 if node_ids.contains(edge.dst.as_str()) {
103 resolved_edges.push(edge.clone());
104 } else if let Some(targets) = export_index.get(&edge.dst) {
105 for target_id in targets {
107 resolved_edges.push(EdgeDef {
108 src: edge.src.clone(),
109 dst: target_id.clone(),
110 kind: EdgeKind::Calls,
111 confidence: 0.8,
112 ..Default::default()
113 });
114 }
115 } else {
116 resolved_edges.push(edge.clone());
118 }
119 }
120 _ => {
121 resolved_edges.push(edge.clone());
123 }
124 }
125 }
126
127 Ok(resolved_edges)
128}
129
130pub fn create_file_nodes(
131 file_paths: &HashSet<String>,
132 language: &HashMap<String, &str>,
133) -> Vec<NodeDef> {
134 let mut nodes = Vec::new();
135
136 for path in file_paths {
137 let id = format!("file:{}", path);
138 let _lang = language.get(path.as_str()).copied().unwrap_or("unknown");
139
140 nodes.push(NodeDef {
141 id,
142 kind: NodeKind::File,
143 name: path.clone(),
144 path: path.clone(),
145 line_start: 1,
146 line_end: 1,
147 ..Default::default()
148 });
149 }
150
151 nodes
152}
153
154pub fn build_language_map(nodes: &[NodeDef]) -> HashMap<String, &'static str> {
155 let mut map = HashMap::new();
156 for node in nodes {
157 let lang = match node.id.split(':').next().unwrap_or("") {
158 "fn" if node.path.ends_with(".ts") || node.path.ends_with(".tsx") => "typescript",
159 "fn" if node.path.ends_with(".js") || node.path.ends_with(".jsx") => "javascript",
160 "fn" if node.path.ends_with(".py") => "python",
161 "fn" if node.path.ends_with(".rs") => "rust",
162 "cls" if node.path.ends_with(".ts") || node.path.ends_with(".tsx") => "typescript",
163 "cls" if node.path.ends_with(".js") || node.path.ends_with(".jsx") => "javascript",
164 "cls" if node.path.ends_with(".py") => "python",
165 "cls" if node.path.ends_with(".rs") => "rust",
166 "file" if node.path.ends_with(".ts") || node.path.ends_with(".tsx") => "typescript",
167 "file" if node.path.ends_with(".js") || node.path.ends_with(".jsx") => "javascript",
168 "file" if node.path.ends_with(".py") => "python",
169 "file" if node.path.ends_with(".rs") => "rust",
170 _ => "unknown",
171 };
172 map.entry(node.path.clone()).or_insert(lang);
174 }
175 map
176}