use super::workspace_graph::{
collect_crate_root_modules, collect_local_symbols, extract_signature_params,
impl_self_ty_segments, resolve_impl_self_type,
};
use crate::adapters::analyzers::architecture::layer_rule::LayerDefinitions;
use crate::adapters::shared::cfg_test::{has_cfg_test, has_test_attr};
use std::collections::{HashMap, HashSet};
use syn::visit::Visit;
use syn::Visibility;
pub(crate) struct PubFnInfo<'ast> {
pub file: String,
pub fn_name: String,
pub line: usize,
pub body: &'ast syn::Block,
pub signature_params: Vec<(String, &'ast syn::Type)>,
pub self_type: Option<Vec<String>>,
}
pub(crate) fn collect_pub_fns_by_layer<'ast>(
files: &[(&'ast str, &'ast syn::File)],
aliases_per_file: &HashMap<String, HashMap<String, Vec<String>>>,
layers: &LayerDefinitions,
cfg_test_files: &HashSet<String>,
) -> HashMap<String, Vec<PubFnInfo<'ast>>> {
let visible_types = collect_visible_type_names_workspace(files, cfg_test_files);
let crate_root_modules = collect_crate_root_modules(files);
let empty_aliases = HashMap::new();
let mut out: HashMap<String, Vec<PubFnInfo<'ast>>> = HashMap::new();
for (path, ast) in files {
if cfg_test_files.contains(*path) {
continue;
}
let Some(layer) = layers.layer_for_file(path) else {
continue;
};
let layer = layer.to_string();
let alias_map = aliases_per_file.get(*path).unwrap_or(&empty_aliases);
let local_symbols = collect_local_symbols(ast);
let mut collector = PubFnCollector {
file: path.to_string(),
found: Vec::new(),
visible_types: &visible_types,
alias_map,
local_symbols: &local_symbols,
crate_root_modules: &crate_root_modules,
impl_stack: Vec::new(),
};
collector.visit_file(ast);
out.entry(layer).or_default().extend(collector.found);
}
out
}
fn collect_visible_type_names_workspace(
files: &[(&str, &syn::File)],
cfg_test_files: &HashSet<String>,
) -> HashSet<String> {
let mut out = HashSet::new();
for (path, ast) in files {
if cfg_test_files.contains(*path) {
continue;
}
for item in &ast.items {
match item {
syn::Item::Struct(s) if is_visible(&s.vis) => {
out.insert(s.ident.to_string());
}
syn::Item::Enum(e) if is_visible(&e.vis) => {
out.insert(e.ident.to_string());
}
syn::Item::Union(u) if is_visible(&u.vis) => {
out.insert(u.ident.to_string());
}
syn::Item::Trait(t) if is_visible(&t.vis) => {
out.insert(t.ident.to_string());
}
syn::Item::Type(t) if is_visible(&t.vis) => {
out.insert(t.ident.to_string());
}
_ => {}
}
}
}
out
}
struct PubFnCollector<'ast, 'vis> {
file: String,
found: Vec<PubFnInfo<'ast>>,
visible_types: &'vis HashSet<String>,
alias_map: &'vis HashMap<String, Vec<String>>,
local_symbols: &'vis HashSet<String>,
crate_root_modules: &'vis HashSet<String>,
impl_stack: Vec<(Vec<String>, bool)>,
}
impl<'ast, 'vis> PubFnCollector<'ast, 'vis> {
fn current_self_type(&self) -> Option<Vec<String>> {
self.impl_stack.last().map(|(segs, _)| segs.clone())
}
fn current_impl_visible(&self) -> bool {
self.impl_stack.last().map(|(_, v)| *v).unwrap_or(false)
}
fn record_fn(
&mut self,
name: String,
line: usize,
body: &'ast syn::Block,
sig: &'ast syn::Signature,
) {
self.found.push(PubFnInfo {
file: self.file.clone(),
fn_name: name,
line,
body,
signature_params: extract_signature_params(sig),
self_type: self.current_self_type(),
});
}
}
fn is_visible(vis: &Visibility) -> bool {
!matches!(vis, Visibility::Inherited)
}
fn is_test_fn(attrs: &[syn::Attribute]) -> bool {
has_test_attr(attrs) || has_cfg_test(attrs)
}
impl<'ast, 'vis> Visit<'ast> for PubFnCollector<'ast, 'vis> {
fn visit_item_fn(&mut self, node: &'ast syn::ItemFn) {
if is_visible(&node.vis) && !is_test_fn(&node.attrs) {
let line = syn::spanned::Spanned::span(&node.sig.ident).start().line;
let name = node.sig.ident.to_string();
self.record_fn(name, line, &node.block, &node.sig);
}
syn::visit::visit_item_fn(self, node);
}
fn visit_item_impl(&mut self, node: &'ast syn::ItemImpl) {
let raw_segs = impl_self_ty_segments(&node.self_ty);
let visible = raw_segs
.as_ref()
.and_then(|segs| segs.last())
.is_some_and(|name| self.visible_types.contains(name));
let canonical_segs = resolve_impl_self_type(
&node.self_ty,
self.alias_map,
self.local_symbols,
self.crate_root_modules,
&self.file,
)
.unwrap_or_default();
self.impl_stack.push((canonical_segs, visible));
syn::visit::visit_item_impl(self, node);
self.impl_stack.pop();
}
fn visit_impl_item_fn(&mut self, node: &'ast syn::ImplItemFn) {
if self.current_impl_visible() && is_visible(&node.vis) && !is_test_fn(&node.attrs) {
let line = syn::spanned::Spanned::span(&node.sig.ident).start().line;
let name = node.sig.ident.to_string();
self.record_fn(name, line, &node.block, &node.sig);
}
syn::visit::visit_impl_item_fn(self, node);
}
fn visit_item_mod(&mut self, node: &'ast syn::ItemMod) {
if has_cfg_test(&node.attrs) {
return;
}
syn::visit::visit_item_mod(self, node);
}
}