Skip to content

Commit 93f315c

Browse files
committed
Add lint to check for invalid clippy:version attributes
1 parent c83e7c7 commit 93f315c

File tree

7 files changed

+184
-22
lines changed

7 files changed

+184
-22
lines changed

clippy_lints/src/lib.register_internal.rs

+1
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ store.register_group(true, "clippy::internal", Some("clippy_internal"), vec![
99
LintId::of(utils::internal_lints::DEFAULT_LINT),
1010
LintId::of(utils::internal_lints::IF_CHAIN_STYLE),
1111
LintId::of(utils::internal_lints::INTERNING_DEFINED_SYMBOL),
12+
LintId::of(utils::internal_lints::INVALID_CLIPPY_VERSION_ATTRIBUTE),
1213
LintId::of(utils::internal_lints::INVALID_PATHS),
1314
LintId::of(utils::internal_lints::LINT_WITHOUT_LINT_PASS),
1415
LintId::of(utils::internal_lints::MATCH_TYPE_ON_DIAGNOSTIC_ITEM),

clippy_lints/src/lib.register_lints.rs

+2
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ store.register_lints(&[
1616
#[cfg(feature = "internal-lints")]
1717
utils::internal_lints::INTERNING_DEFINED_SYMBOL,
1818
#[cfg(feature = "internal-lints")]
19+
utils::internal_lints::INVALID_CLIPPY_VERSION_ATTRIBUTE,
20+
#[cfg(feature = "internal-lints")]
1921
utils::internal_lints::INVALID_PATHS,
2022
#[cfg(feature = "internal-lints")]
2123
utils::internal_lints::LINT_WITHOUT_LINT_PASS,

clippy_lints/src/misc_early/mod.rs

+1
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,7 @@ declare_clippy_lint! {
156156
/// // Good
157157
/// let y = 123832i32;
158158
/// ```
159+
#[clippy::version = "1.58.0"]
159160
pub SEPARATED_LITERAL_SUFFIX,
160161
restriction,
161162
"literals whose suffix is separated by an underscore"

clippy_lints/src/utils/internal_lints.rs

+68-2
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ use clippy_utils::{
88
paths, SpanlessEq,
99
};
1010
use if_chain::if_chain;
11+
use rustc_ast as ast;
1112
use rustc_ast::ast::{Crate, ItemKind, LitKind, ModKind, NodeId};
1213
use rustc_ast::visit::FnKind;
1314
use rustc_data_structures::fx::{FxHashMap, FxHashSet};
@@ -25,10 +26,11 @@ use rustc_lint::{EarlyContext, EarlyLintPass, LateContext, LateLintPass, LintCon
2526
use rustc_middle::hir::map::Map;
2627
use rustc_middle::mir::interpret::ConstValue;
2728
use rustc_middle::ty;
29+
use rustc_semver::RustcVersion;
2830
use rustc_session::{declare_lint_pass, declare_tool_lint, impl_lint_pass};
2931
use rustc_span::source_map::Spanned;
3032
use rustc_span::symbol::{Symbol, SymbolStr};
31-
use rustc_span::{BytePos, Span};
33+
use rustc_span::{sym, BytePos, Span};
3234
use rustc_typeck::hir_ty_to_ty;
3335

3436
use std::borrow::{Borrow, Cow};
@@ -314,6 +316,26 @@ declare_clippy_lint! {
314316
"non-idiomatic `if_chain!` usage"
315317
}
316318

319+
declare_clippy_lint! {
320+
/// ### What it does
321+
/// Checks for invalid `clippy::version` attributes
322+
///
323+
/// ```txt
324+
/// +-------------------------------+
325+
/// | Valid values are: |
326+
/// | * "pre 1.29.0" |
327+
/// | * any valid semantic version |
328+
/// +-------------------------------+
329+
/// \
330+
/// \ (^v^)
331+
/// <( )>
332+
/// w w
333+
/// ```
334+
pub INVALID_CLIPPY_VERSION_ATTRIBUTE,
335+
internal,
336+
"found an invalid `clippy::version` attribute"
337+
}
338+
317339
declare_lint_pass!(ClippyLintsInternal => [CLIPPY_LINTS_INTERNAL]);
318340

319341
impl EarlyLintPass for ClippyLintsInternal {
@@ -351,7 +373,7 @@ pub struct LintWithoutLintPass {
351373
registered_lints: FxHashSet<Symbol>,
352374
}
353375

354-
impl_lint_pass!(LintWithoutLintPass => [DEFAULT_LINT, LINT_WITHOUT_LINT_PASS]);
376+
impl_lint_pass!(LintWithoutLintPass => [DEFAULT_LINT, LINT_WITHOUT_LINT_PASS, INVALID_CLIPPY_VERSION_ATTRIBUTE]);
355377

356378
impl<'tcx> LateLintPass<'tcx> for LintWithoutLintPass {
357379
fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'_>) {
@@ -361,6 +383,8 @@ impl<'tcx> LateLintPass<'tcx> for LintWithoutLintPass {
361383

362384
if let hir::ItemKind::Static(ty, Mutability::Not, body_id) = item.kind {
363385
if is_lint_ref_type(cx, ty) {
386+
check_invalid_clippy_version_attribute(cx, item);
387+
364388
let expr = &cx.tcx.hir().body(body_id).value;
365389
if_chain! {
366390
if let ExprKind::AddrOf(_, _, inner_exp) = expr.kind;
@@ -458,6 +482,48 @@ fn is_lint_ref_type<'tcx>(cx: &LateContext<'tcx>, ty: &Ty<'_>) -> bool {
458482
false
459483
}
460484

485+
fn check_invalid_clippy_version_attribute(cx: &LateContext<'_>, item: &'_ Item<'_>) {
486+
if let Some(value) = extract_clippy_version_value(cx, item) {
487+
// The `sym!` macro doesn't work as it only expects a single token. I think
488+
// It's better to keep it this way and have a direct `Symbol::intern` call here :)
489+
if value == Symbol::intern("pre 1.29.0") {
490+
return;
491+
}
492+
493+
if RustcVersion::parse(&*value.as_str()).is_err() {
494+
span_lint_and_help(
495+
cx,
496+
INVALID_CLIPPY_VERSION_ATTRIBUTE,
497+
item.span,
498+
"this item has an invalid `clippy::version` attribute",
499+
None,
500+
"please use a valid sematic version, see `doc/adding_lints.md`",
501+
);
502+
}
503+
}
504+
}
505+
506+
/// This function extracts the version value of a `clippy::version` attribute if the given value has
507+
/// one
508+
fn extract_clippy_version_value(cx: &LateContext<'_>, item: &'_ Item<'_>) -> Option<Symbol> {
509+
let attrs = cx.tcx.hir().attrs(item.hir_id());
510+
attrs.iter().find_map(|attr| {
511+
if_chain! {
512+
// Identify attribute
513+
if let ast::AttrKind::Normal(ref attr_kind, _) = &attr.kind;
514+
if let [tool_name, attr_name] = &attr_kind.path.segments[..];
515+
if tool_name.ident.name == sym::clippy;
516+
if attr_name.ident.name == sym::version;
517+
if let Some(version) = attr.value_str();
518+
then {
519+
Some(version)
520+
} else {
521+
None
522+
}
523+
}
524+
})
525+
}
526+
461527
struct LintCollector<'a, 'tcx> {
462528
output: &'a mut FxHashSet<Symbol>,
463529
cx: &'a LateContext<'tcx>,

clippy_lints/src/utils/internal_lints/metadata_collector.rs

+5-20
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ use std::fs::{self, OpenOptions};
2525
use std::io::prelude::*;
2626
use std::path::Path;
2727

28-
use crate::utils::internal_lints::is_lint_ref_type;
28+
use crate::utils::internal_lints::{extract_clippy_version_value, is_lint_ref_type};
2929
use clippy_utils::{
3030
diagnostics::span_lint, last_path_segment, match_def_path, match_function_call, match_path, paths, ty::match_type,
3131
ty::walk_ptrs_ty_depth,
@@ -570,25 +570,10 @@ fn extract_attr_docs(cx: &LateContext<'_>, item: &Item<'_>) -> Option<String> {
570570
}
571571

572572
fn get_lint_version(cx: &LateContext<'_>, item: &Item<'_>) -> String {
573-
let attrs = cx.tcx.hir().attrs(item.hir_id());
574-
attrs
575-
.iter()
576-
.find_map(|attr| {
577-
if_chain! {
578-
// Identify attribute
579-
if let ast::AttrKind::Normal(ref attr_kind, _) = &attr.kind;
580-
if let [tool_name, attr_name] = &attr_kind.path.segments[..];
581-
if tool_name.ident.name == sym::clippy;
582-
if attr_name.ident.name == sym::version;
583-
if let Some(version) = attr.value_str();
584-
then {
585-
Some(version.as_str().to_string())
586-
} else {
587-
None
588-
}
589-
}
590-
})
591-
.unwrap_or_else(|| VERION_DEFAULT_STR.to_string())
573+
extract_clippy_version_value(cx, item).map_or_else(
574+
|| VERION_DEFAULT_STR.to_string(),
575+
|version| version.as_str().to_string(),
576+
)
592577
}
593578

594579
fn get_lint_group_and_level_or_lint(
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
#![deny(clippy::internal)]
2+
#![feature(rustc_private)]
3+
4+
#[macro_use]
5+
extern crate rustc_middle;
6+
#[macro_use]
7+
extern crate rustc_session;
8+
extern crate rustc_lint;
9+
10+
///////////////////////
11+
// Valid descriptions
12+
///////////////////////
13+
declare_tool_lint! {
14+
#[clippy::version = "pre 1.29.0"]
15+
pub clippy::VALID_ONE,
16+
Warn,
17+
"One",
18+
report_in_external_macro: true
19+
}
20+
21+
declare_tool_lint! {
22+
#[clippy::version = "1.29.0"]
23+
pub clippy::VALID_TWO,
24+
Warn,
25+
"Two",
26+
report_in_external_macro: true
27+
}
28+
29+
declare_tool_lint! {
30+
#[clippy::version = "1.59.0"]
31+
pub clippy::VALID_THREE,
32+
Warn,
33+
"Three",
34+
report_in_external_macro: true
35+
}
36+
37+
///////////////////////
38+
// Invalid attributes
39+
///////////////////////
40+
declare_tool_lint! {
41+
#[clippy::version = "1.2.3.4.5.6"]
42+
pub clippy::INVALID_ONE,
43+
Warn,
44+
"One",
45+
report_in_external_macro: true
46+
}
47+
48+
declare_tool_lint! {
49+
#[clippy::version = "I'm a string"]
50+
pub clippy::INVALID_TWO,
51+
Warn,
52+
"Two",
53+
report_in_external_macro: true
54+
}
55+
56+
///////////////////////
57+
// Ignored attributes
58+
///////////////////////
59+
declare_tool_lint! {
60+
#[clippy::version]
61+
pub clippy::IGNORED_ONE,
62+
Warn,
63+
"ONE",
64+
report_in_external_macro: true
65+
}
66+
67+
declare_lint_pass!(Pass2 => [VALID_ONE, VALID_TWO, VALID_THREE, INVALID_ONE, INVALID_TWO, IGNORED_ONE]);
68+
69+
fn main() {}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
error: this item has an invalid `clippy::version` attribute
2+
--> $DIR/check_clippy_version_attribute.rs:40:1
3+
|
4+
LL | / declare_tool_lint! {
5+
LL | | #[clippy::version = "1.2.3.4.5.6"]
6+
LL | | pub clippy::INVALID_ONE,
7+
LL | | Warn,
8+
LL | | "One",
9+
LL | | report_in_external_macro: true
10+
LL | | }
11+
| |_^
12+
|
13+
note: the lint level is defined here
14+
--> $DIR/check_clippy_version_attribute.rs:1:9
15+
|
16+
LL | #![deny(clippy::internal)]
17+
| ^^^^^^^^^^^^^^^^
18+
= note: `#[deny(clippy::invalid_clippy_version_attribute)]` implied by `#[deny(clippy::internal)]`
19+
= help: please use a valid sematic version, see `doc/adding_lints.md`
20+
= note: this error originates in the macro `$crate::declare_tool_lint` (in Nightly builds, run with -Z macro-backtrace for more info)
21+
22+
error: this item has an invalid `clippy::version` attribute
23+
--> $DIR/check_clippy_version_attribute.rs:48:1
24+
|
25+
LL | / declare_tool_lint! {
26+
LL | | #[clippy::version = "I'm a string"]
27+
LL | | pub clippy::INVALID_TWO,
28+
LL | | Warn,
29+
LL | | "Two",
30+
LL | | report_in_external_macro: true
31+
LL | | }
32+
| |_^
33+
|
34+
= help: please use a valid sematic version, see `doc/adding_lints.md`
35+
= note: this error originates in the macro `$crate::declare_tool_lint` (in Nightly builds, run with -Z macro-backtrace for more info)
36+
37+
error: aborting due to 2 previous errors
38+

0 commit comments

Comments
 (0)