Skip to content

Commit 39d1d60

Browse files
authored
Merge pull request #2340 from phansch/newline_after_attributes
Warn on empty lines after outer attributes
2 parents dbbae57 + 3d54e56 commit 39d1d60

6 files changed

+182
-6
lines changed

clippy_lints/src/attrs.rs

+70-3
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ use rustc::lint::*;
55
use rustc::hir::*;
66
use rustc::ty::{self, TyCtxt};
77
use semver::Version;
8-
use syntax::ast::{Attribute, Lit, LitKind, MetaItemKind, NestedMetaItem, NestedMetaItemKind};
8+
use syntax::ast::{Attribute, AttrStyle, Lit, LitKind, MetaItemKind, NestedMetaItem, NestedMetaItemKind};
99
use syntax::codemap::Span;
1010
use utils::{in_macro, match_def_path, opt_def_id, paths, snippet_opt, span_lint, span_lint_and_then};
1111

@@ -78,12 +78,44 @@ declare_lint! {
7878
"use of `#[deprecated(since = \"x\")]` where x is not semver"
7979
}
8080

81+
/// **What it does:** Checks for empty lines after outer attributes
82+
///
83+
/// **Why is this bad?**
84+
/// Most likely the attribute was meant to be an inner attribute using a '!'.
85+
/// If it was meant to be an outer attribute, then the following item
86+
/// should not be separated by empty lines.
87+
///
88+
/// **Known problems:** None
89+
///
90+
/// **Example:**
91+
/// ```rust
92+
/// // Bad
93+
/// #[inline(always)]
94+
///
95+
/// fn not_quite_good_code(..) { ... }
96+
///
97+
/// // Good (as inner attribute)
98+
/// #![inline(always)]
99+
///
100+
/// fn this_is_fine_too(..) { ... }
101+
///
102+
/// // Good (as outer attribute)
103+
/// #[inline(always)]
104+
/// fn this_is_fine(..) { ... }
105+
///
106+
/// ```
107+
declare_lint! {
108+
pub EMPTY_LINE_AFTER_OUTER_ATTR,
109+
Warn,
110+
"empty line after outer attribute"
111+
}
112+
81113
#[derive(Copy, Clone)]
82114
pub struct AttrPass;
83115

84116
impl LintPass for AttrPass {
85117
fn get_lints(&self) -> LintArray {
86-
lint_array!(INLINE_ALWAYS, DEPRECATED_SEMVER, USELESS_ATTRIBUTE)
118+
lint_array!(INLINE_ALWAYS, DEPRECATED_SEMVER, USELESS_ATTRIBUTE, EMPTY_LINE_AFTER_OUTER_ATTR)
87119
}
88120
}
89121

@@ -171,7 +203,7 @@ fn is_relevant_item(tcx: TyCtxt, item: &Item) -> bool {
171203
if let ItemFn(_, _, _, _, _, eid) = item.node {
172204
is_relevant_expr(tcx, tcx.body_tables(eid), &tcx.hir.body(eid).value)
173205
} else {
174-
false
206+
true
175207
}
176208
}
177209

@@ -230,6 +262,27 @@ fn check_attrs(cx: &LateContext, span: Span, name: &Name, attrs: &[Attribute]) {
230262
}
231263

232264
for attr in attrs {
265+
if attr.style == AttrStyle::Outer {
266+
if !is_present_in_source(cx, attr.span) {
267+
return;
268+
}
269+
270+
let attr_to_item_span = Span::new(attr.span.lo(), span.lo(), span.ctxt());
271+
272+
if let Some(snippet) = snippet_opt(cx, attr_to_item_span) {
273+
let lines = snippet.split('\n').collect::<Vec<_>>();
274+
if lines.iter().filter(|l| l.trim().is_empty()).count() > 1 {
275+
span_lint(
276+
cx,
277+
EMPTY_LINE_AFTER_OUTER_ATTR,
278+
attr_to_item_span,
279+
&format!("Found an empty line after an outer attribute. Perhaps you forgot to add a '!' to make it an inner attribute?")
280+
);
281+
282+
}
283+
}
284+
}
285+
233286
if let Some(ref values) = attr.meta_item_list() {
234287
if values.len() != 1 || attr.name().map_or(true, |n| n != "inline") {
235288
continue;
@@ -270,3 +323,17 @@ fn is_word(nmi: &NestedMetaItem, expected: &str) -> bool {
270323
false
271324
}
272325
}
326+
327+
// If the snippet is empty, it's an attribute that was inserted during macro
328+
// expansion and we want to ignore those, because they could come from external
329+
// sources that the user has no control over.
330+
// For some reason these attributes don't have any expansion info on them, so
331+
// we have to check it this way until there is a better way.
332+
fn is_present_in_source(cx: &LateContext, span: Span) -> bool {
333+
if let Some(snippet) = snippet_opt(cx, span) {
334+
if snippet.is_empty() {
335+
return false;
336+
}
337+
}
338+
true
339+
}

clippy_lints/src/lib.rs

+1
Original file line numberDiff line numberDiff line change
@@ -440,6 +440,7 @@ pub fn register_plugins(reg: &mut rustc_plugin::Registry) {
440440
attrs::DEPRECATED_SEMVER,
441441
attrs::INLINE_ALWAYS,
442442
attrs::USELESS_ATTRIBUTE,
443+
attrs::EMPTY_LINE_AFTER_OUTER_ATTR,
443444
bit_mask::BAD_BIT_MASK,
444445
bit_mask::INEFFECTIVE_BIT_MASK,
445446
bit_mask::VERBOSE_BIT_MASK,
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
2+
#![warn(empty_line_after_outer_attr)]
3+
4+
// This should produce a warning
5+
#[crate_type = "lib"]
6+
7+
/// some comment
8+
fn with_one_newline_and_comment() { assert!(true) }
9+
10+
// This should not produce a warning
11+
#[crate_type = "lib"]
12+
/// some comment
13+
fn with_no_newline_and_comment() { assert!(true) }
14+
15+
16+
// This should produce a warning
17+
#[crate_type = "lib"]
18+
19+
fn with_one_newline() { assert!(true) }
20+
21+
// This should produce a warning, too
22+
#[crate_type = "lib"]
23+
24+
25+
fn with_two_newlines() { assert!(true) }
26+
27+
28+
// This should produce a warning
29+
#[crate_type = "lib"]
30+
31+
enum Baz {
32+
One,
33+
Two
34+
}
35+
36+
// This should produce a warning
37+
#[crate_type = "lib"]
38+
39+
struct Foo {
40+
one: isize,
41+
two: isize
42+
}
43+
44+
// This should produce a warning
45+
#[crate_type = "lib"]
46+
47+
mod foo {
48+
}
49+
50+
// This should not produce a warning
51+
#[allow(non_camel_case_types)]
52+
#[allow(missing_docs)]
53+
#[allow(missing_docs)]
54+
fn three_attributes() { assert!(true) }
55+
56+
fn main() { }
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
error: Found an empty line after an outer attribute. Perhaps you forgot to add a '!' to make it an inner attribute?
2+
--> $DIR/empty_line_after_outer_attribute.rs:5:1
3+
|
4+
5 | / #[crate_type = "lib"]
5+
6 | |
6+
7 | | /// some comment
7+
8 | | fn with_one_newline_and_comment() { assert!(true) }
8+
| |_
9+
|
10+
= note: `-D empty-line-after-outer-attr` implied by `-D warnings`
11+
12+
error: Found an empty line after an outer attribute. Perhaps you forgot to add a '!' to make it an inner attribute?
13+
--> $DIR/empty_line_after_outer_attribute.rs:17:1
14+
|
15+
17 | / #[crate_type = "lib"]
16+
18 | |
17+
19 | | fn with_one_newline() { assert!(true) }
18+
| |_
19+
20+
error: Found an empty line after an outer attribute. Perhaps you forgot to add a '!' to make it an inner attribute?
21+
--> $DIR/empty_line_after_outer_attribute.rs:22:1
22+
|
23+
22 | / #[crate_type = "lib"]
24+
23 | |
25+
24 | |
26+
25 | | fn with_two_newlines() { assert!(true) }
27+
| |_
28+
29+
error: Found an empty line after an outer attribute. Perhaps you forgot to add a '!' to make it an inner attribute?
30+
--> $DIR/empty_line_after_outer_attribute.rs:29:1
31+
|
32+
29 | / #[crate_type = "lib"]
33+
30 | |
34+
31 | | enum Baz {
35+
| |_
36+
37+
error: Found an empty line after an outer attribute. Perhaps you forgot to add a '!' to make it an inner attribute?
38+
--> $DIR/empty_line_after_outer_attribute.rs:37:1
39+
|
40+
37 | / #[crate_type = "lib"]
41+
38 | |
42+
39 | | struct Foo {
43+
| |_
44+
45+
error: Found an empty line after an outer attribute. Perhaps you forgot to add a '!' to make it an inner attribute?
46+
--> $DIR/empty_line_after_outer_attribute.rs:45:1
47+
|
48+
45 | / #[crate_type = "lib"]
49+
46 | |
50+
47 | | mod foo {
51+
| |_
52+
53+
error: aborting due to 6 previous errors
54+

tests/ui/inline_fn_without_body.rs

-1
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ trait Foo {
1111
#[inline(always)]fn always_inline();
1212

1313
#[inline(never)]
14-
1514
fn never_inline();
1615

1716
#[inline]

tests/ui/inline_fn_without_body.stderr

+1-2
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,7 @@ error: use of `#[inline]` on trait method `never_inline` which has no body
1919
|
2020
13 | #[inline(never)]
2121
| _____-^^^^^^^^^^^^^^^
22-
14 | |
23-
15 | | fn never_inline();
22+
14 | | fn never_inline();
2423
| |____- help: remove
2524

2625
error: aborting due to 3 previous errors

0 commit comments

Comments
 (0)