Refactor lints in clippy_lints::attrs into separate submodules/files
This commit is contained in:
parent
346b094a11
commit
1580af6b55
File diff suppressed because it is too large
Load Diff
37
clippy_lints/src/attrs/allow_attributes_without_reason.rs
Normal file
37
clippy_lints/src/attrs/allow_attributes_without_reason.rs
Normal file
@ -0,0 +1,37 @@
|
||||
use super::{Attribute, ALLOW_ATTRIBUTES_WITHOUT_REASON};
|
||||
use clippy_utils::diagnostics::span_lint_and_help;
|
||||
use clippy_utils::is_from_proc_macro;
|
||||
use rustc_ast::{MetaItemKind, NestedMetaItem};
|
||||
use rustc_lint::{LateContext, LintContext};
|
||||
use rustc_middle::lint::in_external_macro;
|
||||
use rustc_span::sym;
|
||||
use rustc_span::symbol::Symbol;
|
||||
|
||||
pub(super) fn check<'cx>(cx: &LateContext<'cx>, name: Symbol, items: &[NestedMetaItem], attr: &'cx Attribute) {
|
||||
// Check for the feature
|
||||
if !cx.tcx.features().lint_reasons {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if the reason is present
|
||||
if let Some(item) = items.last().and_then(NestedMetaItem::meta_item)
|
||||
&& let MetaItemKind::NameValue(_) = &item.kind
|
||||
&& item.path == sym::reason
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if the attribute is in an external macro and therefore out of the developer's control
|
||||
if in_external_macro(cx.sess(), attr.span) || is_from_proc_macro(cx, &attr) {
|
||||
return;
|
||||
}
|
||||
|
||||
span_lint_and_help(
|
||||
cx,
|
||||
ALLOW_ATTRIBUTES_WITHOUT_REASON,
|
||||
attr.span,
|
||||
&format!("`{}` attribute without specifying a reason", name.as_str()),
|
||||
None,
|
||||
"try adding a reason at the end with `, reason = \"..\"`",
|
||||
);
|
||||
}
|
44
clippy_lints/src/attrs/blanket_clippy_restriction_lints.rs
Normal file
44
clippy_lints/src/attrs/blanket_clippy_restriction_lints.rs
Normal file
@ -0,0 +1,44 @@
|
||||
use super::utils::extract_clippy_lint;
|
||||
use super::BLANKET_CLIPPY_RESTRICTION_LINTS;
|
||||
use clippy_utils::diagnostics::{span_lint_and_help, span_lint_and_then};
|
||||
use rustc_ast::NestedMetaItem;
|
||||
use rustc_lint::{LateContext, Level, LintContext};
|
||||
use rustc_span::symbol::Symbol;
|
||||
use rustc_span::{sym, DUMMY_SP};
|
||||
|
||||
pub(super) fn check(cx: &LateContext<'_>, name: Symbol, items: &[NestedMetaItem]) {
|
||||
for lint in items {
|
||||
if let Some(lint_name) = extract_clippy_lint(lint) {
|
||||
if lint_name.as_str() == "restriction" && name != sym::allow {
|
||||
span_lint_and_help(
|
||||
cx,
|
||||
BLANKET_CLIPPY_RESTRICTION_LINTS,
|
||||
lint.span(),
|
||||
"`clippy::restriction` is not meant to be enabled as a group",
|
||||
None,
|
||||
"enable the restriction lints you need individually",
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn check_command_line(cx: &LateContext<'_>) {
|
||||
for (name, level) in &cx.sess().opts.lint_opts {
|
||||
if name == "clippy::restriction" && *level > Level::Allow {
|
||||
span_lint_and_then(
|
||||
cx,
|
||||
BLANKET_CLIPPY_RESTRICTION_LINTS,
|
||||
DUMMY_SP,
|
||||
"`clippy::restriction` is not meant to be enabled as a group",
|
||||
|diag| {
|
||||
diag.note(format!(
|
||||
"because of the command line `--{} clippy::restriction`",
|
||||
level.as_str()
|
||||
));
|
||||
diag.help("enable the restriction lints you need individually");
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
87
clippy_lints/src/attrs/deprecated_cfg_attr.rs
Normal file
87
clippy_lints/src/attrs/deprecated_cfg_attr.rs
Normal file
@ -0,0 +1,87 @@
|
||||
use super::{unnecessary_clippy_cfg, Attribute, DEPRECATED_CFG_ATTR, DEPRECATED_CLIPPY_CFG_ATTR};
|
||||
use clippy_config::msrvs::{self, Msrv};
|
||||
use clippy_utils::diagnostics::span_lint_and_sugg;
|
||||
use rustc_ast::AttrStyle;
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_lint::EarlyContext;
|
||||
use rustc_span::sym;
|
||||
|
||||
pub(super) fn check(cx: &EarlyContext<'_>, attr: &Attribute, msrv: &Msrv) {
|
||||
// check cfg_attr
|
||||
if attr.has_name(sym::cfg_attr)
|
||||
&& let Some(items) = attr.meta_item_list()
|
||||
&& items.len() == 2
|
||||
&& let Some(feature_item) = items[0].meta_item()
|
||||
{
|
||||
// check for `rustfmt`
|
||||
if feature_item.has_name(sym::rustfmt)
|
||||
&& msrv.meets(msrvs::TOOL_ATTRIBUTES)
|
||||
// check for `rustfmt_skip` and `rustfmt::skip`
|
||||
&& let Some(skip_item) = &items[1].meta_item()
|
||||
&& (skip_item.has_name(sym!(rustfmt_skip))
|
||||
|| skip_item
|
||||
.path
|
||||
.segments
|
||||
.last()
|
||||
.expect("empty path in attribute")
|
||||
.ident
|
||||
.name
|
||||
== sym::skip)
|
||||
// Only lint outer attributes, because custom inner attributes are unstable
|
||||
// Tracking issue: https://github.com/rust-lang/rust/issues/54726
|
||||
&& attr.style == AttrStyle::Outer
|
||||
{
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
DEPRECATED_CFG_ATTR,
|
||||
attr.span,
|
||||
"`cfg_attr` is deprecated for rustfmt and got replaced by tool attributes",
|
||||
"use",
|
||||
"#[rustfmt::skip]".to_string(),
|
||||
Applicability::MachineApplicable,
|
||||
);
|
||||
} else {
|
||||
check_deprecated_cfg_recursively(cx, feature_item);
|
||||
if let Some(behind_cfg_attr) = items[1].meta_item() {
|
||||
unnecessary_clippy_cfg::check(cx, feature_item, behind_cfg_attr, attr);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn check_clippy(cx: &EarlyContext<'_>, attr: &Attribute) {
|
||||
if attr.has_name(sym::cfg)
|
||||
&& let Some(list) = attr.meta_item_list()
|
||||
{
|
||||
for item in list.iter().filter_map(|item| item.meta_item()) {
|
||||
check_deprecated_cfg_recursively(cx, item);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn check_deprecated_cfg_recursively(cx: &EarlyContext<'_>, attr: &rustc_ast::MetaItem) {
|
||||
if let Some(ident) = attr.ident() {
|
||||
if ["any", "all", "not"].contains(&ident.name.as_str()) {
|
||||
let Some(list) = attr.meta_item_list() else { return };
|
||||
for item in list.iter().filter_map(|item| item.meta_item()) {
|
||||
check_deprecated_cfg_recursively(cx, item);
|
||||
}
|
||||
} else {
|
||||
check_cargo_clippy_attr(cx, attr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn check_cargo_clippy_attr(cx: &EarlyContext<'_>, item: &rustc_ast::MetaItem) {
|
||||
if item.has_name(sym::feature) && item.value_str().is_some_and(|v| v.as_str() == "cargo-clippy") {
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
DEPRECATED_CLIPPY_CFG_ATTR,
|
||||
item.span,
|
||||
"`feature = \"cargo-clippy\"` was replaced by `clippy`",
|
||||
"replace with",
|
||||
"clippy".to_string(),
|
||||
Applicability::MachineApplicable,
|
||||
);
|
||||
}
|
||||
}
|
20
clippy_lints/src/attrs/deprecated_semver.rs
Normal file
20
clippy_lints/src/attrs/deprecated_semver.rs
Normal file
@ -0,0 +1,20 @@
|
||||
use super::DEPRECATED_SEMVER;
|
||||
use clippy_utils::diagnostics::span_lint;
|
||||
use rustc_ast::{LitKind, MetaItemLit};
|
||||
use rustc_lint::LateContext;
|
||||
use rustc_span::Span;
|
||||
use semver::Version;
|
||||
|
||||
pub(super) fn check(cx: &LateContext<'_>, span: Span, lit: &MetaItemLit) {
|
||||
if let LitKind::Str(is, _) = lit.kind {
|
||||
if is.as_str() == "TBD" || Version::parse(is.as_str()).is_ok() {
|
||||
return;
|
||||
}
|
||||
}
|
||||
span_lint(
|
||||
cx,
|
||||
DEPRECATED_SEMVER,
|
||||
span,
|
||||
"the since field must contain a semver-compliant version",
|
||||
);
|
||||
}
|
52
clippy_lints/src/attrs/empty_line_after.rs
Normal file
52
clippy_lints/src/attrs/empty_line_after.rs
Normal file
@ -0,0 +1,52 @@
|
||||
use super::{EMPTY_LINE_AFTER_DOC_COMMENTS, EMPTY_LINE_AFTER_OUTER_ATTR};
|
||||
use clippy_utils::diagnostics::span_lint;
|
||||
use clippy_utils::source::{is_present_in_source, snippet_opt, without_block_comments};
|
||||
use rustc_ast::{AttrKind, AttrStyle};
|
||||
use rustc_lint::EarlyContext;
|
||||
use rustc_span::Span;
|
||||
|
||||
/// Check for empty lines after outer attributes.
|
||||
///
|
||||
/// Attributes and documentation comments are both considered outer attributes
|
||||
/// by the AST. However, the average user likely considers them to be different.
|
||||
/// Checking for empty lines after each of these attributes is split into two different
|
||||
/// lints but can share the same logic.
|
||||
pub(super) fn check(cx: &EarlyContext<'_>, item: &rustc_ast::Item) {
|
||||
let mut iter = item.attrs.iter().peekable();
|
||||
while let Some(attr) = iter.next() {
|
||||
if (matches!(attr.kind, AttrKind::Normal(..)) || matches!(attr.kind, AttrKind::DocComment(..)))
|
||||
&& attr.style == AttrStyle::Outer
|
||||
&& is_present_in_source(cx, attr.span)
|
||||
{
|
||||
let begin_of_attr_to_item = Span::new(attr.span.lo(), item.span.lo(), item.span.ctxt(), item.span.parent());
|
||||
let end_of_attr_to_next_attr_or_item = Span::new(
|
||||
attr.span.hi(),
|
||||
iter.peek().map_or(item.span.lo(), |next_attr| next_attr.span.lo()),
|
||||
item.span.ctxt(),
|
||||
item.span.parent(),
|
||||
);
|
||||
|
||||
if let Some(snippet) = snippet_opt(cx, end_of_attr_to_next_attr_or_item) {
|
||||
let lines = snippet.split('\n').collect::<Vec<_>>();
|
||||
let lines = without_block_comments(lines);
|
||||
|
||||
if lines.iter().filter(|l| l.trim().is_empty()).count() > 2 {
|
||||
let (lint_msg, lint_type) = match attr.kind {
|
||||
AttrKind::DocComment(..) => (
|
||||
"found an empty line after a doc comment. \
|
||||
Perhaps you need to use `//!` to make a comment on a module, remove the empty line, or make a regular comment with `//`?",
|
||||
EMPTY_LINE_AFTER_DOC_COMMENTS,
|
||||
),
|
||||
AttrKind::Normal(..) => (
|
||||
"found an empty line after an outer attribute. \
|
||||
Perhaps you forgot to add a `!` to make it an inner attribute?",
|
||||
EMPTY_LINE_AFTER_OUTER_ATTR,
|
||||
),
|
||||
};
|
||||
|
||||
span_lint(cx, lint_type, begin_of_attr_to_item, lint_msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
29
clippy_lints/src/attrs/inline_always.rs
Normal file
29
clippy_lints/src/attrs/inline_always.rs
Normal file
@ -0,0 +1,29 @@
|
||||
use super::utils::is_word;
|
||||
use super::INLINE_ALWAYS;
|
||||
use clippy_utils::diagnostics::span_lint;
|
||||
use rustc_ast::Attribute;
|
||||
use rustc_lint::LateContext;
|
||||
use rustc_span::symbol::Symbol;
|
||||
use rustc_span::{sym, Span};
|
||||
|
||||
pub(super) fn check(cx: &LateContext<'_>, span: Span, name: Symbol, attrs: &[Attribute]) {
|
||||
if span.from_expansion() {
|
||||
return;
|
||||
}
|
||||
|
||||
for attr in attrs {
|
||||
if let Some(values) = attr.meta_item_list() {
|
||||
if values.len() != 1 || !attr.has_name(sym::inline) {
|
||||
continue;
|
||||
}
|
||||
if is_word(&values[0], sym::always) {
|
||||
span_lint(
|
||||
cx,
|
||||
INLINE_ALWAYS,
|
||||
attr.span,
|
||||
&format!("you have declared `#[inline(always)]` on `{name}`. This is usually a bad idea"),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
51
clippy_lints/src/attrs/maybe_misused_cfg.rs
Normal file
51
clippy_lints/src/attrs/maybe_misused_cfg.rs
Normal file
@ -0,0 +1,51 @@
|
||||
use super::{Attribute, MAYBE_MISUSED_CFG};
|
||||
use clippy_utils::diagnostics::span_lint_and_sugg;
|
||||
use rustc_ast::{MetaItemKind, NestedMetaItem};
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_lint::EarlyContext;
|
||||
use rustc_span::sym;
|
||||
|
||||
pub(super) fn check(cx: &EarlyContext<'_>, attr: &Attribute) {
|
||||
if attr.has_name(sym::cfg)
|
||||
&& let Some(items) = attr.meta_item_list()
|
||||
{
|
||||
check_nested_misused_cfg(cx, &items);
|
||||
}
|
||||
}
|
||||
|
||||
fn check_nested_misused_cfg(cx: &EarlyContext<'_>, items: &[NestedMetaItem]) {
|
||||
for item in items {
|
||||
if let NestedMetaItem::MetaItem(meta) = item {
|
||||
if let Some(ident) = meta.ident()
|
||||
&& ident.name.as_str() == "features"
|
||||
&& let Some(val) = meta.value_str()
|
||||
{
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
MAYBE_MISUSED_CFG,
|
||||
meta.span,
|
||||
"'feature' may be misspelled as 'features'",
|
||||
"did you mean",
|
||||
format!("feature = \"{val}\""),
|
||||
Applicability::MaybeIncorrect,
|
||||
);
|
||||
}
|
||||
if let MetaItemKind::List(list) = &meta.kind {
|
||||
check_nested_misused_cfg(cx, list);
|
||||
// If this is not a list, then we check for `cfg(test)`.
|
||||
} else if let Some(ident) = meta.ident()
|
||||
&& matches!(ident.name.as_str(), "tests" | "Test")
|
||||
{
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
MAYBE_MISUSED_CFG,
|
||||
meta.span,
|
||||
&format!("'test' may be misspelled as '{}'", ident.name.as_str()),
|
||||
"did you mean",
|
||||
"test".to_string(),
|
||||
Applicability::MaybeIncorrect,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
90
clippy_lints/src/attrs/mismatched_target_os.rs
Normal file
90
clippy_lints/src/attrs/mismatched_target_os.rs
Normal file
@ -0,0 +1,90 @@
|
||||
use super::{Attribute, MISMATCHED_TARGET_OS};
|
||||
use clippy_utils::diagnostics::span_lint_and_then;
|
||||
use rustc_ast::{MetaItemKind, NestedMetaItem};
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_lint::EarlyContext;
|
||||
use rustc_span::{sym, Span};
|
||||
|
||||
static UNIX_SYSTEMS: &[&str] = &[
|
||||
"android",
|
||||
"dragonfly",
|
||||
"emscripten",
|
||||
"freebsd",
|
||||
"fuchsia",
|
||||
"haiku",
|
||||
"illumos",
|
||||
"ios",
|
||||
"l4re",
|
||||
"linux",
|
||||
"macos",
|
||||
"netbsd",
|
||||
"openbsd",
|
||||
"redox",
|
||||
"solaris",
|
||||
"vxworks",
|
||||
];
|
||||
|
||||
// NOTE: windows is excluded from the list because it's also a valid target family.
|
||||
static NON_UNIX_SYSTEMS: &[&str] = &["hermit", "none", "wasi"];
|
||||
|
||||
pub(super) fn check(cx: &EarlyContext<'_>, attr: &Attribute) {
|
||||
fn find_os(name: &str) -> Option<&'static str> {
|
||||
UNIX_SYSTEMS
|
||||
.iter()
|
||||
.chain(NON_UNIX_SYSTEMS.iter())
|
||||
.find(|&&os| os == name)
|
||||
.copied()
|
||||
}
|
||||
|
||||
fn is_unix(name: &str) -> bool {
|
||||
UNIX_SYSTEMS.iter().any(|&os| os == name)
|
||||
}
|
||||
|
||||
fn find_mismatched_target_os(items: &[NestedMetaItem]) -> Vec<(&str, Span)> {
|
||||
let mut mismatched = Vec::new();
|
||||
|
||||
for item in items {
|
||||
if let NestedMetaItem::MetaItem(meta) = item {
|
||||
match &meta.kind {
|
||||
MetaItemKind::List(list) => {
|
||||
mismatched.extend(find_mismatched_target_os(list));
|
||||
},
|
||||
MetaItemKind::Word => {
|
||||
if let Some(ident) = meta.ident()
|
||||
&& let Some(os) = find_os(ident.name.as_str())
|
||||
{
|
||||
mismatched.push((os, ident.span));
|
||||
}
|
||||
},
|
||||
MetaItemKind::NameValue(..) => {},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mismatched
|
||||
}
|
||||
|
||||
if attr.has_name(sym::cfg)
|
||||
&& let Some(list) = attr.meta_item_list()
|
||||
&& let mismatched = find_mismatched_target_os(&list)
|
||||
&& !mismatched.is_empty()
|
||||
{
|
||||
let mess = "operating system used in target family position";
|
||||
|
||||
span_lint_and_then(cx, MISMATCHED_TARGET_OS, attr.span, mess, |diag| {
|
||||
// Avoid showing the unix suggestion multiple times in case
|
||||
// we have more than one mismatch for unix-like systems
|
||||
let mut unix_suggested = false;
|
||||
|
||||
for (os, span) in mismatched {
|
||||
let sugg = format!("target_os = \"{os}\"");
|
||||
diag.span_suggestion(span, "try", sugg, Applicability::MaybeIncorrect);
|
||||
|
||||
if !unix_suggested && is_unix(os) {
|
||||
diag.help("did you mean `unix`?");
|
||||
unix_suggested = true;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
30
clippy_lints/src/attrs/mixed_attributes_style.rs
Normal file
30
clippy_lints/src/attrs/mixed_attributes_style.rs
Normal file
@ -0,0 +1,30 @@
|
||||
use super::MIXED_ATTRIBUTES_STYLE;
|
||||
use clippy_utils::diagnostics::span_lint;
|
||||
use rustc_ast::AttrStyle;
|
||||
use rustc_lint::EarlyContext;
|
||||
|
||||
pub(super) fn check(cx: &EarlyContext<'_>, item: &rustc_ast::Item) {
|
||||
let mut has_outer = false;
|
||||
let mut has_inner = false;
|
||||
|
||||
for attr in &item.attrs {
|
||||
if attr.span.from_expansion() {
|
||||
continue;
|
||||
}
|
||||
match attr.style {
|
||||
AttrStyle::Inner => has_inner = true,
|
||||
AttrStyle::Outer => has_outer = true,
|
||||
}
|
||||
}
|
||||
if !has_outer || !has_inner {
|
||||
return;
|
||||
}
|
||||
let mut attrs_iter = item.attrs.iter().filter(|attr| !attr.span.from_expansion());
|
||||
let span = attrs_iter.next().unwrap().span;
|
||||
span_lint(
|
||||
cx,
|
||||
MIXED_ATTRIBUTES_STYLE,
|
||||
span.with_hi(attrs_iter.last().unwrap().span.hi()),
|
||||
"item has both inner and outer attributes",
|
||||
);
|
||||
}
|
588
clippy_lints/src/attrs/mod.rs
Normal file
588
clippy_lints/src/attrs/mod.rs
Normal file
@ -0,0 +1,588 @@
|
||||
//! checks for attributes
|
||||
|
||||
mod allow_attributes_without_reason;
|
||||
mod blanket_clippy_restriction_lints;
|
||||
mod deprecated_cfg_attr;
|
||||
mod deprecated_semver;
|
||||
mod empty_line_after;
|
||||
mod inline_always;
|
||||
mod maybe_misused_cfg;
|
||||
mod mismatched_target_os;
|
||||
mod mixed_attributes_style;
|
||||
mod non_minimal_cfg;
|
||||
mod should_panic_without_expect;
|
||||
mod unnecessary_clippy_cfg;
|
||||
mod useless_attribute;
|
||||
mod utils;
|
||||
|
||||
use clippy_config::msrvs::Msrv;
|
||||
use rustc_ast::{Attribute, MetaItemKind, NestedMetaItem};
|
||||
use rustc_hir::{ImplItem, Item, ItemKind, TraitItem};
|
||||
use rustc_lint::{EarlyContext, EarlyLintPass, LateContext, LateLintPass};
|
||||
use rustc_session::{declare_lint_pass, impl_lint_pass};
|
||||
use rustc_span::sym;
|
||||
use utils::{is_lint_level, is_relevant_impl, is_relevant_item, is_relevant_trait};
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
/// Checks for items annotated with `#[inline(always)]`,
|
||||
/// unless the annotated function is empty or simply panics.
|
||||
///
|
||||
/// ### Why is this bad?
|
||||
/// While there are valid uses of this annotation (and once
|
||||
/// you know when to use it, by all means `allow` this lint), it's a common
|
||||
/// newbie-mistake to pepper one's code with it.
|
||||
///
|
||||
/// As a rule of thumb, before slapping `#[inline(always)]` on a function,
|
||||
/// measure if that additional function call really affects your runtime profile
|
||||
/// sufficiently to make up for the increase in compile time.
|
||||
///
|
||||
/// ### Known problems
|
||||
/// False positives, big time. This lint is meant to be
|
||||
/// deactivated by everyone doing serious performance work. This means having
|
||||
/// done the measurement.
|
||||
///
|
||||
/// ### Example
|
||||
/// ```ignore
|
||||
/// #[inline(always)]
|
||||
/// fn not_quite_hot_code(..) { ... }
|
||||
/// ```
|
||||
#[clippy::version = "pre 1.29.0"]
|
||||
pub INLINE_ALWAYS,
|
||||
pedantic,
|
||||
"use of `#[inline(always)]`"
|
||||
}
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
/// Checks for `extern crate` and `use` items annotated with
|
||||
/// lint attributes.
|
||||
///
|
||||
/// This lint permits lint attributes for lints emitted on the items themself.
|
||||
/// For `use` items these lints are:
|
||||
/// * deprecated
|
||||
/// * unreachable_pub
|
||||
/// * unused_imports
|
||||
/// * clippy::enum_glob_use
|
||||
/// * clippy::macro_use_imports
|
||||
/// * clippy::wildcard_imports
|
||||
///
|
||||
/// For `extern crate` items these lints are:
|
||||
/// * `unused_imports` on items with `#[macro_use]`
|
||||
///
|
||||
/// ### Why is this bad?
|
||||
/// Lint attributes have no effect on crate imports. Most
|
||||
/// likely a `!` was forgotten.
|
||||
///
|
||||
/// ### Example
|
||||
/// ```ignore
|
||||
/// #[deny(dead_code)]
|
||||
/// extern crate foo;
|
||||
/// #[forbid(dead_code)]
|
||||
/// use foo::bar;
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```rust,ignore
|
||||
/// #[allow(unused_imports)]
|
||||
/// use foo::baz;
|
||||
/// #[allow(unused_imports)]
|
||||
/// #[macro_use]
|
||||
/// extern crate baz;
|
||||
/// ```
|
||||
#[clippy::version = "pre 1.29.0"]
|
||||
pub USELESS_ATTRIBUTE,
|
||||
correctness,
|
||||
"use of lint attributes on `extern crate` items"
|
||||
}
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
/// Checks for `#[deprecated]` annotations with a `since`
|
||||
/// field that is not a valid semantic version. Also allows "TBD" to signal
|
||||
/// future deprecation.
|
||||
///
|
||||
/// ### Why is this bad?
|
||||
/// For checking the version of the deprecation, it must be
|
||||
/// a valid semver. Failing that, the contained information is useless.
|
||||
///
|
||||
/// ### Example
|
||||
/// ```no_run
|
||||
/// #[deprecated(since = "forever")]
|
||||
/// fn something_else() { /* ... */ }
|
||||
/// ```
|
||||
#[clippy::version = "pre 1.29.0"]
|
||||
pub DEPRECATED_SEMVER,
|
||||
correctness,
|
||||
"use of `#[deprecated(since = \"x\")]` where x is not semver"
|
||||
}
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
/// Checks for empty lines after outer attributes
|
||||
///
|
||||
/// ### Why is this bad?
|
||||
/// Most likely the attribute was meant to be an inner attribute using a '!'.
|
||||
/// If it was meant to be an outer attribute, then the following item
|
||||
/// should not be separated by empty lines.
|
||||
///
|
||||
/// ### Known problems
|
||||
/// Can cause false positives.
|
||||
///
|
||||
/// From the clippy side it's difficult to detect empty lines between an attributes and the
|
||||
/// following item because empty lines and comments are not part of the AST. The parsing
|
||||
/// currently works for basic cases but is not perfect.
|
||||
///
|
||||
/// ### Example
|
||||
/// ```no_run
|
||||
/// #[allow(dead_code)]
|
||||
///
|
||||
/// fn not_quite_good_code() { }
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```no_run
|
||||
/// // Good (as inner attribute)
|
||||
/// #![allow(dead_code)]
|
||||
///
|
||||
/// fn this_is_fine() { }
|
||||
///
|
||||
/// // or
|
||||
///
|
||||
/// // Good (as outer attribute)
|
||||
/// #[allow(dead_code)]
|
||||
/// fn this_is_fine_too() { }
|
||||
/// ```
|
||||
#[clippy::version = "pre 1.29.0"]
|
||||
pub EMPTY_LINE_AFTER_OUTER_ATTR,
|
||||
nursery,
|
||||
"empty line after outer attribute"
|
||||
}
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
/// Checks for empty lines after documentation comments.
|
||||
///
|
||||
/// ### Why is this bad?
|
||||
/// The documentation comment was most likely meant to be an inner attribute or regular comment.
|
||||
/// If it was intended to be a documentation comment, then the empty line should be removed to
|
||||
/// be more idiomatic.
|
||||
///
|
||||
/// ### Known problems
|
||||
/// Only detects empty lines immediately following the documentation. If the doc comment is followed
|
||||
/// by an attribute and then an empty line, this lint will not trigger. Use `empty_line_after_outer_attr`
|
||||
/// in combination with this lint to detect both cases.
|
||||
///
|
||||
/// Does not detect empty lines after doc attributes (e.g. `#[doc = ""]`).
|
||||
///
|
||||
/// ### Example
|
||||
/// ```no_run
|
||||
/// /// Some doc comment with a blank line after it.
|
||||
///
|
||||
/// fn not_quite_good_code() { }
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```no_run
|
||||
/// /// Good (no blank line)
|
||||
/// fn this_is_fine() { }
|
||||
/// ```
|
||||
///
|
||||
/// ```no_run
|
||||
/// // Good (convert to a regular comment)
|
||||
///
|
||||
/// fn this_is_fine_too() { }
|
||||
/// ```
|
||||
///
|
||||
/// ```no_run
|
||||
/// //! Good (convert to a comment on an inner attribute)
|
||||
///
|
||||
/// fn this_is_fine_as_well() { }
|
||||
/// ```
|
||||
#[clippy::version = "1.70.0"]
|
||||
pub EMPTY_LINE_AFTER_DOC_COMMENTS,
|
||||
nursery,
|
||||
"empty line after documentation comments"
|
||||
}
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
/// Checks for `warn`/`deny`/`forbid` attributes targeting the whole clippy::restriction category.
|
||||
///
|
||||
/// ### Why is this bad?
|
||||
/// Restriction lints sometimes are in contrast with other lints or even go against idiomatic rust.
|
||||
/// These lints should only be enabled on a lint-by-lint basis and with careful consideration.
|
||||
///
|
||||
/// ### Example
|
||||
/// ```no_run
|
||||
/// #![deny(clippy::restriction)]
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```no_run
|
||||
/// #![deny(clippy::as_conversions)]
|
||||
/// ```
|
||||
#[clippy::version = "1.47.0"]
|
||||
pub BLANKET_CLIPPY_RESTRICTION_LINTS,
|
||||
suspicious,
|
||||
"enabling the complete restriction group"
|
||||
}
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
/// Checks for `#[cfg_attr(rustfmt, rustfmt_skip)]` and suggests to replace it
|
||||
/// with `#[rustfmt::skip]`.
|
||||
///
|
||||
/// ### Why is this bad?
|
||||
/// Since tool_attributes ([rust-lang/rust#44690](https://github.com/rust-lang/rust/issues/44690))
|
||||
/// are stable now, they should be used instead of the old `cfg_attr(rustfmt)` attributes.
|
||||
///
|
||||
/// ### Known problems
|
||||
/// This lint doesn't detect crate level inner attributes, because they get
|
||||
/// processed before the PreExpansionPass lints get executed. See
|
||||
/// [#3123](https://github.com/rust-lang/rust-clippy/pull/3123#issuecomment-422321765)
|
||||
///
|
||||
/// ### Example
|
||||
/// ```no_run
|
||||
/// #[cfg_attr(rustfmt, rustfmt_skip)]
|
||||
/// fn main() { }
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```no_run
|
||||
/// #[rustfmt::skip]
|
||||
/// fn main() { }
|
||||
/// ```
|
||||
#[clippy::version = "1.32.0"]
|
||||
pub DEPRECATED_CFG_ATTR,
|
||||
complexity,
|
||||
"usage of `cfg_attr(rustfmt)` instead of tool attributes"
|
||||
}
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
/// Checks for cfg attributes having operating systems used in target family position.
|
||||
///
|
||||
/// ### Why is this bad?
|
||||
/// The configuration option will not be recognised and the related item will not be included
|
||||
/// by the conditional compilation engine.
|
||||
///
|
||||
/// ### Example
|
||||
/// ```no_run
|
||||
/// #[cfg(linux)]
|
||||
/// fn conditional() { }
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```no_run
|
||||
/// # mod hidden {
|
||||
/// #[cfg(target_os = "linux")]
|
||||
/// fn conditional() { }
|
||||
/// # }
|
||||
///
|
||||
/// // or
|
||||
///
|
||||
/// #[cfg(unix)]
|
||||
/// fn conditional() { }
|
||||
/// ```
|
||||
/// Check the [Rust Reference](https://doc.rust-lang.org/reference/conditional-compilation.html#target_os) for more details.
|
||||
#[clippy::version = "1.45.0"]
|
||||
pub MISMATCHED_TARGET_OS,
|
||||
correctness,
|
||||
"usage of `cfg(operating_system)` instead of `cfg(target_os = \"operating_system\")`"
|
||||
}
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
/// Checks for attributes that allow lints without a reason.
|
||||
///
|
||||
/// (This requires the `lint_reasons` feature)
|
||||
///
|
||||
/// ### Why is this bad?
|
||||
/// Allowing a lint should always have a reason. This reason should be documented to
|
||||
/// ensure that others understand the reasoning
|
||||
///
|
||||
/// ### Example
|
||||
/// ```no_run
|
||||
/// #![feature(lint_reasons)]
|
||||
///
|
||||
/// #![allow(clippy::some_lint)]
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```no_run
|
||||
/// #![feature(lint_reasons)]
|
||||
///
|
||||
/// #![allow(clippy::some_lint, reason = "False positive rust-lang/rust-clippy#1002020")]
|
||||
/// ```
|
||||
#[clippy::version = "1.61.0"]
|
||||
pub ALLOW_ATTRIBUTES_WITHOUT_REASON,
|
||||
restriction,
|
||||
"ensures that all `allow` and `expect` attributes have a reason"
|
||||
}
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
/// Checks for `#[should_panic]` attributes without specifying the expected panic message.
|
||||
///
|
||||
/// ### Why is this bad?
|
||||
/// The expected panic message should be specified to ensure that the test is actually
|
||||
/// panicking with the expected message, and not another unrelated panic.
|
||||
///
|
||||
/// ### Example
|
||||
/// ```no_run
|
||||
/// fn random() -> i32 { 0 }
|
||||
///
|
||||
/// #[should_panic]
|
||||
/// #[test]
|
||||
/// fn my_test() {
|
||||
/// let _ = 1 / random();
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```no_run
|
||||
/// fn random() -> i32 { 0 }
|
||||
///
|
||||
/// #[should_panic = "attempt to divide by zero"]
|
||||
/// #[test]
|
||||
/// fn my_test() {
|
||||
/// let _ = 1 / random();
|
||||
/// }
|
||||
/// ```
|
||||
#[clippy::version = "1.74.0"]
|
||||
pub SHOULD_PANIC_WITHOUT_EXPECT,
|
||||
pedantic,
|
||||
"ensures that all `should_panic` attributes specify its expected panic message"
|
||||
}
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
/// Checks for `any` and `all` combinators in `cfg` with only one condition.
|
||||
///
|
||||
/// ### Why is this bad?
|
||||
/// If there is only one condition, no need to wrap it into `any` or `all` combinators.
|
||||
///
|
||||
/// ### Example
|
||||
/// ```no_run
|
||||
/// #[cfg(any(unix))]
|
||||
/// pub struct Bar;
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```no_run
|
||||
/// #[cfg(unix)]
|
||||
/// pub struct Bar;
|
||||
/// ```
|
||||
#[clippy::version = "1.71.0"]
|
||||
pub NON_MINIMAL_CFG,
|
||||
style,
|
||||
"ensure that all `cfg(any())` and `cfg(all())` have more than one condition"
|
||||
}
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
/// Checks for `#[cfg(features = "...")]` and suggests to replace it with
|
||||
/// `#[cfg(feature = "...")]`.
|
||||
///
|
||||
/// It also checks if `cfg(test)` was misspelled.
|
||||
///
|
||||
/// ### Why is this bad?
|
||||
/// Misspelling `feature` as `features` or `test` as `tests` can be sometimes hard to spot. It
|
||||
/// may cause conditional compilation not work quietly.
|
||||
///
|
||||
/// ### Example
|
||||
/// ```no_run
|
||||
/// #[cfg(features = "some-feature")]
|
||||
/// fn conditional() { }
|
||||
/// #[cfg(tests)]
|
||||
/// mod tests { }
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```no_run
|
||||
/// #[cfg(feature = "some-feature")]
|
||||
/// fn conditional() { }
|
||||
/// #[cfg(test)]
|
||||
/// mod tests { }
|
||||
/// ```
|
||||
#[clippy::version = "1.69.0"]
|
||||
pub MAYBE_MISUSED_CFG,
|
||||
suspicious,
|
||||
"prevent from misusing the wrong attr name"
|
||||
}
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
/// Checks for `#[cfg_attr(feature = "cargo-clippy", ...)]` and for
|
||||
/// `#[cfg(feature = "cargo-clippy")]` and suggests to replace it with
|
||||
/// `#[cfg_attr(clippy, ...)]` or `#[cfg(clippy)]`.
|
||||
///
|
||||
/// ### Why is this bad?
|
||||
/// This feature has been deprecated for years and shouldn't be used anymore.
|
||||
///
|
||||
/// ### Example
|
||||
/// ```no_run
|
||||
/// #[cfg(feature = "cargo-clippy")]
|
||||
/// struct Bar;
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```no_run
|
||||
/// #[cfg(clippy)]
|
||||
/// struct Bar;
|
||||
/// ```
|
||||
#[clippy::version = "1.78.0"]
|
||||
pub DEPRECATED_CLIPPY_CFG_ATTR,
|
||||
suspicious,
|
||||
"usage of `cfg(feature = \"cargo-clippy\")` instead of `cfg(clippy)`"
|
||||
}
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
/// Checks for `#[cfg_attr(clippy, allow(clippy::lint))]`
|
||||
/// and suggests to replace it with `#[allow(clippy::lint)]`.
|
||||
///
|
||||
/// ### Why is this bad?
|
||||
/// There is no reason to put clippy attributes behind a clippy `cfg` as they are not
|
||||
/// run by anything else than clippy.
|
||||
///
|
||||
/// ### Example
|
||||
/// ```no_run
|
||||
/// #![cfg_attr(clippy, allow(clippy::deprecated_cfg_attr))]
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```no_run
|
||||
/// #![allow(clippy::deprecated_cfg_attr)]
|
||||
/// ```
|
||||
#[clippy::version = "1.78.0"]
|
||||
pub UNNECESSARY_CLIPPY_CFG,
|
||||
suspicious,
|
||||
"usage of `cfg_attr(clippy, allow(clippy::lint))` instead of `allow(clippy::lint)`"
|
||||
}
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
/// Checks that an item has only one kind of attributes.
|
||||
///
|
||||
/// ### Why is this bad?
|
||||
/// Having both kinds of attributes makes it more complicated to read code.
|
||||
///
|
||||
/// ### Example
|
||||
/// ```no_run
|
||||
/// #[cfg(linux)]
|
||||
/// pub fn foo() {
|
||||
/// #![cfg(windows)]
|
||||
/// }
|
||||
/// ```
|
||||
/// Use instead:
|
||||
/// ```no_run
|
||||
/// #[cfg(linux)]
|
||||
/// #[cfg(windows)]
|
||||
/// pub fn foo() {
|
||||
/// }
|
||||
/// ```
|
||||
#[clippy::version = "1.78.0"]
|
||||
pub MIXED_ATTRIBUTES_STYLE,
|
||||
suspicious,
|
||||
"item has both inner and outer attributes"
|
||||
}
|
||||
|
||||
declare_lint_pass!(Attributes => [
|
||||
ALLOW_ATTRIBUTES_WITHOUT_REASON,
|
||||
INLINE_ALWAYS,
|
||||
DEPRECATED_SEMVER,
|
||||
USELESS_ATTRIBUTE,
|
||||
BLANKET_CLIPPY_RESTRICTION_LINTS,
|
||||
SHOULD_PANIC_WITHOUT_EXPECT,
|
||||
]);
|
||||
|
||||
impl<'tcx> LateLintPass<'tcx> for Attributes {
|
||||
fn check_crate(&mut self, cx: &LateContext<'tcx>) {
|
||||
blanket_clippy_restriction_lints::check_command_line(cx);
|
||||
}
|
||||
|
||||
fn check_attribute(&mut self, cx: &LateContext<'tcx>, attr: &'tcx Attribute) {
|
||||
if let Some(items) = &attr.meta_item_list() {
|
||||
if let Some(ident) = attr.ident() {
|
||||
if is_lint_level(ident.name, attr.id) {
|
||||
blanket_clippy_restriction_lints::check(cx, ident.name, items);
|
||||
}
|
||||
if matches!(ident.name, sym::allow | sym::expect) {
|
||||
allow_attributes_without_reason::check(cx, ident.name, items, attr);
|
||||
}
|
||||
if items.is_empty() || !attr.has_name(sym::deprecated) {
|
||||
return;
|
||||
}
|
||||
for item in items {
|
||||
if let NestedMetaItem::MetaItem(mi) = &item
|
||||
&& let MetaItemKind::NameValue(lit) = &mi.kind
|
||||
&& mi.has_name(sym::since)
|
||||
{
|
||||
deprecated_semver::check(cx, item.span(), lit);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if attr.has_name(sym::should_panic) {
|
||||
should_panic_without_expect::check(cx, attr);
|
||||
}
|
||||
}
|
||||
|
||||
fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'_>) {
|
||||
let attrs = cx.tcx.hir().attrs(item.hir_id());
|
||||
if is_relevant_item(cx, item) {
|
||||
inline_always::check(cx, item.span, item.ident.name, attrs);
|
||||
}
|
||||
match item.kind {
|
||||
ItemKind::ExternCrate(..) | ItemKind::Use(..) => useless_attribute::check(cx, item, attrs),
|
||||
_ => {},
|
||||
}
|
||||
}
|
||||
|
||||
fn check_impl_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx ImplItem<'_>) {
|
||||
if is_relevant_impl(cx, item) {
|
||||
inline_always::check(cx, item.span, item.ident.name, cx.tcx.hir().attrs(item.hir_id()));
|
||||
}
|
||||
}
|
||||
|
||||
fn check_trait_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx TraitItem<'_>) {
|
||||
if is_relevant_trait(cx, item) {
|
||||
inline_always::check(cx, item.span, item.ident.name, cx.tcx.hir().attrs(item.hir_id()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct EarlyAttributes {
|
||||
pub msrv: Msrv,
|
||||
}
|
||||
|
||||
impl_lint_pass!(EarlyAttributes => [
|
||||
DEPRECATED_CFG_ATTR,
|
||||
MISMATCHED_TARGET_OS,
|
||||
EMPTY_LINE_AFTER_OUTER_ATTR,
|
||||
EMPTY_LINE_AFTER_DOC_COMMENTS,
|
||||
NON_MINIMAL_CFG,
|
||||
MAYBE_MISUSED_CFG,
|
||||
DEPRECATED_CLIPPY_CFG_ATTR,
|
||||
UNNECESSARY_CLIPPY_CFG,
|
||||
MIXED_ATTRIBUTES_STYLE,
|
||||
]);
|
||||
|
||||
impl EarlyLintPass for EarlyAttributes {
|
||||
fn check_item(&mut self, cx: &EarlyContext<'_>, item: &rustc_ast::Item) {
|
||||
empty_line_after::check(cx, item);
|
||||
mixed_attributes_style::check(cx, item);
|
||||
}
|
||||
|
||||
fn check_attribute(&mut self, cx: &EarlyContext<'_>, attr: &Attribute) {
|
||||
deprecated_cfg_attr::check(cx, attr, &self.msrv);
|
||||
deprecated_cfg_attr::check_clippy(cx, attr);
|
||||
mismatched_target_os::check(cx, attr);
|
||||
non_minimal_cfg::check(cx, attr);
|
||||
maybe_misused_cfg::check(cx, attr);
|
||||
}
|
||||
|
||||
extract_msrv_attr!(EarlyContext);
|
||||
}
|
49
clippy_lints/src/attrs/non_minimal_cfg.rs
Normal file
49
clippy_lints/src/attrs/non_minimal_cfg.rs
Normal file
@ -0,0 +1,49 @@
|
||||
use super::{Attribute, NON_MINIMAL_CFG};
|
||||
use clippy_utils::diagnostics::span_lint_and_then;
|
||||
use clippy_utils::source::snippet_opt;
|
||||
use rustc_ast::{MetaItemKind, NestedMetaItem};
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_lint::EarlyContext;
|
||||
use rustc_span::sym;
|
||||
|
||||
pub(super) fn check(cx: &EarlyContext<'_>, attr: &Attribute) {
|
||||
if attr.has_name(sym::cfg)
|
||||
&& let Some(items) = attr.meta_item_list()
|
||||
{
|
||||
check_nested_cfg(cx, &items);
|
||||
}
|
||||
}
|
||||
|
||||
fn check_nested_cfg(cx: &EarlyContext<'_>, items: &[NestedMetaItem]) {
|
||||
for item in items {
|
||||
if let NestedMetaItem::MetaItem(meta) = item {
|
||||
if !meta.has_name(sym::any) && !meta.has_name(sym::all) {
|
||||
continue;
|
||||
}
|
||||
if let MetaItemKind::List(list) = &meta.kind {
|
||||
check_nested_cfg(cx, list);
|
||||
if list.len() == 1 {
|
||||
span_lint_and_then(
|
||||
cx,
|
||||
NON_MINIMAL_CFG,
|
||||
meta.span,
|
||||
"unneeded sub `cfg` when there is only one condition",
|
||||
|diag| {
|
||||
if let Some(snippet) = snippet_opt(cx, list[0].span()) {
|
||||
diag.span_suggestion(meta.span, "try", snippet, Applicability::MaybeIncorrect);
|
||||
}
|
||||
},
|
||||
);
|
||||
} else if list.is_empty() && meta.has_name(sym::all) {
|
||||
span_lint_and_then(
|
||||
cx,
|
||||
NON_MINIMAL_CFG,
|
||||
meta.span,
|
||||
"unneeded sub `cfg` when there is no condition",
|
||||
|_| {},
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
54
clippy_lints/src/attrs/should_panic_without_expect.rs
Normal file
54
clippy_lints/src/attrs/should_panic_without_expect.rs
Normal file
@ -0,0 +1,54 @@
|
||||
use super::{Attribute, SHOULD_PANIC_WITHOUT_EXPECT};
|
||||
use clippy_utils::diagnostics::span_lint_and_sugg;
|
||||
use rustc_ast::token::{Token, TokenKind};
|
||||
use rustc_ast::tokenstream::TokenTree;
|
||||
use rustc_ast::{AttrArgs, AttrArgsEq, AttrKind};
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_lint::LateContext;
|
||||
use rustc_span::sym;
|
||||
|
||||
pub(super) fn check(cx: &LateContext<'_>, attr: &Attribute) {
|
||||
if let AttrKind::Normal(normal_attr) = &attr.kind {
|
||||
if let AttrArgs::Eq(_, AttrArgsEq::Hir(_)) = &normal_attr.item.args {
|
||||
// `#[should_panic = ".."]` found, good
|
||||
return;
|
||||
}
|
||||
|
||||
if let AttrArgs::Delimited(args) = &normal_attr.item.args
|
||||
&& let mut tt_iter = args.tokens.trees()
|
||||
&& let Some(TokenTree::Token(
|
||||
Token {
|
||||
kind: TokenKind::Ident(sym::expected, _),
|
||||
..
|
||||
},
|
||||
_,
|
||||
)) = tt_iter.next()
|
||||
&& let Some(TokenTree::Token(
|
||||
Token {
|
||||
kind: TokenKind::Eq, ..
|
||||
},
|
||||
_,
|
||||
)) = tt_iter.next()
|
||||
&& let Some(TokenTree::Token(
|
||||
Token {
|
||||
kind: TokenKind::Literal(_),
|
||||
..
|
||||
},
|
||||
_,
|
||||
)) = tt_iter.next()
|
||||
{
|
||||
// `#[should_panic(expected = "..")]` found, good
|
||||
return;
|
||||
}
|
||||
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
SHOULD_PANIC_WITHOUT_EXPECT,
|
||||
attr.span,
|
||||
"#[should_panic] attribute without a reason",
|
||||
"consider specifying the expected panic",
|
||||
"#[should_panic(expected = /* panic message */)]".into(),
|
||||
Applicability::HasPlaceholders,
|
||||
);
|
||||
}
|
||||
}
|
70
clippy_lints/src/attrs/unnecessary_clippy_cfg.rs
Normal file
70
clippy_lints/src/attrs/unnecessary_clippy_cfg.rs
Normal file
@ -0,0 +1,70 @@
|
||||
use super::{Attribute, UNNECESSARY_CLIPPY_CFG};
|
||||
use clippy_utils::diagnostics::{span_lint_and_note, span_lint_and_sugg};
|
||||
use clippy_utils::source::snippet_opt;
|
||||
use rustc_ast::AttrStyle;
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_lint::{EarlyContext, Level};
|
||||
use rustc_span::sym;
|
||||
|
||||
pub(super) fn check(
|
||||
cx: &EarlyContext<'_>,
|
||||
cfg_attr: &rustc_ast::MetaItem,
|
||||
behind_cfg_attr: &rustc_ast::MetaItem,
|
||||
attr: &Attribute,
|
||||
) {
|
||||
if cfg_attr.has_name(sym::clippy)
|
||||
&& let Some(ident) = behind_cfg_attr.ident()
|
||||
&& Level::from_symbol(ident.name, Some(attr.id)).is_some()
|
||||
&& let Some(items) = behind_cfg_attr.meta_item_list()
|
||||
{
|
||||
let nb_items = items.len();
|
||||
let mut clippy_lints = Vec::with_capacity(items.len());
|
||||
for item in items {
|
||||
if let Some(meta_item) = item.meta_item()
|
||||
&& let [part1, _] = meta_item.path.segments.as_slice()
|
||||
&& part1.ident.name == sym::clippy
|
||||
{
|
||||
clippy_lints.push(item.span());
|
||||
}
|
||||
}
|
||||
if clippy_lints.is_empty() {
|
||||
return;
|
||||
}
|
||||
if nb_items == clippy_lints.len() {
|
||||
if let Some(snippet) = snippet_opt(cx, behind_cfg_attr.span) {
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
UNNECESSARY_CLIPPY_CFG,
|
||||
attr.span,
|
||||
"no need to put clippy lints behind a `clippy` cfg",
|
||||
"replace with",
|
||||
format!(
|
||||
"#{}[{}]",
|
||||
if attr.style == AttrStyle::Inner { "!" } else { "" },
|
||||
snippet
|
||||
),
|
||||
Applicability::MachineApplicable,
|
||||
);
|
||||
}
|
||||
} else {
|
||||
let snippet = clippy_lints
|
||||
.iter()
|
||||
.filter_map(|sp| snippet_opt(cx, *sp))
|
||||
.collect::<Vec<_>>()
|
||||
.join(",");
|
||||
span_lint_and_note(
|
||||
cx,
|
||||
UNNECESSARY_CLIPPY_CFG,
|
||||
clippy_lints,
|
||||
"no need to put clippy lints behind a `clippy` cfg",
|
||||
None,
|
||||
&format!(
|
||||
"write instead: `#{}[{}({})]`",
|
||||
if attr.style == AttrStyle::Inner { "!" } else { "" },
|
||||
ident.name,
|
||||
snippet
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
73
clippy_lints/src/attrs/useless_attribute.rs
Normal file
73
clippy_lints/src/attrs/useless_attribute.rs
Normal file
@ -0,0 +1,73 @@
|
||||
use super::utils::{extract_clippy_lint, is_lint_level, is_word};
|
||||
use super::{Attribute, USELESS_ATTRIBUTE};
|
||||
use clippy_utils::diagnostics::span_lint_and_then;
|
||||
use clippy_utils::source::{first_line_of_span, snippet_opt};
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::{Item, ItemKind};
|
||||
use rustc_lint::{LateContext, LintContext};
|
||||
use rustc_middle::lint::in_external_macro;
|
||||
use rustc_span::sym;
|
||||
|
||||
pub(super) fn check(cx: &LateContext<'_>, item: &Item<'_>, attrs: &[Attribute]) {
|
||||
let skip_unused_imports = attrs.iter().any(|attr| attr.has_name(sym::macro_use));
|
||||
|
||||
for attr in attrs {
|
||||
if in_external_macro(cx.sess(), attr.span) {
|
||||
return;
|
||||
}
|
||||
if let Some(lint_list) = &attr.meta_item_list() {
|
||||
if attr.ident().map_or(false, |ident| is_lint_level(ident.name, attr.id)) {
|
||||
for lint in lint_list {
|
||||
match item.kind {
|
||||
ItemKind::Use(..) => {
|
||||
if is_word(lint, sym::unused_imports)
|
||||
|| is_word(lint, sym::deprecated)
|
||||
|| is_word(lint, sym!(unreachable_pub))
|
||||
|| is_word(lint, sym!(unused))
|
||||
|| is_word(lint, sym!(unused_import_braces))
|
||||
|| extract_clippy_lint(lint).map_or(false, |s| {
|
||||
matches!(
|
||||
s.as_str(),
|
||||
"wildcard_imports"
|
||||
| "enum_glob_use"
|
||||
| "redundant_pub_crate"
|
||||
| "macro_use_imports"
|
||||
| "unsafe_removed_from_name"
|
||||
| "module_name_repetitions"
|
||||
| "single_component_path_imports"
|
||||
)
|
||||
})
|
||||
{
|
||||
return;
|
||||
}
|
||||
},
|
||||
ItemKind::ExternCrate(..) => {
|
||||
if is_word(lint, sym::unused_imports) && skip_unused_imports {
|
||||
return;
|
||||
}
|
||||
if is_word(lint, sym!(unused_extern_crates)) {
|
||||
return;
|
||||
}
|
||||
},
|
||||
_ => {},
|
||||
}
|
||||
}
|
||||
let line_span = first_line_of_span(cx, attr.span);
|
||||
|
||||
if let Some(mut sugg) = snippet_opt(cx, line_span) {
|
||||
if sugg.contains("#[") {
|
||||
span_lint_and_then(cx, USELESS_ATTRIBUTE, line_span, "useless lint attribute", |diag| {
|
||||
sugg = sugg.replacen("#[", "#![", 1);
|
||||
diag.span_suggestion(
|
||||
line_span,
|
||||
"if you just forgot a `!`, use",
|
||||
sugg,
|
||||
Applicability::MaybeIncorrect,
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
87
clippy_lints/src/attrs/utils.rs
Normal file
87
clippy_lints/src/attrs/utils.rs
Normal file
@ -0,0 +1,87 @@
|
||||
use clippy_utils::macros::{is_panic, macro_backtrace};
|
||||
use rustc_ast::{AttrId, NestedMetaItem};
|
||||
use rustc_hir::{
|
||||
Block, Expr, ExprKind, ImplItem, ImplItemKind, Item, ItemKind, StmtKind, TraitFn, TraitItem, TraitItemKind,
|
||||
};
|
||||
use rustc_lint::{LateContext, Level};
|
||||
use rustc_middle::ty;
|
||||
use rustc_span::sym;
|
||||
use rustc_span::symbol::Symbol;
|
||||
|
||||
pub(super) fn is_word(nmi: &NestedMetaItem, expected: Symbol) -> bool {
|
||||
if let NestedMetaItem::MetaItem(mi) = &nmi {
|
||||
mi.is_word() && mi.has_name(expected)
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn is_lint_level(symbol: Symbol, attr_id: AttrId) -> bool {
|
||||
Level::from_symbol(symbol, Some(attr_id)).is_some()
|
||||
}
|
||||
|
||||
pub(super) fn is_relevant_item(cx: &LateContext<'_>, item: &Item<'_>) -> bool {
|
||||
if let ItemKind::Fn(_, _, eid) = item.kind {
|
||||
is_relevant_expr(cx, cx.tcx.typeck_body(eid), cx.tcx.hir().body(eid).value)
|
||||
} else {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn is_relevant_impl(cx: &LateContext<'_>, item: &ImplItem<'_>) -> bool {
|
||||
match item.kind {
|
||||
ImplItemKind::Fn(_, eid) => is_relevant_expr(cx, cx.tcx.typeck_body(eid), cx.tcx.hir().body(eid).value),
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn is_relevant_trait(cx: &LateContext<'_>, item: &TraitItem<'_>) -> bool {
|
||||
match item.kind {
|
||||
TraitItemKind::Fn(_, TraitFn::Required(_)) => true,
|
||||
TraitItemKind::Fn(_, TraitFn::Provided(eid)) => {
|
||||
is_relevant_expr(cx, cx.tcx.typeck_body(eid), cx.tcx.hir().body(eid).value)
|
||||
},
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
fn is_relevant_block(cx: &LateContext<'_>, typeck_results: &ty::TypeckResults<'_>, block: &Block<'_>) -> bool {
|
||||
block.stmts.first().map_or(
|
||||
block
|
||||
.expr
|
||||
.as_ref()
|
||||
.map_or(false, |e| is_relevant_expr(cx, typeck_results, e)),
|
||||
|stmt| match &stmt.kind {
|
||||
StmtKind::Local(_) => true,
|
||||
StmtKind::Expr(expr) | StmtKind::Semi(expr) => is_relevant_expr(cx, typeck_results, expr),
|
||||
StmtKind::Item(_) => false,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
fn is_relevant_expr(cx: &LateContext<'_>, typeck_results: &ty::TypeckResults<'_>, expr: &Expr<'_>) -> bool {
|
||||
if macro_backtrace(expr.span).last().map_or(false, |macro_call| {
|
||||
is_panic(cx, macro_call.def_id) || cx.tcx.item_name(macro_call.def_id) == sym::unreachable
|
||||
}) {
|
||||
return false;
|
||||
}
|
||||
match &expr.kind {
|
||||
ExprKind::Block(block, _) => is_relevant_block(cx, typeck_results, block),
|
||||
ExprKind::Ret(Some(e)) => is_relevant_expr(cx, typeck_results, e),
|
||||
ExprKind::Ret(None) | ExprKind::Break(_, None) => false,
|
||||
_ => true,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the lint name if it is clippy lint.
|
||||
pub(super) fn extract_clippy_lint(lint: &NestedMetaItem) -> Option<Symbol> {
|
||||
if let Some(meta_item) = lint.meta_item()
|
||||
&& meta_item.path.segments.len() > 1
|
||||
&& let tool_name = meta_item.path.segments[0].ident
|
||||
&& tool_name.name == sym::clippy
|
||||
{
|
||||
let lint_name = meta_item.path.segments.last().unwrap().ident.name;
|
||||
return Some(lint_name);
|
||||
}
|
||||
None
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user