]> git.lizzy.rs Git - rust.git/blob - clippy_lints/src/attrs.rs
Auto merge of #4809 - iankronquist:patch-1, r=flip1995
[rust.git] / clippy_lints / src / attrs.rs
1 //! checks for attributes
2
3 use crate::reexport::*;
4 use crate::utils::{
5     first_line_of_span, is_present_in_source, match_def_path, paths, snippet_opt, span_lint, span_lint_and_sugg,
6     span_lint_and_then, without_block_comments,
7 };
8 use if_chain::if_chain;
9 use rustc::lint::in_external_macro;
10 use rustc::ty;
11 use rustc_errors::Applicability;
12 use rustc_hir::*;
13 use rustc_lint::{CheckLintNameResult, EarlyContext, EarlyLintPass, LateContext, LateLintPass, LintContext};
14 use rustc_session::{declare_lint_pass, declare_tool_lint};
15 use rustc_span::source_map::Span;
16 use rustc_span::symbol::Symbol;
17 use semver::Version;
18 use syntax::ast::{AttrKind, AttrStyle, Attribute, Lit, LitKind, MetaItemKind, NestedMetaItem};
19 use syntax::util::lev_distance::find_best_match_for_name;
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)]`, `#[allow(deprecated)]` and
52     /// `#[allow(unreachable_pub)]` on `use` items and `#[allow(unused_imports)]` on
53     /// `extern crate` items with a `#[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     /// // Good (as inner attribute)
116     /// #![inline(always)]
117     ///
118     /// fn this_is_fine() { }
119     ///
120     /// // Bad
121     /// #[inline(always)]
122     ///
123     /// fn not_quite_good_code() { }
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 an 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 declare_lint_pass!(Attributes => [
191     INLINE_ALWAYS,
192     DEPRECATED_SEMVER,
193     USELESS_ATTRIBUTE,
194     EMPTY_LINE_AFTER_OUTER_ATTR,
195     UNKNOWN_CLIPPY_LINTS,
196 ]);
197
198 impl<'a, 'tcx> LateLintPass<'a, 'tcx> for Attributes {
199     fn check_attribute(&mut self, cx: &LateContext<'a, 'tcx>, attr: &'tcx Attribute) {
200         if let Some(items) = &attr.meta_item_list() {
201             if let Some(ident) = attr.ident() {
202                 match &*ident.as_str() {
203                     "allow" | "warn" | "deny" | "forbid" => {
204                         check_clippy_lint_names(cx, items);
205                     },
206                     _ => {},
207                 }
208                 if items.is_empty() || !attr.check_name(sym!(deprecated)) {
209                     return;
210                 }
211                 for item in items {
212                     if_chain! {
213                         if let NestedMetaItem::MetaItem(mi) = &item;
214                         if let MetaItemKind::NameValue(lit) = &mi.kind;
215                         if mi.check_name(sym!(since));
216                         then {
217                             check_semver(cx, item.span(), lit);
218                         }
219                     }
220                 }
221             }
222         }
223     }
224
225     fn check_item(&mut self, cx: &LateContext<'a, 'tcx>, item: &'tcx Item<'_>) {
226         if is_relevant_item(cx, item) {
227             check_attrs(cx, item.span, item.ident.name, &item.attrs)
228         }
229         match item.kind {
230             ItemKind::ExternCrate(..) | ItemKind::Use(..) => {
231                 let skip_unused_imports = item.attrs.iter().any(|attr| attr.check_name(sym!(macro_use)));
232
233                 for attr in item.attrs {
234                     if in_external_macro(cx.sess(), attr.span) {
235                         return;
236                     }
237                     if let Some(lint_list) = &attr.meta_item_list() {
238                         if let Some(ident) = attr.ident() {
239                             match &*ident.as_str() {
240                                 "allow" | "warn" | "deny" | "forbid" => {
241                                     // whitelist `unused_imports`, `deprecated` and `unreachable_pub` for `use` items
242                                     // and `unused_imports` for `extern crate` items with `macro_use`
243                                     for lint in lint_list {
244                                         match item.kind {
245                                             ItemKind::Use(..) => {
246                                                 if is_word(lint, sym!(unused_imports))
247                                                     || is_word(lint, sym!(deprecated))
248                                                     || is_word(lint, sym!(unreachable_pub))
249                                                 {
250                                                     return;
251                                                 }
252                                             },
253                                             ItemKind::ExternCrate(..) => {
254                                                 if is_word(lint, sym!(unused_imports)) && skip_unused_imports {
255                                                     return;
256                                                 }
257                                                 if is_word(lint, sym!(unused_extern_crates)) {
258                                                     return;
259                                                 }
260                                             },
261                                             _ => {},
262                                         }
263                                     }
264                                     let line_span = first_line_of_span(cx, attr.span);
265
266                                     if let Some(mut sugg) = snippet_opt(cx, line_span) {
267                                         if sugg.contains("#[") {
268                                             span_lint_and_then(
269                                                 cx,
270                                                 USELESS_ATTRIBUTE,
271                                                 line_span,
272                                                 "useless lint attribute",
273                                                 |db| {
274                                                     sugg = sugg.replacen("#[", "#![", 1);
275                                                     db.span_suggestion(
276                                                         line_span,
277                                                         "if you just forgot a `!`, use",
278                                                         sugg,
279                                                         Applicability::MaybeIncorrect,
280                                                     );
281                                                 },
282                                             );
283                                         }
284                                     }
285                                 },
286                                 _ => {},
287                             }
288                         }
289                     }
290                 }
291             },
292             _ => {},
293         }
294     }
295
296     fn check_impl_item(&mut self, cx: &LateContext<'a, 'tcx>, item: &'tcx ImplItem<'_>) {
297         if is_relevant_impl(cx, item) {
298             check_attrs(cx, item.span, item.ident.name, &item.attrs)
299         }
300     }
301
302     fn check_trait_item(&mut self, cx: &LateContext<'a, 'tcx>, item: &'tcx TraitItem<'_>) {
303         if is_relevant_trait(cx, item) {
304             check_attrs(cx, item.span, item.ident.name, &item.attrs)
305         }
306     }
307 }
308
309 #[allow(clippy::single_match_else)]
310 fn check_clippy_lint_names(cx: &LateContext<'_, '_>, items: &[NestedMetaItem]) {
311     let lint_store = cx.lints();
312     for lint in items {
313         if_chain! {
314             if let Some(meta_item) = lint.meta_item();
315             if meta_item.path.segments.len() > 1;
316             if let tool_name = meta_item.path.segments[0].ident;
317             if tool_name.as_str() == "clippy";
318             let name = meta_item.path.segments.last().unwrap().ident.name;
319             if let CheckLintNameResult::Tool(Err((None, _))) = lint_store.check_lint_name(
320                 &name.as_str(),
321                 Some(tool_name.name),
322             );
323             then {
324                 span_lint_and_then(
325                     cx,
326                     UNKNOWN_CLIPPY_LINTS,
327                     lint.span(),
328                     &format!("unknown clippy lint: clippy::{}", name),
329                     |db| {
330                         let name_lower = name.as_str().to_lowercase();
331                         let symbols = lint_store.get_lints().iter().map(
332                             |l| Symbol::intern(&l.name_lower())
333                         ).collect::<Vec<_>>();
334                         let sugg = find_best_match_for_name(
335                             symbols.iter(),
336                             &format!("clippy::{}", name_lower),
337                             None,
338                         );
339                         if name.as_str().chars().any(char::is_uppercase)
340                             && lint_store.find_lints(&format!("clippy::{}", name_lower)).is_ok() {
341                             db.span_suggestion(
342                                 lint.span(),
343                                 "lowercase the lint name",
344                                 format!("clippy::{}", name_lower),
345                                 Applicability::MachineApplicable,
346                             );
347                         } else if let Some(sugg) = sugg {
348                             db.span_suggestion(
349                                 lint.span(),
350                                 "did you mean",
351                                 sugg.to_string(),
352                                 Applicability::MachineApplicable,
353                             );
354                         }
355                     }
356                 );
357             }
358         };
359     }
360 }
361
362 fn is_relevant_item(cx: &LateContext<'_, '_>, item: &Item<'_>) -> bool {
363     if let ItemKind::Fn(_, _, eid) = item.kind {
364         is_relevant_expr(cx, cx.tcx.body_tables(eid), &cx.tcx.hir().body(eid).value)
365     } else {
366         true
367     }
368 }
369
370 fn is_relevant_impl(cx: &LateContext<'_, '_>, item: &ImplItem<'_>) -> bool {
371     match item.kind {
372         ImplItemKind::Method(_, eid) => is_relevant_expr(cx, cx.tcx.body_tables(eid), &cx.tcx.hir().body(eid).value),
373         _ => false,
374     }
375 }
376
377 fn is_relevant_trait(cx: &LateContext<'_, '_>, item: &TraitItem<'_>) -> bool {
378     match item.kind {
379         TraitItemKind::Method(_, TraitMethod::Required(_)) => true,
380         TraitItemKind::Method(_, TraitMethod::Provided(eid)) => {
381             is_relevant_expr(cx, cx.tcx.body_tables(eid), &cx.tcx.hir().body(eid).value)
382         },
383         _ => false,
384     }
385 }
386
387 fn is_relevant_block(cx: &LateContext<'_, '_>, tables: &ty::TypeckTables<'_>, block: &Block<'_>) -> bool {
388     if let Some(stmt) = block.stmts.first() {
389         match &stmt.kind {
390             StmtKind::Local(_) => true,
391             StmtKind::Expr(expr) | StmtKind::Semi(expr) => is_relevant_expr(cx, tables, expr),
392             _ => false,
393         }
394     } else {
395         block.expr.as_ref().map_or(false, |e| is_relevant_expr(cx, tables, e))
396     }
397 }
398
399 fn is_relevant_expr(cx: &LateContext<'_, '_>, tables: &ty::TypeckTables<'_>, expr: &Expr<'_>) -> bool {
400     match &expr.kind {
401         ExprKind::Block(block, _) => is_relevant_block(cx, tables, block),
402         ExprKind::Ret(Some(e)) => is_relevant_expr(cx, tables, e),
403         ExprKind::Ret(None) | ExprKind::Break(_, None) => false,
404         ExprKind::Call(path_expr, _) => {
405             if let ExprKind::Path(qpath) = &path_expr.kind {
406                 if let Some(fun_id) = tables.qpath_res(qpath, path_expr.hir_id).opt_def_id() {
407                     !match_def_path(cx, fun_id, &paths::BEGIN_PANIC)
408                 } else {
409                     true
410                 }
411             } else {
412                 true
413             }
414         },
415         _ => true,
416     }
417 }
418
419 fn check_attrs(cx: &LateContext<'_, '_>, span: Span, name: Name, attrs: &[Attribute]) {
420     if span.from_expansion() {
421         return;
422     }
423
424     for attr in attrs {
425         let attr_item = if let AttrKind::Normal(ref attr) = attr.kind {
426             attr
427         } else {
428             continue;
429         };
430
431         if attr.style == AttrStyle::Outer {
432             if attr_item.args.inner_tokens().is_empty() || !is_present_in_source(cx, attr.span) {
433                 return;
434             }
435
436             let begin_of_attr_to_item = Span::new(attr.span.lo(), span.lo(), span.ctxt());
437             let end_of_attr_to_item = Span::new(attr.span.hi(), span.lo(), span.ctxt());
438
439             if let Some(snippet) = snippet_opt(cx, end_of_attr_to_item) {
440                 let lines = snippet.split('\n').collect::<Vec<_>>();
441                 let lines = without_block_comments(lines);
442
443                 if lines.iter().filter(|l| l.trim().is_empty()).count() > 2 {
444                     span_lint(
445                         cx,
446                         EMPTY_LINE_AFTER_OUTER_ATTR,
447                         begin_of_attr_to_item,
448                         "Found an empty line after an outer attribute. \
449                          Perhaps you forgot to add a `!` to make it an inner attribute?",
450                     );
451                 }
452             }
453         }
454
455         if let Some(values) = attr.meta_item_list() {
456             if values.len() != 1 || !attr.check_name(sym!(inline)) {
457                 continue;
458             }
459             if is_word(&values[0], sym!(always)) {
460                 span_lint(
461                     cx,
462                     INLINE_ALWAYS,
463                     attr.span,
464                     &format!(
465                         "you have declared `#[inline(always)]` on `{}`. This is usually a bad idea",
466                         name
467                     ),
468                 );
469             }
470         }
471     }
472 }
473
474 fn check_semver(cx: &LateContext<'_, '_>, span: Span, lit: &Lit) {
475     if let LitKind::Str(is, _) = lit.kind {
476         if Version::parse(&is.as_str()).is_ok() {
477             return;
478         }
479     }
480     span_lint(
481         cx,
482         DEPRECATED_SEMVER,
483         span,
484         "the since field must contain a semver-compliant version",
485     );
486 }
487
488 fn is_word(nmi: &NestedMetaItem, expected: Symbol) -> bool {
489     if let NestedMetaItem::MetaItem(mi) = &nmi {
490         mi.is_word() && mi.check_name(expected)
491     } else {
492         false
493     }
494 }
495
496 declare_lint_pass!(DeprecatedCfgAttribute => [DEPRECATED_CFG_ATTR]);
497
498 impl EarlyLintPass for DeprecatedCfgAttribute {
499     fn check_attribute(&mut self, cx: &EarlyContext<'_>, attr: &Attribute) {
500         if_chain! {
501             // check cfg_attr
502             if attr.check_name(sym!(cfg_attr));
503             if let Some(items) = attr.meta_item_list();
504             if items.len() == 2;
505             // check for `rustfmt`
506             if let Some(feature_item) = items[0].meta_item();
507             if feature_item.check_name(sym!(rustfmt));
508             // check for `rustfmt_skip` and `rustfmt::skip`
509             if let Some(skip_item) = &items[1].meta_item();
510             if skip_item.check_name(sym!(rustfmt_skip)) ||
511                 skip_item.path.segments.last().expect("empty path in attribute").ident.name == sym!(skip);
512             // Only lint outer attributes, because custom inner attributes are unstable
513             // Tracking issue: https://github.com/rust-lang/rust/issues/54726
514             if let AttrStyle::Outer = attr.style;
515             then {
516                 span_lint_and_sugg(
517                     cx,
518                     DEPRECATED_CFG_ATTR,
519                     attr.span,
520                     "`cfg_attr` is deprecated for rustfmt and got replaced by tool attributes",
521                     "use",
522                     "#[rustfmt::skip]".to_string(),
523                     Applicability::MachineApplicable,
524                 );
525             }
526         }
527     }
528 }