@@ -5,7 +5,7 @@ use rustc::lint::*;
5
5
use rustc:: hir:: * ;
6
6
use rustc:: ty:: { self , TyCtxt } ;
7
7
use semver:: Version ;
8
- use syntax:: ast:: { Attribute , Lit , LitKind , MetaItemKind , NestedMetaItem , NestedMetaItemKind } ;
8
+ use syntax:: ast:: { Attribute , AttrStyle , Lit , LitKind , MetaItemKind , NestedMetaItem , NestedMetaItemKind } ;
9
9
use syntax:: codemap:: Span ;
10
10
use utils:: { in_macro, match_def_path, opt_def_id, paths, snippet_opt, span_lint, span_lint_and_then} ;
11
11
@@ -78,12 +78,44 @@ declare_lint! {
78
78
"use of `#[deprecated(since = \" x\" )]` where x is not semver"
79
79
}
80
80
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
+
81
113
#[ derive( Copy , Clone ) ]
82
114
pub struct AttrPass ;
83
115
84
116
impl LintPass for AttrPass {
85
117
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 )
87
119
}
88
120
}
89
121
@@ -171,7 +203,7 @@ fn is_relevant_item(tcx: TyCtxt, item: &Item) -> bool {
171
203
if let ItemFn ( _, _, _, _, _, eid) = item. node {
172
204
is_relevant_expr ( tcx, tcx. body_tables ( eid) , & tcx. hir . body ( eid) . value )
173
205
} else {
174
- false
206
+ true
175
207
}
176
208
}
177
209
@@ -230,6 +262,27 @@ fn check_attrs(cx: &LateContext, span: Span, name: &Name, attrs: &[Attribute]) {
230
262
}
231
263
232
264
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
+
233
286
if let Some ( ref values) = attr. meta_item_list ( ) {
234
287
if values. len ( ) != 1 || attr. name ( ) . map_or ( true , |n| n != "inline" ) {
235
288
continue ;
@@ -270,3 +323,17 @@ fn is_word(nmi: &NestedMetaItem, expected: &str) -> bool {
270
323
false
271
324
}
272
325
}
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
+ }
0 commit comments