]> git.lizzy.rs Git - rust.git/blob - clippy_lints/src/attrs.rs
Merge pull request #3465 from flip1995/rustfmt
[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::rustc::hir::*;
14 use crate::rustc::lint::{
15     CheckLintNameResult, EarlyContext, EarlyLintPass, LateContext, LateLintPass, LintArray, LintContext, LintPass,
16 };
17 use crate::rustc::ty::{self, TyCtxt};
18 use crate::rustc::{declare_tool_lint, lint_array};
19 use crate::rustc_errors::Applicability;
20 use crate::syntax::ast::{AttrStyle, Attribute, Lit, LitKind, MetaItemKind, NestedMetaItem, NestedMetaItemKind};
21 use crate::syntax::source_map::Span;
22 use crate::utils::{
23     in_macro, last_line_of_span, match_def_path, opt_def_id, paths, snippet_opt, span_lint, span_lint_and_sugg,
24     span_lint_and_then, without_block_comments,
25 };
26 use if_chain::if_chain;
27 use semver::Version;
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().to_string();
336                             match lint_store.check_lint_name(
337                                 &name_lower,
338                                 Some(tool_name.as_str())
339                             ) {
340                                 CheckLintNameResult::NoLint => (),
341                                 _ => {
342                                     db.span_suggestion(lint.span,
343                                                        "lowercase the lint name",
344                                                        name_lower);
345                                 }
346                             }
347                         }
348                     }
349                 );
350             }
351         };
352     }
353 }
354
355 fn is_relevant_item(tcx: TyCtxt<'_, '_, '_>, item: &Item) -> bool {
356     if let ItemKind::Fn(_, _, _, eid) = item.node {
357         is_relevant_expr(tcx, tcx.body_tables(eid), &tcx.hir.body(eid).value)
358     } else {
359         true
360     }
361 }
362
363 fn is_relevant_impl(tcx: TyCtxt<'_, '_, '_>, item: &ImplItem) -> bool {
364     match item.node {
365         ImplItemKind::Method(_, eid) => is_relevant_expr(tcx, tcx.body_tables(eid), &tcx.hir.body(eid).value),
366         _ => false,
367     }
368 }
369
370 fn is_relevant_trait(tcx: TyCtxt<'_, '_, '_>, item: &TraitItem) -> bool {
371     match item.node {
372         TraitItemKind::Method(_, TraitMethod::Required(_)) => true,
373         TraitItemKind::Method(_, TraitMethod::Provided(eid)) => {
374             is_relevant_expr(tcx, tcx.body_tables(eid), &tcx.hir.body(eid).value)
375         },
376         _ => false,
377     }
378 }
379
380 fn is_relevant_block(tcx: TyCtxt<'_, '_, '_>, tables: &ty::TypeckTables<'_>, block: &Block) -> bool {
381     if let Some(stmt) = block.stmts.first() {
382         match stmt.node {
383             StmtKind::Decl(_, _) => true,
384             StmtKind::Expr(ref expr, _) | StmtKind::Semi(ref expr, _) => is_relevant_expr(tcx, tables, expr),
385         }
386     } else {
387         block.expr.as_ref().map_or(false, |e| is_relevant_expr(tcx, tables, e))
388     }
389 }
390
391 fn is_relevant_expr(tcx: TyCtxt<'_, '_, '_>, tables: &ty::TypeckTables<'_>, expr: &Expr) -> bool {
392     match expr.node {
393         ExprKind::Block(ref block, _) => is_relevant_block(tcx, tables, block),
394         ExprKind::Ret(Some(ref e)) => is_relevant_expr(tcx, tables, e),
395         ExprKind::Ret(None) | ExprKind::Break(_, None) => false,
396         ExprKind::Call(ref path_expr, _) => {
397             if let ExprKind::Path(ref qpath) = path_expr.node {
398                 if let Some(fun_id) = opt_def_id(tables.qpath_def(qpath, path_expr.hir_id)) {
399                     !match_def_path(tcx, fun_id, &paths::BEGIN_PANIC)
400                 } else {
401                     true
402                 }
403             } else {
404                 true
405             }
406         },
407         _ => true,
408     }
409 }
410
411 fn check_attrs(cx: &LateContext<'_, '_>, span: Span, name: Name, attrs: &[Attribute]) {
412     if in_macro(span) {
413         return;
414     }
415
416     for attr in attrs {
417         if attr.is_sugared_doc {
418             return;
419         }
420         if attr.style == AttrStyle::Outer {
421             if attr.tokens.is_empty() || !is_present_in_source(cx, attr.span) {
422                 return;
423             }
424
425             let begin_of_attr_to_item = Span::new(attr.span.lo(), span.lo(), span.ctxt());
426             let end_of_attr_to_item = Span::new(attr.span.hi(), span.lo(), span.ctxt());
427
428             if let Some(snippet) = snippet_opt(cx, end_of_attr_to_item) {
429                 let lines = snippet.split('\n').collect::<Vec<_>>();
430                 let lines = without_block_comments(lines);
431
432                 if lines.iter().filter(|l| l.trim().is_empty()).count() > 2 {
433                     span_lint(
434                         cx,
435                         EMPTY_LINE_AFTER_OUTER_ATTR,
436                         begin_of_attr_to_item,
437                         "Found an empty line after an outer attribute. \
438                          Perhaps you forgot to add a '!' to make it an inner attribute?",
439                     );
440                 }
441             }
442         }
443
444         if let Some(ref values) = attr.meta_item_list() {
445             if values.len() != 1 || attr.name() != "inline" {
446                 continue;
447             }
448             if is_word(&values[0], "always") {
449                 span_lint(
450                     cx,
451                     INLINE_ALWAYS,
452                     attr.span,
453                     &format!(
454                         "you have declared `#[inline(always)]` on `{}`. This is usually a bad idea",
455                         name
456                     ),
457                 );
458             }
459         }
460     }
461 }
462
463 fn check_semver(cx: &LateContext<'_, '_>, span: Span, lit: &Lit) {
464     if let LitKind::Str(ref is, _) = lit.node {
465         if Version::parse(&is.as_str()).is_ok() {
466             return;
467         }
468     }
469     span_lint(
470         cx,
471         DEPRECATED_SEMVER,
472         span,
473         "the since field must contain a semver-compliant version",
474     );
475 }
476
477 fn is_word(nmi: &NestedMetaItem, expected: &str) -> bool {
478     if let NestedMetaItemKind::MetaItem(ref mi) = nmi.node {
479         mi.is_word() && mi.name() == expected
480     } else {
481         false
482     }
483 }
484
485 // If the snippet is empty, it's an attribute that was inserted during macro
486 // expansion and we want to ignore those, because they could come from external
487 // sources that the user has no control over.
488 // For some reason these attributes don't have any expansion info on them, so
489 // we have to check it this way until there is a better way.
490 fn is_present_in_source(cx: &LateContext<'_, '_>, span: Span) -> bool {
491     if let Some(snippet) = snippet_opt(cx, span) {
492         if snippet.is_empty() {
493             return false;
494         }
495     }
496     true
497 }
498
499 #[derive(Copy, Clone)]
500 pub struct CfgAttrPass;
501
502 impl LintPass for CfgAttrPass {
503     fn get_lints(&self) -> LintArray {
504         lint_array!(DEPRECATED_CFG_ATTR,)
505     }
506 }
507
508 impl EarlyLintPass for CfgAttrPass {
509     fn check_attribute(&mut self, cx: &EarlyContext<'_>, attr: &Attribute) {
510         if_chain! {
511             // check cfg_attr
512             if attr.name() == "cfg_attr";
513             if let Some(ref items) = attr.meta_item_list();
514             if items.len() == 2;
515             // check for `rustfmt`
516             if let Some(feature_item) = items[0].meta_item();
517             if feature_item.name() == "rustfmt";
518             // check for `rustfmt_skip` and `rustfmt::skip`
519             if let Some(skip_item) = &items[1].meta_item();
520             if skip_item.name() == "rustfmt_skip" || skip_item.name() == "skip";
521             then {
522                 let attr_style = match attr.style {
523                     AttrStyle::Outer => "#[",
524                     AttrStyle::Inner => "#![",
525                 };
526                 span_lint_and_sugg(
527                     cx,
528                     DEPRECATED_CFG_ATTR,
529                     attr.span,
530                     "`cfg_attr` is deprecated for rustfmt and got replaced by tool_attributes",
531                     "use",
532                     format!("{}rustfmt::skip]", attr_style),
533                     Applicability::MachineApplicable,
534                 );
535             }
536         }
537     }
538 }