Skip to main content

skipper_macros/
lib.rs

1//! `#[skipper]`: re-exported by the `get-skipper` crate, use it from there.
2
3use proc_macro::TokenStream;
4use quote::quote;
5use syn::{parse_macro_input, ItemFn, ReturnType, Type};
6
7/// Skip the test when it is disabled in the Skipper spreadsheet.
8///
9/// Put it on any test function, above or below `#[test]`, `#[tokio::test]`
10/// or `#[rstest]`. The test ID is `file > module::path > fn_name`.
11/// Tests returning `Result<(), E>` return `Ok(())` when skipped.
12#[proc_macro_attribute]
13pub fn skipper(attr: TokenStream, item: TokenStream) -> TokenStream {
14    if !attr.is_empty() {
15        return syn::Error::new(
16            proc_macro2::Span::call_site(),
17            "#[skipper] takes no arguments",
18        )
19        .to_compile_error()
20        .into();
21    }
22    let mut f = parse_macro_input!(item as ItemFn);
23    let ret = match &f.sig.output {
24        ReturnType::Default => quote!(return;),
25        ReturnType::Type(_, ty) if is_result(ty) => quote!(return ::core::result::Result::Ok(());),
26        ReturnType::Type(_, ty) => {
27            return syn::Error::new_spanned(
28                ty,
29                "#[skipper] supports tests returning () or Result<(), E>",
30            )
31            .to_compile_error()
32            .into()
33        }
34    };
35    let name = f.sig.ident.to_string();
36    let check: syn::Stmt = syn::parse_quote! {
37        if ::get_skipper::__should_skip(::core::file!(), ::core::module_path!(), #name) {
38            #ret
39        }
40    };
41    f.block.stmts.insert(0, check);
42    quote!(#f).into()
43}
44
45fn is_result(ty: &Type) -> bool {
46    matches!(ty, Type::Path(p) if p.path.segments.last().is_some_and(|s| s.ident == "Result"))
47}