feat(notgraph): implement module_graph collector
This commit is contained in:
@@ -1,2 +1,3 @@
|
|||||||
pub mod crate_graph;
|
pub mod crate_graph;
|
||||||
|
pub mod module_graph;
|
||||||
pub mod types;
|
pub mod types;
|
||||||
|
|||||||
105
crates/notgraph/src/module_graph.rs
Normal file
105
crates/notgraph/src/module_graph.rs
Normal file
@@ -0,0 +1,105 @@
|
|||||||
|
use crate::types::{CrateName, ModPath, ModuleGraph};
|
||||||
|
use anyhow::Result;
|
||||||
|
use std::path::Path;
|
||||||
|
use syn::visit::Visit;
|
||||||
|
use walkdir::WalkDir;
|
||||||
|
|
||||||
|
struct ModCollector {
|
||||||
|
current_mod: ModPath,
|
||||||
|
edges: Vec<(ModPath, ModPath)>,
|
||||||
|
nodes: Vec<ModPath>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ModCollector {
|
||||||
|
fn new(root_mod: ModPath) -> Self {
|
||||||
|
Self {
|
||||||
|
current_mod: root_mod.clone(),
|
||||||
|
edges: Vec::new(),
|
||||||
|
nodes: vec![root_mod],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'ast> Visit<'ast> for ModCollector {
|
||||||
|
fn visit_item_mod(&mut self, node: &'ast syn::ItemMod) {
|
||||||
|
let child = format!("{}::{}", self.current_mod, node.ident);
|
||||||
|
if !self.nodes.contains(&child) {
|
||||||
|
self.nodes.push(child.clone());
|
||||||
|
}
|
||||||
|
self.edges.push((self.current_mod.clone(), child.clone()));
|
||||||
|
let parent = std::mem::replace(&mut self.current_mod, child);
|
||||||
|
syn::visit::visit_item_mod(self, node);
|
||||||
|
self.current_mod = 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<ModuleGraph> {
|
||||||
|
let mut all_nodes: Vec<ModPath> = Vec::new();
|
||||||
|
let mut all_edges: Vec<(ModPath, ModPath)> = Vec::new();
|
||||||
|
|
||||||
|
for entry in WalkDir::new(src_dir)
|
||||||
|
.into_iter()
|
||||||
|
.filter_map(|e| e.ok())
|
||||||
|
.filter(|e| e.path().extension().map_or(false, |x| x == "rs"))
|
||||||
|
{
|
||||||
|
let path = entry.path();
|
||||||
|
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 mut collector = ModCollector::new(mod_path);
|
||||||
|
collector.visit_file(&file);
|
||||||
|
|
||||||
|
for node in collector.nodes {
|
||||||
|
if !all_nodes.contains(&node) {
|
||||||
|
all_nodes.push(node);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
all_edges.extend(collector.edges);
|
||||||
|
}
|
||||||
|
|
||||||
|
all_edges.sort();
|
||||||
|
all_edges.dedup();
|
||||||
|
|
||||||
|
Ok(ModuleGraph { krate, nodes: all_nodes, edges: all_edges })
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn clean_fixture_has_expected_nodes() {
|
||||||
|
let fixture = std::path::Path::new(env!("CARGO_MANIFEST_DIR"))
|
||||||
|
.join("tests/fixtures/clean/src");
|
||||||
|
let graph = build("clean".to_string(), &fixture).unwrap();
|
||||||
|
assert!(graph.nodes.contains(&"clean".to_string()));
|
||||||
|
assert!(graph.nodes.contains(&"clean::alpha".to_string()));
|
||||||
|
assert!(graph.nodes.contains(&"clean::beta".to_string()));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn clean_fixture_has_edges_from_lib() {
|
||||||
|
let fixture = std::path::Path::new(env!("CARGO_MANIFEST_DIR"))
|
||||||
|
.join("tests/fixtures/clean/src");
|
||||||
|
let graph = build("clean".to_string(), &fixture).unwrap();
|
||||||
|
assert!(graph.edges.contains(&("clean".to_string(), "clean::alpha".to_string())));
|
||||||
|
assert!(graph.edges.contains(&("clean".to_string(), "clean::beta".to_string())));
|
||||||
|
}
|
||||||
|
}
|
||||||
1
crates/notgraph/tests/fixtures/clean/src/alpha.rs
vendored
Normal file
1
crates/notgraph/tests/fixtures/clean/src/alpha.rs
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
pub fn hello() -> &'static str { "alpha" }
|
||||||
1
crates/notgraph/tests/fixtures/clean/src/beta.rs
vendored
Normal file
1
crates/notgraph/tests/fixtures/clean/src/beta.rs
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
pub fn greet() -> &'static str { "beta" }
|
||||||
2
crates/notgraph/tests/fixtures/clean/src/lib.rs
vendored
Normal file
2
crates/notgraph/tests/fixtures/clean/src/lib.rs
vendored
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
pub mod alpha;
|
||||||
|
pub mod beta;
|
||||||
Reference in New Issue
Block a user