use crate::adapters::analyzers::architecture::{MatchLocation, ViolationKind};
use syn::spanned::Spanned;
use syn::visit::{self, Visit};
pub fn find_derive_matches(file: &str, ast: &syn::File, names: &[String]) -> Vec<MatchLocation> {
let mut visitor = DeriveVisitor {
file,
names,
hits: Vec::new(),
};
visitor.visit_file(ast);
visitor.hits
}
struct DeriveVisitor<'a> {
file: &'a str,
names: &'a [String],
hits: Vec<MatchLocation>,
}
impl DeriveVisitor<'_> {
fn inspect(&mut self, item_name: &str, attrs: &[syn::Attribute]) {
attrs
.iter()
.filter(|a| a.path().is_ident("derive"))
.for_each(|attr| self.scan_derive_list(item_name, attr));
}
fn scan_derive_list(&mut self, item_name: &str, attr: &syn::Attribute) {
use syn::punctuated::Punctuated;
let Ok(paths) =
attr.parse_args_with(Punctuated::<syn::Path, syn::Token![,]>::parse_terminated)
else {
return;
};
paths
.iter()
.filter_map(|p| p.segments.last().map(|seg| (seg.ident.to_string(), seg)))
.filter(|(name, _)| self.names.iter().any(|n| n == name))
.for_each(|(name, seg)| {
let start = seg.ident.span().start();
self.hits.push(MatchLocation {
file: self.file.to_string(),
line: start.line,
column: start.column,
kind: ViolationKind::Derive {
trait_name: name,
item_name: item_name.to_string(),
},
});
});
}
}
impl<'ast> Visit<'ast> for DeriveVisitor<'_> {
fn visit_item_struct(&mut self, node: &'ast syn::ItemStruct) {
let name = node.ident.to_string();
self.inspect(&name, &node.attrs);
visit::visit_item_struct(self, node);
}
fn visit_item_enum(&mut self, node: &'ast syn::ItemEnum) {
let name = node.ident.to_string();
self.inspect(&name, &node.attrs);
visit::visit_item_enum(self, node);
}
fn visit_item_union(&mut self, node: &'ast syn::ItemUnion) {
let name = node.ident.to_string();
self.inspect(&name, &node.attrs);
visit::visit_item_union(self, node);
}
}