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