]> git.lizzy.rs Git - rust.git/blobdiff - clippy_lints/src/doc.rs
rustup https://github.com/rust-lang/rust/pull/68944
[rust.git] / clippy_lints / src / doc.rs
index 9a349611b3d0505a3e1a02473a72b27f8b80b4cd..ed870850a8c4fe48803d1d331eac880ccf01accb 100644 (file)
@@ -1,6 +1,9 @@
-use crate::utils::{match_type, paths, return_ty, span_lint};
+use crate::utils::{implements_trait, is_entrypoint_fn, match_type, paths, return_ty, span_lint};
+use if_chain::if_chain;
 use itertools::Itertools;
 use rustc::lint::in_external_macro;
+use rustc::ty;
+use rustc_ast::ast::{AttrKind, Attribute};
 use rustc_data_structures::fx::FxHashSet;
 use rustc_hir as hir;
 use rustc_lint::{LateContext, LateLintPass};
@@ -8,7 +11,6 @@
 use rustc_span::source_map::{BytePos, MultiSpan, Span};
 use rustc_span::Pos;
 use std::ops::Range;
-use syntax::ast::{AttrKind, Attribute};
 use url::Url;
 
 declare_clippy_lint! {
@@ -146,18 +148,23 @@ pub fn new(valid_idents: FxHashSet<String>) -> Self {
 
 impl<'a, 'tcx> LateLintPass<'a, 'tcx> for DocMarkdown {
     fn check_crate(&mut self, cx: &LateContext<'a, 'tcx>, krate: &'tcx hir::Crate<'_>) {
-        check_attrs(cx, &self.valid_idents, &krate.attrs);
+        check_attrs(cx, &self.valid_idents, &krate.item.attrs);
     }
 
     fn check_item(&mut self, cx: &LateContext<'a, 'tcx>, item: &'tcx hir::Item<'_>) {
         let headers = check_attrs(cx, &self.valid_idents, &item.attrs);
         match item.kind {
-            hir::ItemKind::Fn(ref sig, ..) => {
-                if !in_external_macro(cx.tcx.sess, item.span) {
-                    lint_for_missing_headers(cx, item.hir_id, item.span, sig, headers);
+            hir::ItemKind::Fn(ref sig, _, body_id) => {
+                if !(is_entrypoint_fn(cx, cx.tcx.hir().local_def_id(item.hir_id))
+                    || in_external_macro(cx.tcx.sess, item.span))
+                {
+                    lint_for_missing_headers(cx, item.hir_id, item.span, sig, headers, Some(body_id));
                 }
             },
-            hir::ItemKind::Impl(_, _, _, _, ref trait_ref, ..) => {
+            hir::ItemKind::Impl {
+                of_trait: ref trait_ref,
+                ..
+            } => {
                 self.in_trait_impl = trait_ref.is_some();
             },
             _ => {},
@@ -165,16 +172,16 @@ fn check_item(&mut self, cx: &LateContext<'a, 'tcx>, item: &'tcx hir::Item<'_>)
     }
 
     fn check_item_post(&mut self, _cx: &LateContext<'a, 'tcx>, item: &'tcx hir::Item<'_>) {
-        if let hir::ItemKind::Impl(..) = item.kind {
+        if let hir::ItemKind::Impl { .. } = item.kind {
             self.in_trait_impl = false;
         }
     }
 
     fn check_trait_item(&mut self, cx: &LateContext<'a, 'tcx>, item: &'tcx hir::TraitItem<'_>) {
         let headers = check_attrs(cx, &self.valid_idents, &item.attrs);
-        if let hir::TraitItemKind::Method(ref sig, ..) = item.kind {
+        if let hir::TraitItemKind::Fn(ref sig, ..) = item.kind {
             if !in_external_macro(cx.tcx.sess, item.span) {
-                lint_for_missing_headers(cx, item.hir_id, item.span, sig, headers);
+                lint_for_missing_headers(cx, item.hir_id, item.span, sig, headers, None);
             }
         }
     }
@@ -184,8 +191,8 @@ fn check_impl_item(&mut self, cx: &LateContext<'a, 'tcx>, item: &'tcx hir::ImplI
         if self.in_trait_impl || in_external_macro(cx.tcx.sess, item.span) {
             return;
         }
-        if let hir::ImplItemKind::Method(ref sig, ..) = item.kind {
-            lint_for_missing_headers(cx, item.hir_id, item.span, sig, headers);
+        if let hir::ImplItemKind::Method(ref sig, body_id) = item.kind {
+            lint_for_missing_headers(cx, item.hir_id, item.span, sig, headers, Some(body_id));
         }
     }
 }
@@ -196,6 +203,7 @@ fn lint_for_missing_headers<'a, 'tcx>(
     span: impl Into<MultiSpan> + Copy,
     sig: &hir::FnSig<'_>,
     headers: DocHeaders,
+    body_id: Option<hir::BodyId>,
 ) {
     if !cx.access_levels.is_exported(hir_id) {
         return; // Private functions do not require doc comments
@@ -208,20 +216,43 @@ fn lint_for_missing_headers<'a, 'tcx>(
             "unsafe function's docs miss `# Safety` section",
         );
     }
-    if !headers.errors && match_type(cx, return_ty(cx, hir_id), &paths::RESULT) {
-        span_lint(
-            cx,
-            MISSING_ERRORS_DOC,
-            span,
-            "docs for function returning `Result` missing `# Errors` section",
-        );
+    if !headers.errors {
+        if match_type(cx, return_ty(cx, hir_id), &paths::RESULT) {
+            span_lint(
+                cx,
+                MISSING_ERRORS_DOC,
+                span,
+                "docs for function returning `Result` missing `# Errors` section",
+            );
+        } else {
+            if_chain! {
+                if let Some(body_id) = body_id;
+                if let Some(future) = cx.tcx.lang_items().future_trait();
+                let def_id = cx.tcx.hir().body_owner_def_id(body_id);
+                let mir = cx.tcx.optimized_mir(def_id);
+                let ret_ty = mir.return_ty();
+                if implements_trait(cx, ret_ty, future, &[]);
+                if let ty::Opaque(_, subs) = ret_ty.kind;
+                if let Some(gen) = subs.types().next();
+                if let ty::Generator(def_id, subs, _) = gen.kind;
+                if match_type(cx, subs.as_generator().return_ty(def_id, cx.tcx), &paths::RESULT);
+                then {
+                    span_lint(
+                        cx,
+                        MISSING_ERRORS_DOC,
+                        span,
+                        "docs for function returning `Result` missing `# Errors` section",
+                    );
+                }
+            }
+        }
     }
 }
 
 /// Cleanup documentation decoration (`///` and such).
 ///
-/// We can't use `syntax::attr::AttributeMethods::with_desugared_doc` or
-/// `syntax::parse::lexer::comments::strip_doc_comment_decoration` because we
+/// We can't use `rustc_ast::attr::AttributeMethods::with_desugared_doc` or
+/// `rustc_ast::parse::lexer::comments::strip_doc_comment_decoration` because we
 /// need to keep track of
 /// the spans but this function is inspired from the later.
 #[allow(clippy::cast_possible_truncation)]
@@ -319,7 +350,7 @@ fn check_attrs<'a>(cx: &LateContext<'_, '_>, valid_idents: &FxHashSet<String>, a
     let parser = pulldown_cmark::Parser::new(&doc).into_offset_iter();
     // Iterate over all `Events` and combine consecutive events into one
     let events = parser.coalesce(|previous, current| {
-        use pulldown_cmark::Event::*;
+        use pulldown_cmark::Event::Text;
 
         let previous_range = previous.1;
         let current_range = current.1;
@@ -336,6 +367,8 @@ fn check_attrs<'a>(cx: &LateContext<'_, '_>, valid_idents: &FxHashSet<String>, a
     check_doc(cx, valid_idents, events, &spans)
 }
 
+const RUST_CODE: &[&str] = &["rust", "no_run", "should_panic", "compile_fail", "edition2018"];
+
 fn check_doc<'a, Events: Iterator<Item = (pulldown_cmark::Event<'a>, Range<usize>)>>(
     cx: &LateContext<'_, '_>,
     valid_idents: &FxHashSet<String>,
@@ -343,8 +376,11 @@ fn check_doc<'a, Events: Iterator<Item = (pulldown_cmark::Event<'a>, Range<usize
     spans: &[(usize, Span)],
 ) -> DocHeaders {
     // true if a safety header was found
-    use pulldown_cmark::Event::*;
-    use pulldown_cmark::Tag::*;
+    use pulldown_cmark::CodeBlockKind;
+    use pulldown_cmark::Event::{
+        Code, End, FootnoteReference, HardBreak, Html, Rule, SoftBreak, Start, TaskListMarker, Text,
+    };
+    use pulldown_cmark::Tag::{CodeBlock, Heading, Link};
 
     let mut headers = DocHeaders {
         safety: false,
@@ -353,11 +389,20 @@ fn check_doc<'a, Events: Iterator<Item = (pulldown_cmark::Event<'a>, Range<usize
     let mut in_code = false;
     let mut in_link = None;
     let mut in_heading = false;
-
+    let mut is_rust = false;
     for (event, range) in events {
         match event {
-            Start(CodeBlock(_)) => in_code = true,
-            End(CodeBlock(_)) => in_code = false,
+            Start(CodeBlock(ref kind)) => {
+                in_code = true;
+                if let CodeBlockKind::Fenced(lang) = kind {
+                    is_rust =
+                        lang.is_empty() || !lang.contains("ignore") && lang.split(',').any(|i| RUST_CODE.contains(&i));
+                }
+            },
+            End(CodeBlock(_)) => {
+                in_code = false;
+                is_rust = false;
+            },
             Start(Link(_, url, _)) => in_link = Some(url),
             End(Link(..)) => in_link = None,
             Start(Heading(_)) => in_heading = true,
@@ -380,7 +425,9 @@ fn check_doc<'a, Events: Iterator<Item = (pulldown_cmark::Event<'a>, Range<usize
                 };
                 let (begin, span) = spans[index];
                 if in_code {
-                    check_code(cx, &text, span);
+                    if is_rust {
+                        check_code(cx, &text, span);
+                    }
                 } else {
                     // Adjust for the beginning of the current `Event`
                     let span = span.with_lo(span.lo() + BytePos::from_usize(range.start - begin));
@@ -393,7 +440,7 @@ fn check_doc<'a, Events: Iterator<Item = (pulldown_cmark::Event<'a>, Range<usize
     headers
 }
 
-static LEAVE_MAIN_PATTERNS: &[&str] = &["static", "fn main() {}", "extern crate"];
+static LEAVE_MAIN_PATTERNS: &[&str] = &["static", "fn main() {}", "extern crate", "async fn main() {"];
 
 fn check_code(cx: &LateContext<'_, '_>, text: &str, span: Span) {
     if text.contains("fn main() {") && !LEAVE_MAIN_PATTERNS.iter().any(|p| text.contains(p)) {