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