mry_macros 0.14.0

Macro crate for mry, a simple but powerful mocking library that supports struct, trait, and function.
Documentation
use darling::{ast::NestedMeta, FromMeta};
use syn::{visit::Visit, Meta};

#[derive(FromMeta, Default)]
pub(crate) struct MryAttr {
    pub debug: darling::util::Flag,
    pub non_send: Option<NotSend>,
    pub skip_args: Option<Skip>,
    pub skip_fns: Option<Skip>,
}

pub(crate) struct NotSend(pub Vec<syn::Path>);
pub(crate) struct Skip(pub Vec<syn::Path>);

impl FromMeta for NotSend {
    fn from_list(list: &[NestedMeta]) -> darling::Result<Self> {
        list.iter()
            .map(|meta| match meta {
                NestedMeta::Meta(Meta::Path(path)) => Ok(path.clone()),
                _ => Err(darling::Error::custom(
                    "expected a list of types like non_send(T, U)",
                )),
            })
            .collect::<Result<Vec<_>, _>>()
            .map(NotSend)
    }
}

impl FromMeta for Skip {
    fn from_list(list: &[NestedMeta]) -> darling::Result<Self> {
        list.iter()
            .map(|meta| match meta {
                NestedMeta::Meta(Meta::Path(path)) => Ok(path.clone()),
                _ => Err(darling::Error::custom(
                    "expected a list of types like skip(T, U)",
                )),
            })
            .collect::<Result<Vec<_>, _>>()
            .map(Skip)
    }
}

impl MryAttr {
    pub fn test_non_send(&self, ty: &syn::Type) -> bool {
        let Some(non_send) = &self.non_send else {
            return false;
        };
        let mut visitor = TypeVisitor {
            paths: &non_send.0,
            found: false,
        };
        visitor.visit_type(ty);
        visitor.found
    }

    pub fn test_skip_args(&self, ty: &syn::Type) -> bool {
        let Some(skip) = &self.skip_args else {
            return false;
        };
        let mut visitor = TypeVisitor {
            paths: &skip.0,
            found: false,
        };
        visitor.visit_type(ty);
        visitor.found
    }

    pub fn should_skip_method(&self, method_name: &syn::Ident) -> bool {
        if let Some(skip) = &self.skip_fns {
            if skip.0.iter().any(|p| p.is_ident(method_name)) {
                return true;
            }
        }
        false
    }
}

struct TypeVisitor<'a> {
    paths: &'a Vec<syn::Path>,
    found: bool,
}
impl Visit<'_> for TypeVisitor<'_> {
    fn visit_path(&mut self, path: &syn::Path) {
        if self.found {
            return;
        }
        if self.paths.iter().any(|p| p == path) {
            self.found = true;
            return;
        }
        for segment in path.segments.iter() {
            self.visit_path_segment(segment);
        }
    }
    fn visit_ident(&mut self, ident: &syn::Ident) {
        if self.found {
            return;
        }
        if self.paths.iter().any(|p| p.is_ident(ident)) {
            self.found = true;
        }
    }
}

#[cfg(test)]
mod tests {
    use syn::parse_quote;

    use super::*;

    #[test]
    fn test_debug() {
        let attr = MryAttr::from_list(
            &NestedMeta::parse_meta_list(parse_quote! {
                debug
            })
            .unwrap(),
        )
        .unwrap();
        assert!(attr.debug.is_present());
    }

    #[test]
    fn test_non_send() {
        let attr = MryAttr::from_list(
            &NestedMeta::parse_meta_list(parse_quote! {
                debug,non_send(T, U)
            })
            .unwrap(),
        )
        .unwrap();
        let lists = attr.non_send.unwrap().0;
        assert_eq!(lists.len(), 2);
        assert_eq!(lists[0], parse_quote!(T));
        assert_eq!(lists[1], parse_quote!(U));
    }

    #[test]
    fn test_non_send_path() {
        let attr = MryAttr::from_list(
            &NestedMeta::parse_meta_list(parse_quote! {
                debug,non_send(A::B)
            })
            .unwrap(),
        )
        .unwrap();

        assert!(attr.test_non_send(&parse_quote!(A::B)));
        assert!(!attr.test_non_send(&parse_quote!(A::C)));
    }

    #[test]
    fn test_non_send_rc() {
        let attr = MryAttr::from_list(
            &NestedMeta::parse_meta_list(parse_quote! {
                debug,non_send(Rc)
            })
            .unwrap(),
        )
        .unwrap();

        assert!(attr.test_non_send(&parse_quote!(Rc<String>)));
        assert!(!attr.test_non_send(&parse_quote!(String)));
    }

    #[test]
    fn test_skip_method() {
        let attr = MryAttr::from_list(
            &NestedMeta::parse_meta_list(parse_quote! {
                skip_fns(skipped)
            })
            .unwrap(),
        )
        .unwrap();
        assert!(attr.should_skip_method(&parse_quote!(skipped)));
        assert!(!attr.should_skip_method(&parse_quote!(not_skipped)));
    }
}