sif_macro 0.1.0

Part of Sif: attribute macro for parameterized tests
Documentation
#[macro_use]
extern crate syn;
extern crate proc_macro;

use crate::case_parser::CasesFn;
use quote::quote;
use syn::{Attribute, Block, Expr, Ident, Type, Visibility};

mod case_parser;

#[proc_macro_attribute]
pub fn parameterized(
    _args: proc_macro::TokenStream,
    fn_body: proc_macro::TokenStream,
) -> proc_macro::TokenStream {
    let inputs = parse_macro_input!(fn_body as CasesFn);

    let visibility = inputs.fn_visibility();
    let mod_ident = inputs.fn_ident();
    let other_attributes = inputs.regular_attrs();
    let test_parameters = inputs.fn_parameters();
    let arguments = inputs.case_attrs();
    let body = inputs.fn_body();
    let test_cases = arguments
        .iter()
        .map(|exprs| exprs.values.iter().collect::<Vec<&Expr>>())
        .enumerate()
        .map(|(nth, exprs)| {
            create_test_case(
                Ident::new(&format!("case_{}", nth), inputs.fn_span()),
                &test_parameters,
                &exprs,
                body,
                visibility,
                &other_attributes,
            )
        });

    (quote! {
        #[cfg(test)]
        #visibility mod #mod_ident {
            use super::*;

            #(#test_cases)*
        }

    })
    .into()
}

fn create_test_case(
    ident: Ident,
    params: &[(&Ident, &Type)],
    exprs: &[&Expr],
    body: &Block,
    vis: &Visibility,
    attributes: &[&Attribute],
) -> proc_macro2::TokenStream {
    assert_eq!(
        params.len(),
        exprs.len(),
        "[sif_macro] A case has an insufficient amount of arguments ({} parameter(s) registered, but {} argument(s) were supplied)",
        params.len(),
        exprs.len()
    );

    let bindings = (0..params.len()).map(|i| create_binding(params[i], exprs[i]));

    quote! {
        #[test]
        #(#attributes)*
        #vis fn #ident() {
            #(#bindings)*
            #body
        }
    }
}

fn create_binding(param: (&Ident, &Type), expr: &Expr) -> proc_macro2::TokenStream {
    let (ident, typ) = param;

    quote! {
        let #ident: #typ = #expr;
    }
}