about summary refs log tree commit diff
diff options
context:
space:
mode:
authorbors <bors@rust-lang.org>2021-06-21 17:15:12 +0000
committerbors <bors@rust-lang.org>2021-06-21 17:15:12 +0000
commit48fa1dc8bb843aaa7b912beed1666ca9c90f5363 (patch)
tree9714d9d9debe8a1380ec10de8196486a31fb44aa
parenta8b374fefb9056ace1c23944805f6a05061e597d (diff)
parent20cb1bc7c1a381a702f4b81511ad4e92df69c34c (diff)
downloadrust-48fa1dc8bb843aaa7b912beed1666ca9c90f5363.tar.gz
rust-48fa1dc8bb843aaa7b912beed1666ca9c90f5363.zip
Auto merge of #7357 - ebobrow:unbalanced-tick, r=xFrednet,flip1995
check for unbalanced tick pairs in doc-markdown lint

fixes #6753

changelog: check for unbalanced tick pairs in doc-markdown lint
-rw-r--r--clippy_lints/src/doc.rs64
-rw-r--r--clippy_lints/src/if_let_mutex.rs2
-rw-r--r--tests/ui/doc/doc.rs (renamed from tests/ui/doc.rs)0
-rw-r--r--tests/ui/doc/doc.stderr (renamed from tests/ui/doc.stderr)0
-rw-r--r--tests/ui/doc/unbalanced_ticks.rs36
-rw-r--r--tests/ui/doc/unbalanced_ticks.stderr64
6 files changed, 151 insertions, 15 deletions
diff --git a/clippy_lints/src/doc.rs b/clippy_lints/src/doc.rs
index e67ec4e06c5..4e164d33a05 100644
--- a/clippy_lints/src/doc.rs
+++ b/clippy_lints/src/doc.rs
@@ -1,4 +1,5 @@
-use clippy_utils::diagnostics::{span_lint, span_lint_and_note};
+use clippy_utils::diagnostics::{span_lint, span_lint_and_help, span_lint_and_note};
+use clippy_utils::source::first_line_of_span;
 use clippy_utils::ty::{implements_trait, is_type_diagnostic_item};
 use clippy_utils::{is_entrypoint_fn, is_expn_of, match_panic_def_id, method_chain_args, return_ty};
 use if_chain::if_chain;
@@ -37,7 +38,8 @@ declare_clippy_lint! {
     /// consider that.
     ///
     /// **Known problems:** Lots of bad docs won’t be fixed, what the lint checks
-    /// for is limited, and there are still false positives.
+    /// for is limited, and there are still false positives. HTML elements and their
+    /// content are not linted.
     ///
     /// In addition, when writing documentation comments, including `[]` brackets
     /// inside a link text would trip the parser. Therfore, documenting link with
@@ -469,11 +471,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::CodeBlockKind;
     use pulldown_cmark::Event::{
         Code, End, FootnoteReference, HardBreak, Html, Rule, SoftBreak, Start, TaskListMarker, Text,
     };
-    use pulldown_cmark::Tag::{CodeBlock, Heading, Link};
+    use pulldown_cmark::Tag::{CodeBlock, Heading, Item, Link, Paragraph};
+    use pulldown_cmark::{CodeBlockKind, CowStr};
 
     let mut headers = DocHeaders {
         safety: false,
@@ -485,6 +487,9 @@ fn check_doc<'a, Events: Iterator<Item = (pulldown_cmark::Event<'a>, Range<usize
     let mut in_heading = false;
     let mut is_rust = false;
     let mut edition = None;
+    let mut ticks_unbalanced = false;
+    let mut text_to_check: Vec<(CowStr<'_>, Span)> = Vec::new();
+    let mut paragraph_span = spans.get(0).expect("function isn't called if doc comment is empty").1;
     for (event, range) in events {
         match event {
             Start(CodeBlock(ref kind)) => {
@@ -510,13 +515,42 @@ fn check_doc<'a, Events: Iterator<Item = (pulldown_cmark::Event<'a>, Range<usize
             },
             Start(Link(_, url, _)) => in_link = Some(url),
             End(Link(..)) => in_link = None,
-            Start(Heading(_)) => in_heading = true,
-            End(Heading(_)) => in_heading = false,
+            Start(Heading(_) | Paragraph | Item) => {
+                if let Start(Heading(_)) = event {
+                    in_heading = true;
+                }
+                ticks_unbalanced = false;
+                let (_, span) = get_current_span(spans, range.start);
+                paragraph_span = first_line_of_span(cx, span);
+            },
+            End(Heading(_) | Paragraph | Item) => {
+                if let End(Heading(_)) = event {
+                    in_heading = false;
+                }
+                if ticks_unbalanced {
+                    span_lint_and_help(
+                        cx,
+                        DOC_MARKDOWN,
+                        paragraph_span,
+                        "backticks are unbalanced",
+                        None,
+                        "a backtick may be missing a pair",
+                    );
+                } else {
+                    for (text, span) in text_to_check {
+                        check_text(cx, valid_idents, &text, span);
+                    }
+                }
+                text_to_check = Vec::new();
+            },
             Start(_tag) | End(_tag) => (), // We don't care about other tags
             Html(_html) => (),             // HTML is weird, just ignore it
             SoftBreak | HardBreak | TaskListMarker(_) | Code(_) | Rule => (),
             FootnoteReference(text) | Text(text) => {
-                if Some(&text) == in_link.as_ref() {
+                let (begin, span) = get_current_span(spans, range.start);
+                paragraph_span = paragraph_span.with_hi(span.hi());
+                ticks_unbalanced |= text.contains('`');
+                if Some(&text) == in_link.as_ref() || ticks_unbalanced {
                     // Probably a link of the form `<http://example.com>`
                     // Which are represented as a link to "http://example.com" with
                     // text "http://example.com" by pulldown-cmark
@@ -525,11 +559,6 @@ fn check_doc<'a, Events: Iterator<Item = (pulldown_cmark::Event<'a>, Range<usize
                 headers.safety |= in_heading && text.trim() == "Safety";
                 headers.errors |= in_heading && text.trim() == "Errors";
                 headers.panics |= in_heading && text.trim() == "Panics";
-                let index = match spans.binary_search_by(|c| c.0.cmp(&range.start)) {
-                    Ok(o) => o,
-                    Err(e) => e - 1,
-                };
-                let (begin, span) = spans[index];
                 if in_code {
                     if is_rust {
                         let edition = edition.unwrap_or_else(|| cx.tcx.sess.edition());
@@ -538,8 +567,7 @@ fn check_doc<'a, Events: Iterator<Item = (pulldown_cmark::Event<'a>, Range<usize
                 } else {
                     // Adjust for the beginning of the current `Event`
                     let span = span.with_lo(span.lo() + BytePos::from_usize(range.start - begin));
-
-                    check_text(cx, valid_idents, &text, span);
+                    text_to_check.push((text, span));
                 }
             },
         }
@@ -547,6 +575,14 @@ fn check_doc<'a, Events: Iterator<Item = (pulldown_cmark::Event<'a>, Range<usize
     headers
 }
 
+fn get_current_span(spans: &[(usize, Span)], idx: usize) -> (usize, Span) {
+    let index = match spans.binary_search_by(|c| c.0.cmp(&idx)) {
+        Ok(o) => o,
+        Err(e) => e - 1,
+    };
+    spans[index]
+}
+
 fn check_code(cx: &LateContext<'_>, text: &str, edition: Edition, span: Span) {
     fn has_needless_main(code: &str, edition: Edition) -> bool {
         rustc_driver::catch_fatal_errors(|| {
diff --git a/clippy_lints/src/if_let_mutex.rs b/clippy_lints/src/if_let_mutex.rs
index f661f7ede82..5403d76ea30 100644
--- a/clippy_lints/src/if_let_mutex.rs
+++ b/clippy_lints/src/if_let_mutex.rs
@@ -83,7 +83,7 @@ impl<'tcx> LateLintPass<'tcx> for IfLetMutex {
     }
 }
 
-/// Checks if `Mutex::lock` is called in the `if let _ = expr.
+/// Checks if `Mutex::lock` is called in the `if let` expr.
 pub struct OppVisitor<'a, 'tcx> {
     mutex_lock_called: bool,
     found_mutex: Option<&'tcx Expr<'tcx>>,
diff --git a/tests/ui/doc.rs b/tests/ui/doc/doc.rs
index 8afef6b23d4..8afef6b23d4 100644
--- a/tests/ui/doc.rs
+++ b/tests/ui/doc/doc.rs
diff --git a/tests/ui/doc.stderr b/tests/ui/doc/doc.stderr
index 7eab8a85f09..7eab8a85f09 100644
--- a/tests/ui/doc.stderr
+++ b/tests/ui/doc/doc.stderr
diff --git a/tests/ui/doc/unbalanced_ticks.rs b/tests/ui/doc/unbalanced_ticks.rs
new file mode 100644
index 00000000000..78e87bc6906
--- /dev/null
+++ b/tests/ui/doc/unbalanced_ticks.rs
@@ -0,0 +1,36 @@
+//! This file tests for the `DOC_MARKDOWN` lint, specifically cases
+//! where ticks are unbalanced (see issue #6753).
+
+#![allow(dead_code)]
+#![warn(clippy::doc_markdown)]
+
+/// This is a doc comment with `unbalanced_tick marks and several words that
+/// should be `encompassed_by` tick marks because they `contain_underscores`.
+/// Because of the initial `unbalanced_tick` pair, the error message is
+/// very `confusing_and_misleading`.
+fn main() {}
+
+/// This paragraph has `unbalanced_tick marks and should stop_linting.
+///
+/// This paragraph is fine and should_be linted normally.
+///
+/// Double unbalanced backtick from ``here to here` should lint.
+///
+/// Double balanced back ticks ``start end`` is fine.
+fn multiple_paragraphs() {}
+
+/// ```
+/// // Unbalanced tick mark in code block shouldn't warn:
+/// `
+/// ```
+fn in_code_block() {}
+
+/// # `Fine`
+///
+/// ## not_fine
+///
+/// ### `unbalanced
+///
+/// - This `item has unbalanced tick marks
+/// - This item needs backticks_here
+fn other_markdown() {}
diff --git a/tests/ui/doc/unbalanced_ticks.stderr b/tests/ui/doc/unbalanced_ticks.stderr
new file mode 100644
index 00000000000..45ca34e2a8c
--- /dev/null
+++ b/tests/ui/doc/unbalanced_ticks.stderr
@@ -0,0 +1,64 @@
+error: backticks are unbalanced
+  --> $DIR/unbalanced_ticks.rs:7:1
+   |
+LL | / /// This is a doc comment with `unbalanced_tick marks and several words that
+LL | | /// should be `encompassed_by` tick marks because they `contain_underscores`.
+LL | | /// Because of the initial `unbalanced_tick` pair, the error message is
+LL | | /// very `confusing_and_misleading`.
+   | |____________________________________^
+   |
+   = note: `-D clippy::doc-markdown` implied by `-D warnings`
+   = help: a backtick may be missing a pair
+
+error: backticks are unbalanced
+  --> $DIR/unbalanced_ticks.rs:13:1
+   |
+LL | /// This paragraph has `unbalanced_tick marks and should stop_linting.
+   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   |
+   = help: a backtick may be missing a pair
+
+error: you should put `should_be` between ticks in the documentation
+  --> $DIR/unbalanced_ticks.rs:15:32
+   |
+LL | /// This paragraph is fine and should_be linted normally.
+   |                                ^^^^^^^^^
+
+error: backticks are unbalanced
+  --> $DIR/unbalanced_ticks.rs:17:1
+   |
+LL | /// Double unbalanced backtick from ``here to here` should lint.
+   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   |
+   = help: a backtick may be missing a pair
+
+error: you should put `not_fine` between ticks in the documentation
+  --> $DIR/unbalanced_ticks.rs:30:8
+   |
+LL | /// ## not_fine
+   |        ^^^^^^^^
+
+error: backticks are unbalanced
+  --> $DIR/unbalanced_ticks.rs:32:1
+   |
+LL | /// ### `unbalanced
+   | ^^^^^^^^^^^^^^^^^^^
+   |
+   = help: a backtick may be missing a pair
+
+error: backticks are unbalanced
+  --> $DIR/unbalanced_ticks.rs:34:1
+   |
+LL | /// - This `item has unbalanced tick marks
+   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   |
+   = help: a backtick may be missing a pair
+
+error: you should put `backticks_here` between ticks in the documentation
+  --> $DIR/unbalanced_ticks.rs:35:23
+   |
+LL | /// - This item needs backticks_here
+   |                       ^^^^^^^^^^^^^^
+
+error: aborting due to 8 previous errors
+