1use dashmap::DashMap;
2use petgraph::graph::{DiGraph, NodeIndex};
3
4use crate::ir::{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}
26
27impl Default for GraphynGraph {
28 fn default() -> Self {
29 Self::new()
30 }
31}
32
33impl GraphynGraph {
34 pub fn new() -> Self {
35 Self {
36 graph: DiGraph::new(),
37 node_index: DashMap::new(),
38 name_index: DashMap::new(),
39 file_index: DashMap::new(),
40 symbols: DashMap::new(),
41 alias_chains: DashMap::new(),
42 }
43 }
44
45 pub fn add_symbol(&mut self, symbol: Symbol) {
46 if self.node_index.contains_key(&symbol.id) {
47 self.replace_symbol(symbol);
48 return;
49 }
50
51 let symbol_id = symbol.id.clone();
52 let symbol_name = symbol.name.clone();
53 let file = symbol.file.clone();
54
55 let node = self.graph.add_node(symbol_id.clone());
56 self.node_index.insert(symbol_id.clone(), node);
57 self.symbols.insert(symbol_id.clone(), symbol);
58
59 self.name_index
60 .entry(symbol_name)
61 .and_modify(|ids| {
62 ids.push(symbol_id.clone());
63 ids.sort();
64 ids.dedup();
65 })
66 .or_insert_with(|| vec![symbol_id.clone()]);
67
68 self.file_index
69 .entry(file)
70 .and_modify(|ids| {
71 ids.push(symbol_id.clone());
72 ids.sort();
73 ids.dedup();
74 })
75 .or_insert_with(|| vec![symbol_id]);
76 }
77
78 pub fn replace_symbol(&mut self, symbol: Symbol) {
79 let symbol_id = symbol.id.clone();
80 if let Some(existing) = self.symbols.get(&symbol_id) {
81 let existing_name = existing.name.clone();
82 let existing_file = existing.file.clone();
83 drop(existing);
84
85 if existing_name != symbol.name {
86 if let Some(mut ids) = self.name_index.get_mut(&existing_name) {
87 ids.retain(|id| id != &symbol_id);
88 }
89 self.name_index
90 .entry(symbol.name.clone())
91 .and_modify(|ids| {
92 ids.push(symbol_id.clone());
93 ids.sort();
94 ids.dedup();
95 })
96 .or_insert_with(|| vec![symbol_id.clone()]);
97 }
98
99 if existing_file != symbol.file {
100 if let Some(mut ids) = self.file_index.get_mut(&existing_file) {
101 ids.retain(|id| id != &symbol_id);
102 }
103 self.file_index
104 .entry(symbol.file.clone())
105 .and_modify(|ids| {
106 ids.push(symbol_id.clone());
107 ids.sort();
108 ids.dedup();
109 })
110 .or_insert_with(|| vec![symbol_id.clone()]);
111 }
112 }
113
114 self.symbols.insert(symbol_id, symbol);
115 }
116
117 pub fn add_relationship(&mut self, relationship: &Relationship) {
118 let Some(from) = self.node_index.get(&relationship.from).map(|v| *v) else {
119 return;
120 };
121 let Some(to) = self.node_index.get(&relationship.to).map(|v| *v) else {
122 return;
123 };
124
125 let meta = RelationshipMeta {
126 kind: relationship.kind.clone(),
127 alias: relationship.alias.clone(),
128 properties_accessed: relationship.properties_accessed.clone(),
129 context: relationship.context.clone(),
130 file: relationship.file.clone(),
131 line: relationship.line,
132 };
133 self.graph.add_edge(from, to, meta);
134 }
135
136 pub fn remove_relationships_in_file(&mut self, file: &str) -> usize {
137 let edge_ids: Vec<_> = self
138 .graph
139 .edge_indices()
140 .filter(|edge_id| {
141 self.graph
142 .edge_weight(*edge_id)
143 .map(|meta| meta.file == file)
144 .unwrap_or(false)
145 })
146 .collect();
147
148 let removed = edge_ids.len();
149 for edge_id in edge_ids {
150 let _ = self.graph.remove_edge(edge_id);
151 }
152 removed
153 }
154
155 pub fn remove_file(&mut self, file: &str) -> Vec<SymbolId> {
156 let mut removed = Vec::new();
157 let symbol_ids = self
158 .file_index
159 .remove(file)
160 .map(|(_, ids)| ids)
161 .unwrap_or_default();
162
163 for symbol_id in &symbol_ids {
164 if let Some((_, symbol)) = self.symbols.remove(symbol_id) {
165 if let Some(mut ids) = self.name_index.get_mut(&symbol.name) {
166 ids.retain(|id| id != symbol_id);
167 }
168 }
169 if let Some((_, node)) = self.node_index.remove(symbol_id) {
170 let _ = self.graph.remove_node(node);
171 }
172 self.alias_chains.remove(symbol_id);
173 removed.push(symbol_id.clone());
174 }
175
176 self.rebuild_node_index();
177 removed.sort();
178 removed
179 }
180
181 fn rebuild_node_index(&self) {
182 self.node_index.clear();
183 for node_index in self.graph.node_indices() {
184 if let Some(symbol_id) = self.graph.node_weight(node_index) {
185 self.node_index.insert(symbol_id.clone(), node_index);
186 }
187 }
188 }
189}