use crate::resolve::strip_raw;
pub(crate) fn has_path_attr(attrs: &[syn::Attribute]) -> bool {
attrs.iter().any(is_path_remap)
}
pub(crate) fn direct_path_value(attrs: &[syn::Attribute]) -> Option<String> {
attrs.iter().find_map(|attr| {
if !attr.path().is_ident("path") {
return None;
}
match &attr.meta {
syn::Meta::NameValue(syn::MetaNameValue {
value:
syn::Expr::Lit(syn::ExprLit {
lit: syn::Lit::Str(s),
..
}),
..
}) => Some(s.value()),
_ => None,
}
})
}
fn is_path_remap(attr: &syn::Attribute) -> bool {
if attr.path().is_ident("path") {
return true;
}
if attr.path().is_ident("cfg_attr") {
if let Ok(metas) = attr.parse_args_with(cfg_attr_metas) {
return applied_metas_remap(&metas);
}
}
false
}
type MetaList = syn::punctuated::Punctuated<syn::Meta, syn::Token![,]>;
fn cfg_attr_metas(input: syn::parse::ParseStream) -> syn::Result<MetaList> {
MetaList::parse_terminated(input)
}
fn applied_metas_remap(metas: &MetaList) -> bool {
metas.iter().skip(1).any(meta_is_path_remap)
}
fn meta_is_path_remap(meta: &syn::Meta) -> bool {
match meta {
syn::Meta::NameValue(nv) => nv.path.is_ident("path"),
syn::Meta::List(list) if list.path.is_ident("cfg_attr") => list
.parse_args_with(cfg_attr_metas)
.map(|metas| applied_metas_remap(&metas))
.unwrap_or(false),
_ => false,
}
}
pub(crate) fn is_public(vis: &syn::Visibility) -> bool {
matches!(vis, syn::Visibility::Public(_))
}
pub(crate) fn visibility_rank(vis: &syn::Visibility) -> u8 {
match vis {
syn::Visibility::Public(_) => 3,
syn::Visibility::Restricted(r) => {
let single = if r.path.leading_colon.is_none() && r.path.segments.len() == 1 {
r.path.segments.first().map(|s| s.ident.to_string())
} else {
None
};
match single.as_deref() {
Some("crate") => 2,
Some("super") => 1,
Some("self") => 0,
_ => 2,
}
}
syn::Visibility::Inherited => 0,
}
}
fn vis_prefix(vis: &syn::Visibility) -> String {
match vis {
syn::Visibility::Public(_) => "pub".to_string(),
syn::Visibility::Restricted(r) => {
let path: Vec<String> = r
.path
.segments
.iter()
.map(|s| strip_raw(&s.ident.to_string()))
.collect();
let joined = path.join("::");
if r.in_token.is_some() && !matches!(joined.as_str(), "crate" | "super" | "self") {
format!("pub(in {joined})")
} else {
format!("pub({joined})")
}
}
syn::Visibility::Inherited => String::new(),
}
}
pub(crate) struct VisibleItem<'a> {
pub(crate) visibility: &'a syn::Visibility,
pub(crate) kind: VisibleItemKind,
pub(crate) name: String,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub(crate) enum VisibleItemKind {
Fn,
Struct,
Enum,
Union,
Type,
Const,
Static,
Trait,
TraitAlias,
Mod,
ExternCrate,
Use,
}
impl VisibleItemKind {
pub(crate) fn as_str(self) -> &'static str {
match self {
Self::Fn => "fn",
Self::Struct => "struct",
Self::Enum => "enum",
Self::Union => "union",
Self::Type => "type",
Self::Const => "const",
Self::Static => "static",
Self::Trait => "trait",
Self::TraitAlias => "trait_alias",
Self::Mod => "mod",
Self::ExternCrate => "extern_crate",
Self::Use => "use",
}
}
}
fn item_observation_parts(item: &syn::Item) -> Option<VisibleItem<'_>> {
let observed = |visibility, kind, name| VisibleItem {
visibility,
kind,
name,
};
match item {
syn::Item::Fn(i) => Some(observed(
&i.vis,
VisibleItemKind::Fn,
i.sig.ident.to_string(),
)),
syn::Item::Struct(i) => Some(observed(
&i.vis,
VisibleItemKind::Struct,
i.ident.to_string(),
)),
syn::Item::Enum(i) => Some(observed(&i.vis, VisibleItemKind::Enum, i.ident.to_string())),
syn::Item::Union(i) => Some(observed(
&i.vis,
VisibleItemKind::Union,
i.ident.to_string(),
)),
syn::Item::Type(i) => Some(observed(&i.vis, VisibleItemKind::Type, i.ident.to_string())),
syn::Item::Const(i) => Some(observed(
&i.vis,
VisibleItemKind::Const,
i.ident.to_string(),
)),
syn::Item::Static(i) => Some(observed(
&i.vis,
VisibleItemKind::Static,
i.ident.to_string(),
)),
syn::Item::Trait(i) => Some(observed(
&i.vis,
VisibleItemKind::Trait,
i.ident.to_string(),
)),
syn::Item::TraitAlias(i) => Some(observed(
&i.vis,
VisibleItemKind::TraitAlias,
i.ident.to_string(),
)),
syn::Item::Mod(i) => Some(observed(&i.vis, VisibleItemKind::Mod, i.ident.to_string())),
syn::Item::ExternCrate(i) => Some(observed(
&i.vis,
VisibleItemKind::ExternCrate,
i.ident.to_string(),
)),
syn::Item::Use(i) => Some(observed(
&i.vis,
VisibleItemKind::Use,
format!(
"{}{}",
if i.leading_colon.is_some() { "::" } else { "" },
use_tree_desc(&i.tree)
),
)),
_ => None,
}
}
pub(crate) fn item_observation(
item: &syn::Item,
ceiling_rank: u8,
) -> Option<(String, VisibleItemKind, String)> {
let observed = item_observation_parts(item)?;
(visibility_rank(observed.visibility) > ceiling_rank).then(|| {
(
vis_prefix(observed.visibility),
observed.kind,
observed.name,
)
})
}
fn use_tree_desc(tree: &syn::UseTree) -> String {
match tree {
syn::UseTree::Path(p) => {
format!(
"{}::{}",
strip_raw(&p.ident.to_string()),
use_tree_desc(&p.tree)
)
}
syn::UseTree::Name(n) => strip_raw(&n.ident.to_string()),
syn::UseTree::Rename(r) => format!(
"{} as {}",
strip_raw(&r.ident.to_string()),
strip_raw(&r.rename.to_string())
),
syn::UseTree::Glob(_) => "*".to_string(),
syn::UseTree::Group(g) => {
let inner: Vec<String> = g.items.iter().map(use_tree_desc).collect();
format!("{{{}}}", inner.join(", "))
}
}
}