1 use crate::{Error, Result};
2 use std::fmt;
3 use syn::punctuated::Pair;
4 use syn::spanned::Spanned;
5 use syn::{token, Attribute, Meta, MetaList, Path};
6 
7 /// Try to parse an attribute into a meta list. Path-type meta values are accepted and returned
8 /// as empty lists with their passed-in path. Name-value meta values and non-meta attributes
9 /// will cause errors to be returned.
parse_attribute_to_meta_list(attr: &Attribute) -> Result<MetaList>10 pub fn parse_attribute_to_meta_list(attr: &Attribute) -> Result<MetaList> {
11     match &attr.meta {
12         Meta::List(list) => Ok(list.clone()),
13         Meta::NameValue(nv) => Err(Error::custom(format!(
14             "Name-value arguments are not supported. Use #[{}(...)]",
15             DisplayPath(&nv.path)
16         ))
17         .with_span(&nv)),
18         Meta::Path(path) => Ok(MetaList {
19             path: path.clone(),
20             delimiter: syn::MacroDelimiter::Paren(token::Paren {
21                 span: {
22                     let mut group = proc_macro2::Group::new(
23                         proc_macro2::Delimiter::None,
24                         proc_macro2::TokenStream::new(),
25                     );
26                     group.set_span(attr.span());
27                     group.delim_span()
28                 },
29             }),
30             tokens: Default::default(),
31         }),
32     }
33 }
34 
35 struct DisplayPath<'a>(&'a Path);
36 
37 impl fmt::Display for DisplayPath<'_> {
fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result38     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
39         let path = self.0;
40         if path.leading_colon.is_some() {
41             write!(f, "::")?;
42         }
43         for segment in path.segments.pairs() {
44             match segment {
45                 Pair::Punctuated(segment, _) => write!(f, "{}::", segment.ident)?,
46                 Pair::End(segment) => segment.ident.fmt(f)?,
47             }
48         }
49 
50         Ok(())
51     }
52 }
53 
54 #[cfg(test)]
55 mod tests {
56     use super::parse_attribute_to_meta_list;
57     use crate::ast::NestedMeta;
58     use syn::spanned::Spanned;
59     use syn::{parse_quote, Ident};
60 
61     #[test]
parse_list()62     fn parse_list() {
63         let meta = parse_attribute_to_meta_list(&parse_quote!(#[bar(baz = 4)])).unwrap();
64         let nested_meta = NestedMeta::parse_meta_list(meta.tokens).unwrap();
65         assert_eq!(nested_meta.len(), 1);
66     }
67 
68     #[test]
parse_path_returns_empty_list()69     fn parse_path_returns_empty_list() {
70         let meta = parse_attribute_to_meta_list(&parse_quote!(#[bar])).unwrap();
71         let nested_meta = NestedMeta::parse_meta_list(meta.tokens).unwrap();
72         assert!(meta.path.is_ident(&Ident::new("bar", meta.path.span())));
73         assert!(nested_meta.is_empty());
74     }
75 
76     #[test]
parse_name_value_returns_error()77     fn parse_name_value_returns_error() {
78         parse_attribute_to_meta_list(&parse_quote!(#[bar = 4])).unwrap_err();
79     }
80 
81     #[test]
parse_name_value_error_includes_example()82     fn parse_name_value_error_includes_example() {
83         let err = parse_attribute_to_meta_list(&parse_quote!(#[bar = 4])).unwrap_err();
84         assert!(err.to_string().contains("#[bar(...)]"));
85     }
86 }
87