blob: 91c2dc7f3dc6c825d326d71fa5fcd240df3022a1 [file] [log] [blame] [edit]
mod allow_attributes;
mod allow_attributes_without_reason;
mod blanket_clippy_restriction_lints;
mod deprecated_cfg_attr;
mod deprecated_semver;
mod duplicated_attributes;
mod inline_always;
mod mixed_attributes_style;
mod non_minimal_cfg;
mod repr_attributes;
mod should_panic_without_expect;
mod unnecessary_clippy_cfg;
mod useless_attribute;
mod utils;
use clippy_config::Conf;
use clippy_utils::diagnostics::span_lint_and_help;
use clippy_utils::msrvs::{self, Msrv, MsrvStack};
use rustc_ast::{self as ast, AttrArgs, AttrKind, Attribute, MetaItemInner, MetaItemKind};
use rustc_hir::{ImplItem, Item, ItemKind, TraitItem};
use rustc_lint::{EarlyContext, EarlyLintPass, LateContext, LateLintPass};
use rustc_session::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:
/// * ambiguous_glob_reexports
/// * dead_code
/// * deprecated
/// * hidden_glob_reexports
/// * unreachable_pub
/// * unused
/// * unused_braces
/// * unused_import_braces
/// * clippy::disallowed_types
/// * clippy::enum_glob_use
/// * clippy::macro_use_imports
/// * clippy::module_name_repetitions
/// * clippy::redundant_pub_crate
/// * clippy::single_component_path_imports
/// * clippy::unsafe_removed_from_name
/// * 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 `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 attributes that allow lints without a reason.
///
/// ### Why restrict this?
/// Justifying each `allow` helps readers understand the reasoning,
/// and may allow removing `allow` attributes if their purpose is obsolete.
///
/// ### Example
/// ```no_run
/// #![allow(clippy::some_lint)]
/// ```
///
/// Use instead:
/// ```no_run
/// #![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 usage of the `#[allow]` attribute and suggests replacing it with
/// the `#[expect]` attribute (See [RFC 2383](https://rust-lang.github.io/rfcs/2383-lint-reasons.html))
///
/// This lint only warns outer attributes (`#[allow]`), as inner attributes
/// (`#![allow]`) are usually used to enable or disable lints on a global scale.
///
/// ### Why is this bad?
/// `#[expect]` attributes suppress the lint emission, but emit a warning, if
/// the expectation is unfulfilled. This can be useful to be notified when the
/// lint is no longer triggered.
///
/// ### Example
/// ```rust,ignore
/// #[allow(unused_mut)]
/// fn foo() -> usize {
/// let mut a = Vec::new();
/// a.len()
/// }
/// ```
/// Use instead:
/// ```rust,ignore
/// #[expect(unused_mut)]
/// fn foo() -> usize {
/// let mut a = Vec::new();
/// a.len()
/// }
/// ```
#[clippy::version = "1.70.0"]
pub ALLOW_ATTRIBUTES,
restriction,
"`#[allow]` will not trigger if a warning isn't found. `#[expect]` triggers if there are no warnings."
}
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 items with `#[repr(packed)]`-attribute without ABI qualification
///
/// ### Why is this bad?
/// Without qualification, `repr(packed)` implies `repr(Rust)`. The Rust-ABI is inherently unstable.
/// While this is fine as long as the type is accessed correctly within Rust-code, most uses
/// of `#[repr(packed)]` involve FFI and/or data structures specified by network-protocols or
/// other external specifications. In such situations, the unstable Rust-ABI implied in
/// `#[repr(packed)]` may lead to future bugs should the Rust-ABI change.
///
/// In case you are relying on a well defined and stable memory layout, qualify the type's
/// representation using the `C`-ABI. Otherwise, if the type in question is only ever
/// accessed from Rust-code according to Rust's rules, use the `Rust`-ABI explicitly.
///
/// ### Example
/// ```no_run
/// #[repr(packed)]
/// struct NetworkPacketHeader {
/// header_length: u8,
/// header_version: u16
/// }
/// ```
///
/// Use instead:
/// ```no_run
/// #[repr(C, packed)]
/// struct NetworkPacketHeader {
/// header_length: u8,
/// header_version: u16
/// }
/// ```
#[clippy::version = "1.85.0"]
pub REPR_PACKED_WITHOUT_ABI,
suspicious,
"ensures that `repr(packed)` always comes with a qualified ABI"
}
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_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 for items that have the same kind of attributes with mixed styles (inner/outer).
///
/// ### Why is this bad?
/// Having both style of said attributes makes it more complicated to read code.
///
/// ### Known problems
/// This lint currently has false-negatives when mixing same attributes
/// but they have different path symbols, for example:
/// ```ignore
/// #[custom_attribute]
/// pub fn foo() {
/// #![my_crate::custom_attribute]
/// }
/// ```
///
/// ### 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,
style,
"item has both inner and outer attributes"
}
declare_clippy_lint! {
/// ### What it does
/// Checks for attributes that appear two or more times.
///
/// ### Why is this bad?
/// Repeating an attribute on the same item (or globally on the same crate)
/// is unnecessary and doesn't have an effect.
///
/// ### Example
/// ```no_run
/// #[allow(dead_code)]
/// #[allow(dead_code)]
/// fn foo() {}
/// ```
///
/// Use instead:
/// ```no_run
/// #[allow(dead_code)]
/// fn foo() {}
/// ```
#[clippy::version = "1.79.0"]
pub DUPLICATED_ATTRIBUTES,
suspicious,
"duplicated attribute"
}
declare_clippy_lint! {
/// ### What it does
/// Checks for ignored tests without messages.
///
/// ### Why is this bad?
/// The reason for ignoring the test may not be obvious.
///
/// ### Example
/// ```no_run
/// #[test]
/// #[ignore]
/// fn test() {}
/// ```
/// Use instead:
/// ```no_run
/// #[test]
/// #[ignore = "Some good reason"]
/// fn test() {}
/// ```
#[clippy::version = "1.88.0"]
pub IGNORE_WITHOUT_REASON,
pedantic,
"ignored tests without messages"
}
pub struct Attributes {
msrv: Msrv,
}
impl_lint_pass!(Attributes => [
INLINE_ALWAYS,
REPR_PACKED_WITHOUT_ABI,
]);
impl Attributes {
pub fn new(conf: &'static Conf) -> Self {
Self { msrv: conf.msrv }
}
}
impl<'tcx> LateLintPass<'tcx> for Attributes {
fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'_>) {
let attrs = cx.tcx.hir_attrs(item.hir_id());
if let ItemKind::Fn { ident, .. } = item.kind
&& is_relevant_item(cx, item)
{
inline_always::check(cx, item.span, ident.name, attrs);
}
repr_attributes::check(cx, item.span, attrs, self.msrv);
}
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 {
msrv: MsrvStack,
}
impl EarlyAttributes {
pub fn new(conf: &'static Conf) -> Self {
Self {
msrv: MsrvStack::new(conf.msrv),
}
}
}
impl_lint_pass!(EarlyAttributes => [
DEPRECATED_CFG_ATTR,
NON_MINIMAL_CFG,
DEPRECATED_CLIPPY_CFG_ATTR,
UNNECESSARY_CLIPPY_CFG,
]);
impl EarlyLintPass for EarlyAttributes {
fn check_attribute(&mut self, cx: &EarlyContext<'_>, attr: &Attribute) {
deprecated_cfg_attr::check(cx, attr, &self.msrv);
deprecated_cfg_attr::check_clippy(cx, attr);
non_minimal_cfg::check(cx, attr);
}
extract_msrv_attr!();
}
pub struct PostExpansionEarlyAttributes {
msrv: MsrvStack,
}
impl PostExpansionEarlyAttributes {
pub fn new(conf: &'static Conf) -> Self {
Self {
msrv: MsrvStack::new(conf.msrv),
}
}
}
impl_lint_pass!(PostExpansionEarlyAttributes => [
ALLOW_ATTRIBUTES,
ALLOW_ATTRIBUTES_WITHOUT_REASON,
DEPRECATED_SEMVER,
IGNORE_WITHOUT_REASON,
USELESS_ATTRIBUTE,
BLANKET_CLIPPY_RESTRICTION_LINTS,
SHOULD_PANIC_WITHOUT_EXPECT,
MIXED_ATTRIBUTES_STYLE,
DUPLICATED_ATTRIBUTES,
]);
impl EarlyLintPass for PostExpansionEarlyAttributes {
fn check_crate(&mut self, cx: &EarlyContext<'_>, krate: &ast::Crate) {
blanket_clippy_restriction_lints::check_command_line(cx);
duplicated_attributes::check(cx, &krate.attrs);
}
fn check_attribute(&mut self, cx: &EarlyContext<'_>, attr: &Attribute) {
if let Some(items) = &attr.meta_item_list()
&& let Some(ident) = attr.ident()
{
if matches!(ident.name, sym::allow) && self.msrv.meets(msrvs::LINT_REASONS_STABILIZATION) {
allow_attributes::check(cx, attr);
}
if matches!(ident.name, sym::allow | sym::expect) && self.msrv.meets(msrvs::LINT_REASONS_STABILIZATION) {
allow_attributes_without_reason::check(cx, ident.name, items, attr);
}
if is_lint_level(ident.name, attr.id) {
blanket_clippy_restriction_lints::check(cx, ident.name, items);
}
if items.is_empty() || !attr.has_name(sym::deprecated) {
return;
}
for item in items {
if let MetaItemInner::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);
}
if attr.has_name(sym::ignore)
&& match &attr.kind {
AttrKind::Normal(normal_attr) => !matches!(normal_attr.item.args, AttrArgs::Eq { .. }),
AttrKind::DocComment(..) => true,
}
{
span_lint_and_help(
cx,
IGNORE_WITHOUT_REASON,
attr.span,
"`#[ignore]` without reason",
None,
"add a reason with `= \"..\"`",
);
}
}
fn check_item(&mut self, cx: &EarlyContext<'_>, item: &'_ ast::Item) {
match item.kind {
ast::ItemKind::ExternCrate(..) | ast::ItemKind::Use(..) => useless_attribute::check(cx, item, &item.attrs),
_ => {},
}
mixed_attributes_style::check(cx, item.span, &item.attrs);
duplicated_attributes::check(cx, &item.attrs);
}
extract_msrv_attr!();
}