| use rustc_ast::ast::Safety; |
| use rustc_hir::AttrStyle; |
| use rustc_span::Symbol; |
| |
| /// A template that the attribute input must match. |
| /// Only top-level shape (`#[attr]` vs `#[attr(...)]` vs `#[attr = ...]`) is considered now. |
| #[derive(Clone, Copy, Default)] |
| pub struct AttributeTemplate { |
| /// If `true`, the attribute is allowed to be a bare word like `#[test]`. |
| pub word: bool, |
| /// If `Some`, the attribute is allowed to take a list of items like `#[allow(..)]`. |
| pub list: Option<&'static [&'static str]>, |
| /// If non-empty, the attribute is allowed to take a list containing exactly |
| /// one of the listed words, like `#[coverage(off)]`. |
| pub one_of: &'static [Symbol], |
| /// If `Some`, the attribute is allowed to be a name/value pair where the |
| /// value is a string, like `#[must_use = "reason"]`. |
| pub name_value_str: Option<&'static [&'static str]>, |
| /// A link to the document for this attribute. |
| pub docs: Option<&'static str>, |
| } |
| |
| pub enum AttrSuggestionStyle { |
| /// The suggestion is styled for a normal attribute. |
| /// The `AttrStyle` determines whether this is an inner or outer attribute. |
| Attribute(AttrStyle), |
| /// The suggestion is styled for an attribute embedded into another attribute. |
| /// For example, attributes inside `#[cfg_attr(true, attr(...)]`. |
| EmbeddedAttribute, |
| /// The suggestion is styled for macros that are parsed with attribute parsers. |
| /// For example, the `cfg!(predicate)` macro. |
| Macro, |
| } |
| |
| impl AttributeTemplate { |
| pub fn suggestions( |
| &self, |
| style: AttrSuggestionStyle, |
| safety: Safety, |
| name: impl std::fmt::Display, |
| ) -> Vec<String> { |
| let (start, macro_call, end) = match style { |
| AttrSuggestionStyle::Attribute(AttrStyle::Outer) => ("#[", "", "]"), |
| AttrSuggestionStyle::Attribute(AttrStyle::Inner) => ("#![", "", "]"), |
| AttrSuggestionStyle::Macro => ("", "!", ""), |
| AttrSuggestionStyle::EmbeddedAttribute => ("", "", ""), |
| }; |
| |
| let mut suggestions = vec![]; |
| |
| let (safety_start, safety_end) = match safety { |
| Safety::Unsafe(_) => ("unsafe(", ")"), |
| _ => ("", ""), |
| }; |
| |
| if self.word { |
| debug_assert!(macro_call.is_empty(), "Macro suggestions use list style"); |
| suggestions.push(format!("{start}{safety_start}{name}{safety_end}{end}")); |
| } |
| if let Some(descr) = self.list { |
| for descr in descr { |
| suggestions.push(format!( |
| "{start}{safety_start}{name}{macro_call}({descr}){safety_end}{end}" |
| )); |
| } |
| } |
| suggestions.extend( |
| self.one_of |
| .iter() |
| .map(|&word| format!("{start}{safety_start}{name}({word}){safety_end}{end}")), |
| ); |
| if let Some(descr) = self.name_value_str { |
| debug_assert!(macro_call.is_empty(), "Macro suggestions use list style"); |
| for descr in descr { |
| suggestions |
| .push(format!("{start}{safety_start}{name} = \"{descr}\"{safety_end}{end}")); |
| } |
| } |
| suggestions.sort(); |
| |
| suggestions |
| } |
| } |
| |
| /// A convenience macro for constructing attribute templates. |
| /// E.g., `template!(Word, List: "description")` means that the attribute |
| /// supports forms `#[attr]` and `#[attr(description)]`. |
| #[macro_export] |
| macro_rules! template { |
| (Word) => { $crate::template!(@ true, None, &[], None, None) }; |
| (Word, $link: literal) => { $crate::template!(@ true, None, &[], None, Some($link)) }; |
| (List: $descr: expr) => { $crate::template!(@ false, Some($descr), &[], None, None) }; |
| (List: $descr: expr, $link: literal) => { $crate::template!(@ false, Some($descr), &[], None, Some($link)) }; |
| (OneOf: $one_of: expr) => { $crate::template!(@ false, None, $one_of, None, None) }; |
| (NameValueStr: [$($descr: literal),* $(,)?]) => { $crate::template!(@ false, None, &[], Some(&[$($descr,)*]), None) }; |
| (NameValueStr: [$($descr: literal),* $(,)?], $link: literal) => { $crate::template!(@ false, None, &[], Some(&[$($descr,)*]), Some($link)) }; |
| (NameValueStr: $descr: literal) => { $crate::template!(@ false, None, &[], Some(&[$descr]), None) }; |
| (NameValueStr: $descr: literal, $link: literal) => { $crate::template!(@ false, None, &[], Some(&[$descr]), Some($link)) }; |
| (Word, List: $descr: expr) => { $crate::template!(@ true, Some($descr), &[], None, None) }; |
| (Word, List: $descr: expr, $link: literal) => { $crate::template!(@ true, Some($descr), &[], None, Some($link)) }; |
| (Word, NameValueStr: $descr: expr) => { $crate::template!(@ true, None, &[], Some(&[$descr]), None) }; |
| (Word, NameValueStr: $descr: expr, $link: literal) => { $crate::template!(@ true, None, &[], Some(&[$descr]), Some($link)) }; |
| (List: $descr1: expr, NameValueStr: $descr2: expr) => { |
| $crate::template!(@ false, Some($descr1), &[], Some(&[$descr2]), None) |
| }; |
| (List: $descr1: expr, NameValueStr: $descr2: expr, $link: literal) => { |
| $crate::template!(@ false, Some($descr1), &[], Some(&[$descr2]), Some($link)) |
| }; |
| (Word, List: $descr1: expr, NameValueStr: $descr2: expr) => { |
| $crate::template!(@ true, Some($descr1), &[], Some(&[$descr2]), None) |
| }; |
| (Word, List: $descr1: expr, NameValueStr: $descr2: expr, $link: literal) => { |
| $crate::template!(@ true, Some($descr1), &[], Some(&[$descr2]), Some($link)) |
| }; |
| (@ $word: expr, $list: expr, $one_of: expr, $name_value_str: expr, $link: expr) => { $crate::AttributeTemplate { |
| word: $word, list: $list, one_of: $one_of, name_value_str: $name_value_str, docs: $link, |
| } }; |
| } |