1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
//! checks for attributes

use reexport::*;
use rustc::lint::*;
use rustc::hir::*;
use rustc::ty::{self, TyCtxt};
use semver::Version;
use syntax::ast::{Attribute, AttrStyle, Lit, LitKind, MetaItemKind, NestedMetaItem, NestedMetaItemKind};
use syntax::codemap::Span;
use utils::{in_macro, last_line_of_span, match_def_path, opt_def_id, paths, snippet_opt, span_lint, span_lint_and_then, without_block_comments};

/// **What it does:** Checks for items annotated with `#[inline(always)]`,
/// unless the annotated function is empty or simply panics.
///
/// **Why is this bad?** While there are valid uses of this annotation (and once
/// you know when to use it, by all means `allow` this lint), it's a common
/// newbie-mistake to pepper one's code with it.
///
/// As a rule of thumb, before slapping `#[inline(always)]` on a function,
/// measure if that additional function call really affects your runtime profile
/// sufficiently to make up for the increase in compile time.
///
/// **Known problems:** False positives, big time. This lint is meant to be
/// deactivated by everyone doing serious performance work. This means having
/// done the measurement.
///
/// **Example:**
/// ```rust
/// #[inline(always)]
/// fn not_quite_hot_code(..) { ... }
/// ```
declare_clippy_lint! {
    pub INLINE_ALWAYS,
    pedantic,
    "use of `#[inline(always)]`"
}

/// **What it does:** Checks for `extern crate` and `use` items annotated with
/// lint attributes
///
/// **Why is this bad?** Lint attributes have no effect on crate imports. Most
/// likely a `!` was
/// forgotten
///
/// **Known problems:** Technically one might allow `unused_import` on a `use`
/// item,
/// but it's easier to remove the unused item.
///
/// **Example:**
/// ```rust
/// #[deny(dead_code)]
/// extern crate foo;
/// #[allow(unused_import)]
/// use foo::bar;
/// ```
declare_clippy_lint! {
    pub USELESS_ATTRIBUTE,
    correctness,
    "use of lint attributes on `extern crate` items"
}

/// **What it does:** Checks for `#[deprecated]` annotations with a `since`
/// field that is not a valid semantic version.
///
/// **Why is this bad?** For checking the version of the deprecation, it must be
/// a valid semver. Failing that, the contained information is useless.
///
/// **Known problems:** None.
///
/// **Example:**
/// ```rust
/// #[deprecated(since = "forever")]
/// fn something_else(..) { ... }
/// ```
declare_clippy_lint! {
    pub DEPRECATED_SEMVER,
    correctness,
    "use of `#[deprecated(since = \"x\")]` where x is not semver"
}

/// **What it does:** Checks for empty lines after outer attributes
///
/// **Why is this bad?**
/// Most likely the attribute was meant to be an inner attribute using a '!'.
/// If it was meant to be an outer attribute, then the following item
/// should not be separated by empty lines.
///
/// **Known problems:** Can cause false positives.
///
/// From the clippy side it's difficult to detect empty lines between an attributes and the
/// following item because empty lines and comments are not part of the AST. The parsing
/// currently works for basic cases but is not perfect.
///
/// **Example:**
/// ```rust
/// // Bad
/// #[inline(always)]
///
/// fn not_quite_good_code(..) { ... }
///
/// // Good (as inner attribute)
/// #![inline(always)]
///
/// fn this_is_fine(..) { ... }
///
/// // Good (as outer attribute)
/// #[inline(always)]
/// fn this_is_fine_too(..) { ... }
/// ```
declare_clippy_lint! {
    pub EMPTY_LINE_AFTER_OUTER_ATTR,
    nursery,
    "empty line after outer attribute"
}

#[derive(Copy, Clone)]
pub struct AttrPass;

impl LintPass for AttrPass {
    fn get_lints(&self) -> LintArray {
        lint_array!(INLINE_ALWAYS, DEPRECATED_SEMVER, USELESS_ATTRIBUTE, EMPTY_LINE_AFTER_OUTER_ATTR)
    }
}

impl<'a, 'tcx> LateLintPass<'a, 'tcx> for AttrPass {
    fn check_attribute(&mut self, cx: &LateContext<'a, 'tcx>, attr: &'tcx Attribute) {
        if let Some(ref items) = attr.meta_item_list() {
            if items.is_empty() || attr.name().map_or(true, |n| n != "deprecated") {
                return;
            }
            for item in items {
                if_chain! {
                    if let NestedMetaItemKind::MetaItem(ref mi) = item.node;
                    if let MetaItemKind::NameValue(ref lit) = mi.node;
                    if mi.name() == "since";
                    then {
                        check_semver(cx, item.span, lit);
                    }
                }
            }
        }
    }

    fn check_item(&mut self, cx: &LateContext<'a, 'tcx>, item: &'tcx Item) {
        if is_relevant_item(cx.tcx, item) {
            check_attrs(cx, item.span, &item.name, &item.attrs)
        }
        match item.node {
            ItemExternCrate(_) | ItemUse(_, _) => {
                for attr in &item.attrs {
                    if let Some(ref lint_list) = attr.meta_item_list() {
                        if let Some(name) = attr.name() {
                            match &*name.as_str() {
                                "allow" | "warn" | "deny" | "forbid" => {
                                    // whitelist `unused_imports` and `deprecated`
                                    for lint in lint_list {
                                        if is_word(lint, "unused_imports") || is_word(lint, "deprecated") {
                                            if let ItemUse(_, _) = item.node {
                                                return;
                                            }
                                        }
                                    }
                                    let line_span = last_line_of_span(cx, attr.span);

                                    if let Some(mut sugg) = snippet_opt(cx, line_span) {
                                        if sugg.contains("#[") {
                                            span_lint_and_then(
                                                cx,
                                                USELESS_ATTRIBUTE,
                                                line_span,
                                                "useless lint attribute",
                                                |db| {
                                                    sugg = sugg.replacen("#[", "#![", 1);
                                                    db.span_suggestion(
                                                        line_span,
                                                        "if you just forgot a `!`, use",
                                                        sugg,
                                                    );
                                                },
                                            );
                                        }
                                    }
                                },
                                _ => {},
                            }
                        }
                    }
                }
            },
            _ => {},
        }
    }

    fn check_impl_item(&mut self, cx: &LateContext<'a, 'tcx>, item: &'tcx ImplItem) {
        if is_relevant_impl(cx.tcx, item) {
            check_attrs(cx, item.span, &item.name, &item.attrs)
        }
    }

    fn check_trait_item(&mut self, cx: &LateContext<'a, 'tcx>, item: &'tcx TraitItem) {
        if is_relevant_trait(cx.tcx, item) {
            check_attrs(cx, item.span, &item.name, &item.attrs)
        }
    }
}

fn is_relevant_item(tcx: TyCtxt, item: &Item) -> bool {
    if let ItemFn(_, _, _, _, _, eid) = item.node {
        is_relevant_expr(tcx, tcx.body_tables(eid), &tcx.hir.body(eid).value)
    } else {
        true
    }
}

fn is_relevant_impl(tcx: TyCtxt, item: &ImplItem) -> bool {
    match item.node {
        ImplItemKind::Method(_, eid) => is_relevant_expr(tcx, tcx.body_tables(eid), &tcx.hir.body(eid).value),
        _ => false,
    }
}

fn is_relevant_trait(tcx: TyCtxt, item: &TraitItem) -> bool {
    match item.node {
        TraitItemKind::Method(_, TraitMethod::Required(_)) => true,
        TraitItemKind::Method(_, TraitMethod::Provided(eid)) => {
            is_relevant_expr(tcx, tcx.body_tables(eid), &tcx.hir.body(eid).value)
        },
        _ => false,
    }
}

fn is_relevant_block(tcx: TyCtxt, tables: &ty::TypeckTables, block: &Block) -> bool {
    if let Some(stmt) = block.stmts.first() {
        match stmt.node {
            StmtDecl(_, _) => true,
            StmtExpr(ref expr, _) | StmtSemi(ref expr, _) => is_relevant_expr(tcx, tables, expr),
        }
    } else {
        block
            .expr
            .as_ref()
            .map_or(false, |e| is_relevant_expr(tcx, tables, e))
    }
}

fn is_relevant_expr(tcx: TyCtxt, tables: &ty::TypeckTables, expr: &Expr) -> bool {
    match expr.node {
        ExprBlock(ref block) => is_relevant_block(tcx, tables, block),
        ExprRet(Some(ref e)) => is_relevant_expr(tcx, tables, e),
        ExprRet(None) | ExprBreak(_, None) => false,
        ExprCall(ref path_expr, _) => if let ExprPath(ref qpath) = path_expr.node {
            if let Some(fun_id) = opt_def_id(tables.qpath_def(qpath, path_expr.hir_id)) {
                !match_def_path(tcx, fun_id, &paths::BEGIN_PANIC)
            } else {
                true
            }
        } else {
            true
        },
        _ => true,
    }
}

fn check_attrs(cx: &LateContext, span: Span, name: &Name, attrs: &[Attribute]) {
    if in_macro(span) {
        return;
    }

    for attr in attrs {
        if attr.is_sugared_doc {
            return;
        }
        if attr.style == AttrStyle::Outer {
            if attr.tokens.is_empty() || !is_present_in_source(cx, attr.span) {
                return;
            }

            let begin_of_attr_to_item = Span::new(attr.span.lo(), span.lo(), span.ctxt());
            let end_of_attr_to_item = Span::new(attr.span.hi(), span.lo(), span.ctxt());

            if let Some(snippet) = snippet_opt(cx, end_of_attr_to_item) {
                let lines = snippet.split('\n').collect::<Vec<_>>();
                let lines = without_block_comments(lines);

                if lines.iter().filter(|l| l.trim().is_empty()).count() > 2 {
                    span_lint(
                        cx,
                        EMPTY_LINE_AFTER_OUTER_ATTR,
                        begin_of_attr_to_item,
                        "Found an empty line after an outer attribute. Perhaps you forgot to add a '!' to make it an inner attribute?"
                    );
                }
            }
        }

        if let Some(ref values) = attr.meta_item_list() {
            if values.len() != 1 || attr.name().map_or(true, |n| n != "inline") {
                continue;
            }
            if is_word(&values[0], "always") {
                span_lint(
                    cx,
                    INLINE_ALWAYS,
                    attr.span,
                    &format!(
                        "you have declared `#[inline(always)]` on `{}`. This is usually a bad idea",
                        name
                    ),
                );
            }
        }
    }
}

fn check_semver(cx: &LateContext, span: Span, lit: &Lit) {
    if let LitKind::Str(ref is, _) = lit.node {
        if Version::parse(&is.as_str()).is_ok() {
            return;
        }
    }
    span_lint(
        cx,
        DEPRECATED_SEMVER,
        span,
        "the since field must contain a semver-compliant version",
    );
}

fn is_word(nmi: &NestedMetaItem, expected: &str) -> bool {
    if let NestedMetaItemKind::MetaItem(ref mi) = nmi.node {
        mi.is_word() && mi.name() == expected
    } else {
        false
    }
}

// If the snippet is empty, it's an attribute that was inserted during macro
// expansion and we want to ignore those, because they could come from external
// sources that the user has no control over.
// For some reason these attributes don't have any expansion info on them, so
// we have to check it this way until there is a better way.
fn is_present_in_source(cx: &LateContext, span: Span) -> bool {
    if let Some(snippet) = snippet_opt(cx, span) {
        if snippet.is_empty() {
            return false;
        }
    }
    true
}