625 lines
19 KiB
Rust
625 lines
19 KiB
Rust
//! checks for attributes
|
|
|
|
mod allow_attributes;
|
|
mod allow_attributes_without_reason;
|
|
mod blanket_clippy_restriction_lints;
|
|
mod deprecated_cfg_attr;
|
|
mod deprecated_semver;
|
|
mod duplicated_attributes;
|
|
mod empty_line_after;
|
|
mod inline_always;
|
|
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::{self, Msrv};
|
|
use clippy_config::Conf;
|
|
use rustc_ast::{Attribute, MetaItemKind, NestedMetaItem};
|
|
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 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 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]` (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 `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"
|
|
}
|
|
|
|
pub struct Attributes {
|
|
msrv: Msrv,
|
|
}
|
|
|
|
impl_lint_pass!(Attributes => [
|
|
ALLOW_ATTRIBUTES,
|
|
ALLOW_ATTRIBUTES_WITHOUT_REASON,
|
|
INLINE_ALWAYS,
|
|
DEPRECATED_SEMVER,
|
|
USELESS_ATTRIBUTE,
|
|
BLANKET_CLIPPY_RESTRICTION_LINTS,
|
|
SHOULD_PANIC_WITHOUT_EXPECT,
|
|
MIXED_ATTRIBUTES_STYLE,
|
|
DUPLICATED_ATTRIBUTES,
|
|
]);
|
|
|
|
impl Attributes {
|
|
pub fn new(conf: &'static Conf) -> Self {
|
|
Self {
|
|
msrv: conf.msrv.clone(),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl<'tcx> LateLintPass<'tcx> for Attributes {
|
|
fn check_crate(&mut self, cx: &LateContext<'tcx>) {
|
|
blanket_clippy_restriction_lints::check_command_line(cx);
|
|
duplicated_attributes::check(cx, cx.tcx.hir().krate_attrs());
|
|
}
|
|
|
|
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) && 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 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),
|
|
_ => {},
|
|
}
|
|
mixed_attributes_style::check(cx, item.span, attrs);
|
|
duplicated_attributes::check(cx, 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()));
|
|
}
|
|
}
|
|
|
|
extract_msrv_attr!(LateContext);
|
|
}
|
|
|
|
pub struct EarlyAttributes {
|
|
msrv: Msrv,
|
|
}
|
|
|
|
impl EarlyAttributes {
|
|
pub fn new(conf: &'static Conf) -> Self {
|
|
Self {
|
|
msrv: conf.msrv.clone(),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl_lint_pass!(EarlyAttributes => [
|
|
DEPRECATED_CFG_ATTR,
|
|
EMPTY_LINE_AFTER_OUTER_ATTR,
|
|
EMPTY_LINE_AFTER_DOC_COMMENTS,
|
|
NON_MINIMAL_CFG,
|
|
DEPRECATED_CLIPPY_CFG_ATTR,
|
|
UNNECESSARY_CLIPPY_CFG,
|
|
]);
|
|
|
|
impl EarlyLintPass for EarlyAttributes {
|
|
fn check_item(&mut self, cx: &EarlyContext<'_>, item: &rustc_ast::Item) {
|
|
empty_line_after::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);
|
|
non_minimal_cfg::check(cx, attr);
|
|
}
|
|
|
|
extract_msrv_attr!(EarlyContext);
|
|
}
|