use syn::parse::{Parse, ParseStream};
use crate::adapters::shared::cfg_test::has_test_attr;
pub fn expand_test_macros(parsed: &mut [(String, String, syn::File)]) {
parsed.iter_mut().for_each(|(_, _, file)| {
let items = std::mem::take(&mut file.items);
file.items = expand_items(items);
});
}
fn expand_items(items: Vec<syn::Item>) -> Vec<syn::Item> {
items.into_iter().flat_map(expand_item).collect()
}
fn expand_item(item: syn::Item) -> Vec<syn::Item> {
match item {
syn::Item::Macro(m) => expand_macro_item(m),
syn::Item::Mod(m) => vec![syn::Item::Mod(expand_mod(m))],
other => vec![other],
}
}
fn expand_mod(mut m: syn::ItemMod) -> syn::ItemMod {
if let Some((brace, items)) = m.content {
m.content = Some((brace, expand_items(items)));
}
m
}
fn expand_macro_item(m: syn::ItemMacro) -> Vec<syn::Item> {
if !is_test_macro(&m.mac) {
return vec![syn::Item::Macro(m)];
}
let fns = extract_test_fns(&m.mac);
if fns.is_empty() {
vec![syn::Item::Macro(m)]
} else {
fns
}
}
fn is_test_macro(mac: &syn::Macro) -> bool {
mac.path
.segments
.last()
.is_some_and(|seg| seg.ident == "proptest" || seg.ident == "quickcheck")
}
fn extract_test_fns(mac: &syn::Macro) -> Vec<syn::Item> {
syn::parse2::<MacroTestFns>(mac.tokens.clone())
.map(|parsed| parsed.fns)
.unwrap_or_default()
}
struct MacroTestFns {
fns: Vec<syn::Item>,
}
impl Parse for MacroTestFns {
fn parse(input: ParseStream) -> syn::Result<Self> {
let _config = input.call(syn::Attribute::parse_inner)?;
let mut fns = Vec::new();
while !input.is_empty() {
fns.push(parse_one_test_fn(input)?);
}
Ok(Self { fns })
}
}
fn parse_one_test_fn(input: ParseStream) -> syn::Result<syn::Item> {
let mut attrs = input.call(syn::Attribute::parse_outer)?;
let _vis: syn::Visibility = input.parse()?;
let fn_token: syn::Token![fn] = input.parse()?;
let ident: syn::Ident = input.parse()?;
let mut generics: syn::Generics = input.parse()?;
let params;
syn::parenthesized!(params in input);
let _ignored: proc_macro2::TokenStream = params.parse()?;
let output: syn::ReturnType = input.parse()?;
generics.where_clause = input.parse()?;
let block: syn::Block = input.parse()?;
if !has_test_attr(&attrs) {
attrs.push(syn::parse_quote!(#[test]));
}
let sig = syn::Signature {
constness: None,
asyncness: None,
unsafety: None,
abi: None,
fn_token,
ident,
generics,
paren_token: syn::token::Paren::default(),
inputs: syn::punctuated::Punctuated::new(),
variadic: None,
output,
};
Ok(syn::Item::Fn(syn::ItemFn {
attrs,
vis: syn::Visibility::Inherited,
sig,
block: Box::new(block),
}))
}