use header_builder::FnHeaderBuilder;
use syn::{ArgCaptured, Attribute, Block, FnArg, FnDecl, Ident, ImplItem, ImplItemMethod, Item, ItemFn, ItemImpl,
ItemMod, ItemTrait, MethodSig, Pat, PatIdent, TraitItem, TraitItemMethod};
use syn::punctuated::Punctuated;
use syn::token::{Comma, Const, Unsafe};
pub fn inject_item(item: &mut Item) {
match *item {
Item::Fn(ref mut item_fn) => inject_fn(item_fn),
Item::Mod(ref mut item_mod) => inject_mod(item_mod),
Item::Trait(ref mut item_trait) => inject_trait(item_trait),
Item::Impl(ref mut item_impl) => inject_impl(item_impl),
_ => (),
}
}
fn inject_fn(item_fn: &mut ItemFn) {
inject_any_fn(&FnHeaderBuilder::StaticFn, &item_fn.attrs, &item_fn.constness, &item_fn.unsafety, &item_fn.ident,
&mut item_fn.decl, &mut *item_fn.block);
}
fn inject_mod(item_mod: &mut ItemMod) {
if is_not_mockable(&item_mod.attrs) {
return
}
item_mod.content.iter_mut()
.flat_map(|c| &mut c.1)
.for_each(inject_item)
}
fn inject_trait(item_trait: &mut ItemTrait) {
if is_not_mockable(&item_trait.attrs) {
return
}
for item in &mut item_trait.items {
if let TraitItem::Method(TraitItemMethod {
ref attrs,
ref mut sig,
default: Some(ref mut block),
..
}) = *item {
inject_any_method(&FnHeaderBuilder::TraitDefault, attrs, sig, block);
}
}
}
fn inject_impl(item_impl: &mut ItemImpl) {
if is_not_mockable(&item_impl.attrs) {
return
}
let builder = match item_impl.trait_ {
Some((_, ref path, _)) => FnHeaderBuilder::TraitImpl(&path.segments),
None => FnHeaderBuilder::StructImpl,
};
for impl_item in &mut item_impl.items {
if let ImplItem::Method(ref mut item_method) = *impl_item {
if is_impl_fn_mockabile(&builder, item_method) {
inject_any_method(&builder, &item_method.attrs, &mut item_method.sig, &mut item_method.block);
}
}
}
}
fn is_impl_fn_mockabile(builder: &FnHeaderBuilder, item_method: &ImplItemMethod) -> bool {
if let FnHeaderBuilder::TraitImpl(ref segments) = *builder {
if let Some(pair) = segments.last() {
let segment = pair.value();
if segment.arguments.is_empty() && segment.ident.as_ref() == "Drop" {
if item_method.sig.ident.as_ref() == "drop" {
return false
}
}
}
}
true
}
fn inject_any_method(builder: &FnHeaderBuilder, attrs: &Vec<Attribute>, sig: &mut MethodSig, block: &mut Block) {
inject_any_fn(builder, attrs, &sig.constness, &sig.unsafety, &sig.ident, &mut sig.decl, block)
}
fn inject_any_fn(builder: &FnHeaderBuilder, attrs: &Vec<Attribute>, constness: &Option<Const>,
unsafety: &Option<Unsafe>, fn_name: &Ident, fn_decl: &mut FnDecl, block: &mut Block) {
if constness.is_some() || unsafety.is_some() || fn_decl.variadic.is_some() || is_not_mockable(attrs) {
return
}
unignore_fn_args(&mut fn_decl.inputs);
let header_stmt = builder.build(fn_name, fn_decl, block.brace_token.0);
block.stmts.insert(0, header_stmt);
}
fn unignore_fn_args(inputs: &mut Punctuated<FnArg, Comma>) {
for (i, fn_arg) in inputs.iter_mut().enumerate() {
if let FnArg::Captured(
ArgCaptured {
ref mut pat,
..
}
) = *fn_arg {
let span = match *pat {
Pat::Wild(ref pat_wild) => pat_wild.underscore_token.0[0],
_ => continue,
};
*pat = Pat::Ident(
PatIdent {
by_ref: None,
mutability: None,
ident: Ident::new(&format!("__mocktopus_unignored_argument_{}__", i), span),
subpat: None,
}
)
}
}
}
const INJECTOR_STOPPER_ATTRS: [&str; 2] = ["mockable", "not_mockable"];
fn is_not_mockable(attrs: &Vec<Attribute>) -> bool {
attrs.iter()
.filter_map(|a| a.path.segments.last())
.map(|s| s.value().ident.as_ref())
.any(|i| INJECTOR_STOPPER_ATTRS.contains(&i))
}