use std::collections::HashSet;
use syn::visit::{self, Visit};
use syn::{
Ident, ItemConst, ItemEnum, ItemFn, ItemImpl, ItemStruct, ItemTrait, ItemType, Path, Type,
Visibility,
};
#[derive(Debug, Clone)]
pub struct DefInfo {
pub name: String,
pub kind: String,
pub location: String, pub file_path: String,
}
#[derive(Default)]
pub struct DefinitionVisitor {
pub definitions: Vec<DefInfo>,
pub current_file: String,
}
impl DefinitionVisitor {
pub fn new(current_file: String) -> Self {
Self {
current_file,
definitions: Vec::new(),
}
}
fn add_def(&mut self, name: &Ident, kind: &str, vis: &Visibility) {
if let Visibility::Public(_) = vis {
self.definitions.push(DefInfo {
name: name.to_string(),
kind: kind.to_string(),
location: format!("{}:{}", self.current_file, name.span().start().line),
file_path: self.current_file.clone(),
});
}
}
}
impl<'ast> Visit<'ast> for DefinitionVisitor {
fn visit_item_struct(&mut self, node: &'ast ItemStruct) {
self.add_def(&node.ident, "struct", &node.vis);
visit::visit_item_struct(self, node);
}
fn visit_item_enum(&mut self, node: &'ast ItemEnum) {
self.add_def(&node.ident, "enum", &node.vis);
visit::visit_item_enum(self, node);
}
fn visit_item_fn(&mut self, node: &'ast ItemFn) {
self.add_def(&node.sig.ident, "fn", &node.vis);
visit::visit_item_fn(self, node);
}
fn visit_item_const(&mut self, node: &'ast ItemConst) {
self.add_def(&node.ident, "const", &node.vis);
visit::visit_item_const(self, node);
}
fn visit_item_trait(&mut self, node: &'ast ItemTrait) {
self.add_def(&node.ident, "trait", &node.vis);
visit::visit_item_trait(self, node);
}
fn visit_item_type(&mut self, node: &'ast ItemType) {
self.add_def(&node.ident, "type", &node.vis);
visit::visit_item_type(self, node);
}
}
#[derive(Default)]
pub struct UsageVisitor {
pub usages: HashSet<String>,
}
impl UsageVisitor {
fn record_ident(&mut self, ident: &Ident) {
self.usages.insert(ident.to_string());
}
fn record_path(&mut self, path: &Path) {
for segment in &path.segments {
self.record_ident(&segment.ident);
}
}
}
impl<'ast> Visit<'ast> for UsageVisitor {
fn visit_item_struct(&mut self, node: &'ast ItemStruct) {
for attr in &node.attrs {
self.visit_attribute(attr);
}
for field in &node.fields {
visit::visit_field(self, field);
}
visit::visit_generics(self, &node.generics);
}
fn visit_item_enum(&mut self, node: &'ast ItemEnum) {
for attr in &node.attrs {
self.visit_attribute(attr);
}
for variant in &node.variants {
visit::visit_variant(self, variant);
}
visit::visit_generics(self, &node.generics);
}
fn visit_item_fn(&mut self, node: &'ast ItemFn) {
for attr in &node.attrs {
self.visit_attribute(attr);
}
for input in &node.sig.inputs {
visit::visit_fn_arg(self, input);
}
visit::visit_return_type(self, &node.sig.output);
visit::visit_block(self, &node.block);
visit::visit_generics(self, &node.sig.generics);
}
fn visit_item_const(&mut self, node: &'ast ItemConst) {
for attr in &node.attrs {
self.visit_attribute(attr);
}
visit::visit_type(self, &node.ty);
visit::visit_expr(self, &node.expr);
}
fn visit_item_trait(&mut self, node: &'ast ItemTrait) {
for attr in &node.attrs {
self.visit_attribute(attr);
}
for item in &node.items {
visit::visit_trait_item(self, item);
}
visit::visit_generics(self, &node.generics);
for bound in &node.supertraits {
visit::visit_type_param_bound(self, bound);
}
}
fn visit_item_type(&mut self, node: &'ast ItemType) {
for attr in &node.attrs {
self.visit_attribute(attr);
}
visit::visit_type(self, &node.ty);
visit::visit_generics(self, &node.generics);
}
fn visit_item_impl(&mut self, node: &'ast ItemImpl) {
visit::visit_generics(self, &node.generics);
if let Some((_, path, _)) = &node.trait_ {
visit::visit_path(self, path);
}
match &*node.self_ty {
Type::Path(type_path) => {
if let Some(qself) = &type_path.qself {
visit::visit_qself(self, qself);
}
for segment in &type_path.path.segments {
match &segment.arguments {
syn::PathArguments::None => {}
syn::PathArguments::AngleBracketed(args) => {
visit::visit_angle_bracketed_generic_arguments(self, args)
}
syn::PathArguments::Parenthesized(args) => {
visit::visit_parenthesized_generic_arguments(self, args)
}
}
}
}
_ => {
visit::visit_type(self, &node.self_ty);
}
}
for item in &node.items {
visit::visit_impl_item(self, item);
}
}
fn visit_path(&mut self, node: &'ast Path) {
self.record_path(node);
visit::visit_path(self, node);
}
fn visit_use_tree(&mut self, node: &'ast syn::UseTree) {
match node {
syn::UseTree::Path(p) => {
self.record_ident(&p.ident);
visit::visit_use_tree(self, &p.tree);
}
syn::UseTree::Name(n) => {
self.record_ident(&n.ident);
}
syn::UseTree::Rename(r) => {
self.record_ident(&r.ident);
}
syn::UseTree::Glob(_) => {}
syn::UseTree::Group(g) => {
for tree in &g.items {
visit::visit_use_tree(self, tree);
}
}
}
}
fn visit_macro(&mut self, node: &'ast syn::Macro) {
visit::visit_macro(self, node); self.visit_token_stream(node.tokens.clone());
}
fn visit_attribute(&mut self, node: &'ast syn::Attribute) {
if node.path().is_ident("doc") {
if let syn::Meta::NameValue(nv) = &node.meta {
if let syn::Expr::Lit(syn::ExprLit {
lit: syn::Lit::Str(s),
..
}) = &nv.value
{
let text = s.value();
self.scan_string_for_idents(&text);
}
}
}
visit::visit_attribute(self, node);
}
}
impl UsageVisitor {
fn scan_string_for_idents(&mut self, s: &str) {
let mut chars = s.chars().peekable();
while let Some(c) = chars.next() {
if c.is_alphabetic() || c == '_' {
let mut captured = String::new();
captured.push(c);
while let Some(&next_c) = chars.peek() {
if next_c.is_alphanumeric() || next_c == '_' {
captured.push(next_c);
chars.next();
} else {
break;
}
}
if !captured.is_empty() {
self.usages.insert(captured);
}
}
}
}
fn visit_token_stream(&mut self, tokens: proc_macro2::TokenStream) {
use proc_macro2::TokenTree;
for token in tokens {
match token {
TokenTree::Group(group) => {
self.visit_token_stream(group.stream());
}
TokenTree::Ident(ident) => {
self.record_ident(&ident);
}
TokenTree::Punct(_) => {}
TokenTree::Literal(lit) => {
let s = lit.to_string();
if s.starts_with('"') || s.starts_with("r#") || s.starts_with("r\"") {
self.scan_string_for_idents(&s);
}
}
}
}
}
}