feat(notgraph): implement symbols extractor
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
pub mod crate_graph;
|
||||
pub mod module_graph;
|
||||
pub mod symbols;
|
||||
pub mod types;
|
||||
|
||||
133
crates/notgraph/src/symbols.rs
Normal file
133
crates/notgraph/src/symbols.rs
Normal file
@@ -0,0 +1,133 @@
|
||||
use crate::types::{CrateName, ModPath, Symbol, SymbolKind, SymbolTable};
|
||||
use anyhow::Result;
|
||||
use std::path::Path;
|
||||
use syn::{Visibility, visit::Visit};
|
||||
use walkdir::WalkDir;
|
||||
|
||||
struct SymbolCollector {
|
||||
mod_path: ModPath,
|
||||
symbols: Vec<Symbol>,
|
||||
}
|
||||
|
||||
impl SymbolCollector {
|
||||
fn new(mod_path: ModPath) -> Self {
|
||||
Self { mod_path, symbols: Vec::new() }
|
||||
}
|
||||
|
||||
fn is_pub(vis: &Visibility) -> bool {
|
||||
matches!(vis, Visibility::Public(_))
|
||||
}
|
||||
|
||||
fn push(&mut self, kind: SymbolKind, name: String, is_pub: bool) {
|
||||
self.symbols.push(Symbol { mod_path: self.mod_path.clone(), kind, name, is_pub });
|
||||
}
|
||||
}
|
||||
|
||||
impl<'ast> Visit<'ast> for SymbolCollector {
|
||||
fn visit_item_struct(&mut self, node: &'ast syn::ItemStruct) {
|
||||
self.push(SymbolKind::Struct, node.ident.to_string(), Self::is_pub(&node.vis));
|
||||
}
|
||||
fn visit_item_enum(&mut self, node: &'ast syn::ItemEnum) {
|
||||
self.push(SymbolKind::Enum, node.ident.to_string(), Self::is_pub(&node.vis));
|
||||
}
|
||||
fn visit_item_trait(&mut self, node: &'ast syn::ItemTrait) {
|
||||
self.push(SymbolKind::Trait, node.ident.to_string(), Self::is_pub(&node.vis));
|
||||
}
|
||||
fn visit_item_fn(&mut self, node: &'ast syn::ItemFn) {
|
||||
self.push(SymbolKind::Fn, node.sig.ident.to_string(), Self::is_pub(&node.vis));
|
||||
}
|
||||
fn visit_item_type(&mut self, node: &'ast syn::ItemType) {
|
||||
self.push(SymbolKind::Type, node.ident.to_string(), Self::is_pub(&node.vis));
|
||||
}
|
||||
fn visit_item_const(&mut self, node: &'ast syn::ItemConst) {
|
||||
self.push(SymbolKind::Const, node.ident.to_string(), Self::is_pub(&node.vis));
|
||||
}
|
||||
}
|
||||
|
||||
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();
|
||||
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 = SymbolCollector::new(mod_path);
|
||||
collector.visit_file(&file);
|
||||
symbols.extend(collector.symbols);
|
||||
}
|
||||
Ok(SymbolTable { krate, symbols })
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
fn fixture_table() -> SymbolTable {
|
||||
let fixture = std::path::Path::new(env!("CARGO_MANIFEST_DIR"))
|
||||
.join("tests/fixtures/symbols/src");
|
||||
build("symbols".to_string(), &fixture).unwrap()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn detects_pub_struct() {
|
||||
let t = fixture_table();
|
||||
assert!(t.symbols.iter().any(|s| s.name == "Foo" && s.kind == SymbolKind::Struct && s.is_pub));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn detects_pub_enum() {
|
||||
let t = fixture_table();
|
||||
assert!(t.symbols.iter().any(|s| s.name == "Bar" && s.kind == SymbolKind::Enum && s.is_pub));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn detects_pub_trait() {
|
||||
let t = fixture_table();
|
||||
assert!(t.symbols.iter().any(|s| s.name == "Baz" && s.kind == SymbolKind::Trait && s.is_pub));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn detects_pub_fn() {
|
||||
let t = fixture_table();
|
||||
assert!(t.symbols.iter().any(|s| s.name == "qux" && s.kind == SymbolKind::Fn && s.is_pub));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn detects_pub_type_alias() {
|
||||
let t = fixture_table();
|
||||
assert!(t.symbols.iter().any(|s| s.name == "Alias" && s.kind == SymbolKind::Type && s.is_pub));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn detects_pub_const() {
|
||||
let t = fixture_table();
|
||||
assert!(t.symbols.iter().any(|s| s.name == "VALUE" && s.kind == SymbolKind::Const && s.is_pub));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn private_struct_is_not_pub() {
|
||||
let t = fixture_table();
|
||||
let p = t.symbols.iter().find(|s| s.name == "Private").unwrap();
|
||||
assert!(!p.is_pub);
|
||||
}
|
||||
}
|
||||
7
crates/notgraph/tests/fixtures/symbols/src/lib.rs
vendored
Normal file
7
crates/notgraph/tests/fixtures/symbols/src/lib.rs
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
pub struct Foo;
|
||||
pub enum Bar { A, B }
|
||||
pub trait Baz { fn run(&self); }
|
||||
pub fn qux() {}
|
||||
pub type Alias = u32;
|
||||
pub const VALUE: u32 = 42;
|
||||
struct Private;
|
||||
Reference in New Issue
Block a user