Skip to content

Commit 0617c0d

Browse files
committed
Add lint for markdown lazy paragraph continuations
This is a follow-up for rust-lang/rust#121659, since most cases of unintended block quotes are lazy continuations. The lint is designed to be more generally useful than that, though, because it will also catch unintended list items and unintended block quotes that didn't coincidentally hit a pulldown-cmark bug.
1 parent 6e4dda8 commit 0617c0d

8 files changed

+101
-62
lines changed

clippy_lints/src/doc/lazy_continuation.rs

Lines changed: 26 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,13 @@ fn map_container_to_text(c: &super::Container) -> &'static str {
1616
}
1717

1818
// TODO: Adjust the parameters as necessary
19-
pub(super) fn check(cx: &LateContext<'_>, doc: &str, range: Range<usize>, mut span: Span, containers: &[super::Container]) {
19+
pub(super) fn check(
20+
cx: &LateContext<'_>,
21+
doc: &str,
22+
range: Range<usize>,
23+
mut span: Span,
24+
containers: &[super::Container],
25+
) {
2026
if doc[range.clone()].contains('\t') {
2127
// We don't do tab stops correctly.
2228
return;
@@ -30,10 +36,20 @@ pub(super) fn check(cx: &LateContext<'_>, doc: &str, range: Range<usize>, mut sp
3036
let lcount = doc[range.clone()].chars().filter(|c| *c == ' ').count();
3137
let list_indentation = containers
3238
.iter()
33-
.map(|c| if let super::Container::List(indent) = c { *indent } else { 0 })
39+
.map(|c| {
40+
if let super::Container::List(indent) = c {
41+
*indent
42+
} else {
43+
0
44+
}
45+
})
3446
.sum();
3547
if ccount < blockquote_level || lcount < list_indentation {
36-
let msg = if ccount < blockquote_level { "doc quote missing `>` marker" } else { "doc list item missing indentation" };
48+
let msg = if ccount < blockquote_level {
49+
"doc quote missing `>` marker"
50+
} else {
51+
"doc list item missing indentation"
52+
};
3753
span_lint_and_then(cx, DOC_LAZY_CONTINUATION, span, msg, |diag| {
3854
if ccount == 0 && blockquote_level == 0 {
3955
// simpler suggestion style for indentation
@@ -54,10 +70,14 @@ pub(super) fn check(cx: &LateContext<'_>, doc: &str, range: Range<usize>, mut sp
5470
let text = map_container_to_text(c);
5571
if doc_start_range.starts_with(text) {
5672
doc_start_range = &doc_start_range[text.len()..];
57-
span = span.with_lo(span.lo() + BytePos(u32::try_from(text.len()).expect("text is not 2**32 or bigger")));
58-
} else if matches!(c, super::Container::Blockquote) && let Some(i) = doc_start_range.find('>') {
73+
span = span
74+
.with_lo(span.lo() + BytePos(u32::try_from(text.len()).expect("text is not 2**32 or bigger")));
75+
} else if matches!(c, super::Container::Blockquote)
76+
&& let Some(i) = doc_start_range.find('>')
77+
{
5978
doc_start_range = &doc_start_range[i + 1..];
60-
span = span.with_lo(span.lo() + BytePos(u32::try_from(i).expect("text is not 2**32 or bigger") + 1));
79+
span =
80+
span.with_lo(span.lo() + BytePos(u32::try_from(i).expect("text is not 2**32 or bigger") + 1));
6181
} else {
6282
suggested.push_str(text);
6383
}

clippy_lints/src/doc/mod.rs

Lines changed: 53 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -366,20 +366,56 @@ declare_clippy_lint! {
366366
declare_clippy_lint! {
367367
/// ### What it does
368368
///
369+
/// In CommonMark Markdown, the language used to write doc comments, a
370+
/// paragraph nested within a list or block quote does not need any line
371+
/// after the first one to be indented or marked. The specification calls
372+
/// this a "lazy paragraph continuation."
373+
///
369374
/// ### Why is this bad?
370375
///
376+
/// This is easy to write but hard to read. Lazy continuations makes
377+
/// unintended markers hard to see, and make it harder to deduce the
378+
/// document's intended structure.
379+
///
371380
/// ### Example
381+
///
382+
/// This table is probably intended to have two rows,
383+
/// but it does not. It has zero rows, and is followed by
384+
/// a block quote.
372385
/// ```no_run
373-
/// // example code where clippy issues a warning
386+
/// /// Range | Description
387+
/// /// ----- | -----------
388+
/// /// >= 1 | fully opaque
389+
/// /// < 1 | partially see-through
390+
/// fn set_opacity(opacity: f32) {}
374391
/// ```
375-
/// Use instead:
392+
///
393+
/// Fix it by escaping the marker:
394+
/// ```no_run
395+
/// /// Range | Description
396+
/// /// ----- | -----------
397+
/// /// \>= 1 | fully opaque
398+
/// /// < 1 | partially see-through
399+
/// fn set_opacity(opacity: f32) {}
400+
/// ```
401+
///
402+
/// This example is actually intended to be a list:
403+
/// ```no_run
404+
/// /// * Do nothing.
405+
/// /// * Then do something. Whatever it is needs done,
406+
/// /// it should be done right now.
407+
/// ```
408+
///
409+
/// Fix it by indenting the list contents:
376410
/// ```no_run
377-
/// // example code which does not raise clippy warning
411+
/// /// * Do nothing.
412+
/// /// * Then do something. Whatever it is needs done,
413+
/// /// it should be done right now.
378414
/// ```
379415
#[clippy::version = "1.80.0"]
380416
pub DOC_LAZY_CONTINUATION,
381417
style,
382-
"default lint description"
418+
"require every line of a paragraph to be indented and marked"
383419
}
384420

385421
#[derive(Clone)]
@@ -711,20 +747,19 @@ fn check_doc<'a, Events: Iterator<Item = (pulldown_cmark::Event<'a>, Range<usize
711747
End(FootnoteDefinition(..)) => in_footnote_definition = false,
712748
Start(_tag) | End(_tag) => (), // We don't care about other tags
713749
SoftBreak | HardBreak => {
714-
if !containers.is_empty() {
715-
if let Some((_next_event, next_range)) = events.peek()
716-
&& let Some(next_span) = fragments.span(cx, next_range.clone())
717-
&& let Some(span) = fragments.span(cx, range.clone())
718-
&& !in_footnote_definition // not implemented
719-
{
720-
lazy_continuation::check(
721-
cx,
722-
doc,
723-
range.end..next_range.start,
724-
Span::new(span.hi(), next_span.lo(), span.ctxt(), span.parent()),
725-
&containers[..],
726-
);
727-
}
750+
if !containers.is_empty()
751+
&& let Some((_next_event, next_range)) = events.peek()
752+
&& let Some(next_span) = fragments.span(cx, next_range.clone())
753+
&& let Some(span) = fragments.span(cx, range.clone())
754+
&& !in_footnote_definition
755+
{
756+
lazy_continuation::check(
757+
cx,
758+
doc,
759+
range.end..next_range.start,
760+
Span::new(span.hi(), next_span.lo(), span.ctxt(), span.parent()),
761+
&containers[..],
762+
);
728763
}
729764
},
730765
TaskListMarker(_) | Code(_) | Rule => (),

tests/ui/doc/doc_lazy_blockquote.fixed

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,9 +38,7 @@ fn four() {}
3838
//~^ ERROR: doc quote missing `>` marker
3939
fn four_point_1() {}
4040

41-
/// * > nest here
42-
/// > lazy continuation
43-
//~^ ERROR: doc quote missing `>` marker
41+
/// * > nest here lazy continuation
4442
fn five() {}
4543

4644
/// 1. > nest here

tests/ui/doc/doc_lazy_blockquote.rs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,9 +38,7 @@ fn four() {}
3838
//~^ ERROR: doc quote missing `>` marker
3939
fn four_point_1() {}
4040

41-
/// * > nest here
42-
/// lazy continuation
43-
//~^ ERROR: doc quote missing `>` marker
41+
/// * > nest here lazy continuation
4442
fn five() {}
4543

4644
/// 1. > nest here

tests/ui/doc/doc_lazy_blockquote.stderr

Lines changed: 2 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -61,19 +61,7 @@ LL | /// > > lazy continuation
6161
| +++++
6262

6363
error: doc quote missing `>` marker
64-
--> tests/ui/doc/doc_lazy_blockquote.rs:42:5
65-
|
66-
LL | /// lazy continuation
67-
| ^^
68-
|
69-
= help: if this not intended to be a quote at all, escape it with `\>`
70-
help: add markers to start of line
71-
|
72-
LL | /// > lazy continuation
73-
| +
74-
75-
error: doc quote missing `>` marker
76-
--> tests/ui/doc/doc_lazy_blockquote.rs:47:5
64+
--> tests/ui/doc/doc_lazy_blockquote.rs:45:5
7765
|
7866
LL | /// lazy continuation (this results in strange indentation, but still works)
7967
| ^
@@ -84,5 +72,5 @@ help: add markers to start of line
8472
LL | /// > lazy continuation (this results in strange indentation, but still works)
8573
| +
8674

87-
error: aborting due to 7 previous errors
75+
error: aborting due to 6 previous errors
8876

tests/ui/doc/doc_lazy_list.fixed

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ fn one() {}
88
/// 1. first line
99
/// lazy list continuations don't make warnings with this lint
1010
//~^ ERROR: doc list item missing indentation
11-
/// because they don't have the
11+
/// because they don't have the
1212
//~^ ERROR: doc list item missing indentation
1313
fn two() {}
1414

@@ -20,7 +20,7 @@ fn three() {}
2020
/// - first line
2121
/// lazy list continuations don't make warnings with this lint
2222
//~^ ERROR: doc list item missing indentation
23-
/// because they don't have the
23+
/// because they don't have the
2424
//~^ ERROR: doc list item missing indentation
2525
fn four() {}
2626

@@ -29,14 +29,14 @@ fn four() {}
2929
//~^ ERROR: doc list item missing indentation
3030
fn five() {}
3131

32-
/// - - first line
33-
/// this will warn on the lazy continuation
32+
/// - - first line
33+
/// this will warn on the lazy continuation
3434
//~^ ERROR: doc list item missing indentation
35-
/// and so should this
35+
/// and so should this
3636
//~^ ERROR: doc list item missing indentation
3737
fn six() {}
3838

39-
/// - - first line
39+
/// - - first line
4040
///
4141
/// this is not a lazy continuation
4242
fn seven() {}

tests/ui/doc/doc_lazy_list.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ fn one() {}
88
/// 1. first line
99
/// lazy list continuations don't make warnings with this lint
1010
//~^ ERROR: doc list item missing indentation
11-
/// because they don't have the
11+
/// because they don't have the
1212
//~^ ERROR: doc list item missing indentation
1313
fn two() {}
1414

@@ -20,7 +20,7 @@ fn three() {}
2020
/// - first line
2121
/// lazy list continuations don't make warnings with this lint
2222
//~^ ERROR: doc list item missing indentation
23-
/// because they don't have the
23+
/// because they don't have the
2424
//~^ ERROR: doc list item missing indentation
2525
fn four() {}
2626

@@ -29,14 +29,14 @@ fn four() {}
2929
//~^ ERROR: doc list item missing indentation
3030
fn five() {}
3131

32-
/// - - first line
32+
/// - - first line
3333
/// this will warn on the lazy continuation
3434
//~^ ERROR: doc list item missing indentation
3535
/// and so should this
3636
//~^ ERROR: doc list item missing indentation
3737
fn six() {}
3838

39-
/// - - first line
39+
/// - - first line
4040
///
4141
/// this is not a lazy continuation
4242
fn seven() {}

tests/ui/doc/doc_lazy_list.stderr

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -27,13 +27,13 @@ LL | /// lazy list continuations don't make warnings with this lint
2727
error: doc list item missing indentation
2828
--> tests/ui/doc/doc_lazy_list.rs:11:5
2929
|
30-
LL | /// because they don't have the
30+
LL | /// because they don't have the
3131
| ^
3232
|
3333
= help: if this is supposed to be its own paragraph, add a blank line
3434
help: indent this line
3535
|
36-
LL | /// because they don't have the
36+
LL | /// because they don't have the
3737
| +++
3838

3939
error: doc list item missing indentation
@@ -63,13 +63,13 @@ LL | /// lazy list continuations don't make warnings with this lint
6363
error: doc list item missing indentation
6464
--> tests/ui/doc/doc_lazy_list.rs:23:5
6565
|
66-
LL | /// because they don't have the
66+
LL | /// because they don't have the
6767
| ^
6868
|
6969
= help: if this is supposed to be its own paragraph, add a blank line
7070
help: indent this line
7171
|
72-
LL | /// because they don't have the
72+
LL | /// because they don't have the
7373
| ++++
7474

7575
error: doc list item missing indentation
@@ -93,8 +93,8 @@ LL | /// this will warn on the lazy continuation
9393
= help: if this is supposed to be its own paragraph, add a blank line
9494
help: indent this line
9595
|
96-
LL | /// this will warn on the lazy continuation
97-
| ++++++++
96+
LL | /// this will warn on the lazy continuation
97+
| ++++++
9898

9999
error: doc list item missing indentation
100100
--> tests/ui/doc/doc_lazy_list.rs:35:5
@@ -105,8 +105,8 @@ LL | /// and so should this
105105
= help: if this is supposed to be its own paragraph, add a blank line
106106
help: indent this line
107107
|
108-
LL | /// and so should this
109-
| ++++
108+
LL | /// and so should this
109+
| ++
110110

111111
error: aborting due to 9 previous errors
112112

0 commit comments

Comments
 (0)