-use crate::utils::span_lint;
+use crate::utils::{get_trait_def_id, implements_trait, is_entrypoint_fn, match_type, paths, return_ty, span_lint};
use itertools::Itertools;
-use pulldown_cmark;
-use rustc::hir;
-use rustc::impl_lint_pass;
-use rustc::lint::{LateContext, LateLintPass, LintArray, LintPass};
+use rustc::lint::in_external_macro;
+use rustc::ty::TyKind;
use rustc_data_structures::fx::FxHashSet;
-use rustc_session::declare_tool_lint;
+use rustc_hir as hir;
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_session::{declare_tool_lint, impl_lint_pass};
+use rustc_span::source_map::{BytePos, MultiSpan, Span};
+use rustc_span::Pos;
use std::ops::Range;
use syntax::ast::{AttrKind, Attribute};
-use syntax::source_map::{BytePos, Span};
-use syntax_pos::Pos;
use url::Url;
declare_clippy_lint! {
///
/// **Known problems:** None.
///
- /// **Examples**:
+ /// **Examples:**
/// ```rust
///# type Universe = ();
/// /// This function should really be documented
"`pub unsafe fn` without `# Safety` docs"
}
+declare_clippy_lint! {
+ /// **What it does:** Checks the doc comments of publicly visible functions that
+ /// return a `Result` type and warns if there is no `# Errors` section.
+ ///
+ /// **Why is this bad?** Documenting the type of errors that can be returned from a
+ /// function can help callers write code to handle the errors appropriately.
+ ///
+ /// **Known problems:** None.
+ ///
+ /// **Examples:**
+ ///
+ /// Since the following function returns a `Result` it has an `# Errors` section in
+ /// its doc comment:
+ ///
+ /// ```rust
+ ///# use std::io;
+ /// /// # Errors
+ /// ///
+ /// /// Will return `Err` if `filename` does not exist or the user does not have
+ /// /// permission to read it.
+ /// pub fn read(filename: String) -> io::Result<String> {
+ /// unimplemented!();
+ /// }
+ /// ```
+ pub MISSING_ERRORS_DOC,
+ pedantic,
+ "`pub fn` returns `Result` without `# Errors` in doc comment"
+}
+
declare_clippy_lint! {
/// **What it does:** Checks for `fn main() { .. }` in doctests
///
}
}
-impl_lint_pass!(DocMarkdown => [DOC_MARKDOWN, MISSING_SAFETY_DOC, NEEDLESS_DOCTEST_MAIN]);
+impl_lint_pass!(DocMarkdown => [DOC_MARKDOWN, MISSING_SAFETY_DOC, MISSING_ERRORS_DOC, NEEDLESS_DOCTEST_MAIN]);
impl<'a, 'tcx> LateLintPass<'a, 'tcx> for DocMarkdown {
- fn check_crate(&mut self, cx: &LateContext<'a, 'tcx>, krate: &'tcx hir::Crate) {
+ fn check_crate(&mut self, cx: &LateContext<'a, 'tcx>, krate: &'tcx hir::Crate<'_>) {
check_attrs(cx, &self.valid_idents, &krate.attrs);
}
- fn check_item(&mut self, cx: &LateContext<'a, 'tcx>, item: &'tcx hir::Item) {
- if check_attrs(cx, &self.valid_idents, &item.attrs) {
- return;
- }
- // no safety header
+ 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 cx.access_levels.is_exported(item.hir_id) && sig.header.unsafety == hir::Unsafety::Unsafe {
- span_lint(
- cx,
- MISSING_SAFETY_DOC,
- item.span,
- "unsafe function's docs miss `# Safety` section",
- );
+ 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);
}
},
- hir::ItemKind::Impl(_, _, _, _, ref trait_ref, ..) => {
+ hir::ItemKind::Impl {
+ of_trait: ref trait_ref,
+ ..
+ } => {
self.in_trait_impl = trait_ref.is_some();
},
_ => {},
}
}
- fn check_item_post(&mut self, _cx: &LateContext<'a, 'tcx>, item: &'tcx hir::Item) {
- if let hir::ItemKind::Impl(..) = item.kind {
+ fn check_item_post(&mut self, _cx: &LateContext<'a, 'tcx>, item: &'tcx hir::Item<'_>) {
+ 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) {
- if check_attrs(cx, &self.valid_idents, &item.attrs) {
- return;
- }
- // no safety header
+ 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 cx.access_levels.is_exported(item.hir_id) && sig.header.unsafety == hir::Unsafety::Unsafe {
- span_lint(
- cx,
- MISSING_SAFETY_DOC,
- item.span,
- "unsafe function's docs miss `# Safety` section",
- );
+ if !in_external_macro(cx.tcx.sess, item.span) {
+ lint_for_missing_headers(cx, item.hir_id, item.span, sig, headers);
}
}
}
- fn check_impl_item(&mut self, cx: &LateContext<'a, 'tcx>, item: &'tcx hir::ImplItem) {
- if check_attrs(cx, &self.valid_idents, &item.attrs) || self.in_trait_impl {
+ fn check_impl_item(&mut self, cx: &LateContext<'a, 'tcx>, item: &'tcx hir::ImplItem<'_>) {
+ let headers = check_attrs(cx, &self.valid_idents, &item.attrs);
+ if self.in_trait_impl || in_external_macro(cx.tcx.sess, item.span) {
return;
}
- // no safety header
if let hir::ImplItemKind::Method(ref sig, ..) = item.kind {
- if cx.access_levels.is_exported(item.hir_id) && sig.header.unsafety == hir::Unsafety::Unsafe {
- span_lint(
- cx,
- MISSING_SAFETY_DOC,
- item.span,
- "unsafe function's docs miss `# Safety` section",
- );
+ lint_for_missing_headers(cx, item.hir_id, item.span, sig, headers);
+ }
+ }
+}
+
+fn lint_for_missing_headers<'a, 'tcx>(
+ cx: &LateContext<'a, 'tcx>,
+ hir_id: hir::HirId,
+ span: impl Into<MultiSpan> + Copy,
+ sig: &hir::FnSig<'_>,
+ headers: DocHeaders,
+) {
+ if !cx.access_levels.is_exported(hir_id) {
+ return; // Private functions do not require doc comments
+ }
+ if !headers.safety && sig.header.unsafety == hir::Unsafety::Unsafe {
+ span_lint(
+ cx,
+ MISSING_SAFETY_DOC,
+ span,
+ "unsafe function's docs miss `# Safety` 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 {
+ let def_id = cx.tcx.hir().local_def_id(hir_id);
+ let mir = cx.tcx.optimized_mir(def_id);
+ if let Some(future) = get_trait_def_id(cx, &paths::FUTURE) {
+ if implements_trait(cx, mir.return_ty(), future, &[]) {
+ use TyKind::*;
+
+ if let Opaque(_, subs) = mir.return_ty().kind {
+ if let Some(ty) = subs.types().next() {
+ if let Generator(_, subs, _) = ty.kind {
+ if match_type(cx, subs.as_generator().return_ty(def_id, cx.tcx), &paths::RESULT) {
+ span_lint(
+ cx,
+ MISSING_ERRORS_DOC,
+ span,
+ "docs for function returning `Result` missing `# Errors` section",
+ );
+ }
+ }
+ }
+ }
+ }
}
}
}
panic!("not a doc-comment: {}", comment);
}
-pub fn check_attrs<'a>(cx: &LateContext<'_, '_>, valid_idents: &FxHashSet<String>, attrs: &'a [Attribute]) -> bool {
+#[derive(Copy, Clone)]
+struct DocHeaders {
+ safety: bool,
+ errors: bool,
+}
+
+fn check_attrs<'a>(cx: &LateContext<'_, '_>, valid_idents: &FxHashSet<String>, attrs: &'a [Attribute]) -> DocHeaders {
let mut doc = String::new();
let mut spans = vec![];
doc.push_str(&comment);
} else if attr.check_name(sym!(doc)) {
// ignore mix of sugared and non-sugared doc
- return true; // don't trigger the safety check
+ // don't trigger the safety or errors check
+ return DocHeaders {
+ safety: true,
+ errors: true,
+ };
}
}
}
if doc.is_empty() {
- return false;
+ return DocHeaders {
+ safety: false,
+ errors: false,
+ };
}
let parser = pulldown_cmark::Parser::new(&doc).into_offset_iter();
valid_idents: &FxHashSet<String>,
events: Events,
spans: &[(usize, Span)],
-) -> bool {
+) -> DocHeaders {
// true if a safety header was found
use pulldown_cmark::Event::*;
use pulldown_cmark::Tag::*;
- let mut safety_header = false;
+ let mut headers = DocHeaders {
+ safety: false,
+ errors: false,
+ };
let mut in_code = false;
let mut in_link = None;
let mut in_heading = false;
// text "http://example.com" by pulldown-cmark
continue;
}
- safety_header |= in_heading && text.trim() == "Safety";
+ headers.safety |= in_heading && text.trim() == "Safety";
+ headers.errors |= in_heading && text.trim() == "Errors";
let index = match spans.binary_search_by(|c| c.0.cmp(&range.start)) {
Ok(o) => o,
Err(e) => e - 1,
},
}
}
- safety_header
+ headers
}
+static LEAVE_MAIN_PATTERNS: &[&str] = &["static", "fn main() {}", "extern crate"];
+
fn check_code(cx: &LateContext<'_, '_>, text: &str, span: Span) {
- if text.contains("fn main() {") && !(text.contains("static") || text.contains("fn main() {}")) {
+ if text.contains("fn main() {") && !LEAVE_MAIN_PATTERNS.iter().any(|p| text.contains(p)) {
span_lint(cx, NEEDLESS_DOCTEST_MAIN, span, "needless `fn main` in doctest");
}
}