mocktoffel 0.1.0

easy to use mocking library with minimal boilerplate
Documentation
use crate::branch::Traitified;
use proc_macro::TokenStream;
use proc_macro2::Span;
use quote::quote;
use std::iter::FilterMap;
use syn::{
    punctuated::Punctuated, AngleBracketedGenericArguments, GenericParam, Generics, Ident, Item,
    ItemEnum, ItemStruct, Meta, Path, PathArguments, TraitBoundModifier, Type, TypeParam,
    TypeParamBound, TypePath,
};

pub struct MockPrepared {
    pub mocked_type: Type,
    stream: Option<TokenStream>,
}

impl MockPrepared {
    pub const fn new(mocked_type: Type, stream: Option<TokenStream>) -> Self {
        Self {
            mocked_type,
            stream,
        }
    }
}

#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Extracted {
    pub name: Ident,
    pub generics: Option<AngleBracketedGenericArguments>,
}

impl Extracted {
    pub const fn new(name: Ident, generics: Option<AngleBracketedGenericArguments>) -> Self {
        Self { name, generics }
    }

    pub const fn with_ident(name: Ident) -> Self {
        Self {
            name,
            generics: None,
        }
    }
}

#[allow(clippy::module_name_repetitions)]
pub trait ExtractName {
    fn extract_name(&self) -> Extracted;
}

impl ExtractName for Type {
    fn extract_name(&self) -> Extracted {
        match self {
            Self::Array(_) => todo!(),
            Self::BareFn(f) => todo!(),
            Self::Group(g) => todo!(),
            Self::ImplTrait(im) => Extracted::with_ident(extract_name_for_bounds(im)),
            Self::Infer(inf) => unreachable!(),
            Self::Macro(m) => todo!(),
            Self::Paren(p) => todo!(),
            Self::Path(TypePath { qself: None, path }) => path.extract_name(),
            Self::Ptr(p) => todo!(),
            _ => todo!(),
        }
    }
}

impl ExtractName for Path {
    fn extract_name(&self) -> Extracted {
        let segment = self.segments.last().unwrap();
        match &segment.arguments {
            PathArguments::AngleBracketed(abga) => {
                Extracted::new(segment.ident.clone(), Some(abga.clone()))
            }
            PathArguments::None => {
                Extracted::with_ident(self.segments.last().unwrap().ident.clone())
            }

            PathArguments::Parenthesized(_) => unreachable!(),
        }
    }
}

fn extract_name_for_bounds<T: Traitified>(imp: &T) -> Ident {
    let name = imp.bounds().iter().filter_map(|merkmal| {
            if let TypeParamBound::Trait(trait_bound) = merkmal && matches!(trait_bound.modifier, TraitBoundModifier::None) {
                Some(&trait_bound.path)
            } else {
                None
            }
        }).fold(String::new(), | mut mock_name, path| {
            let p: String =  path.segments.iter().map(|x| format!("{}", x.ident)).collect();
            mock_name.push_str(&p);

            mock_name
        });
    Ident::new(&name, Span::call_site())
}

pub fn prepare_mock_name(name: &Extracted) -> Extracted {
    let inp = format!("{}Mock", name.name);
    Extracted::new(Ident::new(&inp, Span::call_site()), name.generics.clone())
}

pub fn parse_fields_and_generate_for_values(schtruct: &mut ItemStruct) -> TokenStream {
    let (fields, values) =
        schtruct
            .fields
            .iter()
            .fold((vec![], vec![]), |(mut fields, mut values), field| {
                match (field.attrs.iter().find(|attr| attr.meta.path().is_ident("mocked_with")), &field.ident) {
            (Some(v), Some(ref ident )) if let Meta::List(ref list) = v.meta => {
                fields.push(ident.clone());
                values.push(list.tokens.clone());
            },
            (Some(v), _) if let Meta::List(ref list) = v.meta => {
                values.push(list.tokens.clone());
            },
            (None, Some(ref ident)) => {
                fields.push(ident.clone());
                values.push(quote!( { Default::default() }));

            },
            (None, _) => {
                values.push(quote!( { Default::default() }));
            },
            _ => todo!(),
        };

                (fields, values)
            });

    let struct_name = &schtruct.ident;
    let generics = &mut schtruct.generics;
    let mut impl_generics = generics.clone();
    extract_generics_from_bounds(&mut impl_generics);

    let tok = match (&fields[..], &values[..]) {
        (&[], &[]) => {
            quote! {
                impl #generics #struct_name #impl_generics {
                    pub fn mock_new() -> Self {
                        Self {}
                    }
                }
            }
        }
        (&[], v) => {
            quote! {
                impl #generics #struct_name #impl_generics {
                    pub fn mock_new() -> Self {
                        Self(#(#values),*)
                    }
                }
            }
        }
        (f, v) => {
            quote! {
                impl #impl_generics #struct_name #impl_generics {
                    pub fn mock_new() -> Self {
                        Self {
                            #(#fields : #values),*
                        }
                    }
                }
            }
        }
    };
    TokenStream::from(tok)
}

#[allow(clippy::option_if_let_else)]
pub fn parse_fields_and_generate_variant(enoom: &ItemEnum) -> TokenStream {
    let enum_name = enoom.ident.clone();

    let tok = if let Some(variant) = enoom.variants.iter().find(|field| {
        field
            .attrs
            .iter()
            .any(|attr| attr.meta.path().is_ident("mocked_value"))
    }) {
        let variant_name = &variant.ident;
        quote! {
            impl #enum_name {
                pub fn mock_new() -> Self {
                    Self::#variant_name
                }
            }
        }
    } else if let Some(variant) = enoom.variants.iter().find(|field| {
        field
            .attrs
            .iter()
            .any(|attr| attr.meta.path().is_ident("mocked_value_with"))
    }) {
        let variant_name = &variant.ident;
        let value = if let Meta::List(ref ml) = variant.attrs.first().unwrap().meta {
            &ml.tokens
        } else {
            unreachable!()
        };

        quote! {
            impl #enum_name {
                pub fn mock_new() -> Self {
                    Self::#variant_name(#value)
                }
            }
        }
    } else {
        quote! {
            impl #enum_name {
                pub fn mock_new() -> Self {
                   Self::default()
                }
            }
        }
    };

    TokenStream::from(tok)
}

pub fn extract_generics_from_bounds(bounds: &mut Generics) {
    bounds.params.iter_mut().for_each(|bound| match bound {
        GenericParam::Type(ty) => {
            ty.colon_token = None;
            ty.bounds = Punctuated::new();
        }
        GenericParam::Const(c) => todo!(),
        GenericParam::Lifetime(lp) => {}
    });
}

pub fn clean_out_attributes(item: &mut Item) {
    match item {
        Item::Struct(s) => {
            s.fields.iter_mut().for_each(|field| {
                field.attrs.clear();
            });
        }
        Item::Enum(e) => {
            e.variants.iter_mut().for_each(|variant| {
                variant.attrs.clear();
            });
        }
        _ => unreachable!(),
    }
}