summaryrefslogtreecommitdiff
path: root/macros/src/lib.rs
blob: f11c064f1c7acb8d7c13ffc5cdc49a4c6d000779 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
//! Macros for Ridicule, a mocking library supporting non-static generics.
#![deny(clippy::all, clippy::pedantic, missing_docs)]
use proc_macro::TokenStream;
use proc_macro_error::{proc_macro_error, ResultExt};
use quote::{format_ident, quote};
use syn::{parse, ImplItem};

use crate::expectation::Expectation;
use crate::mock::Mock;
use crate::mock_input::MockInput;

mod expectation;
mod mock;
mod mock_input;
mod syn_ext;
mod util;

/// Creates a mock.
///
/// # Examples
/// ```
/// use ridicule::mock;
///
/// trait Foo
/// {
///     fn bar<A, B>(&self, a: A) -> B;
/// }
///
/// mock! {
///     MockFoo {}
///
///     impl Foo for MockFoo
///     {
///         fn bar<A, B>(&self, a: A) -> B;
///     }
/// }
///
/// fn main()
/// {
///     let mut mock_foo = MockFoo::new();
///
///     mock_foo
///         .expect_bar()
///         .returning(|foo, a: u32| format!("Hello {a}"));
///
///     assert_eq!(mock_foo.bar::<u32, String>(123), "Hello 123");
/// }
/// ```
#[proc_macro]
#[proc_macro_error]
pub fn mock(input_stream: TokenStream) -> TokenStream
{
    let input = parse::<MockInput>(input_stream.clone()).unwrap_or_abort();

    let mock_ident = input.mock;

    let mock_mod_ident = format_ident!("__{mock_ident}");

    let method_items = input
        .item_impl
        .items
        .into_iter()
        .filter_map(|item| match item {
            ImplItem::Method(item_method) => Some(item_method),
            _ => None,
        })
        .collect::<Vec<_>>();

    let mock = Mock::new(
        mock_ident.clone(),
        input.mocked_trait,
        &method_items,
        input.item_impl.generics.clone(),
    );

    let expectations = method_items.iter().map(|item_method| {
        Expectation::new(
            &mock_ident,
            item_method,
            input.item_impl.generics.params.clone(),
        )
    });

    quote! {
        mod #mock_mod_ident {
            use super::*;

            #mock

            #(#expectations)*
        }

        use #mock_mod_ident::#mock_ident;
    }
    .into()
}