]> git.lizzy.rs Git - rust.git/blob - clippy_lints/src/attrs.rs
Merge pull request #1634 from ensch/master
[rust.git] / clippy_lints / src / attrs.rs
1 //! checks for attributes
2
3 use reexport::*;
4 use rustc::lint::*;
5 use rustc::hir::*;
6 use rustc::ty;
7 use semver::Version;
8 use syntax::ast::{Attribute, Lit, LitKind, MetaItemKind, NestedMetaItem, NestedMetaItemKind};
9 use syntax::codemap::Span;
10 use utils::{in_macro, match_def_path, paths, span_lint, span_lint_and_then, snippet_opt};
11
12 /// **What it does:** Checks for items annotated with `#[inline(always)]`,
13 /// unless the annotated function is empty or simply panics.
14 ///
15 /// **Why is this bad?** While there are valid uses of this annotation (and once
16 /// you know when to use it, by all means `allow` this lint), it's a common
17 /// newbie-mistake to pepper one's code with it.
18 ///
19 /// As a rule of thumb, before slapping `#[inline(always)]` on a function,
20 /// measure if that additional function call really affects your runtime profile
21 /// sufficiently to make up for the increase in compile time.
22 ///
23 /// **Known problems:** False positives, big time. This lint is meant to be
24 /// deactivated by everyone doing serious performance work. This means having
25 /// done the measurement.
26 ///
27 /// **Example:**
28 /// ```rust
29 /// #[inline(always)]
30 /// fn not_quite_hot_code(..) { ... }
31 /// ```
32 declare_lint! {
33     pub INLINE_ALWAYS,
34     Warn,
35     "use of `#[inline(always)]`"
36 }
37
38 /// **What it does:** Checks for `extern crate` and `use` items annotated with lint attributes
39 ///
40 /// **Why is this bad?** Lint attributes have no effect on crate imports. Most likely a `!` was
41 /// forgotten
42 ///
43 /// **Known problems:** Technically one might allow `unused_import` on a `use` item,
44 /// but it's easier to remove the unused item.
45 ///
46 /// **Example:**
47 /// ```rust
48 /// #[deny(dead_code)]
49 /// extern crate foo;
50 /// #[allow(unused_import)]
51 /// use foo::bar;
52 /// ```
53 declare_lint! {
54     pub USELESS_ATTRIBUTE,
55     Warn,
56     "use of lint attributes on `extern crate` items"
57 }
58
59 /// **What it does:** Checks for `#[deprecated]` annotations with a `since`
60 /// field that is not a valid semantic version.
61 ///
62 /// **Why is this bad?** For checking the version of the deprecation, it must be
63 /// a valid semver. Failing that, the contained information is useless.
64 ///
65 /// **Known problems:** None.
66 ///
67 /// **Example:**
68 /// ```rust
69 /// #[deprecated(since = "forever")]
70 /// fn something_else(..) { ... }
71 /// ```
72 declare_lint! {
73     pub DEPRECATED_SEMVER,
74     Warn,
75     "use of `#[deprecated(since = \"x\")]` where x is not semver"
76 }
77
78 #[derive(Copy,Clone)]
79 pub struct AttrPass;
80
81 impl LintPass for AttrPass {
82     fn get_lints(&self) -> LintArray {
83         lint_array!(INLINE_ALWAYS, DEPRECATED_SEMVER, USELESS_ATTRIBUTE)
84     }
85 }
86
87 impl<'a, 'tcx> LateLintPass<'a, 'tcx> for AttrPass {
88     fn check_attribute(&mut self, cx: &LateContext<'a, 'tcx>, attr: &'tcx Attribute) {
89         if let Some(ref items) = attr.meta_item_list() {
90             if items.is_empty() || attr.name().map_or(true, |n| n != "deprecated") {
91                 return;
92             }
93             for item in items {
94                 if_let_chain! {[
95                     let NestedMetaItemKind::MetaItem(ref mi) = item.node,
96                     let MetaItemKind::NameValue(ref lit) = mi.node,
97                     mi.name() == "since",
98                 ], {
99                     check_semver(cx, item.span, lit);
100                 }}
101             }
102         }
103     }
104
105     fn check_item(&mut self, cx: &LateContext<'a, 'tcx>, item: &'tcx Item) {
106         if is_relevant_item(cx.tcx, item) {
107             check_attrs(cx, item.span, &item.name, &item.attrs)
108         }
109         match item.node {
110             ItemExternCrate(_) |
111             ItemUse(_, _) => {
112                 for attr in &item.attrs {
113                     if let Some(ref lint_list) = attr.meta_item_list() {
114                         if let Some(name) = attr.name() {
115                             match &*name.as_str() {
116                                 "allow" | "warn" | "deny" | "forbid" => {
117                                     // whitelist `unused_imports` and `deprecated`
118                                     for lint in lint_list {
119                                         if is_word(lint, "unused_imports") || is_word(lint, "deprecated") {
120                                             if let ItemUse(_, _) = item.node {
121                                                 return;
122                                             }
123                                         }
124                                     }
125                                     if let Some(mut sugg) = snippet_opt(cx, attr.span) {
126                                         if sugg.len() > 1 {
127                                             span_lint_and_then(cx,
128                                                                USELESS_ATTRIBUTE,
129                                                                attr.span,
130                                                                "useless lint attribute",
131                                                                |db| {
132                                                 sugg.insert(1, '!');
133                                                 db.span_suggestion(attr.span, "if you just forgot a `!`, use", sugg);
134                                             });
135                                         }
136                                     }
137                                 },
138                                 _ => {},
139                             }
140                         }
141                     }
142                 }
143             },
144             _ => {},
145         }
146     }
147
148     fn check_impl_item(&mut self, cx: &LateContext<'a, 'tcx>, item: &'tcx ImplItem) {
149         if is_relevant_impl(cx.tcx, item) {
150             check_attrs(cx, item.span, &item.name, &item.attrs)
151         }
152     }
153
154     fn check_trait_item(&mut self, cx: &LateContext<'a, 'tcx>, item: &'tcx TraitItem) {
155         if is_relevant_trait(cx.tcx, item) {
156             check_attrs(cx, item.span, &item.name, &item.attrs)
157         }
158     }
159 }
160
161 fn is_relevant_item(tcx: ty::TyCtxt, item: &Item) -> bool {
162     if let ItemFn(_, _, _, _, _, eid) = item.node {
163         is_relevant_expr(tcx, tcx.body_tables(eid), &tcx.hir.body(eid).value)
164     } else {
165         false
166     }
167 }
168
169 fn is_relevant_impl(tcx: ty::TyCtxt, item: &ImplItem) -> bool {
170     match item.node {
171         ImplItemKind::Method(_, eid) => is_relevant_expr(tcx, tcx.body_tables(eid), &tcx.hir.body(eid).value),
172         _ => false,
173     }
174 }
175
176 fn is_relevant_trait(tcx: ty::TyCtxt, item: &TraitItem) -> bool {
177     match item.node {
178         TraitItemKind::Method(_, TraitMethod::Required(_)) => true,
179         TraitItemKind::Method(_, TraitMethod::Provided(eid)) => {
180             is_relevant_expr(tcx, tcx.body_tables(eid), &tcx.hir.body(eid).value)
181         },
182         _ => false,
183     }
184 }
185
186 fn is_relevant_block(tcx: ty::TyCtxt, tables: &ty::TypeckTables, block: &Block) -> bool {
187     for stmt in &block.stmts {
188         match stmt.node {
189             StmtDecl(_, _) => return true,
190             StmtExpr(ref expr, _) |
191             StmtSemi(ref expr, _) => {
192                 return is_relevant_expr(tcx, tables, expr);
193             },
194         }
195     }
196     block.expr.as_ref().map_or(false, |e| is_relevant_expr(tcx, tables, e))
197 }
198
199 fn is_relevant_expr(tcx: ty::TyCtxt, tables: &ty::TypeckTables, expr: &Expr) -> bool {
200     match expr.node {
201         ExprBlock(ref block) => is_relevant_block(tcx, tables, block),
202         ExprRet(Some(ref e)) => is_relevant_expr(tcx, tables, e),
203         ExprRet(None) |
204         ExprBreak(_, None) => false,
205         ExprCall(ref path_expr, _) => {
206             if let ExprPath(ref qpath) = path_expr.node {
207                 let fun_id = tables.qpath_def(qpath, path_expr.id).def_id();
208                 !match_def_path(tcx, fun_id, &paths::BEGIN_PANIC)
209             } else {
210                 true
211             }
212         },
213         _ => true,
214     }
215 }
216
217 fn check_attrs(cx: &LateContext, span: Span, name: &Name, attrs: &[Attribute]) {
218     if in_macro(cx, span) {
219         return;
220     }
221
222     for attr in attrs {
223         if let Some(ref values) = attr.meta_item_list() {
224             if values.len() != 1 || attr.name().map_or(true, |n| n != "inline") {
225                 continue;
226             }
227             if is_word(&values[0], "always") {
228                 span_lint(cx,
229                           INLINE_ALWAYS,
230                           attr.span,
231                           &format!("you have declared `#[inline(always)]` on `{}`. This is usually a bad idea",
232                                    name));
233             }
234         }
235     }
236 }
237
238 fn check_semver(cx: &LateContext, span: Span, lit: &Lit) {
239     if let LitKind::Str(ref is, _) = lit.node {
240         if Version::parse(&*is.as_str()).is_ok() {
241             return;
242         }
243     }
244     span_lint(cx,
245               DEPRECATED_SEMVER,
246               span,
247               "the since field must contain a semver-compliant version");
248 }
249
250 fn is_word(nmi: &NestedMetaItem, expected: &str) -> bool {
251     if let NestedMetaItemKind::MetaItem(ref mi) = nmi.node {
252         mi.is_word() && mi.name() == expected
253     } else {
254         false
255     }
256 }