diff --git a/crates/notgraph/src/analysis.rs b/crates/notgraph/src/analysis.rs index 1e69a85..17a8451 100644 --- a/crates/notgraph/src/analysis.rs +++ b/crates/notgraph/src/analysis.rs @@ -22,9 +22,12 @@ pub fn detect_cycles(nodes: &[String], edges: &[(String, String)]) -> Vec = nodes.iter().map(|n| (n.as_str(), 0)).collect(); let mut adj: HashMap<&str, Vec<&str>> = nodes.iter().map(|n| (n.as_str(), vec![])).collect(); + let node_set: std::collections::HashSet<&str> = nodes.iter().map(|n| n.as_str()).collect(); for (from, to) in edges { - *in_degree.entry(to.as_str()).or_insert(0) += 1; - adj.entry(from.as_str()).or_default().push(to.as_str()); + if node_set.contains(from.as_str()) && node_set.contains(to.as_str()) { + *in_degree.entry(to.as_str()).or_insert(0) += 1; + adj.entry(from.as_str()).or_default().push(to.as_str()); + } } let mut queue: VecDeque<&str> = in_degree.iter() diff --git a/crates/notgraph/src/module_graph.rs b/crates/notgraph/src/module_graph.rs index 0d2c857..6001b8d 100644 --- a/crates/notgraph/src/module_graph.rs +++ b/crates/notgraph/src/module_graph.rs @@ -33,20 +33,6 @@ impl<'ast> Visit<'ast> for ModCollector { } } -fn path_to_mod(krate: &str, rel: &Path) -> ModPath { - let mut parts: Vec = vec![krate.to_string()]; - for component in rel.components() { - let s = component.as_os_str().to_string_lossy(); - if s == "lib.rs" || s == "main.rs" { - break; - } - let s = s.trim_end_matches(".rs").to_string(); - if s != "mod" { - parts.push(s); - } - } - parts.join("::") -} pub fn build(krate: CrateName, src_dir: &Path) -> Result { let mut all_nodes: Vec = Vec::new(); @@ -61,7 +47,7 @@ pub fn build(krate: CrateName, src_dir: &Path) -> Result { let content = std::fs::read_to_string(path)?; let file = syn::parse_file(&content)?; let rel = path.strip_prefix(src_dir)?; - let mod_path = path_to_mod(&krate, rel); + let mod_path = crate::types::path_to_mod(&krate, rel); let mut collector = ModCollector::new(mod_path); collector.visit_file(&file); diff --git a/crates/notgraph/src/symbols.rs b/crates/notgraph/src/symbols.rs index 7e6a833..f1b6d80 100644 --- a/crates/notgraph/src/symbols.rs +++ b/crates/notgraph/src/symbols.rs @@ -42,22 +42,14 @@ impl<'ast> Visit<'ast> for SymbolCollector { fn visit_item_const(&mut self, node: &'ast syn::ItemConst) { self.push(SymbolKind::Const, node.ident.to_string(), Self::is_pub(&node.vis)); } + fn visit_item_mod(&mut self, node: &'ast syn::ItemMod) { + let child_path = format!("{}::{}", self.mod_path, node.ident); + let parent = std::mem::replace(&mut self.mod_path, child_path); + syn::visit::visit_item_mod(self, node); + self.mod_path = parent; + } } -fn path_to_mod(krate: &str, rel: &Path) -> ModPath { - let mut parts: Vec = vec![krate.to_string()]; - for component in rel.components() { - let s = component.as_os_str().to_string_lossy(); - if s == "lib.rs" || s == "main.rs" { - break; - } - let s = s.trim_end_matches(".rs").to_string(); - if s != "mod" { - parts.push(s); - } - } - parts.join("::") -} pub fn build(krate: CrateName, src_dir: &Path) -> Result { let mut symbols: Vec = Vec::new(); @@ -70,7 +62,7 @@ pub fn build(krate: CrateName, src_dir: &Path) -> Result { let content = std::fs::read_to_string(path)?; let file = syn::parse_file(&content)?; let rel = path.strip_prefix(src_dir)?; - let mod_path = path_to_mod(&krate, rel); + let mod_path = crate::types::path_to_mod(&krate, rel); let mut collector = SymbolCollector::new(mod_path); collector.visit_file(&file); symbols.extend(collector.symbols); diff --git a/crates/notgraph/src/types.rs b/crates/notgraph/src/types.rs index 1aec755..e8e4cda 100644 --- a/crates/notgraph/src/types.rs +++ b/crates/notgraph/src/types.rs @@ -96,3 +96,19 @@ pub struct GraphStats { pub hotspots: Vec, pub cycles: Vec>, } + +/// Convert a source file path (relative to src/) to a Rust module path like `crate::foo::bar`. +pub fn path_to_mod(krate: &str, rel: &std::path::Path) -> ModPath { + let mut parts: Vec = vec![krate.to_string()]; + for component in rel.components() { + let s = component.as_os_str().to_string_lossy(); + if s == "lib.rs" || s == "main.rs" { + break; + } + let s = s.trim_end_matches(".rs").to_string(); + if s != "mod" { + parts.push(s); + } + } + parts.join("::") +}