1use dashmap::DashMap;
2use petgraph::graph::{DiGraph, NodeIndex};
3
4use crate::ir::{ReExportEntry, Relationship, RelationshipKind, Symbol, SymbolId};
5use crate::resolver::AliasEntry;
6
7#[derive(Debug, Clone)]
8pub struct RelationshipMeta {
9 pub kind: RelationshipKind,
10 pub alias: Option<String>,
11 pub properties_accessed: Vec<String>,
12 pub context: String,
13 pub file: String,
14 pub line: u32,
15}
16
17#[derive(Debug)]
18pub struct GraphynGraph {
19 pub graph: DiGraph<SymbolId, RelationshipMeta>,
20 pub node_index: DashMap<SymbolId, NodeIndex>,
21 pub name_index: DashMap<String, Vec<SymbolId>>,
22 pub file_index: DashMap<String, Vec<SymbolId>>,
23 pub symbols: DashMap<SymbolId, Symbol>,
24 pub alias_chains: DashMap<SymbolId, Vec<AliasEntry>>,
25 pub file_reexports: DashMap<String, Vec<ReExportEntry>>,
26}
27
28impl Default for GraphynGraph {
29 fn default() -> Self {
30 Self::new()
31 }
32}
33
34impl GraphynGraph {
35 pub fn new() -> Self {
36 Self {
37 graph: DiGraph::new(),
38 node_index: DashMap::new(),
39 name_index: DashMap::new(),
40 file_index: DashMap::new(),
41 symbols: DashMap::new(),
42 alias_chains: DashMap::new(),
43 file_reexports: DashMap::new(),
44 }
45 }
46
47 pub fn add_symbol(&mut self, symbol: Symbol) {
48 if self.node_index.contains_key(&symbol.id) {
49 self.replace_symbol(symbol);
50 return;
51 }
52
53 let symbol_id = symbol.id.clone();
54 let symbol_name = symbol.name.clone();
55 let file = symbol.file.clone();
56
57 let node = self.graph.add_node(symbol_id.clone());
58 self.node_index.insert(symbol_id.clone(), node);
59 self.symbols.insert(symbol_id.clone(), symbol);
60
61 self.name_index
62 .entry(symbol_name)
63 .and_modify(|ids| {
64 ids.push(symbol_id.clone());
65 ids.sort();
66 ids.dedup();
67 })
68 .or_insert_with(|| vec![symbol_id.clone()]);
69
70 self.file_index
71 .entry(file)
72 .and_modify(|ids| {
73 ids.push(symbol_id.clone());
74 ids.sort();
75 ids.dedup();
76 })
77 .or_insert_with(|| vec![symbol_id]);
78 }
79
80 pub fn replace_symbol(&mut self, symbol: Symbol) {
81 let symbol_id = symbol.id.clone();
82 if let Some(existing) = self.symbols.get(&symbol_id) {
83 let existing_name = existing.name.clone();
84 let existing_file = existing.file.clone();
85 drop(existing);
86
87 if existing_name != symbol.name {
88 if let Some(mut ids) = self.name_index.get_mut(&existing_name) {
89 ids.retain(|id| id != &symbol_id);
90 }
91 self.name_index
92 .entry(symbol.name.clone())
93 .and_modify(|ids| {
94 ids.push(symbol_id.clone());
95 ids.sort();
96 ids.dedup();
97 })
98 .or_insert_with(|| vec![symbol_id.clone()]);
99 }
100
101 if existing_file != symbol.file {
102 if let Some(mut ids) = self.file_index.get_mut(&existing_file) {
103 ids.retain(|id| id != &symbol_id);
104 }
105 self.file_index
106 .entry(symbol.file.clone())
107 .and_modify(|ids| {
108 ids.push(symbol_id.clone());
109 ids.sort();
110 ids.dedup();
111 })
112 .or_insert_with(|| vec![symbol_id.clone()]);
113 }
114 }
115
116 self.symbols.insert(symbol_id, symbol);
117 }
118
119 pub fn add_relationship(&mut self, relationship: &Relationship) {
120 let Some(from) = self.node_index.get(&relationship.from).map(|v| *v) else {
121 return;
122 };
123
124 if !self.node_index.contains_key(&relationship.to) && relationship.to.starts_with("ext::") {
126 let package_name = relationship
127 .to
128 .strip_prefix("ext::")
129 .and_then(|s| s.strip_suffix("::package"))
130 .unwrap_or(&relationship.to)
131 .to_string();
132 self.add_symbol(crate::ir::Symbol {
133 id: relationship.to.clone(),
134 name: package_name,
135 kind: crate::ir::SymbolKind::ExternalPackage,
136 language: crate::ir::Language::TypeScript,
137 file: String::new(),
138 line_start: 0,
139 line_end: 0,
140 signature: None,
141 });
142 }
143
144 let Some(to) = self.node_index.get(&relationship.to).map(|v| *v) else {
145 return;
146 };
147
148 let meta = RelationshipMeta {
149 kind: relationship.kind.clone(),
150 alias: relationship.alias.clone(),
151 properties_accessed: relationship.properties_accessed.clone(),
152 context: relationship.context.clone(),
153 file: relationship.file.clone(),
154 line: relationship.line,
155 };
156 self.graph.add_edge(from, to, meta);
157 }
158
159 pub fn remove_relationships_in_file(&mut self, file: &str) -> usize {
160 let edge_ids: Vec<_> = self
161 .graph
162 .edge_indices()
163 .filter(|edge_id| {
164 self.graph
165 .edge_weight(*edge_id)
166 .map(|meta| meta.file == file)
167 .unwrap_or(false)
168 })
169 .collect();
170
171 let removed = edge_ids.len();
172 for edge_id in edge_ids {
173 let _ = self.graph.remove_edge(edge_id);
174 }
175 removed
176 }
177
178 pub fn remove_file(&mut self, file: &str) -> Vec<SymbolId> {
179 let mut removed = Vec::new();
180 let symbol_ids = self
181 .file_index
182 .remove(file)
183 .map(|(_, ids)| ids)
184 .unwrap_or_default();
185
186 for symbol_id in &symbol_ids {
187 if let Some((_, symbol)) = self.symbols.remove(symbol_id) {
188 if let Some(mut ids) = self.name_index.get_mut(&symbol.name) {
189 ids.retain(|id| id != symbol_id);
190 }
191 }
192 if let Some((_, node)) = self.node_index.remove(symbol_id) {
193 let _ = self.graph.remove_node(node);
194 }
195 self.alias_chains.remove(symbol_id);
196 removed.push(symbol_id.clone());
197 }
198 self.file_reexports.remove(file);
199
200 self.rebuild_node_index();
201 removed.sort();
202 removed
203 }
204
205 fn rebuild_node_index(&self) {
206 self.node_index.clear();
207 for node_index in self.graph.node_indices() {
208 if let Some(symbol_id) = self.graph.node_weight(node_index) {
209 self.node_index.insert(symbol_id.clone(), node_index);
210 }
211 }
212 }
213}