fix(notgraph): phantom node cycles, inline mod symbols, dedup path_to_mod

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Joseph O'Brien
2026-04-02 02:38:22 -04:00
parent b1f3b36fc2
commit 88fa939867
4 changed files with 29 additions and 32 deletions

View File

@@ -22,10 +22,13 @@ pub fn detect_cycles(nodes: &[String], edges: &[(String, String)]) -> Vec<Vec<St
let mut in_degree: HashMap<&str, usize> = 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 {
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()
.filter(|&(_, &d)| d == 0)

View File

@@ -33,20 +33,6 @@ impl<'ast> Visit<'ast> for ModCollector {
}
}
fn path_to_mod(krate: &str, rel: &Path) -> ModPath {
let mut parts: Vec<String> = 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<ModuleGraph> {
let mut all_nodes: Vec<ModPath> = Vec::new();
@@ -61,7 +47,7 @@ pub fn build(krate: CrateName, src_dir: &Path) -> Result<ModuleGraph> {
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);

View File

@@ -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<String> = 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<SymbolTable> {
let mut symbols: Vec<Symbol> = Vec::new();
@@ -70,7 +62,7 @@ pub fn build(krate: CrateName, src_dir: &Path) -> Result<SymbolTable> {
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);

View File

@@ -96,3 +96,19 @@ pub struct GraphStats {
pub hotspots: Vec<Hotspot>,
pub cycles: Vec<Vec<ModPath>>,
}
/// 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<String> = 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("::")
}