codama_syn_helpers/extensions/
attribute.rs

1use super::PathExtension;
2use syn::{punctuated::Punctuated, Attribute};
3
4pub trait AttributeExtension {
5    fn get_self(&self) -> &Attribute;
6
7    /// Parse all arguments as comma-separated types.
8    fn parse_comma_args<T: syn::parse::Parse>(&self) -> syn::Result<Vec<T>> {
9        self.get_self()
10            .parse_args_with(Punctuated::<T, syn::Token![,]>::parse_terminated)
11            .map(|metas| metas.into_iter().collect::<Vec<_>>())
12    }
13
14    /// Unwrap the feature flag from the attribute.
15    /// E.g. `#[cfg_attr(feature = "some_feature", derive(Debug))]`
16    /// becomes the syn::Meta defined as `derive(Debug)`.
17    fn unfeatured(&self) -> Option<Attribute> {
18        let this = self.get_self();
19        if !this.path().is_strict("cfg_attr") {
20            return None;
21        }
22        let metas = this.parse_comma_args::<syn::Meta>().ok()?;
23        let [feature, inner_meta] = metas.as_slice() else {
24            return None;
25        };
26        match feature {
27            syn::Meta::NameValue(m) if m.path.is_strict("feature") => (),
28            _ => return None,
29        }
30        Some(Attribute {
31            pound_token: this.pound_token,
32            style: this.style,
33            bracket_token: this.bracket_token,
34            meta: inner_meta.clone(),
35        })
36    }
37}
38
39impl AttributeExtension for Attribute {
40    fn get_self(&self) -> &Attribute {
41        self
42    }
43}
44
45#[cfg(test)]
46mod tests {
47    use super::*;
48    use quote::{quote, ToTokens};
49    use syn::parse_quote;
50
51    #[test]
52    fn parse_comma_args_ok() {
53        let attribute: Attribute = parse_quote! { #[foo(42, "bar")] };
54        let args = attribute.parse_comma_args::<syn::Lit>().unwrap();
55        assert_eq!(args.len(), 2);
56    }
57
58    #[test]
59    fn parse_comma_args_err() {
60        let attribute: Attribute = parse_quote! { #[foo] };
61        let args = attribute.parse_comma_args::<syn::Path>();
62        assert!(args.is_err());
63    }
64
65    #[test]
66    fn unfeatured() {
67        let attribute: Attribute =
68            parse_quote! { #[cfg_attr(feature = "some_feature", derive(Debug))] };
69        let unfeatured = attribute.unfeatured();
70        assert_eq!(
71            unfeatured.to_token_stream().to_string(),
72            quote! { #[derive(Debug)] }.to_string()
73        );
74    }
75
76    #[test]
77    fn unfeatured_unchanged() {
78        let attribute: Attribute = parse_quote! { #[derive(Debug)] };
79        let unfeatured = attribute.unfeatured();
80        assert_eq!(unfeatured, None);
81    }
82}