use std::collections::HashSet;
use super::types::*;
impl CodeGraph {
pub fn build_indexes(&mut self) {
self.node_index.clear();
self.outgoing.clear();
self.incoming.clear();
for (i, node) in self.nodes.iter().enumerate() {
self.node_index.insert(node.id.clone(), i);
}
for (i, edge) in self.edges.iter().enumerate() {
self.outgoing.entry(edge.from.clone()).or_default().push(i);
self.incoming.entry(edge.to.clone()).or_default().push(i);
}
}
#[inline]
pub fn outgoing_edges(&self, node_id: &str) -> impl Iterator<Item = &CodeEdge> {
self.outgoing
.get(node_id)
.map(|indices| indices.as_slice())
.unwrap_or(&[])
.iter()
.map(move |&i| &self.edges[i])
}
#[inline]
pub fn incoming_edges(&self, node_id: &str) -> impl Iterator<Item = &CodeEdge> {
self.incoming
.get(node_id)
.map(|indices| indices.as_slice())
.unwrap_or(&[])
.iter()
.map(move |&i| &self.edges[i])
}
#[inline]
pub fn node_by_id(&self, node_id: &str) -> Option<&CodeNode> {
self.node_index.get(node_id).map(|&i| &self.nodes[i])
}
pub fn get_callers(&self, node_id: &str) -> Vec<&CodeNode> {
self.incoming_edges(node_id)
.filter(|e| e.relation == EdgeRelation::Calls)
.filter_map(|e| self.node_by_id(&e.from))
.collect()
}
pub fn get_callees(&self, node_id: &str) -> Vec<&CodeNode> {
self.outgoing_edges(node_id)
.filter(|e| e.relation == EdgeRelation::Calls)
.filter_map(|e| self.node_by_id(&e.to))
.collect()
}
pub fn get_dependencies(&self, node_id: &str) -> Vec<&CodeNode> {
self.outgoing_edges(node_id)
.filter_map(|e| self.node_by_id(&e.to))
.collect()
}
pub fn get_impact(&self, node_id: &str) -> Vec<&CodeNode> {
self.get_impact_filtered(node_id, None)
}
pub fn get_impact_filtered(&self, node_id: &str, relations: Option<&[EdgeRelation]>) -> Vec<&CodeNode> {
let mut impacted = Vec::new();
let mut visited = HashSet::new();
self.collect_dependents(node_id, relations, &mut impacted, &mut visited);
impacted
}
fn collect_dependents<'a>(
&'a self,
node_id: &str,
relations: Option<&[EdgeRelation]>,
result: &mut Vec<&'a CodeNode>,
visited: &mut HashSet<String>,
) {
if !visited.insert(node_id.to_string()) {
return;
}
for edge in self.incoming_edges(node_id) {
if let Some(rels) = relations {
if !rels.contains(&edge.relation) {
continue;
}
}
if let Some(node) = self.node_by_id(&edge.from) {
result.push(node);
self.collect_dependents(&edge.from, relations, result, visited);
}
}
}
pub fn find_relevant_nodes(&self, keywords: &[&str]) -> Vec<&CodeNode> {
let mut scored: Vec<(usize, &CodeNode)> = self
.nodes
.iter()
.map(|n| {
let score: usize = keywords
.iter()
.filter(|kw| {
let kw_lower = kw.to_lowercase();
let name_lower = n.name.to_lowercase();
let path_lower = n.file_path.to_lowercase();
name_lower.contains(&kw_lower)
|| path_lower.contains(&kw_lower)
|| (name_lower.len() >= 5
&& kw_lower.contains(name_lower.trim_start_matches('_')))
})
.count();
(score, n)
})
.filter(|(score, _)| *score > 0)
.collect();
scored.sort_by(|a, b| b.0.cmp(&a.0));
let mut results: Vec<&CodeNode> = scored.into_iter().map(|(_, n)| n).collect();
let relevant_files: HashSet<String> = results.iter().map(|n| n.file_path.clone()).collect();
for node in &self.nodes {
if relevant_files.contains(&node.file_path) && !results.iter().any(|r| r.id == node.id) {
results.push(node);
}
}
let mut inheritance_additions: Vec<&CodeNode> = Vec::new();
let result_ids: HashSet<String> = results.iter().map(|n| n.id.clone()).collect();
for node in &results {
if node.kind == NodeKind::Class {
let chain = self.get_inheritance_chain(&node.id);
for ancestor_id in &chain {
if !result_ids.contains(ancestor_id) {
if let Some(ancestor) = self.node_by_id(ancestor_id) {
inheritance_additions.push(ancestor);
}
}
}
for edge in self.incoming_edges(&node.id) {
if edge.relation == EdgeRelation::Inherits && !result_ids.contains(&edge.from) {
if let Some(child) = self.node_by_id(&edge.from) {
inheritance_additions.push(child);
}
}
}
}
}
let mut extra_files: HashSet<String> = HashSet::new();
for node in &inheritance_additions {
if !results.iter().any(|r| r.id == node.id) {
extra_files.insert(node.file_path.clone());
results.push(node);
}
}
for node in &self.nodes {
if extra_files.contains(&node.file_path) && !results.iter().any(|r| r.id == node.id) {
results.push(node);
}
}
let mut import_additions: Vec<&CodeNode> = Vec::new();
let current_ids: HashSet<String> = results.iter().map(|n| n.id.clone()).collect();
for node in &results {
if node.kind == NodeKind::File {
for edge in self.outgoing_edges(&node.id) {
if edge.relation == EdgeRelation::Imports {
if !current_ids.contains(&edge.to) {
if let Some(imported) = self.node_by_id(&edge.to) {
import_additions.push(imported);
}
}
}
}
}
}
for node in &import_additions {
if node.kind == NodeKind::File {
let has_keyword_match = self
.nodes
.iter()
.filter(|n| n.file_path == node.file_path && n.kind != NodeKind::File)
.any(|n| {
let name_lower = n.name.to_lowercase();
keywords.iter().any(|kw| {
let kw_lower = kw.to_lowercase();
name_lower.contains(&kw_lower) || kw_lower.contains(&name_lower)
})
});
if has_keyword_match && !results.iter().any(|r| r.id == node.id) {
results.push(node);
for entity in &self.nodes {
if entity.file_path == node.file_path
&& !results.iter().any(|r| r.id == entity.id)
{
results.push(entity);
}
}
}
}
}
results
}
}