facilitest_macros/
lib.rs

1use proc_macro::{Delimiter, Group, Ident, Punct, Spacing, Span, TokenStream, TokenTree};
2use std::iter;
3
4#[proc_macro]
5pub fn build_ident(input: TokenStream) -> TokenStream {
6    let mut ident_name = String::new();
7    for token in input.into_iter() {
8        match token {
9            TokenTree::Ident(ident) => ident_name += &ident.to_string(),
10            TokenTree::Group(group) => {
11                if let Some(ident) = group.stream().into_iter().last() {
12                    ident_name += &ident.to_string();
13                }
14            }
15            _ => (),
16        }
17    }
18    TokenStream::from(TokenTree::Ident(Ident::new(&ident_name, Span::call_site())))
19}
20
21#[proc_macro]
22pub fn make_test_fn(input: TokenStream) -> TokenStream {
23    let mut tokens = input.into_iter();
24    let mut ts = TokenStream::new();
25
26    // unwrap macro arguments
27    let func = tokens.next().unwrap();
28    let _ = tokens.next().unwrap();
29    let suffix = tokens.next().unwrap();
30    let _ = tokens.next().unwrap();
31    let args = tokens.next().unwrap();
32    let _ = tokens.next().unwrap();
33    let expected = tokens.next().unwrap();
34
35    // start with "test" attribute
36    let ts_attr = TokenStream::from_iter([
37        TokenTree::from(Punct::new('#', Spacing::Alone)),
38        TokenTree::from(Group::new(
39            Delimiter::Bracket,
40            TokenStream::from(TokenTree::from(Ident::new("test", Span::call_site()))),
41        )),
42    ]);
43    ts.extend(ts_attr);
44
45    // append "fn" keyword
46    ts.extend(iter::once(TokenTree::from(Ident::new(
47        "fn",
48        Span::call_site(),
49    ))));
50
51    // append function name
52    let ts_name = TokenStream::from_iter([
53        TokenTree::from(Ident::new("test_", Span::call_site())),
54        func.clone(),
55        suffix,
56    ]);
57    let name = build_ident(ts_name);
58    ts.extend(iter::once(name));
59
60    // add empty parameter list
61    ts.extend(iter::once(TokenTree::from(Group::new(
62        Delimiter::Parenthesis,
63        TokenStream::new(),
64    ))));
65
66    // add function body
67    let mut ts_body = TokenStream::new();
68    if let TokenTree::Group(g_args) = args {
69        ts_body.extend([
70            TokenTree::from(Ident::new("assert_eq", Span::call_site())),
71            TokenTree::from(Punct::new('!', Spacing::Alone)),
72            TokenTree::from(Group::new(
73                Delimiter::Parenthesis,
74                TokenStream::from_iter([
75                    func,
76                    TokenTree::from(Group::new(
77                        Delimiter::Parenthesis,
78                        g_args.stream()
79                    )),
80                    TokenTree::from(Punct::new(',', Spacing::Alone)),
81                    expected,
82                ]),
83            )),
84            TokenTree::from(Punct::new(';', Spacing::Alone)),
85        ]);
86    }
87
88    ts.extend(iter::once(TokenTree::from(Group::new(
89        Delimiter::Brace,
90        ts_body,
91    ))));
92
93    ts
94}