use std::collections::BTreeSet;
use syn::visit::Visit;
use super::face::{SourceReferences, path_to_string};
pub(super) fn scan(file: &syn::File) -> SourceReferences {
let mut visitor = ReferenceVisitor::default();
visitor.visit_file(file);
visitor.references
}
fn is_build_macro(name: &str) -> bool {
matches!(
name,
"host" | "application" | "static_graft_plan" | "graft_plan" | "__graft_plan_cuts"
) ||
matches!(
name,
"control_object"
| "external_object"
| "__control_object"
| "__external_object"
| "__nichlink_object"
| "__registration_face"
) || name.ends_with("_object")
}
fn collect_macro_paths(tokens: &proc_macro2::TokenStream, paths: &mut BTreeSet<String>) {
let mut segment = String::new();
let mut colons = 0usize;
fn flush(segment: &mut String, paths: &mut BTreeSet<String>) {
if segment.contains("::") {
paths.insert(std::mem::take(segment));
} else {
segment.clear();
}
}
for tree in tokens.clone() {
match tree {
proc_macro2::TokenTree::Ident(ident) => {
if colons >= 2 && !segment.is_empty() {
segment.push_str("::");
segment.push_str(&ident.to_string());
} else {
flush(&mut segment, paths);
segment.push_str(&ident.to_string());
}
colons = 0;
}
proc_macro2::TokenTree::Punct(punct) if punct.as_char() == ':' => colons += 1,
proc_macro2::TokenTree::Punct(_) | proc_macro2::TokenTree::Literal(_) => {
flush(&mut segment, paths);
colons = 0;
}
proc_macro2::TokenTree::Group(group) => {
flush(&mut segment, paths);
colons = 0;
collect_macro_paths(&group.stream(), paths);
}
}
}
flush(&mut segment, paths);
}
#[derive(Default)]
struct ReferenceVisitor {
references: SourceReferences,
}
impl<'ast> Visit<'ast> for ReferenceVisitor {
fn visit_expr_path(&mut self, expression: &'ast syn::ExprPath) {
self.references
.paths
.insert(path_to_string(&expression.path));
syn::visit::visit_expr_path(self, expression);
}
fn visit_expr_call(&mut self, expression: &'ast syn::ExprCall) {
if !matches!(expression.func.as_ref(), syn::Expr::Path(_)) {
self.references.conservative = true;
}
syn::visit::visit_expr_call(self, expression);
}
fn visit_type_trait_object(&mut self, object: &'ast syn::TypeTraitObject) {
self.references.conservative = true;
syn::visit::visit_type_trait_object(self, object);
}
fn visit_item_extern_crate(&mut self, item: &'ast syn::ItemExternCrate) {
self.references.conservative = true;
syn::visit::visit_item_extern_crate(self, item);
}
fn visit_macro(&mut self, item: &'ast syn::Macro) {
let name = item
.path
.segments
.last()
.map(|segment| segment.ident.to_string());
if !name.as_deref().is_some_and(is_build_macro) {
collect_macro_paths(&item.tokens, &mut self.references.paths);
}
}
}