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,35 @@ 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
+ ///# use std::io;
89
+ /// /// # Errors
90
+ /// ///
91
+ /// /// Will return `Err` if `filename` does not exist or the user does not have
92
+ /// /// permission to read it.
93
+ /// pub fn read(filename: String) -> io::Result<String> {
94
+ /// unimplemented!();
95
+ /// }
96
+ /// ```
97
+ pub MISSING_ERRORS_DOC ,
98
+ pedantic,
99
+ "`pub fn` returns `Result` without `# Errors` in doc comment"
100
+ }
101
+
73
102
declare_clippy_lint ! {
74
103
/// **What it does:** Checks for `fn main() { .. }` in doctests
75
104
///
@@ -114,28 +143,18 @@ impl DocMarkdown {
114
143
}
115
144
}
116
145
117
- impl_lint_pass ! ( DocMarkdown => [ DOC_MARKDOWN , MISSING_SAFETY_DOC , NEEDLESS_DOCTEST_MAIN ] ) ;
146
+ impl_lint_pass ! ( DocMarkdown => [ DOC_MARKDOWN , MISSING_SAFETY_DOC , MISSING_ERRORS_DOC , NEEDLESS_DOCTEST_MAIN ] ) ;
118
147
119
148
impl < ' a , ' tcx > LateLintPass < ' a , ' tcx > for DocMarkdown {
120
149
fn check_crate ( & mut self , cx : & LateContext < ' a , ' tcx > , krate : & ' tcx hir:: Crate ) {
121
150
check_attrs ( cx, & self . valid_idents , & krate. attrs ) ;
122
151
}
123
152
124
153
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
154
+ let headers = check_attrs ( cx, & self . valid_idents , & item. attrs ) ;
129
155
match item. kind {
130
156
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
- }
157
+ lint_for_missing_headers ( cx, item. hir_id , item. span , sig, headers) ;
139
158
} ,
140
159
hir:: ItemKind :: Impl ( _, _, _, _, ref trait_ref, ..) => {
141
160
self . in_trait_impl = trait_ref. is_some ( ) ;
@@ -151,40 +170,51 @@ 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
173
+ let headers = check_attrs ( cx, & self . valid_idents , & item. attrs ) ;
158
174
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
- }
175
+ lint_for_missing_headers ( cx, item. hir_id , item. span , sig, headers) ;
167
176
}
168
177
}
169
178
170
179
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 {
180
+ let headers = check_attrs ( cx, & self . valid_idents , & item. attrs ) ;
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
+ lint_for_missing_headers ( cx, item. hir_id , item. span , sig, headers) ;
184
186
}
185
187
}
186
188
}
187
189
190
+ fn lint_for_missing_headers < ' a , ' tcx > (
191
+ cx : & LateContext < ' a , ' tcx > ,
192
+ hir_id : hir:: HirId ,
193
+ span : impl Into < MultiSpan > + Copy ,
194
+ sig : & hir:: FnSig ,
195
+ headers : DocHeaders ,
196
+ ) {
197
+ if !cx. access_levels . is_exported ( hir_id) {
198
+ return ; // Private functions do not require doc comments
199
+ }
200
+ if !headers. safety && sig. header . unsafety == hir:: Unsafety :: Unsafe {
201
+ span_lint (
202
+ cx,
203
+ MISSING_SAFETY_DOC ,
204
+ span,
205
+ "unsafe function's docs miss `# Safety` section" ,
206
+ ) ;
207
+ }
208
+ if !headers. errors && match_type ( cx, return_ty ( cx, hir_id) , & paths:: RESULT ) {
209
+ span_lint (
210
+ cx,
211
+ MISSING_ERRORS_DOC ,
212
+ span,
213
+ "docs for function returning `Result` missing `# Errors` section" ,
214
+ ) ;
215
+ }
216
+ }
217
+
188
218
/// Cleanup documentation decoration (`///` and such).
189
219
///
190
220
/// We can't use `syntax::attr::AttributeMethods::with_desugared_doc` or
@@ -243,7 +273,13 @@ pub fn strip_doc_comment_decoration(comment: &str, span: Span) -> (String, Vec<(
243
273
panic ! ( "not a doc-comment: {}" , comment) ;
244
274
}
245
275
246
- pub fn check_attrs < ' a > ( cx : & LateContext < ' _ , ' _ > , valid_idents : & FxHashSet < String > , attrs : & ' a [ Attribute ] ) -> bool {
276
+ #[ derive( Copy , Clone ) ]
277
+ struct DocHeaders {
278
+ safety : bool ,
279
+ errors : bool ,
280
+ }
281
+
282
+ fn check_attrs < ' a > ( cx : & LateContext < ' _ , ' _ > , valid_idents : & FxHashSet < String > , attrs : & ' a [ Attribute ] ) -> DocHeaders {
247
283
let mut doc = String :: new ( ) ;
248
284
let mut spans = vec ! [ ] ;
249
285
@@ -255,7 +291,11 @@ pub fn check_attrs<'a>(cx: &LateContext<'_, '_>, valid_idents: &FxHashSet<String
255
291
doc. push_str ( & comment) ;
256
292
} else if attr. check_name ( sym ! ( doc) ) {
257
293
// ignore mix of sugared and non-sugared doc
258
- return true ; // don't trigger the safety check
294
+ // don't trigger the safety or errors check
295
+ return DocHeaders {
296
+ safety : true ,
297
+ errors : true ,
298
+ } ;
259
299
}
260
300
}
261
301
@@ -267,7 +307,10 @@ pub fn check_attrs<'a>(cx: &LateContext<'_, '_>, valid_idents: &FxHashSet<String
267
307
}
268
308
269
309
if doc. is_empty ( ) {
270
- return false ;
310
+ return DocHeaders {
311
+ safety : false ,
312
+ errors : false ,
313
+ } ;
271
314
}
272
315
273
316
let parser = pulldown_cmark:: Parser :: new ( & doc) . into_offset_iter ( ) ;
@@ -295,12 +338,15 @@ fn check_doc<'a, Events: Iterator<Item = (pulldown_cmark::Event<'a>, Range<usize
295
338
valid_idents : & FxHashSet < String > ,
296
339
events : Events ,
297
340
spans : & [ ( usize , Span ) ] ,
298
- ) -> bool {
341
+ ) -> DocHeaders {
299
342
// true if a safety header was found
300
343
use pulldown_cmark:: Event :: * ;
301
344
use pulldown_cmark:: Tag :: * ;
302
345
303
- let mut safety_header = false ;
346
+ let mut headers = DocHeaders {
347
+ safety : false ,
348
+ errors : false ,
349
+ } ;
304
350
let mut in_code = false ;
305
351
let mut in_link = None ;
306
352
let mut in_heading = false ;
@@ -323,7 +369,8 @@ fn check_doc<'a, Events: Iterator<Item = (pulldown_cmark::Event<'a>, Range<usize
323
369
// text "http://example.com" by pulldown-cmark
324
370
continue ;
325
371
}
326
- safety_header |= in_heading && text. trim ( ) == "Safety" ;
372
+ headers. safety |= in_heading && text. trim ( ) == "Safety" ;
373
+ headers. errors |= in_heading && text. trim ( ) == "Errors" ;
327
374
let index = match spans. binary_search_by ( |c| c. 0 . cmp ( & range. start ) ) {
328
375
Ok ( o) => o,
329
376
Err ( e) => e - 1 ,
@@ -340,7 +387,7 @@ fn check_doc<'a, Events: Iterator<Item = (pulldown_cmark::Event<'a>, Range<usize
340
387
} ,
341
388
}
342
389
}
343
- safety_header
390
+ headers
344
391
}
345
392
346
393
fn check_code ( cx : & LateContext < ' _ , ' _ > , text : & str , span : Span ) {
0 commit comments