1
- use crate :: utils:: span_lint;
1
+ use crate :: utils:: { match_type , paths , return_ty , span_lint} ;
2
2
use itertools:: Itertools ;
3
3
use pulldown_cmark;
4
4
use rustc:: hir;
@@ -8,7 +8,7 @@ use rustc_data_structures::fx::FxHashSet;
8
8
use rustc_session:: declare_tool_lint;
9
9
use std:: ops:: Range ;
10
10
use syntax:: ast:: { AttrKind , Attribute } ;
11
- use syntax:: source_map:: { BytePos , Span } ;
11
+ use syntax:: source_map:: { BytePos , MultiSpan , Span } ;
12
12
use syntax_pos:: Pos ;
13
13
use url:: Url ;
14
14
@@ -45,7 +45,7 @@ declare_clippy_lint! {
45
45
///
46
46
/// **Known problems:** None.
47
47
///
48
- /// **Examples**:
48
+ /// **Examples:**
49
49
/// ```rust
50
50
///# type Universe = ();
51
51
/// /// This function should really be documented
@@ -70,6 +70,34 @@ declare_clippy_lint! {
70
70
"`pub unsafe fn` without `# Safety` docs"
71
71
}
72
72
73
+ declare_clippy_lint ! {
74
+ /// **What it does:** Checks the doc comments of publicly visible functions that
75
+ /// return a `Result` type and warns if there is no `# Errors` section.
76
+ ///
77
+ /// **Why is this bad?** Documenting the type of errors that can be returned from a
78
+ /// function can help callers write code to handle the errors appropriately.
79
+ ///
80
+ /// **Known problems:** None.
81
+ ///
82
+ /// **Examples:**
83
+ ///
84
+ /// Since the following function returns a `Result` it has an `# Errors` section in
85
+ /// its doc comment:
86
+ ///
87
+ /// ```rust
88
+ /// /// # Errors
89
+ /// ///
90
+ /// /// Will return `Err` if `filename` does not exist or the user does not have
91
+ /// /// permission to read it.
92
+ /// pub fn read(filename: String) -> io::Result<String> {
93
+ /// unimplemented!();
94
+ /// }
95
+ /// ```
96
+ pub MISSING_ERRORS_DOC ,
97
+ pedantic,
98
+ "`pub fn` returns `Result` without `# Errors` in doc comment"
99
+ }
100
+
73
101
declare_clippy_lint ! {
74
102
/// **What it does:** Checks for `fn main() { .. }` in doctests
75
103
///
@@ -114,28 +142,19 @@ impl DocMarkdown {
114
142
}
115
143
}
116
144
117
- impl_lint_pass ! ( DocMarkdown => [ DOC_MARKDOWN , MISSING_SAFETY_DOC , NEEDLESS_DOCTEST_MAIN ] ) ;
145
+ impl_lint_pass ! ( DocMarkdown => [ DOC_MARKDOWN , MISSING_SAFETY_DOC , MISSING_ERRORS_DOC , NEEDLESS_DOCTEST_MAIN ] ) ;
118
146
119
147
impl < ' a , ' tcx > LateLintPass < ' a , ' tcx > for DocMarkdown {
120
148
fn check_crate ( & mut self , cx : & LateContext < ' a , ' tcx > , krate : & ' tcx hir:: Crate ) {
121
149
check_attrs ( cx, & self . valid_idents , & krate. attrs ) ;
122
150
}
123
151
124
152
fn check_item ( & mut self , cx : & LateContext < ' a , ' tcx > , item : & ' tcx hir:: Item ) {
125
- if check_attrs ( cx, & self . valid_idents , & item. attrs ) {
126
- return ;
127
- }
128
- // no safety header
129
153
match item. kind {
130
154
hir:: ItemKind :: Fn ( ref sig, ..) => {
131
- if cx. access_levels . is_exported ( item. hir_id ) && sig. header . unsafety == hir:: Unsafety :: Unsafe {
132
- span_lint (
133
- cx,
134
- MISSING_SAFETY_DOC ,
135
- item. span ,
136
- "unsafe function's docs miss `# Safety` section" ,
137
- ) ;
138
- }
155
+ let headers = check_attrs ( cx, & self . valid_idents , & item. attrs ) ;
156
+ lint_missing_safety_doc ( cx, item. hir_id , item. span , sig, headers) ;
157
+ lint_missing_errors_doc ( cx, item. hir_id , item. span , headers) ;
139
158
} ,
140
159
hir:: ItemKind :: Impl ( _, _, _, _, ref trait_ref, ..) => {
141
160
self . in_trait_impl = trait_ref. is_some ( ) ;
@@ -151,40 +170,59 @@ impl<'a, 'tcx> LateLintPass<'a, 'tcx> for DocMarkdown {
151
170
}
152
171
153
172
fn check_trait_item ( & mut self , cx : & LateContext < ' a , ' tcx > , item : & ' tcx hir:: TraitItem ) {
154
- if check_attrs ( cx, & self . valid_idents , & item. attrs ) {
155
- return ;
156
- }
157
- // no safety header
158
173
if let hir:: TraitItemKind :: Method ( ref sig, ..) = item. kind {
159
- if cx. access_levels . is_exported ( item. hir_id ) && sig. header . unsafety == hir:: Unsafety :: Unsafe {
160
- span_lint (
161
- cx,
162
- MISSING_SAFETY_DOC ,
163
- item. span ,
164
- "unsafe function's docs miss `# Safety` section" ,
165
- ) ;
166
- }
174
+ let headers = check_attrs ( cx, & self . valid_idents , & item. attrs ) ;
175
+ lint_missing_safety_doc ( cx, item. hir_id , item. span , sig, headers) ;
176
+ lint_missing_errors_doc ( cx, item. hir_id , item. span , headers) ;
167
177
}
168
178
}
169
179
170
180
fn check_impl_item ( & mut self , cx : & LateContext < ' a , ' tcx > , item : & ' tcx hir:: ImplItem ) {
171
- if check_attrs ( cx , & self . valid_idents , & item . attrs ) || self . in_trait_impl {
181
+ if self . in_trait_impl {
172
182
return ;
173
183
}
174
- // no safety header
175
184
if let hir:: ImplItemKind :: Method ( ref sig, ..) = item. kind {
176
- if cx. access_levels . is_exported ( item. hir_id ) && sig. header . unsafety == hir:: Unsafety :: Unsafe {
177
- span_lint (
178
- cx,
179
- MISSING_SAFETY_DOC ,
180
- item. span ,
181
- "unsafe function's docs miss `# Safety` section" ,
182
- ) ;
183
- }
185
+ let headers = check_attrs ( cx, & self . valid_idents , & item. attrs ) ;
186
+ lint_missing_safety_doc ( cx, item. hir_id , item. span , sig, headers) ;
187
+ lint_missing_errors_doc ( cx, item. hir_id , item. span , headers) ;
184
188
}
185
189
}
186
190
}
187
191
192
+ fn lint_missing_safety_doc < ' a , ' tcx > (
193
+ cx : & LateContext < ' a , ' tcx > ,
194
+ hir_id : hir:: HirId ,
195
+ span : impl Into < MultiSpan > ,
196
+ sig : & hir:: FnSig ,
197
+ headers : DocHeaders ,
198
+ ) {
199
+ if !headers. safety && cx. access_levels . is_exported ( hir_id) && sig. header . unsafety == hir:: Unsafety :: Unsafe {
200
+ span_lint (
201
+ cx,
202
+ MISSING_SAFETY_DOC ,
203
+ span,
204
+ "unsafe function's docs miss `# Safety` section" ,
205
+ ) ;
206
+ }
207
+ }
208
+
209
+ fn lint_missing_errors_doc < ' a , ' tcx > (
210
+ cx : & LateContext < ' a , ' tcx > ,
211
+ hir_id : hir:: HirId ,
212
+ span : impl Into < MultiSpan > ,
213
+ headers : DocHeaders ,
214
+ ) {
215
+ if !headers. errors && cx. access_levels . is_exported ( hir_id) && match_type ( cx, return_ty ( cx, hir_id) , & paths:: RESULT )
216
+ {
217
+ span_lint (
218
+ cx,
219
+ MISSING_ERRORS_DOC ,
220
+ span,
221
+ "docs for function returning `Result` missing `# Errors` section" ,
222
+ ) ;
223
+ }
224
+ }
225
+
188
226
/// Cleanup documentation decoration (`///` and such).
189
227
///
190
228
/// We can't use `syntax::attr::AttributeMethods::with_desugared_doc` or
@@ -243,7 +281,13 @@ pub fn strip_doc_comment_decoration(comment: &str, span: Span) -> (String, Vec<(
243
281
panic ! ( "not a doc-comment: {}" , comment) ;
244
282
}
245
283
246
- pub fn check_attrs < ' a > ( cx : & LateContext < ' _ , ' _ > , valid_idents : & FxHashSet < String > , attrs : & ' a [ Attribute ] ) -> bool {
284
+ #[ derive( Copy , Clone ) ]
285
+ struct DocHeaders {
286
+ safety : bool ,
287
+ errors : bool ,
288
+ }
289
+
290
+ fn check_attrs < ' a > ( cx : & LateContext < ' _ , ' _ > , valid_idents : & FxHashSet < String > , attrs : & ' a [ Attribute ] ) -> DocHeaders {
247
291
let mut doc = String :: new ( ) ;
248
292
let mut spans = vec ! [ ] ;
249
293
@@ -255,7 +299,11 @@ pub fn check_attrs<'a>(cx: &LateContext<'_, '_>, valid_idents: &FxHashSet<String
255
299
doc. push_str ( & comment) ;
256
300
} else if attr. check_name ( sym ! ( doc) ) {
257
301
// ignore mix of sugared and non-sugared doc
258
- return true ; // don't trigger the safety check
302
+ // don't trigger the safety or errors check
303
+ return DocHeaders {
304
+ safety : true ,
305
+ errors : true ,
306
+ } ;
259
307
}
260
308
}
261
309
@@ -267,7 +315,10 @@ pub fn check_attrs<'a>(cx: &LateContext<'_, '_>, valid_idents: &FxHashSet<String
267
315
}
268
316
269
317
if doc. is_empty ( ) {
270
- return false ;
318
+ return DocHeaders {
319
+ safety : false ,
320
+ errors : false ,
321
+ } ;
271
322
}
272
323
273
324
let parser = pulldown_cmark:: Parser :: new ( & doc) . into_offset_iter ( ) ;
@@ -295,12 +346,15 @@ fn check_doc<'a, Events: Iterator<Item = (pulldown_cmark::Event<'a>, Range<usize
295
346
valid_idents : & FxHashSet < String > ,
296
347
events : Events ,
297
348
spans : & [ ( usize , Span ) ] ,
298
- ) -> bool {
349
+ ) -> DocHeaders {
299
350
// true if a safety header was found
300
351
use pulldown_cmark:: Event :: * ;
301
352
use pulldown_cmark:: Tag :: * ;
302
353
303
- let mut safety_header = false ;
354
+ let mut headers = DocHeaders {
355
+ safety : false ,
356
+ errors : false ,
357
+ } ;
304
358
let mut in_code = false ;
305
359
let mut in_link = None ;
306
360
let mut in_heading = false ;
@@ -323,7 +377,8 @@ fn check_doc<'a, Events: Iterator<Item = (pulldown_cmark::Event<'a>, Range<usize
323
377
// text "http://example.com" by pulldown-cmark
324
378
continue ;
325
379
}
326
- safety_header |= in_heading && text. trim ( ) == "Safety" ;
380
+ headers. safety |= in_heading && text. trim ( ) == "Safety" ;
381
+ headers. errors |= in_heading && text. trim ( ) == "Errors" ;
327
382
let index = match spans. binary_search_by ( |c| c. 0 . cmp ( & range. start ) ) {
328
383
Ok ( o) => o,
329
384
Err ( e) => e - 1 ,
@@ -340,7 +395,7 @@ fn check_doc<'a, Events: Iterator<Item = (pulldown_cmark::Event<'a>, Range<usize
340
395
} ,
341
396
}
342
397
}
343
- safety_header
398
+ headers
344
399
}
345
400
346
401
fn check_code ( cx : & LateContext < ' _ , ' _ > , text : & str , span : Span ) {
0 commit comments