use dashmap::DashMap;
use petgraph::graph::{DiGraph, NodeIndex};
use crate::ir::{ReExportEntry, Relationship, RelationshipKind, Symbol, SymbolId};
use crate::resolver::AliasEntry;
#[derive(Debug, Clone)]
pub struct RelationshipMeta {
pub kind: RelationshipKind,
pub alias: Option<String>,
pub properties_accessed: Vec<String>,
pub context: String,
pub file: String,
pub line: u32,
}
#[derive(Debug)]
pub struct GraphynGraph {
pub graph: DiGraph<SymbolId, RelationshipMeta>,
pub node_index: DashMap<SymbolId, NodeIndex>,
pub name_index: DashMap<String, Vec<SymbolId>>,
pub file_index: DashMap<String, Vec<SymbolId>>,
pub symbols: DashMap<SymbolId, Symbol>,
pub alias_chains: DashMap<SymbolId, Vec<AliasEntry>>,
pub file_reexports: DashMap<String, Vec<ReExportEntry>>,
}
impl Default for GraphynGraph {
fn default() -> Self {
Self::new()
}
}
impl GraphynGraph {
pub fn new() -> Self {
Self {
graph: DiGraph::new(),
node_index: DashMap::new(),
name_index: DashMap::new(),
file_index: DashMap::new(),
symbols: DashMap::new(),
alias_chains: DashMap::new(),
file_reexports: DashMap::new(),
}
}
pub fn add_symbol(&mut self, symbol: Symbol) {
if self.node_index.contains_key(&symbol.id) {
self.replace_symbol(symbol);
return;
}
let symbol_id = symbol.id.clone();
let symbol_name = symbol.name.clone();
let file = symbol.file.clone();
let node = self.graph.add_node(symbol_id.clone());
self.node_index.insert(symbol_id.clone(), node);
self.symbols.insert(symbol_id.clone(), symbol);
self.name_index
.entry(symbol_name)
.and_modify(|ids| {
ids.push(symbol_id.clone());
ids.sort();
ids.dedup();
})
.or_insert_with(|| vec![symbol_id.clone()]);
self.file_index
.entry(file)
.and_modify(|ids| {
ids.push(symbol_id.clone());
ids.sort();
ids.dedup();
})
.or_insert_with(|| vec![symbol_id]);
}
pub fn replace_symbol(&mut self, symbol: Symbol) {
let symbol_id = symbol.id.clone();
if let Some(existing) = self.symbols.get(&symbol_id) {
let existing_name = existing.name.clone();
let existing_file = existing.file.clone();
drop(existing);
if existing_name != symbol.name {
if let Some(mut ids) = self.name_index.get_mut(&existing_name) {
ids.retain(|id| id != &symbol_id);
}
self.name_index
.entry(symbol.name.clone())
.and_modify(|ids| {
ids.push(symbol_id.clone());
ids.sort();
ids.dedup();
})
.or_insert_with(|| vec![symbol_id.clone()]);
}
if existing_file != symbol.file {
if let Some(mut ids) = self.file_index.get_mut(&existing_file) {
ids.retain(|id| id != &symbol_id);
}
self.file_index
.entry(symbol.file.clone())
.and_modify(|ids| {
ids.push(symbol_id.clone());
ids.sort();
ids.dedup();
})
.or_insert_with(|| vec![symbol_id.clone()]);
}
}
self.symbols.insert(symbol_id, symbol);
}
pub fn add_relationship(&mut self, relationship: &Relationship) {
let Some(from) = self.node_index.get(&relationship.from).map(|v| *v) else {
return;
};
if !self.node_index.contains_key(&relationship.to) && relationship.to.starts_with("ext::") {
let package_name = relationship
.to
.strip_prefix("ext::")
.and_then(|s| s.strip_suffix("::package"))
.unwrap_or(&relationship.to)
.to_string();
self.add_symbol(crate::ir::Symbol {
id: relationship.to.clone(),
name: package_name,
kind: crate::ir::SymbolKind::ExternalPackage,
language: crate::ir::Language::TypeScript,
file: String::new(),
line_start: 0,
line_end: 0,
signature: None,
});
}
let Some(to) = self.node_index.get(&relationship.to).map(|v| *v) else {
return;
};
let meta = RelationshipMeta {
kind: relationship.kind.clone(),
alias: relationship.alias.clone(),
properties_accessed: relationship.properties_accessed.clone(),
context: relationship.context.clone(),
file: relationship.file.clone(),
line: relationship.line,
};
self.graph.add_edge(from, to, meta);
}
pub fn remove_relationships_in_file(&mut self, file: &str) -> usize {
let edge_ids: Vec<_> = self
.graph
.edge_indices()
.filter(|edge_id| {
self.graph
.edge_weight(*edge_id)
.map(|meta| meta.file == file)
.unwrap_or(false)
})
.collect();
let removed = edge_ids.len();
for edge_id in edge_ids {
let _ = self.graph.remove_edge(edge_id);
}
removed
}
pub fn remove_file(&mut self, file: &str) -> Vec<SymbolId> {
let mut removed = Vec::new();
let symbol_ids = self
.file_index
.remove(file)
.map(|(_, ids)| ids)
.unwrap_or_default();
for symbol_id in &symbol_ids {
if let Some((_, symbol)) = self.symbols.remove(symbol_id) {
if let Some(mut ids) = self.name_index.get_mut(&symbol.name) {
ids.retain(|id| id != symbol_id);
}
}
if let Some((_, node)) = self.node_index.remove(symbol_id) {
let _ = self.graph.remove_node(node);
}
self.alias_chains.remove(symbol_id);
removed.push(symbol_id.clone());
}
self.file_reexports.remove(file);
self.rebuild_node_index();
removed.sort();
removed
}
fn rebuild_node_index(&self) {
self.node_index.clear();
for node_index in self.graph.node_indices() {
if let Some(symbol_id) = self.graph.node_weight(node_index) {
self.node_index.insert(symbol_id.clone(), node_index);
}
}
}
}