fixrs 0.1.0

Blazing-fast CLI to replace Rust qualified paths with use statements, auto-fixing clippy::absolute_paths
Documentation
use syn::{
  Attribute, Item, ItemMod, ItemUse, Macro, Path, Token,
  parse::Parser,
  punctuated::Punctuated,
  visit::{
    Visit, visit_arm, visit_attribute, visit_block, visit_expr, visit_field, visit_foreign_item,
    visit_impl_item, visit_item, visit_item_mod, visit_item_use, visit_path, visit_stmt,
    visit_trait_item, visit_variant,
  },
};

use super::collector::PathCollector;

impl<'ast> Visit<'ast> for PathCollector<'_> {
  fn visit_item(&mut self, i: &'ast Item) {
    let has_cfg = item_has_cfg(i);
    if has_cfg {
      self.cfg_depth += 1;
    }
    let prev_len = self.local_idents.len();
    if let Item::Fn(f) = i {
      collect_sig_inputs(&f.sig, &mut self.local_idents);
    }
    visit_item(self, i);
    self.local_idents.truncate(prev_len);
    if has_cfg {
      self.cfg_depth -= 1;
    }
  }

  fn visit_impl_item(&mut self, i: &'ast syn::ImplItem) {
    let has_cfg = match i {
      syn::ImplItem::Const(c) => has_cfg_or_test(&c.attrs),
      syn::ImplItem::Fn(f) => has_cfg_or_test(&f.attrs),
      syn::ImplItem::Type(t) => has_cfg_or_test(&t.attrs),
      syn::ImplItem::Macro(m) => has_cfg_or_test(&m.attrs),
      _ => false,
    };
    if has_cfg {
      self.cfg_depth += 1;
    }
    let prev_len = self.local_idents.len();
    if let syn::ImplItem::Fn(f) = i {
      collect_sig_inputs(&f.sig, &mut self.local_idents);
    }
    visit_impl_item(self, i);
    self.local_idents.truncate(prev_len);
    if has_cfg {
      self.cfg_depth -= 1;
    }
  }

  fn visit_trait_item(&mut self, i: &'ast syn::TraitItem) {
    let has_cfg = match i {
      syn::TraitItem::Const(c) => has_cfg_or_test(&c.attrs),
      syn::TraitItem::Fn(f) => has_cfg_or_test(&f.attrs),
      syn::TraitItem::Type(t) => has_cfg_or_test(&t.attrs),
      syn::TraitItem::Macro(m) => has_cfg_or_test(&m.attrs),
      _ => false,
    };
    if has_cfg {
      self.cfg_depth += 1;
    }
    let prev_len = self.local_idents.len();
    if let syn::TraitItem::Fn(f) = i {
      collect_sig_inputs(&f.sig, &mut self.local_idents);
    }
    visit_trait_item(self, i);
    self.local_idents.truncate(prev_len);
    if has_cfg {
      self.cfg_depth -= 1;
    }
  }

  fn visit_expr(&mut self, i: &'ast syn::Expr) {
    let attrs = match i {
      syn::Expr::Array(e) => &e.attrs,
      syn::Expr::Assign(e) => &e.attrs,
      syn::Expr::Async(e) => &e.attrs,
      syn::Expr::Await(e) => &e.attrs,
      syn::Expr::Binary(e) => &e.attrs,
      syn::Expr::Block(e) => &e.attrs,
      syn::Expr::Break(e) => &e.attrs,
      syn::Expr::Call(e) => &e.attrs,
      syn::Expr::Cast(e) => &e.attrs,
      syn::Expr::Closure(e) => &e.attrs,
      syn::Expr::Const(e) => &e.attrs,
      syn::Expr::Continue(e) => &e.attrs,
      syn::Expr::Field(e) => &e.attrs,
      syn::Expr::ForLoop(e) => &e.attrs,
      syn::Expr::Group(e) => &e.attrs,
      syn::Expr::If(e) => &e.attrs,
      syn::Expr::Index(e) => &e.attrs,
      syn::Expr::Infer(e) => &e.attrs,
      syn::Expr::Let(e) => &e.attrs,
      syn::Expr::Lit(e) => &e.attrs,
      syn::Expr::Loop(e) => &e.attrs,
      syn::Expr::Macro(e) => &e.attrs,
      syn::Expr::Match(e) => &e.attrs,
      syn::Expr::MethodCall(e) => &e.attrs,
      syn::Expr::Paren(e) => &e.attrs,
      syn::Expr::Path(e) => &e.attrs,
      syn::Expr::Range(e) => &e.attrs,
      syn::Expr::Reference(e) => &e.attrs,
      syn::Expr::Repeat(e) => &e.attrs,
      syn::Expr::Return(e) => &e.attrs,
      syn::Expr::Struct(e) => &e.attrs,
      syn::Expr::Try(e) => &e.attrs,
      syn::Expr::TryBlock(e) => &e.attrs,
      syn::Expr::Tuple(e) => &e.attrs,
      syn::Expr::Unary(e) => &e.attrs,
      syn::Expr::Unsafe(e) => &e.attrs,
      syn::Expr::While(e) => &e.attrs,
      syn::Expr::Yield(e) => &e.attrs,
      _ => &[][..],
    };
    let has_cfg = has_cfg_or_test(attrs);
    if has_cfg {
      self.cfg_depth += 1;
    }
    let prev_len = self.local_idents.len();
    if let syn::Expr::Closure(c) = i {
      for input in &c.inputs {
        collect_pat_idents(input, &mut self.local_idents);
      }
    }
    visit_expr(self, i);
    self.local_idents.truncate(prev_len);
    if has_cfg {
      self.cfg_depth -= 1;
    }
  }

  fn visit_block(&mut self, i: &'ast syn::Block) {
    let prev_len = self.local_idents.len();
    visit_block(self, i);
    self.local_idents.truncate(prev_len);
  }

  fn visit_stmt(&mut self, i: &'ast syn::Stmt) {
    let has_cfg = match i {
      syn::Stmt::Local(l) => has_cfg_or_test(&l.attrs),
      syn::Stmt::Item(it) => item_has_cfg(it),
      syn::Stmt::Expr(..) => false,
      syn::Stmt::Macro(m) => has_cfg_or_test(&m.attrs),
    };
    if has_cfg {
      self.cfg_depth += 1;
    }
    if let syn::Stmt::Local(l) = i {
      collect_pat_idents(&l.pat, &mut self.local_idents);
    } else if let syn::Stmt::Item(it) = i {
      collect_item_idents(it, &mut self.local_idents);
    }
    visit_stmt(self, i);
    if has_cfg {
      self.cfg_depth -= 1;
    }
  }

  fn visit_arm(&mut self, i: &'ast syn::Arm) {
    let has_cfg = has_cfg_or_test(&i.attrs);
    if has_cfg {
      self.cfg_depth += 1;
    }
    visit_arm(self, i);
    if has_cfg {
      self.cfg_depth -= 1;
    }
  }

  fn visit_field(&mut self, i: &'ast syn::Field) {
    let has_cfg = has_cfg_or_test(&i.attrs);
    if has_cfg {
      self.cfg_depth += 1;
    }
    visit_field(self, i);
    if has_cfg {
      self.cfg_depth -= 1;
    }
  }

  fn visit_variant(&mut self, i: &'ast syn::Variant) {
    let has_cfg = has_cfg_or_test(&i.attrs);
    if has_cfg {
      self.cfg_depth += 1;
    }
    visit_variant(self, i);
    if has_cfg {
      self.cfg_depth -= 1;
    }
  }

  fn visit_foreign_item(&mut self, i: &'ast syn::ForeignItem) {
    let has_cfg = match i {
      syn::ForeignItem::Fn(f) => has_cfg_or_test(&f.attrs),
      syn::ForeignItem::Static(s) => has_cfg_or_test(&s.attrs),
      syn::ForeignItem::Type(t) => has_cfg_or_test(&t.attrs),
      syn::ForeignItem::Macro(m) => has_cfg_or_test(&m.attrs),
      _ => false,
    };
    if has_cfg {
      self.cfg_depth += 1;
    }
    visit_foreign_item(self, i);
    if has_cfg {
      self.cfg_depth -= 1;
    }
  }

  fn visit_item_use(&mut self, i: &'ast ItemUse) {
    self.use_depth += 1;
    visit_item_use(self, i);
    self.use_depth -= 1;
  }

  fn visit_attribute(&mut self, i: &'ast Attribute) {
    self.attr_depth += 1;
    visit_attribute(self, i);
    self.attr_depth -= 1;
  }

  fn visit_item_mod(&mut self, i: &'ast ItemMod) {
    if i.content.is_some() {
      self.mod_depth += 1;
      visit_item_mod(self, i);
      self.mod_depth -= 1;
    } else {
      visit_item_mod(self, i);
    }
  }

  fn visit_item_macro(&mut self, _i: &'ast syn::ItemMacro) {
    // 宏定义(macro_rules!)内部绝对不替换,直接跳过!
  }

  fn visit_macro(&mut self, i: &'ast Macro) {
    // 宏调用处(如 format!(...), println!(...), vec![...] 等)
    if self.use_depth == 0 && self.attr_depth == 0 && self.mod_depth == 0 && self.cfg_depth == 0 {
      self.inspect_path(&i.path);

      let parser = Punctuated::<syn::Expr, Token![,]>::parse_terminated;
      if let Ok(exprs) = parser.parse2(i.tokens.clone()) {
        for expr in &exprs {
          self.visit_expr(expr);
        }
      }
    }
  }

  fn visit_path(&mut self, p: &'ast Path) {
    if self.use_depth == 0 && self.attr_depth == 0 && self.mod_depth == 0 && self.cfg_depth == 0 {
      self.inspect_path(p);
    }
    visit_path(self, p);
  }
}

fn collect_pat_idents(pat: &syn::Pat, out: &mut Vec<String>) {
  match pat {
    syn::Pat::Ident(i) => {
      out.push(i.ident.to_string());
    }
    syn::Pat::Tuple(t) => {
      for p in &t.elems {
        collect_pat_idents(p, out);
      }
    }
    syn::Pat::TupleStruct(ts) => {
      for p in &ts.elems {
        collect_pat_idents(p, out);
      }
    }
    syn::Pat::Struct(s) => {
      for f in &s.fields {
        collect_pat_idents(&f.pat, out);
      }
    }
    syn::Pat::Slice(s) => {
      for p in &s.elems {
        collect_pat_idents(p, out);
      }
    }
    syn::Pat::Reference(r) => {
      collect_pat_idents(&r.pat, out);
    }
    syn::Pat::Type(t) => {
      collect_pat_idents(&t.pat, out);
    }
    _ => {}
  }
}

fn collect_sig_inputs(sig: &syn::Signature, out: &mut Vec<String>) {
  for input in &sig.inputs {
    if let syn::FnArg::Typed(t) = input {
      collect_pat_idents(&t.pat, out);
    }
  }
}

fn collect_item_idents(item: &syn::Item, out: &mut Vec<String>) {
  let ident = match item {
    Item::Fn(f) => Some(&f.sig.ident),
    Item::Struct(s) => Some(&s.ident),
    Item::Enum(e) => Some(&e.ident),
    Item::Const(c) => Some(&c.ident),
    Item::Static(s) => Some(&s.ident),
    Item::Trait(t) => Some(&t.ident),
    Item::Type(t) => Some(&t.ident),
    Item::Mod(m) => Some(&m.ident),
    _ => None,
  };
  if let Some(ident) = ident {
    out.push(ident.to_string());
  }
}

#[inline]
fn has_cfg_or_test(attrs: &[Attribute]) -> bool {
  attrs.iter().any(|a| {
    let p = a.path();
    p.is_ident("cfg") || p.is_ident("cfg_attr") || p.is_ident("test")
  })
}

fn item_has_cfg(item: &Item) -> bool {
  let attrs = match item {
    Item::Fn(i) => &i.attrs,
    Item::Struct(i) => &i.attrs,
    Item::Enum(i) => &i.attrs,
    Item::Impl(i) => &i.attrs,
    Item::Trait(i) => &i.attrs,
    Item::Type(i) => &i.attrs,
    Item::Const(i) => &i.attrs,
    Item::Static(i) => &i.attrs,
    Item::Mod(i) => &i.attrs,
    Item::ForeignMod(i) => &i.attrs,
    Item::Macro(i) => &i.attrs,
    Item::Union(i) => &i.attrs,
    Item::Use(i) => &i.attrs,
    _ => return false,
  };
  has_cfg_or_test(attrs)
}