use proc_macro::TokenStream;
use quote::quote;
use syn::{
Attribute, Item, ItemFn, ItemMod, parse_macro_input,
visit_mut::{self, VisitMut},
};
#[proc_macro_attribute]
pub fn before_all(_attr: TokenStream, item: TokenStream) -> TokenStream {
let input_fn = parse_macro_input!(item as ItemFn);
let fn_name = &input_fn.sig.ident;
let register_fn_name = syn::Ident::new(&format!("__register_before_all_fixture_{}", fn_name), fn_name.span());
let output = quote! {
#input_fn
#[ctor::ctor]
fn #register_fn_name() {
rest::backend::fixtures::register_before_all(
module_path!(),
Box::new(|| #fn_name())
);
}
};
TokenStream::from(output)
}
#[proc_macro_attribute]
pub fn after_all(_attr: TokenStream, item: TokenStream) -> TokenStream {
let input_fn = parse_macro_input!(item as ItemFn);
let fn_name = &input_fn.sig.ident;
let register_fn_name = syn::Ident::new(&format!("__register_after_all_fixture_{}", fn_name), fn_name.span());
let output = quote! {
#input_fn
#[ctor::ctor]
fn #register_fn_name() {
rest::backend::fixtures::register_after_all(
module_path!(),
Box::new(|| #fn_name())
);
}
};
TokenStream::from(output)
}
#[proc_macro_attribute]
pub fn setup(_attr: TokenStream, item: TokenStream) -> TokenStream {
let input_fn = parse_macro_input!(item as ItemFn);
let fn_name = &input_fn.sig.ident;
let register_fn_name = syn::Ident::new(&format!("__register_setup_fixture_{}", fn_name), fn_name.span());
let output = quote! {
#input_fn
#[ctor::ctor]
fn #register_fn_name() {
rest::backend::fixtures::register_setup(
module_path!(),
Box::new(|| #fn_name())
);
}
};
TokenStream::from(output)
}
#[proc_macro_attribute]
pub fn tear_down(_attr: TokenStream, item: TokenStream) -> TokenStream {
let input_fn = parse_macro_input!(item as ItemFn);
let fn_name = &input_fn.sig.ident;
let register_fn_name = syn::Ident::new(&format!("__register_teardown_fixture_{}", fn_name), fn_name.span());
let output = quote! {
#input_fn
#[ctor::ctor]
fn #register_fn_name() {
rest::backend::fixtures::register_teardown(
module_path!(),
Box::new(|| #fn_name())
);
}
};
TokenStream::from(output)
}
#[proc_macro_attribute]
pub fn with_fixtures(_attr: TokenStream, item: TokenStream) -> TokenStream {
let input_fn = parse_macro_input!(item as ItemFn);
let fn_name = &input_fn.sig.ident;
let fn_body = &input_fn.block;
let vis = &input_fn.vis; let attrs = &input_fn.attrs; let sig = &input_fn.sig;
let impl_name = syn::Ident::new(&format!("__{}_impl", fn_name), fn_name.span());
let output = quote! {
fn #impl_name() #fn_body
#(#attrs)*
#vis #sig {
let module_path = module_path!();
rest::backend::fixtures::run_test_with_fixtures(
module_path,
std::panic::AssertUnwindSafe(|| #impl_name())
);
}
};
TokenStream::from(output)
}
struct TestFunctionVisitor {}
impl VisitMut for TestFunctionVisitor {
fn visit_item_fn_mut(&mut self, node: &mut ItemFn) {
let is_test = node.attrs.iter().any(|attr| attr.path().is_ident("test"));
let already_has_fixtures = node.attrs.iter().any(|attr| attr.path().is_ident("with_fixtures"));
if is_test && !already_has_fixtures {
let with_fixtures_attr: Attribute = syn::parse_quote!(#[with_fixtures]);
node.attrs.push(with_fixtures_attr);
}
visit_mut::visit_item_fn_mut(self, node);
}
}
#[proc_macro_attribute]
pub fn with_fixtures_module(_attr: TokenStream, item: TokenStream) -> TokenStream {
let mut input_mod = parse_macro_input!(item as ItemMod);
if let Some((_, items)) = &mut input_mod.content {
let mut visitor = TestFunctionVisitor {};
for item in items.iter_mut() {
if let Item::Fn(func) = item {
visitor.visit_item_fn_mut(func);
}
if let Item::Mod(nested_mod) = item
&& let Some((_, nested_items)) = &mut nested_mod.content
{
for nested_item in nested_items.iter_mut() {
if let Item::Fn(func) = nested_item {
visitor.visit_item_fn_mut(func);
}
}
}
}
}
TokenStream::from(quote! {
#input_mod
})
}