rust/clippy_utils/src/attrs.rs

160 lines
5.5 KiB
Rust

use rustc_ast::ast;
use rustc_ast::attr;
use rustc_errors::Applicability;
use rustc_session::Session;
use rustc_span::sym;
use std::str::FromStr;
/// Deprecation status of attributes known by Clippy.
pub enum DeprecationStatus {
/// Attribute is deprecated
Deprecated,
/// Attribute is deprecated and was replaced by the named attribute
Replaced(&'static str),
None,
}
#[rustfmt::skip]
pub const BUILTIN_ATTRIBUTES: &[(&str, DeprecationStatus)] = &[
("author", DeprecationStatus::None),
("version", DeprecationStatus::None),
("cognitive_complexity", DeprecationStatus::None),
("cyclomatic_complexity", DeprecationStatus::Replaced("cognitive_complexity")),
("dump", DeprecationStatus::None),
("msrv", DeprecationStatus::None),
("has_significant_drop", DeprecationStatus::None),
];
pub struct LimitStack {
stack: Vec<u64>,
}
impl Drop for LimitStack {
fn drop(&mut self) {
assert_eq!(self.stack.len(), 1);
}
}
impl LimitStack {
#[must_use]
pub fn new(limit: u64) -> Self {
Self { stack: vec![limit] }
}
pub fn limit(&self) -> u64 {
*self.stack.last().expect("there should always be a value in the stack")
}
pub fn push_attrs(&mut self, sess: &Session, attrs: &[ast::Attribute], name: &'static str) {
let stack = &mut self.stack;
parse_attrs(sess, attrs, name, |val| stack.push(val));
}
pub fn pop_attrs(&mut self, sess: &Session, attrs: &[ast::Attribute], name: &'static str) {
let stack = &mut self.stack;
parse_attrs(sess, attrs, name, |val| assert_eq!(stack.pop(), Some(val)));
}
}
pub fn get_attr<'a>(
sess: &'a Session,
attrs: &'a [ast::Attribute],
name: &'static str,
) -> impl Iterator<Item = &'a ast::Attribute> {
attrs.iter().filter(move |attr| {
let attr = if let ast::AttrKind::Normal(ref normal) = attr.kind {
&normal.item
} else {
return false;
};
let attr_segments = &attr.path.segments;
if attr_segments.len() == 2 && attr_segments[0].ident.name == sym::clippy {
BUILTIN_ATTRIBUTES
.iter()
.find_map(|&(builtin_name, ref deprecation_status)| {
if attr_segments[1].ident.name.as_str() == builtin_name {
Some(deprecation_status)
} else {
None
}
})
.map_or_else(
|| {
sess.span_err(attr_segments[1].ident.span, "usage of unknown attribute");
false
},
|deprecation_status| {
let mut diag =
sess.struct_span_err(attr_segments[1].ident.span, "usage of deprecated attribute");
match *deprecation_status {
DeprecationStatus::Deprecated => {
diag.emit();
false
},
DeprecationStatus::Replaced(new_name) => {
diag.span_suggestion(
attr_segments[1].ident.span,
"consider using",
new_name,
Applicability::MachineApplicable,
);
diag.emit();
false
},
DeprecationStatus::None => {
diag.cancel();
attr_segments[1].ident.name.as_str() == name
},
}
},
)
} else {
false
}
})
}
fn parse_attrs<F: FnMut(u64)>(sess: &Session, attrs: &[ast::Attribute], name: &'static str, mut f: F) {
for attr in get_attr(sess, attrs, name) {
if let Some(ref value) = attr.value_str() {
if let Ok(value) = FromStr::from_str(value.as_str()) {
f(value);
} else {
sess.span_err(attr.span, "not a number");
}
} else {
sess.span_err(attr.span, "bad clippy attribute");
}
}
}
pub fn get_unique_attr<'a>(
sess: &'a Session,
attrs: &'a [ast::Attribute],
name: &'static str,
) -> Option<&'a ast::Attribute> {
let mut unique_attr: Option<&ast::Attribute> = None;
for attr in get_attr(sess, attrs, name) {
if let Some(duplicate) = unique_attr {
sess.struct_span_err(attr.span, &format!("`{name}` is defined multiple times"))
.span_note(duplicate.span, "first definition found here")
.emit();
} else {
unique_attr = Some(attr);
}
}
unique_attr
}
/// Return true if the attributes contain any of `proc_macro`,
/// `proc_macro_derive` or `proc_macro_attribute`, false otherwise
pub fn is_proc_macro(sess: &Session, attrs: &[ast::Attribute]) -> bool {
attrs.iter().any(|attr| sess.is_proc_macro_attr(attr))
}
/// Return true if the attributes contain `#[doc(hidden)]`
pub fn is_doc_hidden(attrs: &[ast::Attribute]) -> bool {
attrs
.iter()
.filter(|attr| attr.has_name(sym::doc))
.filter_map(ast::Attribute::meta_item_list)
.any(|l| attr::list_contains_name(&l, sym::hidden))
}