]> git.lizzy.rs Git - rust.git/blob - clippy_lints/src/attrs.rs
Auto merge of #3946 - rchaser53:issue-3920, r=flip1995
[rust.git] / clippy_lints / src / attrs.rs
1 //! checks for attributes
2
3 use crate::reexport::*;
4 use crate::utils::{
5     in_macro, last_line_of_span, match_def_path, paths, snippet_opt, span_lint, span_lint_and_sugg, span_lint_and_then,
6     without_block_comments,
7 };
8 use if_chain::if_chain;
9 use rustc::hir::*;
10 use rustc::lint::{
11     in_external_macro, CheckLintNameResult, EarlyContext, EarlyLintPass, LateContext, LateLintPass, LintArray,
12     LintContext, LintPass,
13 };
14 use rustc::ty::{self, TyCtxt};
15 use rustc::{declare_tool_lint, lint_array};
16 use rustc_errors::Applicability;
17 use semver::Version;
18 use syntax::ast::{AttrStyle, Attribute, Lit, LitKind, MetaItemKind, NestedMetaItem};
19 use syntax::source_map::Span;
20
21 declare_clippy_lint! {
22     /// **What it does:** Checks for items annotated with `#[inline(always)]`,
23     /// unless the annotated function is empty or simply panics.
24     ///
25     /// **Why is this bad?** While there are valid uses of this annotation (and once
26     /// you know when to use it, by all means `allow` this lint), it's a common
27     /// newbie-mistake to pepper one's code with it.
28     ///
29     /// As a rule of thumb, before slapping `#[inline(always)]` on a function,
30     /// measure if that additional function call really affects your runtime profile
31     /// sufficiently to make up for the increase in compile time.
32     ///
33     /// **Known problems:** False positives, big time. This lint is meant to be
34     /// deactivated by everyone doing serious performance work. This means having
35     /// done the measurement.
36     ///
37     /// **Example:**
38     /// ```ignore
39     /// #[inline(always)]
40     /// fn not_quite_hot_code(..) { ... }
41     /// ```
42     pub INLINE_ALWAYS,
43     pedantic,
44     "use of `#[inline(always)]`"
45 }
46
47 declare_clippy_lint! {
48     /// **What it does:** Checks for `extern crate` and `use` items annotated with
49     /// lint attributes.
50     ///
51     /// This lint whitelists `#[allow(unused_imports)]` and `#[allow(deprecated)]` on
52     /// `use` items and `#[allow(unused_imports)]` on `extern crate` items with a
53     /// `#[macro_use]` attribute.
54     ///
55     /// **Why is this bad?** Lint attributes have no effect on crate imports. Most
56     /// likely a `!` was forgotten.
57     ///
58     /// **Known problems:** None.
59     ///
60     /// **Example:**
61     /// ```ignore
62     /// // Bad
63     /// #[deny(dead_code)]
64     /// extern crate foo;
65     /// #[forbid(dead_code)]
66     /// use foo::bar;
67     ///
68     /// // Ok
69     /// #[allow(unused_imports)]
70     /// use foo::baz;
71     /// #[allow(unused_imports)]
72     /// #[macro_use]
73     /// extern crate baz;
74     /// ```
75     pub USELESS_ATTRIBUTE,
76     correctness,
77     "use of lint attributes on `extern crate` items"
78 }
79
80 declare_clippy_lint! {
81     /// **What it does:** Checks for `#[deprecated]` annotations with a `since`
82     /// field that is not a valid semantic version.
83     ///
84     /// **Why is this bad?** For checking the version of the deprecation, it must be
85     /// a valid semver. Failing that, the contained information is useless.
86     ///
87     /// **Known problems:** None.
88     ///
89     /// **Example:**
90     /// ```rust
91     /// #[deprecated(since = "forever")]
92     /// fn something_else() { /* ... */ }
93     /// ```
94     pub DEPRECATED_SEMVER,
95     correctness,
96     "use of `#[deprecated(since = \"x\")]` where x is not semver"
97 }
98
99 declare_clippy_lint! {
100     /// **What it does:** Checks for empty lines after outer attributes
101     ///
102     /// **Why is this bad?**
103     /// Most likely the attribute was meant to be an inner attribute using a '!'.
104     /// If it was meant to be an outer attribute, then the following item
105     /// should not be separated by empty lines.
106     ///
107     /// **Known problems:** Can cause false positives.
108     ///
109     /// From the clippy side it's difficult to detect empty lines between an attributes and the
110     /// following item because empty lines and comments are not part of the AST. The parsing
111     /// currently works for basic cases but is not perfect.
112     ///
113     /// **Example:**
114     /// ```rust
115     /// // Bad
116     /// #[inline(always)]
117     ///
118     /// fn not_quite_good_code(..) { ... }
119     ///
120     /// // Good (as inner attribute)
121     /// #![inline(always)]
122     ///
123     /// fn this_is_fine(..) { ... }
124     ///
125     /// // Good (as outer attribute)
126     /// #[inline(always)]
127     /// fn this_is_fine_too(..) { ... }
128     /// ```
129     pub EMPTY_LINE_AFTER_OUTER_ATTR,
130     nursery,
131     "empty line after outer attribute"
132 }
133
134 declare_clippy_lint! {
135     /// **What it does:** Checks for `allow`/`warn`/`deny`/`forbid` attributes with scoped clippy
136     /// lints and if those lints exist in clippy. If there is a uppercase letter in the lint name
137     /// (not the tool name) and a lowercase version of this lint exists, it will suggest to lowercase
138     /// the lint name.
139     ///
140     /// **Why is this bad?** A lint attribute with a mistyped lint name won't have an effect.
141     ///
142     /// **Known problems:** None.
143     ///
144     /// **Example:**
145     /// Bad:
146     /// ```rust
147     /// #![warn(if_not_els)]
148     /// #![deny(clippy::All)]
149     /// ```
150     ///
151     /// Good:
152     /// ```rust
153     /// #![warn(if_not_else)]
154     /// #![deny(clippy::all)]
155     /// ```
156     pub UNKNOWN_CLIPPY_LINTS,
157     style,
158     "unknown_lints for scoped Clippy lints"
159 }
160
161 declare_clippy_lint! {
162     /// **What it does:** Checks for `#[cfg_attr(rustfmt, rustfmt_skip)]` and suggests to replace it
163     /// with `#[rustfmt::skip]`.
164     ///
165     /// **Why is this bad?** Since tool_attributes ([rust-lang/rust#44690](https://github.com/rust-lang/rust/issues/44690))
166     /// are stable now, they should be used instead of the old `cfg_attr(rustfmt)` attributes.
167     ///
168     /// **Known problems:** This lint doesn't detect crate level inner attributes, because they get
169     /// processed before the PreExpansionPass lints get executed. See
170     /// [#3123](https://github.com/rust-lang/rust-clippy/pull/3123#issuecomment-422321765)
171     ///
172     /// **Example:**
173     ///
174     /// Bad:
175     /// ```rust
176     /// #[cfg_attr(rustfmt, rustfmt_skip)]
177     /// fn main() { }
178     /// ```
179     ///
180     /// Good:
181     /// ```rust
182     /// #[rustfmt::skip]
183     /// fn main() { }
184     /// ```
185     pub DEPRECATED_CFG_ATTR,
186     complexity,
187     "usage of `cfg_attr(rustfmt)` instead of `tool_attributes`"
188 }
189
190 #[derive(Copy, Clone)]
191 pub struct AttrPass;
192
193 impl LintPass for AttrPass {
194     fn get_lints(&self) -> LintArray {
195         lint_array!(
196             INLINE_ALWAYS,
197             DEPRECATED_SEMVER,
198             USELESS_ATTRIBUTE,
199             EMPTY_LINE_AFTER_OUTER_ATTR,
200             UNKNOWN_CLIPPY_LINTS,
201         )
202     }
203
204     fn name(&self) -> &'static str {
205         "Attributes"
206     }
207 }
208
209 impl<'a, 'tcx> LateLintPass<'a, 'tcx> for AttrPass {
210     fn check_attribute(&mut self, cx: &LateContext<'a, 'tcx>, attr: &'tcx Attribute) {
211         if let Some(items) = &attr.meta_item_list() {
212             if let Some(ident) = attr.ident() {
213                 match &*ident.as_str() {
214                     "allow" | "warn" | "deny" | "forbid" => {
215                         check_clippy_lint_names(cx, items);
216                     },
217                     _ => {},
218                 }
219                 if items.is_empty() || !attr.check_name("deprecated") {
220                     return;
221                 }
222                 for item in items {
223                     if_chain! {
224                         if let NestedMetaItem::MetaItem(mi) = &item;
225                         if let MetaItemKind::NameValue(lit) = &mi.node;
226                         if mi.check_name("since");
227                         then {
228                             check_semver(cx, item.span(), lit);
229                         }
230                     }
231                 }
232             }
233         }
234     }
235
236     fn check_item(&mut self, cx: &LateContext<'a, 'tcx>, item: &'tcx Item) {
237         if is_relevant_item(cx.tcx, item) {
238             check_attrs(cx, item.span, item.ident.name, &item.attrs)
239         }
240         match item.node {
241             ItemKind::ExternCrate(..) | ItemKind::Use(..) => {
242                 let skip_unused_imports = item.attrs.iter().any(|attr| attr.check_name("macro_use"));
243
244                 for attr in &item.attrs {
245                     if in_external_macro(cx.sess(), attr.span) {
246                         return;
247                     }
248                     if let Some(lint_list) = &attr.meta_item_list() {
249                         if let Some(ident) = attr.ident() {
250                             match &*ident.as_str() {
251                                 "allow" | "warn" | "deny" | "forbid" => {
252                                     // whitelist `unused_imports` and `deprecated` for `use` items
253                                     // and `unused_imports` for `extern crate` items with `macro_use`
254                                     for lint in lint_list {
255                                         match item.node {
256                                             ItemKind::Use(..) => {
257                                                 if is_word(lint, "unused_imports") || is_word(lint, "deprecated") {
258                                                     return;
259                                                 }
260                                             },
261                                             ItemKind::ExternCrate(..) => {
262                                                 if is_word(lint, "unused_imports") && skip_unused_imports {
263                                                     return;
264                                                 }
265                                                 if is_word(lint, "unused_extern_crates") {
266                                                     return;
267                                                 }
268                                             },
269                                             _ => {},
270                                         }
271                                     }
272                                     let line_span = last_line_of_span(cx, attr.span);
273
274                                     if let Some(mut sugg) = snippet_opt(cx, line_span) {
275                                         if sugg.contains("#[") {
276                                             span_lint_and_then(
277                                                 cx,
278                                                 USELESS_ATTRIBUTE,
279                                                 line_span,
280                                                 "useless lint attribute",
281                                                 |db| {
282                                                     sugg = sugg.replacen("#[", "#![", 1);
283                                                     db.span_suggestion(
284                                                         line_span,
285                                                         "if you just forgot a `!`, use",
286                                                         sugg,
287                                                         Applicability::MachineApplicable,
288                                                     );
289                                                 },
290                                             );
291                                         }
292                                     }
293                                 },
294                                 _ => {},
295                             }
296                         }
297                     }
298                 }
299             },
300             _ => {},
301         }
302     }
303
304     fn check_impl_item(&mut self, cx: &LateContext<'a, 'tcx>, item: &'tcx ImplItem) {
305         if is_relevant_impl(cx.tcx, item) {
306             check_attrs(cx, item.span, item.ident.name, &item.attrs)
307         }
308     }
309
310     fn check_trait_item(&mut self, cx: &LateContext<'a, 'tcx>, item: &'tcx TraitItem) {
311         if is_relevant_trait(cx.tcx, item) {
312             check_attrs(cx, item.span, item.ident.name, &item.attrs)
313         }
314     }
315 }
316
317 #[allow(clippy::single_match_else)]
318 fn check_clippy_lint_names(cx: &LateContext<'_, '_>, items: &[NestedMetaItem]) {
319     let lint_store = cx.lints();
320     for lint in items {
321         if_chain! {
322             if let Some(meta_item) = lint.meta_item();
323             if meta_item.path.segments.len() > 1;
324             if let tool_name = meta_item.path.segments[0].ident;
325             if tool_name.as_str() == "clippy";
326             let name = meta_item.path.segments.last().unwrap().ident.name;
327             if let CheckLintNameResult::Tool(Err((None, _))) = lint_store.check_lint_name(
328                 &name.as_str(),
329                 Some(tool_name.as_str()),
330             );
331             then {
332                 span_lint_and_then(
333                     cx,
334                     UNKNOWN_CLIPPY_LINTS,
335                     lint.span(),
336                     &format!("unknown clippy lint: clippy::{}", name),
337                     |db| {
338                         if name.as_str().chars().any(char::is_uppercase) {
339                             let name_lower = name.as_str().to_lowercase();
340                             match lint_store.check_lint_name(
341                                 &name_lower,
342                                 Some(tool_name.as_str())
343                             ) {
344                                 // FIXME: can we suggest similar lint names here?
345                                 // https://github.com/rust-lang/rust/pull/56992
346                                 CheckLintNameResult::NoLint(None) => (),
347                                 _ => {
348                                     db.span_suggestion(
349                                         lint.span(),
350                                         "lowercase the lint name",
351                                         name_lower,
352                                         Applicability::MaybeIncorrect,
353                                     );
354                                 }
355                             }
356                         }
357                     }
358                 );
359             }
360         };
361     }
362 }
363
364 fn is_relevant_item<'a, 'tcx>(tcx: TyCtxt<'a, 'tcx, 'tcx>, item: &Item) -> bool {
365     if let ItemKind::Fn(_, _, _, eid) = item.node {
366         is_relevant_expr(tcx, tcx.body_tables(eid), &tcx.hir().body(eid).value)
367     } else {
368         true
369     }
370 }
371
372 fn is_relevant_impl<'a, 'tcx>(tcx: TyCtxt<'a, 'tcx, 'tcx>, item: &ImplItem) -> bool {
373     match item.node {
374         ImplItemKind::Method(_, eid) => is_relevant_expr(tcx, tcx.body_tables(eid), &tcx.hir().body(eid).value),
375         _ => false,
376     }
377 }
378
379 fn is_relevant_trait<'a, 'tcx>(tcx: TyCtxt<'a, 'tcx, 'tcx>, item: &TraitItem) -> bool {
380     match item.node {
381         TraitItemKind::Method(_, TraitMethod::Required(_)) => true,
382         TraitItemKind::Method(_, TraitMethod::Provided(eid)) => {
383             is_relevant_expr(tcx, tcx.body_tables(eid), &tcx.hir().body(eid).value)
384         },
385         _ => false,
386     }
387 }
388
389 fn is_relevant_block<'a, 'tcx>(tcx: TyCtxt<'a, 'tcx, 'tcx>, tables: &ty::TypeckTables<'_>, block: &Block) -> bool {
390     if let Some(stmt) = block.stmts.first() {
391         match &stmt.node {
392             StmtKind::Local(_) => true,
393             StmtKind::Expr(expr) | StmtKind::Semi(expr) => is_relevant_expr(tcx, tables, expr),
394             _ => false,
395         }
396     } else {
397         block.expr.as_ref().map_or(false, |e| is_relevant_expr(tcx, tables, e))
398     }
399 }
400
401 fn is_relevant_expr<'a, 'tcx>(tcx: TyCtxt<'a, 'tcx, 'tcx>, tables: &ty::TypeckTables<'_>, expr: &Expr) -> bool {
402     match &expr.node {
403         ExprKind::Block(block, _) => is_relevant_block(tcx, tables, block),
404         ExprKind::Ret(Some(e)) => is_relevant_expr(tcx, tables, e),
405         ExprKind::Ret(None) | ExprKind::Break(_, None) => false,
406         ExprKind::Call(path_expr, _) => {
407             if let ExprKind::Path(qpath) = &path_expr.node {
408                 if let Some(fun_id) = tables.qpath_def(qpath, path_expr.hir_id).opt_def_id() {
409                     !match_def_path(tcx, fun_id, &paths::BEGIN_PANIC)
410                 } else {
411                     true
412                 }
413             } else {
414                 true
415             }
416         },
417         _ => true,
418     }
419 }
420
421 fn check_attrs(cx: &LateContext<'_, '_>, span: Span, name: Name, attrs: &[Attribute]) {
422     if in_macro(span) {
423         return;
424     }
425
426     for attr in attrs {
427         if attr.is_sugared_doc {
428             return;
429         }
430         if attr.style == AttrStyle::Outer {
431             if attr.tokens.is_empty() || !is_present_in_source(cx, attr.span) {
432                 return;
433             }
434
435             let begin_of_attr_to_item = Span::new(attr.span.lo(), span.lo(), span.ctxt());
436             let end_of_attr_to_item = Span::new(attr.span.hi(), span.lo(), span.ctxt());
437
438             if let Some(snippet) = snippet_opt(cx, end_of_attr_to_item) {
439                 let lines = snippet.split('\n').collect::<Vec<_>>();
440                 let lines = without_block_comments(lines);
441
442                 if lines.iter().filter(|l| l.trim().is_empty()).count() > 2 {
443                     span_lint(
444                         cx,
445                         EMPTY_LINE_AFTER_OUTER_ATTR,
446                         begin_of_attr_to_item,
447                         "Found an empty line after an outer attribute. \
448                          Perhaps you forgot to add a '!' to make it an inner attribute?",
449                     );
450                 }
451             }
452         }
453
454         if let Some(values) = attr.meta_item_list() {
455             if values.len() != 1 || !attr.check_name("inline") {
456                 continue;
457             }
458             if is_word(&values[0], "always") {
459                 span_lint(
460                     cx,
461                     INLINE_ALWAYS,
462                     attr.span,
463                     &format!(
464                         "you have declared `#[inline(always)]` on `{}`. This is usually a bad idea",
465                         name
466                     ),
467                 );
468             }
469         }
470     }
471 }
472
473 fn check_semver(cx: &LateContext<'_, '_>, span: Span, lit: &Lit) {
474     if let LitKind::Str(is, _) = lit.node {
475         if Version::parse(&is.as_str()).is_ok() {
476             return;
477         }
478     }
479     span_lint(
480         cx,
481         DEPRECATED_SEMVER,
482         span,
483         "the since field must contain a semver-compliant version",
484     );
485 }
486
487 fn is_word(nmi: &NestedMetaItem, expected: &str) -> bool {
488     if let NestedMetaItem::MetaItem(mi) = &nmi {
489         mi.is_word() && mi.check_name(expected)
490     } else {
491         false
492     }
493 }
494
495 // If the snippet is empty, it's an attribute that was inserted during macro
496 // expansion and we want to ignore those, because they could come from external
497 // sources that the user has no control over.
498 // For some reason these attributes don't have any expansion info on them, so
499 // we have to check it this way until there is a better way.
500 fn is_present_in_source(cx: &LateContext<'_, '_>, span: Span) -> bool {
501     if let Some(snippet) = snippet_opt(cx, span) {
502         if snippet.is_empty() {
503             return false;
504         }
505     }
506     true
507 }
508
509 #[derive(Copy, Clone)]
510 pub struct CfgAttrPass;
511
512 impl LintPass for CfgAttrPass {
513     fn get_lints(&self) -> LintArray {
514         lint_array!(DEPRECATED_CFG_ATTR,)
515     }
516
517     fn name(&self) -> &'static str {
518         "DeprecatedCfgAttribute"
519     }
520 }
521
522 impl EarlyLintPass for CfgAttrPass {
523     fn check_attribute(&mut self, cx: &EarlyContext<'_>, attr: &Attribute) {
524         if_chain! {
525             // check cfg_attr
526             if attr.check_name("cfg_attr");
527             if let Some(items) = attr.meta_item_list();
528             if items.len() == 2;
529             // check for `rustfmt`
530             if let Some(feature_item) = items[0].meta_item();
531             if feature_item.check_name("rustfmt");
532             // check for `rustfmt_skip` and `rustfmt::skip`
533             if let Some(skip_item) = &items[1].meta_item();
534             if skip_item.check_name("rustfmt_skip") ||
535                 skip_item.path.segments.last().expect("empty path in attribute").ident.name == "skip";
536             // Only lint outer attributes, because custom inner attributes are unstable
537             // Tracking issue: https://github.com/rust-lang/rust/issues/54726
538             if let AttrStyle::Outer = attr.style;
539             then {
540                 span_lint_and_sugg(
541                     cx,
542                     DEPRECATED_CFG_ATTR,
543                     attr.span,
544                     "`cfg_attr` is deprecated for rustfmt and got replaced by tool_attributes",
545                     "use",
546                     "#[rustfmt::skip]".to_string(),
547                     Applicability::MachineApplicable,
548                 );
549             }
550         }
551     }
552 }