mocktopus_macros 0.7.5

Mocktopus procedural macros
Documentation
use crate::header_builder::FnHeaderBuilder;
use syn::punctuated::Punctuated;
use syn::token::Comma;
use syn::{
    Attribute, Block, FnArg, Ident, ImplItem, ImplItemMethod, Item, ItemFn, ItemImpl, ItemMod,
    ItemTrait, Pat, PatIdent, PatType, Signature, TraitItem, TraitItemMethod,
};

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,
        &mut item_fn.sig,
        &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_fn(&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_fn(
                    &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(segment) = segments.last() {
            if segment.arguments.is_empty() && segment.ident == "Drop" {
                if item_method.sig.ident == "drop" {
                    return false;
                }
            }
        }
    }
    true
}

fn inject_any_fn(
    builder: &FnHeaderBuilder,
    attrs: &Vec<Attribute>,
    fn_decl: &mut Signature,
    block: &mut Block,
) {
    if fn_decl.constness.is_some()
        || fn_decl.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_decl, block.brace_token.span);
    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::Typed(PatType { ref mut pat, .. }) = *fn_arg {
            let (span, attrs) = match **pat {
                Pat::Wild(ref pat_wild) => {
                    (pat_wild.underscore_token.spans[0], pat_wild.attrs.clone())
                }
                _ => continue,
            };
            *pat = Box::new(Pat::Ident(PatIdent {
                by_ref: None,
                mutability: None,
                ident: Ident::new(&format!("__mocktopus_unignored_argument_{}__", i), span),
                subpat: None,
                attrs,
            }));
        }
    }
}

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(|segment| segment.ident.to_string())
        .any(|i| INJECTOR_STOPPER_ATTRS.contains(&&*i))
}