]> git.lizzy.rs Git - rust.git/blob - clippy_lints/src/attrs.rs
Auto merge of #4087 - phansch:move_tests, r=matthiaskrgr
[rust.git] / clippy_lints / src / attrs.rs
1 //! checks for attributes
2
3 use crate::reexport::*;
4 use crate::utils::{
5     in_macro_or_desugar, is_present_in_source, last_line_of_span, 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::hir::*;
10 use rustc::lint::{
11     in_external_macro, CheckLintNameResult, EarlyContext, EarlyLintPass, LateContext, LateLintPass, LintArray,
12     LintContext, LintPass,
13 };
14 use rustc::ty;
15 use rustc::{declare_lint_pass, declare_tool_lint};
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 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("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.node;
215                         if mi.check_name("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.node {
230             ItemKind::ExternCrate(..) | ItemKind::Use(..) => {
231                 let skip_unused_imports = item.attrs.iter().any(|attr| attr.check_name("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` and `deprecated` for `use` items
242                                     // and `unused_imports` for `extern crate` items with `macro_use`
243                                     for lint in lint_list {
244                                         match item.node {
245                                             ItemKind::Use(..) => {
246                                                 if is_word(lint, "unused_imports") || is_word(lint, "deprecated") {
247                                                     return;
248                                                 }
249                                             },
250                                             ItemKind::ExternCrate(..) => {
251                                                 if is_word(lint, "unused_imports") && skip_unused_imports {
252                                                     return;
253                                                 }
254                                                 if is_word(lint, "unused_extern_crates") {
255                                                     return;
256                                                 }
257                                             },
258                                             _ => {},
259                                         }
260                                     }
261                                     let line_span = last_line_of_span(cx, attr.span);
262
263                                     if let Some(mut sugg) = snippet_opt(cx, line_span) {
264                                         if sugg.contains("#[") {
265                                             span_lint_and_then(
266                                                 cx,
267                                                 USELESS_ATTRIBUTE,
268                                                 line_span,
269                                                 "useless lint attribute",
270                                                 |db| {
271                                                     sugg = sugg.replacen("#[", "#![", 1);
272                                                     db.span_suggestion(
273                                                         line_span,
274                                                         "if you just forgot a `!`, use",
275                                                         sugg,
276                                                         Applicability::MachineApplicable,
277                                                     );
278                                                 },
279                                             );
280                                         }
281                                     }
282                                 },
283                                 _ => {},
284                             }
285                         }
286                     }
287                 }
288             },
289             _ => {},
290         }
291     }
292
293     fn check_impl_item(&mut self, cx: &LateContext<'a, 'tcx>, item: &'tcx ImplItem) {
294         if is_relevant_impl(cx, item) {
295             check_attrs(cx, item.span, item.ident.name, &item.attrs)
296         }
297     }
298
299     fn check_trait_item(&mut self, cx: &LateContext<'a, 'tcx>, item: &'tcx TraitItem) {
300         if is_relevant_trait(cx, item) {
301             check_attrs(cx, item.span, item.ident.name, &item.attrs)
302         }
303     }
304 }
305
306 #[allow(clippy::single_match_else)]
307 fn check_clippy_lint_names(cx: &LateContext<'_, '_>, items: &[NestedMetaItem]) {
308     let lint_store = cx.lints();
309     for lint in items {
310         if_chain! {
311             if let Some(meta_item) = lint.meta_item();
312             if meta_item.path.segments.len() > 1;
313             if let tool_name = meta_item.path.segments[0].ident;
314             if tool_name.as_str() == "clippy";
315             let name = meta_item.path.segments.last().unwrap().ident.name;
316             if let CheckLintNameResult::Tool(Err((None, _))) = lint_store.check_lint_name(
317                 &name.as_str(),
318                 Some(tool_name.as_str()),
319             );
320             then {
321                 span_lint_and_then(
322                     cx,
323                     UNKNOWN_CLIPPY_LINTS,
324                     lint.span(),
325                     &format!("unknown clippy lint: clippy::{}", name),
326                     |db| {
327                         if name.as_str().chars().any(char::is_uppercase) {
328                             let name_lower = name.as_str().to_lowercase();
329                             match lint_store.check_lint_name(
330                                 &name_lower,
331                                 Some(tool_name.as_str())
332                             ) {
333                                 // FIXME: can we suggest similar lint names here?
334                                 // https://github.com/rust-lang/rust/pull/56992
335                                 CheckLintNameResult::NoLint(None) => (),
336                                 _ => {
337                                     db.span_suggestion(
338                                         lint.span(),
339                                         "lowercase the lint name",
340                                         name_lower,
341                                         Applicability::MaybeIncorrect,
342                                     );
343                                 }
344                             }
345                         }
346                     }
347                 );
348             }
349         };
350     }
351 }
352
353 fn is_relevant_item(cx: &LateContext<'_, '_>, item: &Item) -> bool {
354     if let ItemKind::Fn(_, _, _, eid) = item.node {
355         is_relevant_expr(cx, cx.tcx.body_tables(eid), &cx.tcx.hir().body(eid).value)
356     } else {
357         true
358     }
359 }
360
361 fn is_relevant_impl(cx: &LateContext<'_, '_>, item: &ImplItem) -> bool {
362     match item.node {
363         ImplItemKind::Method(_, eid) => is_relevant_expr(cx, cx.tcx.body_tables(eid), &cx.tcx.hir().body(eid).value),
364         _ => false,
365     }
366 }
367
368 fn is_relevant_trait(cx: &LateContext<'_, '_>, item: &TraitItem) -> bool {
369     match item.node {
370         TraitItemKind::Method(_, TraitMethod::Required(_)) => true,
371         TraitItemKind::Method(_, TraitMethod::Provided(eid)) => {
372             is_relevant_expr(cx, cx.tcx.body_tables(eid), &cx.tcx.hir().body(eid).value)
373         },
374         _ => false,
375     }
376 }
377
378 fn is_relevant_block(cx: &LateContext<'_, '_>, tables: &ty::TypeckTables<'_>, block: &Block) -> bool {
379     if let Some(stmt) = block.stmts.first() {
380         match &stmt.node {
381             StmtKind::Local(_) => true,
382             StmtKind::Expr(expr) | StmtKind::Semi(expr) => is_relevant_expr(cx, tables, expr),
383             _ => false,
384         }
385     } else {
386         block.expr.as_ref().map_or(false, |e| is_relevant_expr(cx, tables, e))
387     }
388 }
389
390 fn is_relevant_expr(cx: &LateContext<'_, '_>, tables: &ty::TypeckTables<'_>, expr: &Expr) -> bool {
391     match &expr.node {
392         ExprKind::Block(block, _) => is_relevant_block(cx, tables, block),
393         ExprKind::Ret(Some(e)) => is_relevant_expr(cx, tables, e),
394         ExprKind::Ret(None) | ExprKind::Break(_, None) => false,
395         ExprKind::Call(path_expr, _) => {
396             if let ExprKind::Path(qpath) = &path_expr.node {
397                 if let Some(fun_id) = tables.qpath_res(qpath, path_expr.hir_id).opt_def_id() {
398                     !cx.match_def_path(fun_id, &paths::BEGIN_PANIC)
399                 } else {
400                     true
401                 }
402             } else {
403                 true
404             }
405         },
406         _ => true,
407     }
408 }
409
410 fn check_attrs(cx: &LateContext<'_, '_>, span: Span, name: Name, attrs: &[Attribute]) {
411     if in_macro_or_desugar(span) {
412         return;
413     }
414
415     for attr in attrs {
416         if attr.is_sugared_doc {
417             return;
418         }
419         if attr.style == AttrStyle::Outer {
420             if attr.tokens.is_empty() || !is_present_in_source(cx, attr.span) {
421                 return;
422             }
423
424             let begin_of_attr_to_item = Span::new(attr.span.lo(), span.lo(), span.ctxt());
425             let end_of_attr_to_item = Span::new(attr.span.hi(), span.lo(), span.ctxt());
426
427             if let Some(snippet) = snippet_opt(cx, end_of_attr_to_item) {
428                 let lines = snippet.split('\n').collect::<Vec<_>>();
429                 let lines = without_block_comments(lines);
430
431                 if lines.iter().filter(|l| l.trim().is_empty()).count() > 2 {
432                     span_lint(
433                         cx,
434                         EMPTY_LINE_AFTER_OUTER_ATTR,
435                         begin_of_attr_to_item,
436                         "Found an empty line after an outer attribute. \
437                          Perhaps you forgot to add a '!' to make it an inner attribute?",
438                     );
439                 }
440             }
441         }
442
443         if let Some(values) = attr.meta_item_list() {
444             if values.len() != 1 || !attr.check_name("inline") {
445                 continue;
446             }
447             if is_word(&values[0], "always") {
448                 span_lint(
449                     cx,
450                     INLINE_ALWAYS,
451                     attr.span,
452                     &format!(
453                         "you have declared `#[inline(always)]` on `{}`. This is usually a bad idea",
454                         name
455                     ),
456                 );
457             }
458         }
459     }
460 }
461
462 fn check_semver(cx: &LateContext<'_, '_>, span: Span, lit: &Lit) {
463     if let LitKind::Str(is, _) = lit.node {
464         if Version::parse(&is.as_str()).is_ok() {
465             return;
466         }
467     }
468     span_lint(
469         cx,
470         DEPRECATED_SEMVER,
471         span,
472         "the since field must contain a semver-compliant version",
473     );
474 }
475
476 fn is_word(nmi: &NestedMetaItem, expected: &str) -> bool {
477     if let NestedMetaItem::MetaItem(mi) = &nmi {
478         mi.is_word() && mi.check_name(expected)
479     } else {
480         false
481     }
482 }
483
484 declare_lint_pass!(DeprecatedCfgAttribute => [DEPRECATED_CFG_ATTR]);
485
486 impl EarlyLintPass for DeprecatedCfgAttribute {
487     fn check_attribute(&mut self, cx: &EarlyContext<'_>, attr: &Attribute) {
488         if_chain! {
489             // check cfg_attr
490             if attr.check_name("cfg_attr");
491             if let Some(items) = attr.meta_item_list();
492             if items.len() == 2;
493             // check for `rustfmt`
494             if let Some(feature_item) = items[0].meta_item();
495             if feature_item.check_name("rustfmt");
496             // check for `rustfmt_skip` and `rustfmt::skip`
497             if let Some(skip_item) = &items[1].meta_item();
498             if skip_item.check_name("rustfmt_skip") ||
499                 skip_item.path.segments.last().expect("empty path in attribute").ident.name == "skip";
500             // Only lint outer attributes, because custom inner attributes are unstable
501             // Tracking issue: https://github.com/rust-lang/rust/issues/54726
502             if let AttrStyle::Outer = attr.style;
503             then {
504                 span_lint_and_sugg(
505                     cx,
506                     DEPRECATED_CFG_ATTR,
507                     attr.span,
508                     "`cfg_attr` is deprecated for rustfmt and got replaced by tool_attributes",
509                     "use",
510                     "#[rustfmt::skip]".to_string(),
511                     Applicability::MachineApplicable,
512                 );
513             }
514         }
515     }
516 }