@@ -8,6 +8,7 @@ use clippy_utils::{
8
8
paths, SpanlessEq ,
9
9
} ;
10
10
use if_chain:: if_chain;
11
+ use rustc_ast as ast;
11
12
use rustc_ast:: ast:: { Crate , ItemKind , LitKind , ModKind , NodeId } ;
12
13
use rustc_ast:: visit:: FnKind ;
13
14
use rustc_data_structures:: fx:: { FxHashMap , FxHashSet } ;
@@ -25,10 +26,11 @@ use rustc_lint::{EarlyContext, EarlyLintPass, LateContext, LateLintPass, LintCon
25
26
use rustc_middle:: hir:: map:: Map ;
26
27
use rustc_middle:: mir:: interpret:: ConstValue ;
27
28
use rustc_middle:: ty;
29
+ use rustc_semver:: RustcVersion ;
28
30
use rustc_session:: { declare_lint_pass, declare_tool_lint, impl_lint_pass} ;
29
31
use rustc_span:: source_map:: Spanned ;
30
32
use rustc_span:: symbol:: { Symbol , SymbolStr } ;
31
- use rustc_span:: { BytePos , Span } ;
33
+ use rustc_span:: { sym , BytePos , Span } ;
32
34
use rustc_typeck:: hir_ty_to_ty;
33
35
34
36
use std:: borrow:: { Borrow , Cow } ;
@@ -314,6 +316,26 @@ declare_clippy_lint! {
314
316
"non-idiomatic `if_chain!` usage"
315
317
}
316
318
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
+
317
339
declare_lint_pass ! ( ClippyLintsInternal => [ CLIPPY_LINTS_INTERNAL ] ) ;
318
340
319
341
impl EarlyLintPass for ClippyLintsInternal {
@@ -351,7 +373,7 @@ pub struct LintWithoutLintPass {
351
373
registered_lints : FxHashSet < Symbol > ,
352
374
}
353
375
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 ] ) ;
355
377
356
378
impl < ' tcx > LateLintPass < ' tcx > for LintWithoutLintPass {
357
379
fn check_item ( & mut self , cx : & LateContext < ' tcx > , item : & ' tcx Item < ' _ > ) {
@@ -361,6 +383,8 @@ impl<'tcx> LateLintPass<'tcx> for LintWithoutLintPass {
361
383
362
384
if let hir:: ItemKind :: Static ( ty, Mutability :: Not , body_id) = item. kind {
363
385
if is_lint_ref_type ( cx, ty) {
386
+ check_invalid_clippy_version_attribute ( cx, item) ;
387
+
364
388
let expr = & cx. tcx . hir ( ) . body ( body_id) . value ;
365
389
if_chain ! {
366
390
if let ExprKind :: AddrOf ( _, _, inner_exp) = expr. kind;
@@ -458,6 +482,48 @@ fn is_lint_ref_type<'tcx>(cx: &LateContext<'tcx>, ty: &Ty<'_>) -> bool {
458
482
false
459
483
}
460
484
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
+
461
527
struct LintCollector < ' a , ' tcx > {
462
528
output : & ' a mut FxHashSet < Symbol > ,
463
529
cx : & ' a LateContext < ' tcx > ,
0 commit comments