1 use crate::utils
::internal_lints
::metadata_collector
::is_deprecated_lint
;
2 use clippy_utils
::diagnostics
::{span_lint, span_lint_and_help}
;
3 use clippy_utils
::macros
::root_macro_call_first_node
;
4 use clippy_utils
::{is_lint_allowed, match_def_path, paths}
;
5 use rustc_ast
::ast
::LitKind
;
6 use rustc_data_structures
::fx
::{FxHashMap, FxHashSet}
;
7 use rustc_hir
::def
::{DefKind, Res}
;
8 use rustc_hir
::hir_id
::CRATE_HIR_ID
;
9 use rustc_hir
::intravisit
::Visitor
;
10 use rustc_hir
::{ExprKind, HirId, Item, MutTy, Mutability, Path, TyKind}
;
11 use rustc_lint
::{LateContext, LateLintPass}
;
12 use rustc_middle
::hir
::nested_filter
;
13 use rustc_semver
::RustcVersion
;
14 use rustc_session
::impl_lint_pass
;
15 use rustc_span
::source_map
::Spanned
;
16 use rustc_span
::symbol
::Symbol
;
17 use rustc_span
::{sym, Span}
;
18 use {rustc_ast as ast, rustc_hir as hir}
;
20 declare_clippy_lint
! {
22 /// Ensures every lint is associated to a `LintPass`.
24 /// ### Why is this bad?
25 /// The compiler only knows lints via a `LintPass`. Without
26 /// putting a lint to a `LintPass::get_lints()`'s return, the compiler will not
27 /// know the name of the lint.
29 /// ### Known problems
30 /// Only checks for lints associated using the `declare_lint_pass!` and
31 /// `impl_lint_pass!` macros.
35 /// declare_lint! { pub LINT_1, ... }
36 /// declare_lint! { pub LINT_2, ... }
37 /// declare_lint! { pub FORGOTTEN_LINT, ... }
39 /// declare_lint_pass!(Pass => [LINT_1, LINT_2]);
40 /// // missing FORGOTTEN_LINT
42 pub LINT_WITHOUT_LINT_PASS
,
44 "declaring a lint without associating it in a LintPass"
47 declare_clippy_lint
! {
49 /// Checks for cases of an auto-generated lint without an updated description,
50 /// i.e. `default lint description`.
52 /// ### Why is this bad?
53 /// Indicates that the lint is not finished.
57 /// declare_lint! { pub COOL_LINT, nursery, "default lint description" }
62 /// declare_lint! { pub COOL_LINT, nursery, "a great new lint" }
66 "found 'default lint description' in a lint declaration"
69 declare_clippy_lint
! {
71 /// Checks for invalid `clippy::version` attributes.
75 /// * any valid semantic version
76 pub INVALID_CLIPPY_VERSION_ATTRIBUTE
,
78 "found an invalid `clippy::version` attribute"
81 declare_clippy_lint
! {
83 /// Checks for declared clippy lints without the `clippy::version` attribute.
85 pub MISSING_CLIPPY_VERSION_ATTRIBUTE
,
87 "found clippy lint without `clippy::version` attribute"
90 declare_clippy_lint
! {
92 /// Checks for cases of an auto-generated deprecated lint without an updated reason,
93 /// i.e. `"default deprecation note"`.
95 /// ### Why is this bad?
96 /// Indicates that the documentation is incomplete.
100 /// declare_deprecated_lint! {
101 /// /// ### What it does
102 /// /// Nothing. This lint has been deprecated.
104 /// /// ### Deprecation reason
106 /// #[clippy::version = "1.63.0"]
108 /// "default deprecation note"
114 /// declare_deprecated_lint! {
115 /// /// ### What it does
116 /// /// Nothing. This lint has been deprecated.
118 /// /// ### Deprecation reason
119 /// /// This lint has been replaced by `cooler_lint`
120 /// #[clippy::version = "1.63.0"]
122 /// "this lint has been replaced by `cooler_lint`"
125 pub DEFAULT_DEPRECATION_REASON
,
127 "found 'default deprecation note' in a deprecated lint declaration"
130 #[derive(Clone, Debug, Default)]
131 pub struct LintWithoutLintPass
{
132 declared_lints
: FxHashMap
<Symbol
, Span
>,
133 registered_lints
: FxHashSet
<Symbol
>,
136 impl_lint_pass
!(LintWithoutLintPass
=> [DEFAULT_LINT
, LINT_WITHOUT_LINT_PASS
, INVALID_CLIPPY_VERSION_ATTRIBUTE
, MISSING_CLIPPY_VERSION_ATTRIBUTE
, DEFAULT_DEPRECATION_REASON
]);
138 impl<'tcx
> LateLintPass
<'tcx
> for LintWithoutLintPass
{
139 fn check_item(&mut self, cx
: &LateContext
<'tcx
>, item
: &'tcx Item
<'_
>) {
140 if is_lint_allowed(cx
, DEFAULT_LINT
, item
.hir_id())
141 || is_lint_allowed(cx
, DEFAULT_DEPRECATION_REASON
, item
.hir_id())
146 if let hir
::ItemKind
::Static(ty
, Mutability
::Not
, body_id
) = item
.kind
{
147 let is_lint_ref_ty
= is_lint_ref_type(cx
, ty
);
148 if is_deprecated_lint(cx
, ty
) || is_lint_ref_ty
{
149 check_invalid_clippy_version_attribute(cx
, item
);
151 let expr
= &cx
.tcx
.hir().body(body_id
).value
;
154 if let ExprKind
::AddrOf(_
, _
, inner_exp
) = expr
.kind
155 && let ExprKind
::Struct(_
, struct_fields
, _
) = inner_exp
.kind
157 fields
= struct_fields
;
161 } else if let ExprKind
::Struct(_
, struct_fields
, _
) = expr
.kind
{
162 fields
= struct_fields
;
169 .find(|f
| f
.ident
.as_str() == "desc")
170 .expect("lints must have a description field");
172 if let ExprKind
::Lit(Spanned
{
173 node
: LitKind
::Str(ref sym
, _
),
177 let sym_str
= sym
.as_str();
179 if sym_str
== "default lint description" {
184 format
!("the lint `{}` has the default lint description", item
.ident
.name
),
188 self.declared_lints
.insert(item
.ident
.name
, item
.span
);
189 } else if sym_str
== "default deprecation note" {
192 DEFAULT_DEPRECATION_REASON
,
194 format
!("the lint `{}` has the default deprecation reason", item
.ident
.name
),
199 } else if let Some(macro_call
) = root_macro_call_first_node(cx
, item
) {
201 cx
.tcx
.item_name(macro_call
.def_id
).as_str(),
202 "impl_lint_pass" | "declare_lint_pass"
206 if let hir
::ItemKind
::Impl(hir
::Impl
{
208 items
: impl_item_refs
,
212 let mut collector
= LintCollector
{
213 output
: &mut self.registered_lints
,
216 let body_id
= cx
.tcx
.hir().body_owned_by(
219 .find(|iiref
| iiref
.ident
.as_str() == "get_lints")
220 .expect("LintPass needs to implement get_lints")
225 collector
.visit_expr(cx
.tcx
.hir().body(body_id
).value
);
230 fn check_crate_post(&mut self, cx
: &LateContext
<'tcx
>) {
231 if is_lint_allowed(cx
, LINT_WITHOUT_LINT_PASS
, CRATE_HIR_ID
) {
235 for (lint_name
, &lint_span
) in &self.declared_lints
{
236 // When using the `declare_tool_lint!` macro, the original `lint_span`'s
237 // file points to "<rustc macros>".
238 // `compiletest-rs` thinks that's an error in a different file and
239 // just ignores it. This causes the test in compile-fail/lint_pass
240 // not able to capture the error.
241 // Therefore, we need to climb the macro expansion tree and find the
242 // actual span that invoked `declare_tool_lint!`:
243 let lint_span
= lint_span
.ctxt().outer_expn_data().call_site
;
245 if !self.registered_lints
.contains(lint_name
) {
248 LINT_WITHOUT_LINT_PASS
,
250 format
!("the lint `{lint_name}` is not added to any `LintPass`"),
257 pub(super) fn is_lint_ref_type(cx
: &LateContext
<'_
>, ty
: &hir
::Ty
<'_
>) -> bool
{
262 mutbl
: Mutability
::Not
,
266 if let TyKind
::Path(ref path
) = inner
.kind
{
267 if let Res
::Def(DefKind
::Struct
, def_id
) = cx
.qpath_res(path
, inner
.hir_id
) {
268 return match_def_path(cx
, def_id
, &paths
::LINT
);
276 fn check_invalid_clippy_version_attribute(cx
: &LateContext
<'_
>, item
: &'_ Item
<'_
>) {
277 if let Some(value
) = extract_clippy_version_value(cx
, item
) {
278 // The `sym!` macro doesn't work as it only expects a single token.
279 // It's better to keep it this way and have a direct `Symbol::intern` call here.
280 if value
== Symbol
::intern("pre 1.29.0") {
284 if RustcVersion
::parse(value
.as_str()).is_err() {
287 INVALID_CLIPPY_VERSION_ATTRIBUTE
,
289 "this item has an invalid `clippy::version` attribute",
291 "please use a valid semantic version, see `doc/adding_lints.md`",
297 MISSING_CLIPPY_VERSION_ATTRIBUTE
,
299 "this lint is missing the `clippy::version` attribute or version value",
301 "please use a `clippy::version` attribute, see `doc/adding_lints.md`",
306 /// This function extracts the version value of a `clippy::version` attribute if the given value has
308 pub(super) fn extract_clippy_version_value(cx
: &LateContext
<'_
>, item
: &'_ Item
<'_
>) -> Option
<Symbol
> {
309 let attrs
= cx
.tcx
.hir().attrs(item
.hir_id());
310 attrs
.iter().find_map(|attr
| {
311 if let ast
::AttrKind
::Normal(ref attr_kind
) = &attr
.kind
312 // Identify attribute
313 && let [tool_name
, attr_name
] = &attr_kind
.item
.path
.segments
[..]
314 && tool_name
.ident
.name
== sym
::clippy
315 && attr_name
.ident
.name
== sym
::version
316 && let Some(version
) = attr
.value_str()
325 struct LintCollector
<'a
, 'tcx
> {
326 output
: &'a
mut FxHashSet
<Symbol
>,
327 cx
: &'a LateContext
<'tcx
>,
330 impl<'a
, 'tcx
> Visitor
<'tcx
> for LintCollector
<'a
, 'tcx
> {
331 type NestedFilter
= nested_filter
::All
;
333 fn visit_path(&mut self, path
: &Path
<'_
>, _
: HirId
) {
334 if path
.segments
.len() == 1 {
335 self.output
.insert(path
.segments
[0].ident
.name
);
339 fn nested_visit_map(&mut self) -> Self::Map
{