summaryrefslogtreecommitdiff
path: root/src/mock_item.rs
blob: 109d9c221f9cc6877c9c4d05fc8b5505758288e6 (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
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
// vim: tw=80
use super::*;

use crate::{
    mock_function::MockFunction,
    mockable_item::{MockableItem, MockableModule},
};

/// A Mock item
pub(crate) enum MockItem {
    Module(MockItemModule),
    Struct(MockItemStruct),
}

impl From<MockableItem> for MockItem {
    fn from(mockable: MockableItem) -> MockItem {
        match mockable {
            MockableItem::Struct(s) => MockItem::Struct(MockItemStruct::from(s)),
            MockableItem::Module(mod_) => MockItem::Module(MockItemModule::from(mod_)),
        }
    }
}

impl ToTokens for MockItem {
    fn to_tokens(&self, tokens: &mut TokenStream) {
        match self {
            MockItem::Module(mod_) => mod_.to_tokens(tokens),
            MockItem::Struct(s) => s.to_tokens(tokens),
        }
    }
}

enum MockItemContent {
    Fn(Box<MockFunction>),
    Tokens(TokenStream),
}

pub(crate) struct MockItemModule {
    attrs: TokenStream,
    vis: Visibility,
    mock_ident: Ident,
    orig_ident: Option<Ident>,
    content: Vec<MockItemContent>,
}

impl From<MockableModule> for MockItemModule {
    fn from(mod_: MockableModule) -> MockItemModule {
        let mock_ident = mod_.mock_ident.clone();
        let orig_ident = mod_.orig_ident;
        let mut content = Vec::new();
        for item in mod_.content.into_iter() {
            let span = item.span();
            match item {
                Item::ExternCrate(_) | Item::Impl(_) => {
                    // Ignore
                }
                Item::Static(is) => {
                    content.push(MockItemContent::Tokens(is.into_token_stream()));
                }
                Item::Const(ic) => {
                    content.push(MockItemContent::Tokens(ic.into_token_stream()));
                }
                Item::Fn(f) => {
                    let mf = mock_function::Builder::new(&f.sig, &f.vis)
                        .attrs(&f.attrs)
                        .parent(&mock_ident)
                        .levels(1)
                        .call_levels(0)
                        .build();
                    content.push(MockItemContent::Fn(Box::new(mf)));
                }
                Item::ForeignMod(ifm) => {
                    for item in ifm.items {
                        if let ForeignItem::Fn(mut f) = item {
                            // Foreign functions are always unsafe.  Mock
                            // foreign functions should be unsafe too, to
                            // prevent "warning: unused unsafe" messages.
                            f.sig.unsafety = Some(Token![unsafe](f.span()));
                            let mf = mock_function::Builder::new(&f.sig, &f.vis)
                                .attrs(&f.attrs)
                                .parent(&mock_ident)
                                .levels(1)
                                .call_levels(0)
                                .build();
                            content.push(MockItemContent::Fn(Box::new(mf)));
                        } else {
                            compile_error(item.span(),
                                "Mockall does not yet support  this type in this position.  Please open an issue with your use case at https://github.com/asomers/mockall");
                        }
                    }
                }
                Item::Mod(_)
                | Item::Struct(_)
                | Item::Enum(_)
                | Item::Union(_)
                | Item::Trait(_) => {
                    compile_error(span, "Mockall does not yet support deriving nested mocks");
                }
                Item::Type(ty) => {
                    content.push(MockItemContent::Tokens(ty.into_token_stream()));
                }
                Item::TraitAlias(ta) => {
                    content.push(MockItemContent::Tokens(ta.into_token_stream()));
                }
                Item::Use(u) => {
                    content.push(MockItemContent::Tokens(u.into_token_stream()));
                }
                _ => compile_error(span, "Unsupported item"),
            }
        }
        MockItemModule {
            attrs: mod_.attrs,
            vis: mod_.vis,
            mock_ident: mod_.mock_ident,
            orig_ident,
            content,
        }
    }
}

impl ToTokens for MockItemModule {
    fn to_tokens(&self, tokens: &mut TokenStream) {
        let mut body = TokenStream::new();
        let mut cp_body = TokenStream::new();
        let attrs = &self.attrs;
        let modname = &self.mock_ident;
        let vis = &self.vis;

        for item in self.content.iter() {
            match item {
                MockItemContent::Tokens(ts) => ts.to_tokens(&mut body),
                MockItemContent::Fn(f) => {
                    let call = f.call(None);
                    let ctx_fn = f.context_fn(None);
                    let priv_mod = f.priv_module();
                    quote!(
                        #priv_mod
                        #call
                        #ctx_fn
                    )
                    .to_tokens(&mut body);
                    f.checkpoint().to_tokens(&mut cp_body);
                }
            }
        }

        quote!(
            /// Verify that all current expectations for every function in
            /// this module are satisfied and clear them.
            pub fn checkpoint() { #cp_body }
        )
        .to_tokens(&mut body);
        let docstr = {
            if let Some(ident) = &self.orig_ident {
                let inner = format!("Mock version of the `{}` module", ident);
                quote!( #[doc = #inner])
            } else {
                // Typically an extern FFI block.  Not really anything good we
                // can put in the doc string.
                quote!(#[allow(missing_docs)])
            }
        };
        quote!(
            #[allow(unused_imports)]
            #attrs
            #docstr
            #vis mod #modname {
                #body
        })
        .to_tokens(tokens);
    }
}