use crate::util::to_snake;
use proc_macro2::Span;
use quote::quote;
use syn::spanned::Spanned;
use syn::{Attribute, Field, ImplItem, Item, Type, Visibility};
pub enum ItemClass {
Keep,
Move(MoveInfo),
}
pub struct MoveInfo {
pub group: String,
pub reexport: Option<(String, String, Vec<String>)>,
pub vis_edits_abs: Vec<AbsVisEdit>,
}
pub struct AbsVisEdit {
pub start: usize,
pub end: usize,
pub text: String,
}
fn render_vis(vis: &Visibility) -> String {
match vis {
Visibility::Inherited => String::new(),
v => quote!(#v).to_string(),
}
}
fn cfg_attrs(attrs: &[Attribute]) -> Vec<String> {
attrs
.iter()
.filter(|a| a.path().is_ident("cfg") || a.path().is_ident("cfg_attr"))
.map(|a| quote!(#a).to_string())
.collect()
}
fn tok_start(span: Span) -> usize {
span.byte_range().start
}
fn widen_to_crate(vis: &Visibility, insert_at: usize) -> Option<AbsVisEdit> {
match vis {
Visibility::Public(_) => None,
Visibility::Restricted(r) => {
if r.in_token.is_none() && r.path.is_ident("crate") {
None
} else {
let span = vis.span().byte_range();
Some(AbsVisEdit { start: span.start, end: span.end, text: "pub(crate)".into() })
}
}
Visibility::Inherited => Some(AbsVisEdit {
start: insert_at,
end: insert_at,
text: "pub(crate) ".into(),
}),
}
}
fn type_base_ident(ty: &Type) -> Option<String> {
match ty {
Type::Path(p) => p.path.segments.last().map(|s| s.ident.to_string()),
Type::Reference(r) => type_base_ident(&r.elem),
Type::Paren(p) => type_base_ident(&p.elem),
Type::Group(g) => type_base_ident(&g.elem),
Type::Slice(s) => type_base_ident(&s.elem),
Type::Array(a) => type_base_ident(&a.elem),
Type::Ptr(p) => type_base_ident(&p.elem),
_ => None,
}
}
fn min_start(opts: &[Option<Span>], keyword: Span) -> usize {
let mut m = tok_start(keyword);
for o in opts.iter().flatten() {
m = m.min(tok_start(*o));
}
m
}
fn field_insert_at(field: &Field) -> usize {
match &field.ident {
Some(id) => tok_start(id.span()),
None => tok_start(field.ty.span()),
}
}
fn member_edits(item: &Item) -> Vec<AbsVisEdit> {
let mut edits = Vec::new();
match item {
Item::Struct(it) => {
for f in &it.fields {
if let Some(e) = widen_to_crate(&f.vis, field_insert_at(f)) {
edits.push(e);
}
}
}
Item::Union(it) => {
for f in &it.fields.named {
if let Some(e) = widen_to_crate(&f.vis, field_insert_at(f)) {
edits.push(e);
}
}
}
Item::Impl(it) if it.trait_.is_none() => {
for member in &it.items {
match member {
ImplItem::Fn(f) => {
let insert = min_start(
&[
f.sig.constness.map(|t| t.span()),
f.sig.asyncness.map(|t| t.span()),
f.sig.unsafety.map(|t| t.span()),
f.sig.abi.as_ref().map(|a| a.extern_token.span()),
],
f.sig.fn_token.span(),
);
if let Some(e) = widen_to_crate(&f.vis, insert) {
edits.push(e);
}
}
ImplItem::Const(c) => {
if let Some(e) = widen_to_crate(&c.vis, tok_start(c.const_token.span())) {
edits.push(e);
}
}
ImplItem::Type(t) => {
if let Some(e) = widen_to_crate(&t.vis, tok_start(t.type_token.span())) {
edits.push(e);
}
}
_ => {}
}
}
}
_ => {}
}
edits
}
pub fn classify(item: &Item) -> ItemClass {
let mut members = member_edits(item);
members.extend(crate::pathfix::relative_path_edits(item));
match item {
Item::Struct(it) => named(&it.vis, &it.ident, tok_start(it.struct_token.span()), &it.attrs, members),
Item::Enum(it) => named(&it.vis, &it.ident, tok_start(it.enum_token.span()), &it.attrs, members),
Item::Union(it) => named(&it.vis, &it.ident, tok_start(it.union_token.span()), &it.attrs, members),
Item::Trait(it) => {
let insert = min_start(
&[it.unsafety.map(|t| t.span()), it.auto_token.map(|t| t.span())],
it.trait_token.span(),
);
named(&it.vis, &it.ident, insert, &it.attrs, members)
}
Item::TraitAlias(it) => named(&it.vis, &it.ident, tok_start(it.trait_token.span()), &it.attrs, members),
Item::Type(it) => named(&it.vis, &it.ident, tok_start(it.type_token.span()), &it.attrs, members),
Item::Const(it) => named(&it.vis, &it.ident, tok_start(it.const_token.span()), &it.attrs, members),
Item::Static(it) => named(&it.vis, &it.ident, tok_start(it.static_token.span()), &it.attrs, members),
Item::Fn(it) => {
let insert = min_start(
&[
it.sig.constness.map(|t| t.span()),
it.sig.asyncness.map(|t| t.span()),
it.sig.unsafety.map(|t| t.span()),
it.sig.abi.as_ref().map(|a| a.extern_token.span()),
],
it.sig.fn_token.span(),
);
named(&it.vis, &it.sig.ident, insert, &it.attrs, members)
}
Item::Impl(it) => {
let group = type_base_ident(&it.self_ty)
.map(|s| to_snake(&s))
.unwrap_or_else(|| "impls".to_string());
ItemClass::Move(MoveInfo { group, reexport: None, vis_edits_abs: members })
}
_ => ItemClass::Keep,
}
}
fn named(
vis: &Visibility,
ident: &syn::Ident,
insert_at: usize,
attrs: &[Attribute],
mut edits: Vec<AbsVisEdit>,
) -> ItemClass {
let name = ident.to_string();
if name.starts_with('_') || name.starts_with("r#") {
return ItemClass::Keep;
}
let group = to_snake(&name);
let reexport = Some((render_vis(vis), name, cfg_attrs(attrs)));
if let Some(e) = widen_to_crate(vis, insert_at) {
edits.push(e);
}
ItemClass::Move(MoveInfo { group, reexport, vis_edits_abs: edits })
}