1use crate::util::to_snake;
17use proc_macro2::Span;
18use quote::quote;
19use syn::spanned::Spanned;
20use syn::{Attribute, Field, ImplItem, Item, Type, Visibility};
21
22pub enum ItemClass {
24 Keep,
26 Move(MoveInfo),
28}
29
30pub struct MoveInfo {
31 pub group: String,
33 pub reexport: Option<(String, String, Vec<String>)>,
35 pub vis_edits_abs: Vec<AbsVisEdit>,
37}
38
39pub struct AbsVisEdit {
41 pub start: usize,
42 pub end: usize,
43 pub text: String,
44}
45
46fn render_vis(vis: &Visibility) -> String {
48 match vis {
49 Visibility::Inherited => String::new(),
50 v => quote!(#v).to_string(),
51 }
52}
53
54fn cfg_attrs(attrs: &[Attribute]) -> Vec<String> {
56 attrs
57 .iter()
58 .filter(|a| a.path().is_ident("cfg") || a.path().is_ident("cfg_attr"))
59 .map(|a| quote!(#a).to_string())
60 .collect()
61}
62
63fn tok_start(span: Span) -> usize {
64 span.byte_range().start
65}
66
67fn widen_to_crate(vis: &Visibility, insert_at: usize) -> Option<AbsVisEdit> {
72 match vis {
73 Visibility::Public(_) => None,
74 Visibility::Restricted(r) => {
75 if r.in_token.is_none() && r.path.is_ident("crate") {
76 None
77 } else {
78 let span = vis.span().byte_range();
79 Some(AbsVisEdit { start: span.start, end: span.end, text: "pub(crate)".into() })
80 }
81 }
82 Visibility::Inherited => Some(AbsVisEdit {
83 start: insert_at,
84 end: insert_at,
85 text: "pub(crate) ".into(),
86 }),
87 }
88}
89
90fn type_base_ident(ty: &Type) -> Option<String> {
92 match ty {
93 Type::Path(p) => p.path.segments.last().map(|s| s.ident.to_string()),
94 Type::Reference(r) => type_base_ident(&r.elem),
95 Type::Paren(p) => type_base_ident(&p.elem),
96 Type::Group(g) => type_base_ident(&g.elem),
97 Type::Slice(s) => type_base_ident(&s.elem),
98 Type::Array(a) => type_base_ident(&a.elem),
99 Type::Ptr(p) => type_base_ident(&p.elem),
100 _ => None,
101 }
102}
103
104fn min_start(opts: &[Option<Span>], keyword: Span) -> usize {
105 let mut m = tok_start(keyword);
106 for o in opts.iter().flatten() {
107 m = m.min(tok_start(*o));
108 }
109 m
110}
111
112fn field_insert_at(field: &Field) -> usize {
114 match &field.ident {
115 Some(id) => tok_start(id.span()),
116 None => tok_start(field.ty.span()),
117 }
118}
119
120fn member_edits(item: &Item) -> Vec<AbsVisEdit> {
122 let mut edits = Vec::new();
123 match item {
124 Item::Struct(it) => {
125 for f in &it.fields {
126 if let Some(e) = widen_to_crate(&f.vis, field_insert_at(f)) {
127 edits.push(e);
128 }
129 }
130 }
131 Item::Union(it) => {
132 for f in &it.fields.named {
133 if let Some(e) = widen_to_crate(&f.vis, field_insert_at(f)) {
134 edits.push(e);
135 }
136 }
137 }
138 Item::Impl(it) if it.trait_.is_none() => {
139 for member in &it.items {
142 match member {
143 ImplItem::Fn(f) => {
144 let insert = min_start(
145 &[
146 f.sig.constness.map(|t| t.span()),
147 f.sig.asyncness.map(|t| t.span()),
148 f.sig.unsafety.map(|t| t.span()),
149 f.sig.abi.as_ref().map(|a| a.extern_token.span()),
150 ],
151 f.sig.fn_token.span(),
152 );
153 if let Some(e) = widen_to_crate(&f.vis, insert) {
154 edits.push(e);
155 }
156 }
157 ImplItem::Const(c) => {
158 if let Some(e) = widen_to_crate(&c.vis, tok_start(c.const_token.span())) {
159 edits.push(e);
160 }
161 }
162 ImplItem::Type(t) => {
163 if let Some(e) = widen_to_crate(&t.vis, tok_start(t.type_token.span())) {
164 edits.push(e);
165 }
166 }
167 _ => {}
168 }
169 }
170 }
171 _ => {}
172 }
173 edits
174}
175
176pub fn classify(item: &Item) -> ItemClass {
177 let mut members = member_edits(item);
179 members.extend(crate::pathfix::relative_path_edits(item));
180 match item {
181 Item::Struct(it) => named(&it.vis, &it.ident, tok_start(it.struct_token.span()), &it.attrs, members),
182 Item::Enum(it) => named(&it.vis, &it.ident, tok_start(it.enum_token.span()), &it.attrs, members),
183 Item::Union(it) => named(&it.vis, &it.ident, tok_start(it.union_token.span()), &it.attrs, members),
184 Item::Trait(it) => {
185 let insert = min_start(
186 &[it.unsafety.map(|t| t.span()), it.auto_token.map(|t| t.span())],
187 it.trait_token.span(),
188 );
189 named(&it.vis, &it.ident, insert, &it.attrs, members)
190 }
191 Item::TraitAlias(it) => named(&it.vis, &it.ident, tok_start(it.trait_token.span()), &it.attrs, members),
192 Item::Type(it) => named(&it.vis, &it.ident, tok_start(it.type_token.span()), &it.attrs, members),
193 Item::Const(it) => named(&it.vis, &it.ident, tok_start(it.const_token.span()), &it.attrs, members),
194 Item::Static(it) => named(&it.vis, &it.ident, tok_start(it.static_token.span()), &it.attrs, members),
195 Item::Fn(it) => {
196 let insert = min_start(
197 &[
198 it.sig.constness.map(|t| t.span()),
199 it.sig.asyncness.map(|t| t.span()),
200 it.sig.unsafety.map(|t| t.span()),
201 it.sig.abi.as_ref().map(|a| a.extern_token.span()),
202 ],
203 it.sig.fn_token.span(),
204 );
205 named(&it.vis, &it.sig.ident, insert, &it.attrs, members)
206 }
207 Item::Impl(it) => {
208 let group = type_base_ident(&it.self_ty)
209 .map(|s| to_snake(&s))
210 .unwrap_or_else(|| "impls".to_string());
211 ItemClass::Move(MoveInfo { group, reexport: None, vis_edits_abs: members })
212 }
213 _ => ItemClass::Keep,
214 }
215}
216
217fn named(
218 vis: &Visibility,
219 ident: &syn::Ident,
220 insert_at: usize,
221 attrs: &[Attribute],
222 mut edits: Vec<AbsVisEdit>,
223) -> ItemClass {
224 let name = ident.to_string();
225 if name.starts_with('_') || name.starts_with("r#") {
232 return ItemClass::Keep;
233 }
234 let group = to_snake(&name);
235 let reexport = Some((render_vis(vis), name, cfg_attrs(attrs)));
236 if let Some(e) = widen_to_crate(vis, insert_at) {
237 edits.push(e);
238 }
239 ItemClass::Move(MoveInfo { group, reexport, vis_edits_abs: edits })
240}