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:
@@ -22,9 +22,12 @@ 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 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 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 {
|
for (from, to) in edges {
|
||||||
*in_degree.entry(to.as_str()).or_insert(0) += 1;
|
if node_set.contains(from.as_str()) && node_set.contains(to.as_str()) {
|
||||||
adj.entry(from.as_str()).or_default().push(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()
|
let mut queue: VecDeque<&str> = in_degree.iter()
|
||||||
|
|||||||
@@ -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> {
|
pub fn build(krate: CrateName, src_dir: &Path) -> Result<ModuleGraph> {
|
||||||
let mut all_nodes: Vec<ModPath> = Vec::new();
|
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 content = std::fs::read_to_string(path)?;
|
||||||
let file = syn::parse_file(&content)?;
|
let file = syn::parse_file(&content)?;
|
||||||
let rel = path.strip_prefix(src_dir)?;
|
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);
|
let mut collector = ModCollector::new(mod_path);
|
||||||
collector.visit_file(&file);
|
collector.visit_file(&file);
|
||||||
|
|||||||
@@ -42,22 +42,14 @@ impl<'ast> Visit<'ast> for SymbolCollector {
|
|||||||
fn visit_item_const(&mut self, node: &'ast syn::ItemConst) {
|
fn visit_item_const(&mut self, node: &'ast syn::ItemConst) {
|
||||||
self.push(SymbolKind::Const, node.ident.to_string(), Self::is_pub(&node.vis));
|
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> {
|
pub fn build(krate: CrateName, src_dir: &Path) -> Result<SymbolTable> {
|
||||||
let mut symbols: Vec<Symbol> = Vec::new();
|
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 content = std::fs::read_to_string(path)?;
|
||||||
let file = syn::parse_file(&content)?;
|
let file = syn::parse_file(&content)?;
|
||||||
let rel = path.strip_prefix(src_dir)?;
|
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);
|
let mut collector = SymbolCollector::new(mod_path);
|
||||||
collector.visit_file(&file);
|
collector.visit_file(&file);
|
||||||
symbols.extend(collector.symbols);
|
symbols.extend(collector.symbols);
|
||||||
|
|||||||
@@ -96,3 +96,19 @@ pub struct GraphStats {
|
|||||||
pub hotspots: Vec<Hotspot>,
|
pub hotspots: Vec<Hotspot>,
|
||||||
pub cycles: Vec<Vec<ModPath>>,
|
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("::")
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user