use super::bindings::{canonicalise_workspace_path, CanonScope};
use super::local_symbols::FileScope;
use super::workspace_graph::{apply_edge_rewrite, CallGraph};
use crate::adapters::analyzers::architecture::forbidden_rule::file_to_module_segments;
use crate::adapters::shared::cfg_test::has_cfg_test;
use std::collections::HashMap;
const MAX_REEXPORT_CHAIN_DEPTH: usize = 16;
pub(super) fn collect_reexport_map(
files: &[(&str, &syn::File)],
workspace_files: &HashMap<String, FileScope<'_>>,
) -> HashMap<String, String> {
let mut map: HashMap<String, String> = HashMap::new();
for (path, ast) in files {
let Some(file) = workspace_files.get(*path) else {
continue;
};
walk_pub_uses(&ast.items, &mut Vec::new(), file, &mut map);
}
flatten_chains(&mut map);
map
}
pub(super) fn rewrite_reexport_edges(graph: &mut CallGraph, reexports: &HashMap<String, String>) {
if reexports.is_empty() {
return;
}
let rewrites = collect_reexport_rewrites(graph, reexports);
for (caller, old, new) in rewrites {
apply_edge_rewrite(graph, &caller, &old, new);
}
}
fn walk_pub_uses(
items: &[syn::Item],
mod_stack: &mut Vec<String>,
file: &FileScope<'_>,
map: &mut HashMap<String, String>,
) {
let recurse =
|inner: &[syn::Item], stack: &mut Vec<String>, map: &mut HashMap<String, String>| {
walk_pub_uses(inner, stack, file, map);
};
for item in items {
if let syn::Item::Use(u) = item {
if !is_pub_use(&u.vis) {
continue;
}
let mut leaves: Vec<(Vec<String>, String)> = Vec::new();
collect_pub_use_leaves(&[], &u.tree, &mut leaves);
register_pub_use_leaves(&leaves, u.leading_colon.is_some(), file, mod_stack, map);
}
if let syn::Item::Mod(m) = item {
if has_cfg_test(&m.attrs) {
continue;
}
if let Some((_, inner)) = m.content.as_ref() {
mod_stack.push(m.ident.to_string());
recurse(inner, mod_stack, map);
mod_stack.pop();
}
}
}
}
fn register_pub_use_leaves(
leaves: &[(Vec<String>, String)],
leading_colon_set: bool,
file: &FileScope<'_>,
mod_stack: &[String],
map: &mut HashMap<String, String>,
) {
for (path_segs, name) in leaves {
let Some(target) = canonicalise_workspace_path(
path_segs,
leading_colon_set,
&CanonScope { file, mod_stack },
) else {
continue;
};
let reexport = build_reexport_canonical(file.path, mod_stack, name);
let target_str = target.join("::");
if reexport == target_str {
continue;
}
map.insert(reexport, target_str);
}
}
fn build_reexport_canonical(file_path: &str, mod_stack: &[String], name: &str) -> String {
let mut segs: Vec<String> = vec!["crate".to_string()];
segs.extend(file_to_module_segments(file_path));
segs.extend(mod_stack.iter().cloned());
segs.push(name.to_string());
segs.join("::")
}
fn is_pub_use(vis: &syn::Visibility) -> bool {
!matches!(vis, syn::Visibility::Inherited)
}
fn collect_pub_use_leaves(
prefix: &[String],
tree: &syn::UseTree,
out: &mut Vec<(Vec<String>, String)>,
) {
let recurse = |p: &[String], t: &syn::UseTree, o: &mut Vec<(Vec<String>, String)>| {
collect_pub_use_leaves(p, t, o);
};
match tree {
syn::UseTree::Path(p) => {
let mut next = prefix.to_vec();
next.push(p.ident.to_string());
recurse(&next, &p.tree, out);
}
syn::UseTree::Name(n) => {
let ident = n.ident.to_string();
if ident == "self" {
if let Some(last) = prefix.last().cloned() {
out.push((prefix.to_vec(), last));
}
} else {
let mut full = prefix.to_vec();
full.push(ident.clone());
out.push((full, ident));
}
}
syn::UseTree::Rename(r) => {
if r.ident == "self" {
out.push((prefix.to_vec(), r.rename.to_string()));
} else {
let mut full = prefix.to_vec();
full.push(r.ident.to_string());
out.push((full, r.rename.to_string()));
}
}
syn::UseTree::Glob(_) => {
}
syn::UseTree::Group(g) => {
for sub in &g.items {
recurse(prefix, sub, out);
}
}
}
}
fn flatten_chains(map: &mut HashMap<String, String>) {
let keys: Vec<String> = map.keys().cloned().collect();
for key in keys {
let mut current = map.get(&key).cloned().unwrap_or_default();
let mut depth = 0;
while depth < MAX_REEXPORT_CHAIN_DEPTH {
match map.get(¤t) {
Some(next) if next != ¤t => {
current = next.clone();
depth += 1;
}
_ => break,
}
}
map.insert(key, current);
}
}
fn collect_reexport_rewrites(
graph: &CallGraph,
reexports: &HashMap<String, String>,
) -> Vec<(String, String, String)> {
let mut rewrites: Vec<(String, String, String)> = Vec::new();
for (caller, callees) in &graph.forward {
for callee in callees {
if let Some(target) = reexports.get(callee) {
rewrites.push((caller.clone(), callee.clone(), target.clone()));
}
}
}
rewrites
}