1 use super::*;
2 
3 use std::iter;
4 
5 /// Doc-comments are promoted to attributes that have `is_sugared_doc` = true
6 #[derive(Debug, Clone, Eq, PartialEq, Hash)]
7 pub struct Attribute {
8     pub style: AttrStyle,
9     pub value: MetaItem,
10     pub is_sugared_doc: bool,
11 }
12 
13 impl Attribute {
name(&self) -> &str14     pub fn name(&self) -> &str {
15         self.value.name()
16     }
17 }
18 
19 /// Distinguishes between Attributes that decorate items and Attributes that
20 /// are contained as statements within items. These two cases need to be
21 /// distinguished for pretty-printing.
22 #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
23 pub enum AttrStyle {
24     /// Attribute of the form `#![...]`.
25     Outer,
26 
27     /// Attribute of the form `#[...]`.
28     Inner,
29 }
30 
31 /// A compile-time attribute item.
32 ///
33 /// E.g. `#[test]`, `#[derive(..)]` or `#[feature = "foo"]`
34 #[derive(Debug, Clone, Eq, PartialEq, Hash)]
35 pub enum MetaItem {
36     /// Word meta item.
37     ///
38     /// E.g. `test` as in `#[test]`
39     Word(Ident),
40 
41     /// List meta item.
42     ///
43     /// E.g. `derive(..)` as in `#[derive(..)]`
44     List(Ident, Vec<NestedMetaItem>),
45 
46     /// Name-value meta item.
47     ///
48     /// E.g. `feature = "foo"` as in `#[feature = "foo"]`
49     NameValue(Ident, Lit),
50 }
51 
52 impl MetaItem {
53     /// Name of the item.
54     ///
55     /// E.g. `test` as in `#[test]`, `derive` as in `#[derive(..)]`, and
56     /// `feature` as in `#[feature = "foo"]`.
name(&self) -> &str57     pub fn name(&self) -> &str {
58         match *self {
59             MetaItem::Word(ref name) |
60             MetaItem::List(ref name, _) |
61             MetaItem::NameValue(ref name, _) => name.as_ref(),
62         }
63     }
64 }
65 
66 /// Possible values inside of compile-time attribute lists.
67 ///
68 /// E.g. the '..' in `#[name(..)]`.
69 #[derive(Debug, Clone, Eq, PartialEq, Hash)]
70 pub enum NestedMetaItem {
71     /// A full `MetaItem`.
72     ///
73     /// E.g. `Copy` in `#[derive(Copy)]` would be a `MetaItem::Word(Ident::from("Copy"))`.
74     MetaItem(MetaItem),
75 
76     /// A Rust literal.
77     ///
78     /// E.g. `"name"` in `#[rename("name")]`.
79     Literal(Lit),
80 }
81 
82 pub trait FilterAttrs<'a> {
83     type Ret: Iterator<Item = &'a Attribute>;
84 
outer(self) -> Self::Ret85     fn outer(self) -> Self::Ret;
inner(self) -> Self::Ret86     fn inner(self) -> Self::Ret;
87 }
88 
89 impl<'a, T> FilterAttrs<'a> for T
90     where T: IntoIterator<Item = &'a Attribute>
91 {
92     type Ret = iter::Filter<T::IntoIter, fn(&&Attribute) -> bool>;
93 
outer(self) -> Self::Ret94     fn outer(self) -> Self::Ret {
95         fn is_outer(attr: &&Attribute) -> bool {
96             attr.style == AttrStyle::Outer
97         }
98         self.into_iter().filter(is_outer)
99     }
100 
inner(self) -> Self::Ret101     fn inner(self) -> Self::Ret {
102         fn is_inner(attr: &&Attribute) -> bool {
103             attr.style == AttrStyle::Inner
104         }
105         self.into_iter().filter(is_inner)
106     }
107 }
108 
109 #[cfg(feature = "parsing")]
110 pub mod parsing {
111     use super::*;
112     use ident::parsing::ident;
113     use lit::parsing::lit;
114     use synom::space::{block_comment, whitespace};
115 
116     #[cfg(feature = "full")]
117     named!(pub inner_attr -> Attribute, alt!(
118         do_parse!(
119             punct!("#") >>
120             punct!("!") >>
121             punct!("[") >>
122             meta_item: meta_item >>
123             punct!("]") >>
124             (Attribute {
125                 style: AttrStyle::Inner,
126                 value: meta_item,
127                 is_sugared_doc: false,
128             })
129         )
130         |
131         do_parse!(
132             punct!("//!") >>
133             content: take_until!("\n") >>
134             (Attribute {
135                 style: AttrStyle::Inner,
136                 value: MetaItem::NameValue(
137                     "doc".into(),
138                     format!("//!{}", content).into(),
139                 ),
140                 is_sugared_doc: true,
141             })
142         )
143         |
144         do_parse!(
145             option!(whitespace) >>
146             peek!(tag!("/*!")) >>
147             com: block_comment >>
148             (Attribute {
149                 style: AttrStyle::Inner,
150                 value: MetaItem::NameValue(
151                     "doc".into(),
152                     com.into(),
153                 ),
154                 is_sugared_doc: true,
155             })
156         )
157     ));
158 
159     named!(pub outer_attr -> Attribute, alt!(
160         do_parse!(
161             punct!("#") >>
162             punct!("[") >>
163             meta_item: meta_item >>
164             punct!("]") >>
165             (Attribute {
166                 style: AttrStyle::Outer,
167                 value: meta_item,
168                 is_sugared_doc: false,
169             })
170         )
171         |
172         do_parse!(
173             punct!("///") >>
174             not!(tag!("/")) >>
175             content: take_until!("\n") >>
176             (Attribute {
177                 style: AttrStyle::Outer,
178                 value: MetaItem::NameValue(
179                     "doc".into(),
180                     format!("///{}", content).into(),
181                 ),
182                 is_sugared_doc: true,
183             })
184         )
185         |
186         do_parse!(
187             option!(whitespace) >>
188             peek!(tuple!(tag!("/**"), not!(tag!("*")))) >>
189             com: block_comment >>
190             (Attribute {
191                 style: AttrStyle::Outer,
192                 value: MetaItem::NameValue(
193                     "doc".into(),
194                     com.into(),
195                 ),
196                 is_sugared_doc: true,
197             })
198         )
199     ));
200 
201     named!(meta_item -> MetaItem, alt!(
202         do_parse!(
203             id: ident >>
204             punct!("(") >>
205             inner: terminated_list!(punct!(","), nested_meta_item) >>
206             punct!(")") >>
207             (MetaItem::List(id, inner))
208         )
209         |
210         do_parse!(
211             name: ident >>
212             punct!("=") >>
213             value: lit >>
214             (MetaItem::NameValue(name, value))
215         )
216         |
217         map!(ident, MetaItem::Word)
218     ));
219 
220     named!(nested_meta_item -> NestedMetaItem, alt!(
221         meta_item => { NestedMetaItem::MetaItem }
222         |
223         lit => { NestedMetaItem::Literal }
224     ));
225 }
226 
227 #[cfg(feature = "printing")]
228 mod printing {
229     use super::*;
230     use lit::{Lit, StrStyle};
231     use quote::{Tokens, ToTokens};
232 
233     impl ToTokens for Attribute {
to_tokens(&self, tokens: &mut Tokens)234         fn to_tokens(&self, tokens: &mut Tokens) {
235             if let Attribute { style,
236                                value: MetaItem::NameValue(ref name,
237                                                    Lit::Str(ref value, StrStyle::Cooked)),
238                                is_sugared_doc: true } = *self {
239                 if name == "doc" {
240                     match style {
241                         AttrStyle::Inner if value.starts_with("//!") => {
242                             tokens.append(&format!("{}\n", value));
243                             return;
244                         }
245                         AttrStyle::Inner if value.starts_with("/*!") => {
246                             tokens.append(value);
247                             return;
248                         }
249                         AttrStyle::Outer if value.starts_with("///") => {
250                             tokens.append(&format!("{}\n", value));
251                             return;
252                         }
253                         AttrStyle::Outer if value.starts_with("/**") => {
254                             tokens.append(value);
255                             return;
256                         }
257                         _ => {}
258                     }
259                 }
260             }
261 
262             tokens.append("#");
263             if let AttrStyle::Inner = self.style {
264                 tokens.append("!");
265             }
266             tokens.append("[");
267             self.value.to_tokens(tokens);
268             tokens.append("]");
269         }
270     }
271 
272     impl ToTokens for MetaItem {
to_tokens(&self, tokens: &mut Tokens)273         fn to_tokens(&self, tokens: &mut Tokens) {
274             match *self {
275                 MetaItem::Word(ref ident) => {
276                     ident.to_tokens(tokens);
277                 }
278                 MetaItem::List(ref ident, ref inner) => {
279                     ident.to_tokens(tokens);
280                     tokens.append("(");
281                     tokens.append_separated(inner, ",");
282                     tokens.append(")");
283                 }
284                 MetaItem::NameValue(ref name, ref value) => {
285                     name.to_tokens(tokens);
286                     tokens.append("=");
287                     value.to_tokens(tokens);
288                 }
289             }
290         }
291     }
292 
293     impl ToTokens for NestedMetaItem {
to_tokens(&self, tokens: &mut Tokens)294         fn to_tokens(&self, tokens: &mut Tokens) {
295             match *self {
296                 NestedMetaItem::MetaItem(ref nested) => {
297                     nested.to_tokens(tokens);
298                 }
299                 NestedMetaItem::Literal(ref lit) => {
300                     lit.to_tokens(tokens);
301                 }
302             }
303         }
304     }
305 }
306