@@ -8,6 +8,7 @@ use clippy_utils::{
88 paths, SpanlessEq ,
99} ;
1010use if_chain:: if_chain;
11+ use rustc_ast as ast;
1112use rustc_ast:: ast:: { Crate , ItemKind , LitKind , ModKind , NodeId } ;
1213use rustc_ast:: visit:: FnKind ;
1314use rustc_data_structures:: fx:: { FxHashMap , FxHashSet } ;
@@ -25,10 +26,11 @@ use rustc_lint::{EarlyContext, EarlyLintPass, LateContext, LateLintPass, LintCon
2526use rustc_middle:: hir:: map:: Map ;
2627use rustc_middle:: mir:: interpret:: ConstValue ;
2728use rustc_middle:: ty;
29+ use rustc_semver:: RustcVersion ;
2830use rustc_session:: { declare_lint_pass, declare_tool_lint, impl_lint_pass} ;
2931use rustc_span:: source_map:: Spanned ;
3032use rustc_span:: symbol:: { Symbol , SymbolStr } ;
31- use rustc_span:: { BytePos , Span } ;
33+ use rustc_span:: { sym , BytePos , Span } ;
3234use rustc_typeck:: hir_ty_to_ty;
3335
3436use 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+
317339declare_lint_pass ! ( ClippyLintsInternal => [ CLIPPY_LINTS_INTERNAL ] ) ;
318340
319341impl 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
356378impl < ' 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+
461527struct LintCollector < ' a , ' tcx > {
462528 output : & ' a mut FxHashSet < Symbol > ,
463529 cx : & ' a LateContext < ' tcx > ,
0 commit comments