mocktoffel 0.1.0

easy to use mocking library with minimal boilerplate
Documentation
use std::ops::Deref;

use proc_macro::TokenStream;
use quote::quote;
use syn::{
    parse, parse_str, FnArg, ImplItem, ImplItemFn, ImplItemType, ItemImpl, ReturnType, Type,
    TypePath,
};

use crate::extract::{prepare_mock_name, ExtractName, Extracted};

pub struct MockContext {
    original_type: Box<Type>,
    mocked_extract: Extracted,
}

impl MockContext {
    pub fn new(original_type: Box<Type>) -> Self {
        let mock_name = prepare_mock_name(&original_type.extract_name());
        Self {
            original_type,
            mocked_extract: mock_name,
        }
    }

    #[allow(clippy::redundant_clone)]
    pub fn mock_impl(&self, tokens: ItemImpl) -> TokenStream {
        let original_type = tokens.self_ty.clone();
        let original_name = original_type.extract_name();
        let Extracted { name, .. } = prepare_mock_name(&original_name);

        if let Some((_, tr, _)) = tokens.trait_ {
            let functions = tokens.items.into_iter().map(|item| match item {
                ImplItem::Fn(f) => self.replace_self_from_function_with_mocks(f),
                ImplItem::Type(mut ty) => {
                    self.replace_mocks_in_associated_types(&mut ty);
                    ImplItem::Type(ty)
                }
                _ => todo!(),
            });

            let Extracted {
                name: trait_,
                generics: trait_generics,
            } = tr.extract_name();
            let impl_generics = tokens.generics.clone();

            TokenStream::from(quote! {
                impl #impl_generics #trait_ #trait_generics for #name {
                    #(#functions)*
                }
            })
        } else {
            let functions = tokens.items.into_iter().map(|item| match item {
                ImplItem::Fn(f) => self.replace_self_from_function_with_mocks(f),
                _ => unreachable!(),
            });

            let impl_generics = tokens.generics.clone();
            let generics = original_name.generics.clone();

            TokenStream::from(quote! {
                impl #impl_generics #name #generics {
                    #(#functions)*
                }
            })
        }
    }

    #[allow(clippy::explicit_deref_methods)]
    fn replace_self_from_function_with_mocks(&self, mut f: ImplItemFn) -> ImplItem {
        let visibility = f.vis;

        f.sig.inputs.iter_mut().filter_map(|arg| {
                      if let FnArg::Typed(ref mut typ) = arg && typ.ty == self.original_type && let Type::Path(ref mut p) = &mut *typ.ty {
                          Some(p)
                      } else {
                          None
                      }
              }).for_each(|p| {
                    let segments = p.path.segments.last_mut().unwrap();
                    let fn_generics = segments.arguments.clone();
                    *p = parse_str(format!("{}", self.mocked_extract.name).as_str()).unwrap();

                        let segment = p.path.segments.last_mut().unwrap();
                        segment.arguments = fn_generics;
              });

        let inputs = f.sig.inputs.iter();

        let function_name = f.sig.ident;
        let name = self.mocked_extract.name.clone();
        let original_name = self.original_type.extract_name();
        let generics = original_name.generics;
        syn::parse(match &f.sig.output {
               ReturnType::Type(_, ref p) if let Type::Path(TypePath { path: pat, .. }) = p.deref() && pat.extract_name().name == original_name.name => {
                   if pat.extract_name().name == original_name.name {
                       TokenStream::from(quote! {
                           #visibility fn #function_name (#(#inputs),*) -> #name #generics {
                               <#name>::mock_new()
                           }
                       })
                   } else {
                      TokenStream::from(quote!(f))
                   }
               },

               ReturnType::Type(_, ty) => {
                   TokenStream::from(quote!{
                       #visibility fn #function_name(#(#inputs), *) -> #ty {
                           Default::default()
                       }
                   })
               },

               ReturnType::Default => {
                   TokenStream::from(quote!{
                       #visibility fn #function_name(#(#inputs), *) {
                       }
                   })
               }
           }).unwrap()
    }

    fn replace_mocks_in_associated_types(&self, associated_type: &mut ImplItemType) {
        let Extracted { name, generics } = associated_type.ty.extract_name();
        if name == self.original_type.extract_name().name {
            let original_type = self.original_type.clone();
            let Extracted {
                name: mocked_name,
                generics: mocked_generics,
            } = prepare_mock_name(&original_type.extract_name());
            associated_type.ty =
                parse(TokenStream::from(quote! { #mocked_name #mocked_generics })).unwrap();
        }
    }
}