Skip to content

Commit 562200c

Browse files
committed
check for unbalanced tick pairs in doc-markdown
1 parent 6bf8772 commit 562200c

File tree

4 files changed

+85
-13
lines changed

4 files changed

+85
-13
lines changed

clippy_lints/src/doc.rs

+43-11
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
use clippy_utils::diagnostics::{span_lint, span_lint_and_note};
2+
use clippy_utils::source::first_line_of_span;
23
use clippy_utils::ty::{implements_trait, is_type_diagnostic_item};
34
use clippy_utils::{is_entrypoint_fn, is_expn_of, match_panic_def_id, method_chain_args, return_ty};
45
use if_chain::if_chain;
@@ -37,7 +38,7 @@ declare_clippy_lint! {
3738
/// consider that.
3839
///
3940
/// **Known problems:** Lots of bad docs won’t be fixed, what the lint checks
40-
/// for is limited, and there are still false positives.
41+
/// for is limited, and there are still false positives. HTML is not linted.
4142
///
4243
/// In addition, when writing documentation comments, including `[]` brackets
4344
/// inside a link text would trip the parser. Therfore, documenting link with
@@ -469,11 +470,11 @@ fn check_doc<'a, Events: Iterator<Item = (pulldown_cmark::Event<'a>, Range<usize
469470
spans: &[(usize, Span)],
470471
) -> DocHeaders {
471472
// true if a safety header was found
472-
use pulldown_cmark::CodeBlockKind;
473473
use pulldown_cmark::Event::{
474474
Code, End, FootnoteReference, HardBreak, Html, Rule, SoftBreak, Start, TaskListMarker, Text,
475475
};
476-
use pulldown_cmark::Tag::{CodeBlock, Heading, Link};
476+
use pulldown_cmark::Tag::{CodeBlock, Heading, Link, Paragraph};
477+
use pulldown_cmark::{CodeBlockKind, CowStr};
477478

478479
let mut headers = DocHeaders {
479480
safety: false,
@@ -485,6 +486,9 @@ fn check_doc<'a, Events: Iterator<Item = (pulldown_cmark::Event<'a>, Range<usize
485486
let mut in_heading = false;
486487
let mut is_rust = false;
487488
let mut edition = None;
489+
let mut ticks_unbalanced = false;
490+
let mut text_to_check: Vec<(CowStr<'_>, Span)> = Vec::new();
491+
let mut paragraph_span = spans[0].1; // First line
488492
for (event, range) in events {
489493
match event {
490494
Start(CodeBlock(ref kind)) => {
@@ -512,24 +516,45 @@ fn check_doc<'a, Events: Iterator<Item = (pulldown_cmark::Event<'a>, Range<usize
512516
End(Link(..)) => in_link = None,
513517
Start(Heading(_)) => in_heading = true,
514518
End(Heading(_)) => in_heading = false,
519+
Start(Paragraph) => {
520+
ticks_unbalanced = false;
521+
let (_, span) = get_current_span(spans, range.start);
522+
paragraph_span = first_line_of_span(cx, span);
523+
},
524+
End(Paragraph) => {
525+
if ticks_unbalanced {
526+
span_lint(
527+
cx,
528+
DOC_MARKDOWN,
529+
paragraph_span,
530+
"backticks are unbalanced; one may be missing a pair",
531+
);
532+
} else {
533+
for (text, span) in text_to_check {
534+
check_text(cx, valid_idents, &text, span);
535+
}
536+
}
537+
text_to_check = Vec::new();
538+
},
515539
Start(_tag) | End(_tag) => (), // We don't care about other tags
516540
Html(_html) => (), // HTML is weird, just ignore it
517541
SoftBreak | HardBreak | TaskListMarker(_) | Code(_) | Rule => (),
518542
FootnoteReference(text) | Text(text) => {
519-
if Some(&text) == in_link.as_ref() {
543+
let (begin, span) = get_current_span(spans, range.start);
544+
paragraph_span = paragraph_span.with_hi(span.hi());
545+
if Some(&text) == in_link.as_ref() || ticks_unbalanced {
520546
// Probably a link of the form `<http://example.com>`
521547
// Which are represented as a link to "http://example.com" with
522548
// text "http://example.com" by pulldown-cmark
523549
continue;
524550
}
551+
if text.contains('`') && !in_code {
552+
ticks_unbalanced = true;
553+
continue;
554+
}
525555
headers.safety |= in_heading && text.trim() == "Safety";
526556
headers.errors |= in_heading && text.trim() == "Errors";
527557
headers.panics |= in_heading && text.trim() == "Panics";
528-
let index = match spans.binary_search_by(|c| c.0.cmp(&range.start)) {
529-
Ok(o) => o,
530-
Err(e) => e - 1,
531-
};
532-
let (begin, span) = spans[index];
533558
if in_code {
534559
if is_rust {
535560
let edition = edition.unwrap_or_else(|| cx.tcx.sess.edition());
@@ -538,15 +563,22 @@ fn check_doc<'a, Events: Iterator<Item = (pulldown_cmark::Event<'a>, Range<usize
538563
} else {
539564
// Adjust for the beginning of the current `Event`
540565
let span = span.with_lo(span.lo() + BytePos::from_usize(range.start - begin));
541-
542-
check_text(cx, valid_idents, &text, span);
566+
text_to_check.push((text, span));
543567
}
544568
},
545569
}
546570
}
547571
headers
548572
}
549573

574+
fn get_current_span(spans: &[(usize, Span)], idx: usize) -> (usize, Span) {
575+
let index = match spans.binary_search_by(|c| c.0.cmp(&idx)) {
576+
Ok(o) => o,
577+
Err(e) => e - 1,
578+
};
579+
spans[index]
580+
}
581+
550582
fn check_code(cx: &LateContext<'_>, text: &str, edition: Edition, span: Span) {
551583
fn has_needless_main(code: &str, edition: Edition) -> bool {
552584
rustc_driver::catch_fatal_errors(|| {

clippy_lints/src/if_let_mutex.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ impl<'tcx> LateLintPass<'tcx> for IfLetMutex {
8383
}
8484
}
8585

86-
/// Checks if `Mutex::lock` is called in the `if let _ = expr.
86+
/// Checks if `Mutex::lock` is called in the `if let` expr.
8787
pub struct OppVisitor<'a, 'tcx> {
8888
mutex_lock_called: bool,
8989
found_mutex: Option<&'tcx Expr<'tcx>>,

tests/ui/doc.rs

+18
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,24 @@ fn issue_2343() {}
203203
/// __|_ _|__||_|
204204
fn pulldown_cmark_crash() {}
205205

206+
/// This is a doc comment with `unbalanced_tick marks and several words that
207+
/// should be `encompassed_by` tick marks because they `contain_underscores`.
208+
/// Because of the initial `unbalanced_tick` pair, the error message is
209+
/// very `confusing_and_misleading`.
210+
fn issue_6753() {}
211+
212+
/// This paragraph has `unbalanced_tick marks and should
213+
/// throw an error.
214+
///
215+
/// This paragraph is fine and should_be linted normally.
216+
fn unbalanced_ticks() {}
217+
218+
/// ```
219+
/// // Unbalanced tick mark in code block shouldn't warn:
220+
/// `
221+
/// ```
222+
fn unbalanced_ticks_in_code() {}
223+
206224
// issue #7033 - const_evaluatable_checked ICE
207225
struct S<T, const N: usize>
208226
where [(); N.checked_next_power_of_two().unwrap()]: {

tests/ui/doc.stderr

+23-1
Original file line numberDiff line numberDiff line change
@@ -186,5 +186,27 @@ error: you should put `mycrate::Collection` between ticks in the documentation
186186
LL | /// An iterator over mycrate::Collection's values.
187187
| ^^^^^^^^^^^^^^^^^^^
188188

189-
error: aborting due to 31 previous errors
189+
error: backticks are unbalanced; one may be missing a pair
190+
--> $DIR/doc.rs:206:1
191+
|
192+
LL | / /// This is a doc comment with `unbalanced_tick marks and several words that
193+
LL | | /// should be `encompassed_by` tick marks because they `contain_underscores`.
194+
LL | | /// Because of the initial `unbalanced_tick` pair, the error message is
195+
LL | | /// very `confusing_and_misleading`.
196+
| |____________________________________^
197+
198+
error: backticks are unbalanced; one may be missing a pair
199+
--> $DIR/doc.rs:212:1
200+
|
201+
LL | / /// This paragraph has `unbalanced_tick marks and should
202+
LL | | /// throw an error.
203+
| |___________________^
204+
205+
error: you should put `should_be` between ticks in the documentation
206+
--> $DIR/doc.rs:215:32
207+
|
208+
LL | /// This paragraph is fine and should_be linted normally.
209+
| ^^^^^^^^^
210+
211+
error: aborting due to 34 previous errors
190212

0 commit comments

Comments
 (0)