faux_macros/
lib.rs

1extern crate proc_macro;
2
3mod create;
4mod methods;
5mod self_type;
6
7use darling::{export::NestedMeta, FromMeta};
8use proc_macro::TokenStream;
9use quote::quote;
10
11#[proc_macro_attribute]
12pub fn create(args: TokenStream, original: TokenStream) -> TokenStream {
13    let original = syn::parse_macro_input!(original as syn::ItemStruct);
14
15    let args = match NestedMeta::parse_meta_list(args.into())
16        .map_err(darling::Error::from)
17        .and_then(|v| create::Args::from_list(&v))
18    {
19        Ok(v) => v,
20        Err(e) => return e.write_errors().into(),
21    };
22
23    let mockable = create::Mockable::new(original, args);
24
25    TokenStream::from(mockable)
26}
27
28#[proc_macro_attribute]
29pub fn methods(args: TokenStream, original: TokenStream) -> TokenStream {
30    let original = syn::parse_macro_input!(original as syn::ItemImpl);
31
32    let args = match NestedMeta::parse_meta_list(args.into())
33        .map_err(darling::Error::from)
34        .and_then(|v| methods::Args::from_list(&v))
35    {
36        Ok(v) => v,
37        Err(e) => return e.write_errors().into(),
38    };
39
40    match methods::Mockable::new(original, args) {
41        Ok(mockable) => TokenStream::from(mockable),
42        Err(e) => e.write_errors().into(),
43    }
44}
45
46#[proc_macro]
47pub fn when(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
48    match syn::parse_macro_input!(input as syn::Expr) {
49        syn::Expr::Field(syn::ExprField {
50            base,
51            member: syn::Member::Named(ident),
52            ..
53        }) => {
54            let when = quote::format_ident!("_when_{}", ident);
55            TokenStream::from(quote!( { #base.#when() }))
56        }
57        syn::Expr::MethodCall(syn::ExprMethodCall {
58            receiver,
59            method,
60            args,
61            turbofish,
62            ..
63        }) => {
64            let when = quote::format_ident!("_when_{}", method);
65
66            let args = args
67                .into_iter()
68                .map(expr_to_matcher)
69                .collect::<Result<Vec<_>, _>>();
70
71            match args {
72                Err(e) => e.write_errors().into(),
73                Ok(args) if args.is_empty() => { TokenStream::from(quote!({ #receiver.#when #turbofish() }))}
74                Ok(args) => { TokenStream::from(quote!({ #receiver.#when #turbofish().with_args((#(#args,)*)) }))}
75            }
76        }
77        expr => darling::Error::custom("faux::when! only accepts arguments in the format of: `when!(receiver.method)` or `receiver.method(args...)`")
78             .with_span(&expr)
79             .write_errors()
80             .into(),
81    }
82}
83
84use quote::ToTokens;
85
86fn ref_matcher_maybe(
87    expr: &syn::Expr,
88    left: &syn::Expr,
89    matcher: impl FnOnce() -> darling::Result<proc_macro2::TokenStream>,
90) -> darling::Result<proc_macro2::TokenStream> {
91    match left {
92        syn::Expr::Infer(_) => matcher(),
93        syn::Expr::Unary(syn::ExprUnary {
94            op: syn::UnOp::Deref(_),
95            expr,
96            ..
97        }) => {
98            let matcher = matcher()?;
99            Ok(quote! { faux::matcher::ArgMatcher::<#expr>::into_ref_matcher(#matcher) })
100        }
101        _ => Ok(quote! { faux::matcher::eq(#expr) }),
102    }
103}
104
105fn expr_to_matcher(expr: syn::Expr) -> darling::Result<proc_macro2::TokenStream> {
106    match &expr {
107        syn::Expr::Infer(_) => Ok(quote! { faux::matcher::any() }),
108        syn::Expr::Assign(syn::ExprAssign { left, right, .. }) => {
109            ref_matcher_maybe(&expr, left, || Ok(right.to_token_stream()))
110        }
111        syn::Expr::Binary(syn::ExprBinary {
112            left, op, right, ..
113        }) => ref_matcher_maybe(&expr, left, || match op {
114            syn::BinOp::Eq(_) => Ok(quote! { faux::matcher::eq_against(#right) }),
115            _ => Err(darling::Error::custom(format!(
116                "faux:when! does not handle argument matchers with syntax: '{}'",
117                expr.to_token_stream()
118            ))
119            .with_span(&expr)),
120        }),
121        arg => Ok(quote! { faux::matcher::eq(#arg) }),
122    }
123}