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 if_chain
::if_chain
;
7 use rustc_ast
::ast
::LitKind
;
8 use rustc_data_structures
::fx
::{FxHashMap, FxHashSet}
;
10 use rustc_hir
::def
::{DefKind, Res}
;
11 use rustc_hir
::hir_id
::CRATE_HIR_ID
;
12 use rustc_hir
::intravisit
::Visitor
;
13 use rustc_hir
::{ExprKind, HirId, Item, MutTy, Mutability, Path, TyKind}
;
14 use rustc_lint
::{LateContext, LateLintPass}
;
15 use rustc_middle
::hir
::nested_filter
;
16 use rustc_semver
::RustcVersion
;
17 use rustc_session
::{declare_tool_lint, impl_lint_pass}
;
18 use rustc_span
::source_map
::Spanned
;
19 use rustc_span
::symbol
::Symbol
;
20 use rustc_span
::{sym, Span}
;
22 declare_clippy_lint
! {
24 /// Ensures every lint is associated to a `LintPass`.
26 /// ### Why is this bad?
27 /// The compiler only knows lints via a `LintPass`. Without
28 /// putting a lint to a `LintPass::get_lints()`'s return, the compiler will not
29 /// know the name of the lint.
31 /// ### Known problems
32 /// Only checks for lints associated using the
33 /// `declare_lint_pass!`, `impl_lint_pass!`, and `lint_array!` macros.
37 /// declare_lint! { pub LINT_1, ... }
38 /// declare_lint! { pub LINT_2, ... }
39 /// declare_lint! { pub FORGOTTEN_LINT, ... }
41 /// declare_lint_pass!(Pass => [LINT_1, LINT_2]);
42 /// // missing FORGOTTEN_LINT
44 pub LINT_WITHOUT_LINT_PASS
,
46 "declaring a lint without associating it in a LintPass"
49 declare_clippy_lint
! {
51 /// Checks for cases of an auto-generated lint without an updated description,
52 /// i.e. `default lint description`.
54 /// ### Why is this bad?
55 /// Indicates that the lint is not finished.
59 /// declare_lint! { pub COOL_LINT, nursery, "default lint description" }
64 /// declare_lint! { pub COOL_LINT, nursery, "a great new lint" }
68 "found 'default lint description' in a lint declaration"
71 declare_clippy_lint
! {
73 /// Checks for invalid `clippy::version` attributes.
77 /// * any valid semantic version
78 pub INVALID_CLIPPY_VERSION_ATTRIBUTE
,
80 "found an invalid `clippy::version` attribute"
83 declare_clippy_lint
! {
85 /// Checks for declared clippy lints without the `clippy::version` attribute.
87 pub MISSING_CLIPPY_VERSION_ATTRIBUTE
,
89 "found clippy lint without `clippy::version` attribute"
92 declare_clippy_lint
! {
94 /// Checks for cases of an auto-generated deprecated lint without an updated reason,
95 /// i.e. `"default deprecation note"`.
97 /// ### Why is this bad?
98 /// Indicates that the documentation is incomplete.
102 /// declare_deprecated_lint! {
103 /// /// ### What it does
104 /// /// Nothing. This lint has been deprecated.
106 /// /// ### Deprecation reason
108 /// #[clippy::version = "1.63.0"]
110 /// "default deprecation note"
116 /// declare_deprecated_lint! {
117 /// /// ### What it does
118 /// /// Nothing. This lint has been deprecated.
120 /// /// ### Deprecation reason
121 /// /// This lint has been replaced by `cooler_lint`
122 /// #[clippy::version = "1.63.0"]
124 /// "this lint has been replaced by `cooler_lint`"
127 pub DEFAULT_DEPRECATION_REASON
,
129 "found 'default deprecation note' in a deprecated lint declaration"
132 #[derive(Clone, Debug, Default)]
133 pub struct LintWithoutLintPass
{
134 declared_lints
: FxHashMap
<Symbol
, Span
>,
135 registered_lints
: FxHashSet
<Symbol
>,
138 impl_lint_pass
!(LintWithoutLintPass
=> [DEFAULT_LINT
, LINT_WITHOUT_LINT_PASS
, INVALID_CLIPPY_VERSION_ATTRIBUTE
, MISSING_CLIPPY_VERSION_ATTRIBUTE
, DEFAULT_DEPRECATION_REASON
]);
140 impl<'tcx
> LateLintPass
<'tcx
> for LintWithoutLintPass
{
141 fn check_item(&mut self, cx
: &LateContext
<'tcx
>, item
: &'tcx Item
<'_
>) {
142 if is_lint_allowed(cx
, DEFAULT_LINT
, item
.hir_id())
143 || is_lint_allowed(cx
, DEFAULT_DEPRECATION_REASON
, item
.hir_id())
148 if let hir
::ItemKind
::Static(ty
, Mutability
::Not
, body_id
) = item
.kind
{
149 let is_lint_ref_ty
= is_lint_ref_type(cx
, ty
);
150 if is_deprecated_lint(cx
, ty
) || is_lint_ref_ty
{
151 check_invalid_clippy_version_attribute(cx
, item
);
153 let expr
= &cx
.tcx
.hir().body(body_id
).value
;
156 if let ExprKind
::AddrOf(_
, _
, inner_exp
) = expr
.kind
157 && let ExprKind
::Struct(_
, struct_fields
, _
) = inner_exp
.kind
{
158 fields
= struct_fields
;
162 } else if let ExprKind
::Struct(_
, struct_fields
, _
) = expr
.kind
{
163 fields
= struct_fields
;
170 .find(|f
| f
.ident
.as_str() == "desc")
171 .expect("lints must have a description field");
173 if let ExprKind
::Lit(Spanned
{
174 node
: LitKind
::Str(ref sym
, _
),
178 let sym_str
= sym
.as_str();
180 if sym_str
== "default lint description" {
185 &format
!("the lint `{}` has the default lint description", item
.ident
.name
),
189 self.declared_lints
.insert(item
.ident
.name
, item
.span
);
190 } else if sym_str
== "default deprecation note" {
193 DEFAULT_DEPRECATION_REASON
,
195 &format
!("the lint `{}` has the default deprecation reason", item
.ident
.name
),
200 } else if let Some(macro_call
) = root_macro_call_first_node(cx
, item
) {
202 cx
.tcx
.item_name(macro_call
.def_id
).as_str(),
203 "impl_lint_pass" | "declare_lint_pass"
207 if let hir
::ItemKind
::Impl(hir
::Impl
{
209 items
: impl_item_refs
,
213 let mut collector
= LintCollector
{
214 output
: &mut self.registered_lints
,
217 let body_id
= cx
.tcx
.hir().body_owned_by(
218 cx
.tcx
.hir().local_def_id(
221 .find(|iiref
| iiref
.ident
.as_str() == "get_lints")
222 .expect("LintPass needs to implement get_lints")
227 collector
.visit_expr(cx
.tcx
.hir().body(body_id
).value
);
232 fn check_crate_post(&mut self, cx
: &LateContext
<'tcx
>) {
233 if is_lint_allowed(cx
, LINT_WITHOUT_LINT_PASS
, CRATE_HIR_ID
) {
237 for (lint_name
, &lint_span
) in &self.declared_lints
{
238 // When using the `declare_tool_lint!` macro, the original `lint_span`'s
239 // file points to "<rustc macros>".
240 // `compiletest-rs` thinks that's an error in a different file and
241 // just ignores it. This causes the test in compile-fail/lint_pass
242 // not able to capture the error.
243 // Therefore, we need to climb the macro expansion tree and find the
244 // actual span that invoked `declare_tool_lint!`:
245 let lint_span
= lint_span
.ctxt().outer_expn_data().call_site
;
247 if !self.registered_lints
.contains(lint_name
) {
250 LINT_WITHOUT_LINT_PASS
,
252 &format
!("the lint `{lint_name}` is not added to any `LintPass`"),
259 pub(super) fn is_lint_ref_type(cx
: &LateContext
<'_
>, ty
: &hir
::Ty
<'_
>) -> bool
{
264 mutbl
: Mutability
::Not
,
268 if let TyKind
::Path(ref path
) = inner
.kind
{
269 if let Res
::Def(DefKind
::Struct
, def_id
) = cx
.qpath_res(path
, inner
.hir_id
) {
270 return match_def_path(cx
, def_id
, &paths
::LINT
);
278 fn check_invalid_clippy_version_attribute(cx
: &LateContext
<'_
>, item
: &'_ Item
<'_
>) {
279 if let Some(value
) = extract_clippy_version_value(cx
, item
) {
280 // The `sym!` macro doesn't work as it only expects a single token.
281 // It's better to keep it this way and have a direct `Symbol::intern` call here.
282 if value
== Symbol
::intern("pre 1.29.0") {
286 if RustcVersion
::parse(value
.as_str()).is_err() {
289 INVALID_CLIPPY_VERSION_ATTRIBUTE
,
291 "this item has an invalid `clippy::version` attribute",
293 "please use a valid semantic version, see `doc/adding_lints.md`",
299 MISSING_CLIPPY_VERSION_ATTRIBUTE
,
301 "this lint is missing the `clippy::version` attribute or version value",
303 "please use a `clippy::version` attribute, see `doc/adding_lints.md`",
308 /// This function extracts the version value of a `clippy::version` attribute if the given value has
310 pub(super) fn extract_clippy_version_value(cx
: &LateContext
<'_
>, item
: &'_ Item
<'_
>) -> Option
<Symbol
> {
311 let attrs
= cx
.tcx
.hir().attrs(item
.hir_id());
312 attrs
.iter().find_map(|attr
| {
314 // Identify attribute
315 if let ast
::AttrKind
::Normal(ref attr_kind
) = &attr
.kind
;
316 if let [tool_name
, attr_name
] = &attr_kind
.item
.path
.segments
[..];
317 if tool_name
.ident
.name
== sym
::clippy
;
318 if attr_name
.ident
.name
== sym
::version
;
319 if let Some(version
) = attr
.value_str();
320 then { Some(version) }
else { None }
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
{