1
1
use crate :: utils:: span_lint;
2
2
use itertools:: Itertools ;
3
3
use pulldown_cmark;
4
- use rustc:: lint:: { EarlyContext , EarlyLintPass , LintArray , LintPass } ;
4
+ use rustc:: hir;
5
+ use rustc:: lint:: { LateContext , LateLintPass , LintArray , LintPass } ;
5
6
use rustc:: { declare_tool_lint, impl_lint_pass} ;
6
7
use rustc_data_structures:: fx:: FxHashSet ;
7
8
use std:: ops:: Range ;
8
- use syntax:: ast;
9
+ use syntax:: ast:: Attribute ;
9
10
use syntax:: source_map:: { BytePos , Span } ;
10
11
use syntax_pos:: Pos ;
11
12
use url:: Url ;
@@ -68,32 +69,110 @@ declare_clippy_lint! {
68
69
"`pub unsafe fn` without `# Safety` docs"
69
70
}
70
71
72
+ declare_clippy_lint ! {
73
+ /// **What it does:** Checks for `fn main() { .. }` in doctests
74
+ ///
75
+ /// **Why is this bad?** The test can be shorter (and likely more readable)
76
+ /// if the `fn main()` is left implicit.
77
+ ///
78
+ /// **Known problems:** None.
79
+ ///
80
+ /// **Examples:**
81
+ /// ``````rust
82
+ /// /// An example of a doctest with a `main()` function
83
+ /// ///
84
+ /// /// # Examples
85
+ /// ///
86
+ /// /// ```
87
+ /// /// fn main() {
88
+ /// /// // this needs not be in an `fn`
89
+ /// /// }
90
+ /// /// ```
91
+ /// fn needless_main() {
92
+ /// unimplemented!();
93
+ /// }
94
+ /// ``````
95
+ pub NEEDLESS_DOCTEST_MAIN ,
96
+ style,
97
+ "presence of `fn main() {` in code examples"
98
+ }
99
+
71
100
#[ allow( clippy:: module_name_repetitions) ]
72
101
#[ derive( Clone ) ]
73
102
pub struct DocMarkdown {
74
103
valid_idents : FxHashSet < String > ,
104
+ in_trait_impl : bool ,
75
105
}
76
106
77
107
impl DocMarkdown {
78
108
pub fn new ( valid_idents : FxHashSet < String > ) -> Self {
79
- Self { valid_idents }
109
+ Self {
110
+ valid_idents,
111
+ in_trait_impl : false ,
112
+ }
80
113
}
81
114
}
82
115
83
- impl_lint_pass ! ( DocMarkdown => [ DOC_MARKDOWN , MISSING_SAFETY_DOC ] ) ;
116
+ impl_lint_pass ! ( DocMarkdown => [ DOC_MARKDOWN , MISSING_SAFETY_DOC , NEEDLESS_DOCTEST_MAIN ] ) ;
84
117
85
- impl EarlyLintPass for DocMarkdown {
86
- fn check_crate ( & mut self , cx : & EarlyContext < ' _ > , krate : & ast :: Crate ) {
118
+ impl < ' a , ' tcx > LateLintPass < ' a , ' tcx > for DocMarkdown {
119
+ fn check_crate ( & mut self , cx : & LateContext < ' a , ' tcx > , krate : & ' tcx hir :: Crate ) {
87
120
check_attrs ( cx, & self . valid_idents , & krate. attrs ) ;
88
121
}
89
122
90
- fn check_item ( & mut self , cx : & EarlyContext < ' _ > , item : & ast:: Item ) {
123
+ fn check_item ( & mut self , cx : & LateContext < ' a , ' tcx > , item : & ' tcx hir:: Item ) {
124
+ if check_attrs ( cx, & self . valid_idents , & item. attrs ) {
125
+ return ;
126
+ }
127
+ // no safety header
128
+ match item. kind {
129
+ hir:: ItemKind :: Fn ( _, ref header, ..) => {
130
+ if cx. access_levels . is_exported ( item. hir_id ) && header. unsafety == hir:: Unsafety :: Unsafe {
131
+ span_lint (
132
+ cx,
133
+ MISSING_SAFETY_DOC ,
134
+ item. span ,
135
+ "unsafe function's docs miss `# Safety` section" ,
136
+ ) ;
137
+ }
138
+ } ,
139
+ hir:: ItemKind :: Impl ( _, _, _, _, ref trait_ref, ..) => {
140
+ self . in_trait_impl = trait_ref. is_some ( ) ;
141
+ } ,
142
+ _ => { } ,
143
+ }
144
+ }
145
+
146
+ fn check_item_post ( & mut self , _cx : & LateContext < ' a , ' tcx > , item : & ' tcx hir:: Item ) {
147
+ if let hir:: ItemKind :: Impl ( ..) = item. kind {
148
+ self . in_trait_impl = false ;
149
+ }
150
+ }
151
+
152
+ fn check_trait_item ( & mut self , cx : & LateContext < ' a , ' tcx > , item : & ' tcx hir:: TraitItem ) {
91
153
if check_attrs ( cx, & self . valid_idents , & item. attrs ) {
92
154
return ;
93
155
}
94
156
// no safety header
95
- if let ast:: ItemKind :: Fn ( _, ref header, ..) = item. kind {
96
- if item. vis . node . is_pub ( ) && header. unsafety == ast:: Unsafety :: Unsafe {
157
+ if let hir:: TraitItemKind :: Method ( ref sig, ..) = item. kind {
158
+ if cx. access_levels . is_exported ( item. hir_id ) && sig. header . unsafety == hir:: Unsafety :: Unsafe {
159
+ span_lint (
160
+ cx,
161
+ MISSING_SAFETY_DOC ,
162
+ item. span ,
163
+ "unsafe function's docs miss `# Safety` section" ,
164
+ ) ;
165
+ }
166
+ }
167
+ }
168
+
169
+ fn check_impl_item ( & mut self , cx : & LateContext < ' a , ' tcx > , item : & ' tcx hir:: ImplItem ) {
170
+ if check_attrs ( cx, & self . valid_idents , & item. attrs ) || self . in_trait_impl {
171
+ return ;
172
+ }
173
+ // no safety header
174
+ if let hir:: ImplItemKind :: Method ( ref sig, ..) = item. kind {
175
+ if cx. access_levels . is_exported ( item. hir_id ) && sig. header . unsafety == hir:: Unsafety :: Unsafe {
97
176
span_lint (
98
177
cx,
99
178
MISSING_SAFETY_DOC ,
@@ -162,7 +241,7 @@ pub fn strip_doc_comment_decoration(comment: &str, span: Span) -> (String, Vec<(
162
241
panic ! ( "not a doc-comment: {}" , comment) ;
163
242
}
164
243
165
- pub fn check_attrs < ' a > ( cx : & EarlyContext < ' _ > , valid_idents : & FxHashSet < String > , attrs : & ' a [ ast :: Attribute ] ) -> bool {
244
+ pub fn check_attrs < ' a > ( cx : & LateContext < ' _ , ' _ > , valid_idents : & FxHashSet < String > , attrs : & ' a [ Attribute ] ) -> bool {
166
245
let mut doc = String :: new ( ) ;
167
246
let mut spans = vec ! [ ] ;
168
247
@@ -212,7 +291,7 @@ pub fn check_attrs<'a>(cx: &EarlyContext<'_>, valid_idents: &FxHashSet<String>,
212
291
}
213
292
214
293
fn check_doc < ' a , Events : Iterator < Item = ( pulldown_cmark:: Event < ' a > , Range < usize > ) > > (
215
- cx : & EarlyContext < ' _ > ,
294
+ cx : & LateContext < ' _ , ' _ > ,
216
295
valid_idents : & FxHashSet < String > ,
217
296
events : Events ,
218
297
spans : & [ ( usize , Span ) ] ,
@@ -245,14 +324,14 @@ fn check_doc<'a, Events: Iterator<Item = (pulldown_cmark::Event<'a>, Range<usize
245
324
continue ;
246
325
}
247
326
safety_header |= in_heading && text. trim ( ) == "Safety" ;
248
- if !in_code {
249
- let index = match spans . binary_search_by ( |c| c . 0 . cmp ( & range . start ) ) {
250
- Ok ( o ) => o ,
251
- Err ( e ) => e - 1 ,
252
- } ;
253
-
254
- let ( begin , span) = spans [ index ] ;
255
-
327
+ let index = match spans . binary_search_by ( |c| c . 0 . cmp ( & range . start ) ) {
328
+ Ok ( o ) => o ,
329
+ Err ( e ) => e - 1 ,
330
+ } ;
331
+ let ( begin , span ) = spans [ index ] ;
332
+ if in_code {
333
+ check_code ( cx , & text , span) ;
334
+ } else {
256
335
// Adjust for the beginning of the current `Event`
257
336
let span = span. with_lo ( span. lo ( ) + BytePos :: from_usize ( range. start - begin) ) ;
258
337
@@ -264,7 +343,13 @@ fn check_doc<'a, Events: Iterator<Item = (pulldown_cmark::Event<'a>, Range<usize
264
343
safety_header
265
344
}
266
345
267
- fn check_text ( cx : & EarlyContext < ' _ > , valid_idents : & FxHashSet < String > , text : & str , span : Span ) {
346
+ fn check_code ( cx : & LateContext < ' _ , ' _ > , text : & str , span : Span ) {
347
+ if text. contains ( "fn main() {" ) {
348
+ span_lint ( cx, NEEDLESS_DOCTEST_MAIN , span, "needless `fn main` in doctest" ) ;
349
+ }
350
+ }
351
+
352
+ fn check_text ( cx : & LateContext < ' _ , ' _ > , valid_idents : & FxHashSet < String > , text : & str , span : Span ) {
268
353
for word in text. split ( |c : char | c. is_whitespace ( ) || c == '\'' ) {
269
354
// Trim punctuation as in `some comment (see foo::bar).`
270
355
// ^^
@@ -287,7 +372,7 @@ fn check_text(cx: &EarlyContext<'_>, valid_idents: &FxHashSet<String>, text: &st
287
372
}
288
373
}
289
374
290
- fn check_word ( cx : & EarlyContext < ' _ > , word : & str , span : Span ) {
375
+ fn check_word ( cx : & LateContext < ' _ , ' _ > , word : & str , span : Span ) {
291
376
/// Checks if a string is camel-case, i.e., contains at least two uppercase
292
377
/// letters (`Clippy` is ok) and one lower-case letter (`NASA` is ok).
293
378
/// Plurals are also excluded (`IDs` is ok).
0 commit comments