mirror of
https://github.com/Noratrieb/cargo-minimize.git
synced 2026-01-14 16:35:01 +01:00
181 lines
6.1 KiB
Rust
181 lines
6.1 KiB
Rust
use std::ops::DerefMut;
|
|
|
|
use crate::processor::{tracking, Pass, PassController, ProcessState, SourceFile};
|
|
use quote::ToTokens;
|
|
|
|
use syn::{visit_mut::VisitMut, Item, ItemUse, UseName, UsePath, UseRename, UseTree};
|
|
|
|
struct Visitor<'a> {
|
|
process_state: ProcessState,
|
|
current_path: Vec<String>,
|
|
checker: &'a mut PassController,
|
|
}
|
|
|
|
impl<'a> Visitor<'a> {
|
|
fn new(checker: &'a mut PassController) -> Self {
|
|
Self {
|
|
process_state: ProcessState::NoChange,
|
|
current_path: Vec::new(),
|
|
checker,
|
|
}
|
|
}
|
|
|
|
// given a "some::group::{a, b::{c,d}, e}" tree, and assuming checker allows processing of (only) "some::group",
|
|
// returns a ["some::group::a", "some::group::b::{c,d}", "some::group::e"] list of trees.
|
|
fn expand_use_groups(&mut self, top: &syn::ItemUse, tree: &UseTree) -> Vec<UseTree> {
|
|
// It would probably be nice if instead of *expanding* the whole "some::group" group, we could instead
|
|
// *extract* individual items ("some::group::a"), but that makes code much more convoluted, sadly
|
|
match tree {
|
|
UseTree::Path(p) => {
|
|
self.current_path.push(p.ident.to_string());
|
|
|
|
let out = self
|
|
.expand_use_groups(top, &p.tree)
|
|
.into_iter()
|
|
.map(|x| {
|
|
let mut new = p.clone();
|
|
new.tree = Box::new(x);
|
|
UseTree::Path(new)
|
|
})
|
|
.collect();
|
|
|
|
self.current_path.pop();
|
|
out
|
|
}
|
|
UseTree::Group(g) => {
|
|
let new_trees = g
|
|
.items
|
|
.iter()
|
|
.map(|subtree| self.expand_use_groups(top, subtree))
|
|
.flatten()
|
|
.collect::<Vec<_>>();
|
|
|
|
self.current_path.push("{{group}}".to_string());
|
|
let can_process = self.checker.can_process(&self.current_path);
|
|
self.current_path.pop();
|
|
|
|
if can_process {
|
|
self.process_state = ProcessState::Changed;
|
|
return new_trees;
|
|
} else {
|
|
// Do not expand the group.
|
|
// recreate the UseTree::Group item (but with new subtrees), and return a single-element list
|
|
let mut g = g.clone();
|
|
g.items.clear();
|
|
g.items.extend(new_trees);
|
|
return vec![syn::UseTree::Group(g)];
|
|
}
|
|
}
|
|
_ => return vec![tree.clone()],
|
|
}
|
|
}
|
|
|
|
fn visit_item_list(&mut self, items: &mut Vec<syn::Item>) {
|
|
let mut pos = 0; // index into the `items` list
|
|
while pos < items.len() {
|
|
let item_use: ItemUse = {
|
|
match &items[pos] {
|
|
Item::Use(u) => u.clone(),
|
|
_ => {
|
|
pos += 1; // if it's not a `use`` - simply advance to the next item
|
|
continue;
|
|
}
|
|
}
|
|
};
|
|
|
|
let new_use_trees = self.expand_use_groups(&item_use, &item_use.tree);
|
|
// decorate each of the UseTree with a `use` keyword (and any attributes inherited)
|
|
let new_uses = new_use_trees.into_iter().map(|x| {
|
|
let mut new = item_use.clone();
|
|
new.tree = x;
|
|
trim_trailing_self(&mut new.tree);
|
|
syn::Item::Use(new)
|
|
});
|
|
|
|
let step = new_uses.len();
|
|
// replace the old use with the new uses
|
|
items.splice(pos..pos + 1, new_uses);
|
|
pos += step; // do not process freshly inserted items
|
|
}
|
|
}
|
|
}
|
|
|
|
// It is legal to write "use module::{self};", but not "use module::self;".
|
|
// If we do end up with the latter on our hands, convert it to "use module;" instead.
|
|
fn trim_trailing_self(use_tree: &mut UseTree) {
|
|
match use_tree {
|
|
UseTree::Path(UsePath {
|
|
tree: subtree,
|
|
ident: base_ident,
|
|
..
|
|
}) => match subtree.deref_mut() {
|
|
UseTree::Name(UseName { ident: sub_ident }) => {
|
|
if sub_ident == "self" {
|
|
*use_tree = UseTree::Name(UseName {
|
|
ident: base_ident.clone(),
|
|
});
|
|
return;
|
|
}
|
|
}
|
|
UseTree::Rename(syn::UseRename {
|
|
ident: sub_ident,
|
|
rename,
|
|
as_token,
|
|
}) => {
|
|
if sub_ident == "self" {
|
|
*use_tree = UseTree::Rename(UseRename {
|
|
ident: base_ident.clone(),
|
|
rename: rename.clone(),
|
|
as_token: as_token.clone(),
|
|
});
|
|
return;
|
|
}
|
|
}
|
|
UseTree::Path(_) => trim_trailing_self(&mut *subtree),
|
|
_ => {}
|
|
},
|
|
_ => {}
|
|
}
|
|
}
|
|
|
|
impl VisitMut for Visitor<'_> {
|
|
fn visit_item_mod_mut(&mut self, item_mod: &mut syn::ItemMod) {
|
|
self.current_path.push(item_mod.ident.to_string());
|
|
if let Some((_, items)) = &mut item_mod.content {
|
|
self.visit_item_list(items);
|
|
}
|
|
syn::visit_mut::visit_item_mod_mut(self, item_mod);
|
|
self.current_path.pop();
|
|
}
|
|
fn visit_file_mut(&mut self, file: &mut syn::File) {
|
|
self.visit_item_list(&mut file.items);
|
|
syn::visit_mut::visit_file_mut(self, file);
|
|
}
|
|
|
|
tracking!(visit_item_fn_mut);
|
|
tracking!(visit_impl_item_method_mut);
|
|
tracking!(visit_item_impl_mut);
|
|
tracking!(visit_field_mut);
|
|
tracking!(visit_item_struct_mut);
|
|
tracking!(visit_item_trait_mut);
|
|
}
|
|
|
|
#[derive(Default)]
|
|
pub struct SplitUse {}
|
|
|
|
impl Pass for SplitUse {
|
|
fn process_file(
|
|
&mut self,
|
|
krate: &mut syn::File,
|
|
_: &SourceFile,
|
|
checker: &mut PassController,
|
|
) -> ProcessState {
|
|
let mut visitor = Visitor::new(checker);
|
|
visitor.visit_file_mut(krate);
|
|
visitor.process_state
|
|
}
|
|
|
|
fn name(&self) -> &'static str {
|
|
"split-use"
|
|
}
|
|
}
|