1 use proc_macro2::TokenStream;
2 use quote::{quote, ToTokens};
3 use syn::{Attribute, LitStr, Meta, Result};
4 
5 #[derive(Clone)]
6 pub(crate) struct Display {
7     pub(crate) fmt: LitStr,
8     pub(crate) args: TokenStream,
9 }
10 
11 pub(crate) struct VariantDisplay {
12     pub(crate) r#enum: Option<Display>,
13     pub(crate) variant: Display,
14 }
15 
16 impl ToTokens for Display {
to_tokens(&self, tokens: &mut TokenStream)17     fn to_tokens(&self, tokens: &mut TokenStream) {
18         let fmt = &self.fmt;
19         let args = &self.args;
20         tokens.extend(quote! {
21             write!(formatter, #fmt #args)
22         });
23     }
24 }
25 
26 impl ToTokens for VariantDisplay {
to_tokens(&self, tokens: &mut TokenStream)27     fn to_tokens(&self, tokens: &mut TokenStream) {
28         if let Some(ref r#enum) = self.r#enum {
29             r#enum.to_tokens(tokens);
30             tokens.extend(quote! { ?; write!(formatter, ": ")?; });
31         }
32         self.variant.to_tokens(tokens);
33     }
34 }
35 
36 pub(crate) struct AttrsHelper {
37     ignore_extra_doc_attributes: bool,
38     prefix_enum_doc_attributes: bool,
39 }
40 
41 impl AttrsHelper {
new(attrs: &[Attribute]) -> Self42     pub(crate) fn new(attrs: &[Attribute]) -> Self {
43         let ignore_extra_doc_attributes = attrs
44             .iter()
45             .any(|attr| attr.path().is_ident("ignore_extra_doc_attributes"));
46         let prefix_enum_doc_attributes = attrs
47             .iter()
48             .any(|attr| attr.path().is_ident("prefix_enum_doc_attributes"));
49 
50         Self {
51             ignore_extra_doc_attributes,
52             prefix_enum_doc_attributes,
53         }
54     }
55 
display(&self, attrs: &[Attribute]) -> Result<Option<Display>>56     pub(crate) fn display(&self, attrs: &[Attribute]) -> Result<Option<Display>> {
57         let displaydoc_attr = attrs.iter().find(|attr| attr.path().is_ident("displaydoc"));
58 
59         if let Some(displaydoc_attr) = displaydoc_attr {
60             let lit = displaydoc_attr
61                 .parse_args()
62                 .expect("#[displaydoc(\"foo\")] must contain string arguments");
63             let mut display = Display {
64                 fmt: lit,
65                 args: TokenStream::new(),
66             };
67 
68             display.expand_shorthand();
69             return Ok(Some(display));
70         }
71 
72         let num_doc_attrs = attrs
73             .iter()
74             .filter(|attr| attr.path().is_ident("doc"))
75             .count();
76 
77         if !self.ignore_extra_doc_attributes && num_doc_attrs > 1 {
78             panic!("Multi-line comments are disabled by default by displaydoc. Please consider using block doc comments (/** */) or adding the #[ignore_extra_doc_attributes] attribute to your type next to the derive.");
79         }
80 
81         for attr in attrs {
82             if attr.path().is_ident("doc") {
83                 let lit = match &attr.meta {
84                     Meta::NameValue(syn::MetaNameValue {
85                         value:
86                             syn::Expr::Lit(syn::ExprLit {
87                                 lit: syn::Lit::Str(lit),
88                                 ..
89                             }),
90                         ..
91                     }) => lit,
92                     _ => unimplemented!(),
93                 };
94 
95                 // Make an attempt at cleaning up multiline doc comments.
96                 let doc_str = lit
97                     .value()
98                     .lines()
99                     .map(|line| line.trim().trim_start_matches('*').trim())
100                     .collect::<Vec<&str>>()
101                     .join("\n");
102 
103                 let lit = LitStr::new(doc_str.trim(), lit.span());
104 
105                 let mut display = Display {
106                     fmt: lit,
107                     args: TokenStream::new(),
108                 };
109 
110                 display.expand_shorthand();
111                 return Ok(Some(display));
112             }
113         }
114 
115         Ok(None)
116     }
117 
display_with_input( &self, r#enum: &[Attribute], variant: &[Attribute], ) -> Result<Option<VariantDisplay>>118     pub(crate) fn display_with_input(
119         &self,
120         r#enum: &[Attribute],
121         variant: &[Attribute],
122     ) -> Result<Option<VariantDisplay>> {
123         let r#enum = if self.prefix_enum_doc_attributes {
124             let result = self
125                 .display(r#enum)?
126                 .expect("Missing doc comment on enum with #[prefix_enum_doc_attributes]. Please remove the attribute or add a doc comment to the enum itself.");
127 
128             Some(result)
129         } else {
130             None
131         };
132 
133         Ok(self
134             .display(variant)?
135             .map(|variant| VariantDisplay { r#enum, variant }))
136     }
137 }
138