1- use crate :: utils:: span_lint;
1+ use crate :: utils:: { match_type , paths , return_ty , span_lint} ;
22use itertools:: Itertools ;
33use pulldown_cmark;
44use rustc:: hir;
@@ -8,7 +8,7 @@ use rustc_data_structures::fx::FxHashSet;
88use rustc_session:: declare_tool_lint;
99use std:: ops:: Range ;
1010use syntax:: ast:: { AttrKind , Attribute } ;
11- use syntax:: source_map:: { BytePos , Span } ;
11+ use syntax:: source_map:: { BytePos , MultiSpan , Span } ;
1212use syntax_pos:: Pos ;
1313use url:: Url ;
1414
@@ -45,7 +45,7 @@ declare_clippy_lint! {
4545 ///
4646 /// **Known problems:** None.
4747 ///
48- /// **Examples**:
48+ /// **Examples:**
4949 /// ```rust
5050 ///# type Universe = ();
5151 /// /// This function should really be documented
@@ -70,6 +70,35 @@ declare_clippy_lint! {
7070 "`pub unsafe fn` without `# Safety` docs"
7171}
7272
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+
73102declare_clippy_lint ! {
74103 /// **What it does:** Checks for `fn main() { .. }` in doctests
75104 ///
@@ -114,28 +143,18 @@ impl DocMarkdown {
114143 }
115144}
116145
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 ] ) ;
118147
119148impl < ' a , ' tcx > LateLintPass < ' a , ' tcx > for DocMarkdown {
120149 fn check_crate ( & mut self , cx : & LateContext < ' a , ' tcx > , krate : & ' tcx hir:: Crate ) {
121150 check_attrs ( cx, & self . valid_idents , & krate. attrs ) ;
122151 }
123152
124153 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 ) ;
129155 match item. kind {
130156 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) ;
139158 } ,
140159 hir:: ItemKind :: Impl ( _, _, _, _, ref trait_ref, ..) => {
141160 self . in_trait_impl = trait_ref. is_some ( ) ;
@@ -151,40 +170,51 @@ impl<'a, 'tcx> LateLintPass<'a, 'tcx> for DocMarkdown {
151170 }
152171
153172 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 ) ;
158174 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) ;
167176 }
168177 }
169178
170179 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 {
172182 return ;
173183 }
174- // no safety header
175184 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) ;
184186 }
185187 }
186188}
187189
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+
188218/// Cleanup documentation decoration (`///` and such).
189219///
190220/// 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<(
243273 panic ! ( "not a doc-comment: {}" , comment) ;
244274}
245275
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 {
247283 let mut doc = String :: new ( ) ;
248284 let mut spans = vec ! [ ] ;
249285
@@ -255,7 +291,11 @@ pub fn check_attrs<'a>(cx: &LateContext<'_, '_>, valid_idents: &FxHashSet<String
255291 doc. push_str ( & comment) ;
256292 } else if attr. check_name ( sym ! ( doc) ) {
257293 // 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+ } ;
259299 }
260300 }
261301
@@ -267,7 +307,10 @@ pub fn check_attrs<'a>(cx: &LateContext<'_, '_>, valid_idents: &FxHashSet<String
267307 }
268308
269309 if doc. is_empty ( ) {
270- return false ;
310+ return DocHeaders {
311+ safety : false ,
312+ errors : false ,
313+ } ;
271314 }
272315
273316 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
295338 valid_idents : & FxHashSet < String > ,
296339 events : Events ,
297340 spans : & [ ( usize , Span ) ] ,
298- ) -> bool {
341+ ) -> DocHeaders {
299342 // true if a safety header was found
300343 use pulldown_cmark:: Event :: * ;
301344 use pulldown_cmark:: Tag :: * ;
302345
303- let mut safety_header = false ;
346+ let mut headers = DocHeaders {
347+ safety : false ,
348+ errors : false ,
349+ } ;
304350 let mut in_code = false ;
305351 let mut in_link = None ;
306352 let mut in_heading = false ;
@@ -323,7 +369,8 @@ fn check_doc<'a, Events: Iterator<Item = (pulldown_cmark::Event<'a>, Range<usize
323369 // text "http://example.com" by pulldown-cmark
324370 continue ;
325371 }
326- safety_header |= in_heading && text. trim ( ) == "Safety" ;
372+ headers. safety |= in_heading && text. trim ( ) == "Safety" ;
373+ headers. errors |= in_heading && text. trim ( ) == "Errors" ;
327374 let index = match spans. binary_search_by ( |c| c. 0 . cmp ( & range. start ) ) {
328375 Ok ( o) => o,
329376 Err ( e) => e - 1 ,
@@ -340,7 +387,7 @@ fn check_doc<'a, Events: Iterator<Item = (pulldown_cmark::Event<'a>, Range<usize
340387 } ,
341388 }
342389 }
343- safety_header
390+ headers
344391}
345392
346393fn check_code ( cx : & LateContext < ' _ , ' _ > , text : & str , span : Span ) {
0 commit comments