1#![forbid(unsafe_code)]
2#![deny(
3 clippy::dbg_macro,
4 missing_copy_implementations,
5 rustdoc::missing_crate_level_docs,
6 missing_debug_implementations,
7 nonstandard_style,
8 unused_qualifications
9)]
10#![warn(missing_docs, clippy::nursery, clippy::cargo)]
11#![allow(clippy::must_use_candidate, clippy::module_name_repetitions)]
12#![doc = include_str!("../README.md")]
13use proc_macro::TokenStream;
14use proc_macro2::TokenStream as TokenStream2;
15use quote::{quote, TokenStreamExt};
16use syn::{
17 parse::{Parse, ParseStream},
18 parse_macro_input,
19 token::Eq,
20 AttrStyle, ExprPath, ItemFn, Path, Token,
21};
22
23struct Args {
24 harness: Option<Path>,
25}
26
27impl Parse for Args {
28 fn parse(input: ParseStream) -> syn::Result<Self> {
29 if input.is_empty() {
30 return Ok(Self { harness: None });
31 }
32
33 let harness_ident: ExprPath = input.parse()?;
34 if !harness_ident.path.is_ident("harness") {
35 Err(input.error(
36 "we only recognize #[test(harness = some::path)], #[test(harness)], and #[test]",
37 ))
38 } else if input.peek(Token![=]) {
39 let Eq { .. } = input.parse()?;
40 let ExprPath { path, .. } = input.parse()?;
41 Ok(Self {
42 harness: Some(path),
43 })
44 } else {
45 Ok(Self {
46 harness: Some(harness_ident.path),
47 })
48 }
49 }
50}
51
52#[proc_macro_attribute]
55pub fn test(args: TokenStream, input: TokenStream) -> TokenStream {
56 let Args { harness } = parse_macro_input!(args as Args);
57 match harness {
58 Some(harness) => with_harness(harness, input),
59 None => without_harness(input),
60 }
61}
62
63fn without_harness(input: TokenStream) -> TokenStream {
64 let input = TokenStream2::from(input);
65 quote! {
66 #[::core::prelude::v1::test]
67 #input
68 }
69 .into()
70}
71
72fn with_harness(harness: Path, input: TokenStream) -> TokenStream {
73 let ItemFn {
74 attrs,
75 sig,
76 block,
77 vis,
78 } = parse_macro_input!(input as ItemFn);
79
80 let mut outer = TokenStream2::new();
81 outer.append_all(
82 attrs
83 .iter()
84 .filter(|attr| matches!(attr.style, AttrStyle::Outer)),
85 );
86
87 let mut inner = TokenStream2::new();
88 inner.append_all(
89 attrs
90 .iter()
91 .filter(|attr| matches!(attr.style, AttrStyle::Inner(_))),
92 );
93
94 let ident = &sig.ident;
95 let output = if attrs.iter().any(|x| x.meta.path().is_ident("should_panic")) {
96 quote!()
97 } else {
98 quote!(-> impl ::std::process::Termination)
99 };
100
101 quote! {
102 #[::core::prelude::v1::test]
103 #outer
104 #vis fn #ident() #output {
105 #inner
106 #sig #block
107 #harness(#ident)
108 }
109 }
110 .into()
111}