Skip to content

Commit 4693d0a

Browse files
committed
Add new lint doc_overindented_list_items
Add a new lint `doc_overindented_list_items` to detect and fix list items in docs that are overindented. For example, ```rs /// - first line /// second line fn foo() {} ``` this would be fixed to: ```rs /// - first line /// second line fn foo() {} ``` This lint improves readabiliy and consistency in doc.
1 parent 66dc8a1 commit 4693d0a

11 files changed

+219
-52
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -5533,6 +5533,7 @@ Released 2018-09-13
55335533
[`doc_link_with_quotes`]: https://rust-lang.github.io/rust-clippy/master/index.html#doc_link_with_quotes
55345534
[`doc_markdown`]: https://rust-lang.github.io/rust-clippy/master/index.html#doc_markdown
55355535
[`doc_nested_refdefs`]: https://rust-lang.github.io/rust-clippy/master/index.html#doc_nested_refdefs
5536+
[`doc_overindented_list_items`]: https://rust-lang.github.io/rust-clippy/master/index.html#doc_overindented_list_items
55365537
[`double_comparisons`]: https://rust-lang.github.io/rust-clippy/master/index.html#double_comparisons
55375538
[`double_ended_iterator_last`]: https://rust-lang.github.io/rust-clippy/master/index.html#double_ended_iterator_last
55385539
[`double_must_use`]: https://rust-lang.github.io/rust-clippy/master/index.html#double_must_use

README.md

+3-3
Original file line numberDiff line numberDiff line change
@@ -159,11 +159,11 @@ line. (You can swap `clippy::all` with the specific lint category you are target
159159
You can add options to your code to `allow`/`warn`/`deny` Clippy lints:
160160

161161
* the whole set of `Warn` lints using the `clippy` lint group (`#![deny(clippy::all)]`).
162-
Note that `rustc` has additional [lint groups](https://doc.rust-lang.org/rustc/lints/groups.html).
162+
Note that `rustc` has additional [lint groups](https://doc.rust-lang.org/rustc/lints/groups.html).
163163

164164
* all lints using both the `clippy` and `clippy::pedantic` lint groups (`#![deny(clippy::all)]`,
165-
`#![deny(clippy::pedantic)]`). Note that `clippy::pedantic` contains some very aggressive
166-
lints prone to false positives.
165+
`#![deny(clippy::pedantic)]`). Note that `clippy::pedantic` contains some very aggressive
166+
lints prone to false positives.
167167

168168
* only some lints (`#![deny(clippy::single_match, clippy::box_vec)]`, etc.)
169169

clippy_lints/src/declared_lints.rs

+1
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,7 @@ pub static LINTS: &[&crate::LintInfo] = &[
142142
crate::doc::DOC_LINK_WITH_QUOTES_INFO,
143143
crate::doc::DOC_MARKDOWN_INFO,
144144
crate::doc::DOC_NESTED_REFDEFS_INFO,
145+
crate::doc::DOC_OVERINDENTED_LIST_ITEMS_INFO,
145146
crate::doc::EMPTY_DOCS_INFO,
146147
crate::doc::EMPTY_LINE_AFTER_DOC_COMMENTS_INFO,
147148
crate::doc::EMPTY_LINE_AFTER_OUTER_ATTR_INFO,
+70-38
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
1-
use clippy_utils::diagnostics::span_lint_and_then;
1+
use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_and_then};
22
use itertools::Itertools;
33
use rustc_errors::Applicability;
44
use rustc_lint::LateContext;
55
use rustc_span::{BytePos, Span};
6+
use std::cmp::Ordering;
67
use std::ops::Range;
78

8-
use super::DOC_LAZY_CONTINUATION;
9+
use super::{DOC_LAZY_CONTINUATION, DOC_OVERINDENTED_LIST_ITEMS};
910

1011
fn map_container_to_text(c: &super::Container) -> &'static str {
1112
match c {
@@ -28,12 +29,57 @@ pub(super) fn check(
2829
return;
2930
}
3031

32+
// Blockquote
3133
let ccount = doc[range.clone()].chars().filter(|c| *c == '>').count();
3234
let blockquote_level = containers
3335
.iter()
3436
.filter(|c| matches!(c, super::Container::Blockquote))
3537
.count();
36-
let lcount = doc[range.clone()].chars().filter(|c| *c == ' ').count();
38+
if ccount < blockquote_level {
39+
span_lint_and_then(
40+
cx,
41+
DOC_LAZY_CONTINUATION,
42+
span,
43+
"doc quote line without `>` marker",
44+
|diag| {
45+
let mut doc_start_range = &doc[range];
46+
let mut suggested = String::new();
47+
for c in containers {
48+
let text = map_container_to_text(c);
49+
if doc_start_range.starts_with(text) {
50+
doc_start_range = &doc_start_range[text.len()..];
51+
span = span.with_lo(
52+
span.lo() + BytePos(u32::try_from(text.len()).expect("text is not 2**32 or bigger")),
53+
);
54+
} else if matches!(c, super::Container::Blockquote)
55+
&& let Some(i) = doc_start_range.find('>')
56+
{
57+
doc_start_range = &doc_start_range[i + 1..];
58+
span = span
59+
.with_lo(span.lo() + BytePos(u32::try_from(i).expect("text is not 2**32 or bigger") + 1));
60+
} else {
61+
suggested.push_str(text);
62+
}
63+
}
64+
diag.span_suggestion_verbose(
65+
span,
66+
"add markers to start of line",
67+
suggested,
68+
Applicability::MachineApplicable,
69+
);
70+
diag.help("if this not intended to be a quote at all, escape it with `\\>`");
71+
},
72+
);
73+
return;
74+
}
75+
76+
if ccount != 0 && blockquote_level != 0 {
77+
// If this doc is a blockquote, we don't go further.
78+
return;
79+
}
80+
81+
// List
82+
let leading_spaces = doc[range].chars().filter(|c| *c == ' ').count();
3783
let list_indentation = containers
3884
.iter()
3985
.map(|c| {
@@ -44,50 +90,36 @@ pub(super) fn check(
4490
}
4591
})
4692
.sum();
47-
if ccount < blockquote_level || lcount < list_indentation {
48-
let msg = if ccount < blockquote_level {
49-
"doc quote line without `>` marker"
50-
} else {
51-
"doc list item without indentation"
52-
};
53-
span_lint_and_then(cx, DOC_LAZY_CONTINUATION, span, msg, |diag| {
54-
if ccount == 0 && blockquote_level == 0 {
93+
match leading_spaces.cmp(&list_indentation) {
94+
Ordering::Less => span_lint_and_then(
95+
cx,
96+
DOC_LAZY_CONTINUATION,
97+
span,
98+
"doc list item without indentation",
99+
|diag| {
55100
// simpler suggestion style for indentation
56-
let indent = list_indentation - lcount;
101+
let indent = list_indentation - leading_spaces;
57102
diag.span_suggestion_verbose(
58103
span.shrink_to_hi(),
59104
"indent this line",
60105
std::iter::repeat_n(" ", indent).join(""),
61106
Applicability::MaybeIncorrect,
62107
);
63108
diag.help("if this is supposed to be its own paragraph, add a blank line");
64-
return;
65-
}
66-
let mut doc_start_range = &doc[range];
67-
let mut suggested = String::new();
68-
for c in containers {
69-
let text = map_container_to_text(c);
70-
if doc_start_range.starts_with(text) {
71-
doc_start_range = &doc_start_range[text.len()..];
72-
span = span
73-
.with_lo(span.lo() + BytePos(u32::try_from(text.len()).expect("text is not 2**32 or bigger")));
74-
} else if matches!(c, super::Container::Blockquote)
75-
&& let Some(i) = doc_start_range.find('>')
76-
{
77-
doc_start_range = &doc_start_range[i + 1..];
78-
span =
79-
span.with_lo(span.lo() + BytePos(u32::try_from(i).expect("text is not 2**32 or bigger") + 1));
80-
} else {
81-
suggested.push_str(text);
82-
}
83-
}
84-
diag.span_suggestion_verbose(
109+
},
110+
),
111+
Ordering::Greater => {
112+
let sugg = std::iter::repeat_n(" ", list_indentation).join("");
113+
span_lint_and_sugg(
114+
cx,
115+
DOC_OVERINDENTED_LIST_ITEMS,
85116
span,
86-
"add markers to start of line",
87-
suggested,
88-
Applicability::MachineApplicable,
117+
"doc list item overindented",
118+
format!("try using `{sugg}` ({list_indentation} spaces)"),
119+
sugg,
120+
Applicability::MaybeIncorrect,
89121
);
90-
diag.help("if this not intended to be a quote at all, escape it with `\\>`");
91-
});
122+
},
123+
Ordering::Equal => {},
92124
}
93125
}

clippy_lints/src/doc/mod.rs

+34
Original file line numberDiff line numberDiff line change
@@ -428,6 +428,39 @@ declare_clippy_lint! {
428428
"require every line of a paragraph to be indented and marked"
429429
}
430430

431+
declare_clippy_lint! {
432+
/// ### What it does
433+
///
434+
/// Detects overindented list items in doc comments where the continuation
435+
/// lines are indented more than necessary.
436+
///
437+
/// ### Why is this bad?
438+
///
439+
/// Overindented list items in doc comments can lead to inconsistent and
440+
/// poorly formatted documentation when rendered. Excessive indentation may
441+
/// cause the text to be misinterpreted as a nested list item or code block,
442+
/// affecting readability and the overall structure of the documentation.
443+
///
444+
/// ### Example
445+
///
446+
/// ```no_run
447+
/// /// - This is the first item in a list
448+
/// /// and this line is overindented.
449+
/// # fn foo() {}
450+
/// ```
451+
///
452+
/// Fixes this into:
453+
/// ```no_run
454+
/// /// - This is the first item in a list
455+
/// /// and this line is overindented.
456+
/// # fn foo() {}
457+
/// ```
458+
#[clippy::version = "1.80.0"]
459+
pub DOC_OVERINDENTED_LIST_ITEMS,
460+
style,
461+
"ensure list items are not overindented"
462+
}
463+
431464
declare_clippy_lint! {
432465
/// ### What it does
433466
/// Checks if the first paragraph in the documentation of items listed in the module page is too long.
@@ -617,6 +650,7 @@ impl_lint_pass!(Documentation => [
617650
SUSPICIOUS_DOC_COMMENTS,
618651
EMPTY_DOCS,
619652
DOC_LAZY_CONTINUATION,
653+
DOC_OVERINDENTED_LIST_ITEMS,
620654
EMPTY_LINE_AFTER_OUTER_ATTR,
621655
EMPTY_LINE_AFTER_DOC_COMMENTS,
622656
TOO_LONG_FIRST_DOC_PARAGRAPH,

tests/ui/doc/doc_lazy_list.fixed

+1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
#![warn(clippy::doc_lazy_continuation)]
2+
#![allow(clippy::doc_overindented_list_items)]
23

34
/// 1. nest here
45
/// lazy continuation

tests/ui/doc/doc_lazy_list.rs

+1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
#![warn(clippy::doc_lazy_continuation)]
2+
#![allow(clippy::doc_overindented_list_items)]
23

34
/// 1. nest here
45
/// lazy continuation

tests/ui/doc/doc_lazy_list.stderr

+11-11
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
error: doc list item without indentation
2-
--> tests/ui/doc/doc_lazy_list.rs:4:5
2+
--> tests/ui/doc/doc_lazy_list.rs:5:5
33
|
44
LL | /// lazy continuation
55
| ^
@@ -13,7 +13,7 @@ LL | /// lazy continuation
1313
| +++
1414

1515
error: doc list item without indentation
16-
--> tests/ui/doc/doc_lazy_list.rs:9:5
16+
--> tests/ui/doc/doc_lazy_list.rs:10:5
1717
|
1818
LL | /// lazy list continuations don't make warnings with this lint
1919
| ^
@@ -25,7 +25,7 @@ LL | /// lazy list continuations don't make warnings with this lint
2525
| +++
2626

2727
error: doc list item without indentation
28-
--> tests/ui/doc/doc_lazy_list.rs:11:5
28+
--> tests/ui/doc/doc_lazy_list.rs:12:5
2929
|
3030
LL | /// because they don't have the
3131
| ^
@@ -37,7 +37,7 @@ LL | /// because they don't have the
3737
| +++
3838

3939
error: doc list item without indentation
40-
--> tests/ui/doc/doc_lazy_list.rs:16:5
40+
--> tests/ui/doc/doc_lazy_list.rs:17:5
4141
|
4242
LL | /// lazy continuation
4343
| ^
@@ -49,7 +49,7 @@ LL | /// lazy continuation
4949
| ++++
5050

5151
error: doc list item without indentation
52-
--> tests/ui/doc/doc_lazy_list.rs:21:5
52+
--> tests/ui/doc/doc_lazy_list.rs:22:5
5353
|
5454
LL | /// lazy list continuations don't make warnings with this lint
5555
| ^
@@ -61,7 +61,7 @@ LL | /// lazy list continuations don't make warnings with this lint
6161
| ++++
6262

6363
error: doc list item without indentation
64-
--> tests/ui/doc/doc_lazy_list.rs:23:5
64+
--> tests/ui/doc/doc_lazy_list.rs:24:5
6565
|
6666
LL | /// because they don't have the
6767
| ^
@@ -73,7 +73,7 @@ LL | /// because they don't have the
7373
| ++++
7474

7575
error: doc list item without indentation
76-
--> tests/ui/doc/doc_lazy_list.rs:28:5
76+
--> tests/ui/doc/doc_lazy_list.rs:29:5
7777
|
7878
LL | /// lazy continuation
7979
| ^
@@ -85,7 +85,7 @@ LL | /// lazy continuation
8585
| ++++
8686

8787
error: doc list item without indentation
88-
--> tests/ui/doc/doc_lazy_list.rs:33:5
88+
--> tests/ui/doc/doc_lazy_list.rs:34:5
8989
|
9090
LL | /// this will warn on the lazy continuation
9191
| ^
@@ -97,7 +97,7 @@ LL | /// this will warn on the lazy continuation
9797
| ++++++
9898

9999
error: doc list item without indentation
100-
--> tests/ui/doc/doc_lazy_list.rs:35:5
100+
--> tests/ui/doc/doc_lazy_list.rs:36:5
101101
|
102102
LL | /// and so should this
103103
| ^^^^
@@ -109,7 +109,7 @@ LL | /// and so should this
109109
| ++
110110

111111
error: doc list item without indentation
112-
--> tests/ui/doc/doc_lazy_list.rs:56:5
112+
--> tests/ui/doc/doc_lazy_list.rs:57:5
113113
|
114114
LL | /// 'protocol_descriptors': [
115115
| ^
@@ -121,7 +121,7 @@ LL | /// 'protocol_descriptors': [
121121
| +
122122

123123
error: doc list item without indentation
124-
--> tests/ui/doc/doc_lazy_list.rs:75:5
124+
--> tests/ui/doc/doc_lazy_list.rs:76:5
125125
|
126126
LL | /// ]
127127
| ^
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
#![warn(clippy::doc_overindented_list_items)]
2+
3+
#[rustfmt::skip]
4+
/// - first list item
5+
/// overindented line
6+
//~^ ERROR: doc list item overindented
7+
/// this is overindented line too
8+
//~^ ERROR: doc list item overindented
9+
/// - second list item
10+
fn foo() {}
11+
12+
#[rustfmt::skip]
13+
/// - first list item
14+
/// overindented line
15+
//~^ ERROR: doc list item overindented
16+
/// this is overindented line too
17+
//~^ ERROR: doc list item overindented
18+
/// - second list item
19+
fn bar() {}
20+
21+
#[rustfmt::skip]
22+
/// * first list item
23+
/// overindented line
24+
//~^ ERROR: doc list item overindented
25+
/// this is overindented line too
26+
//~^ ERROR: doc list item overindented
27+
/// * second list item
28+
fn baz() {}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
#![warn(clippy::doc_overindented_list_items)]
2+
3+
#[rustfmt::skip]
4+
/// - first list item
5+
/// overindented line
6+
//~^ ERROR: doc list item overindented
7+
/// this is overindented line too
8+
//~^ ERROR: doc list item overindented
9+
/// - second list item
10+
fn foo() {}
11+
12+
#[rustfmt::skip]
13+
/// - first list item
14+
/// overindented line
15+
//~^ ERROR: doc list item overindented
16+
/// this is overindented line too
17+
//~^ ERROR: doc list item overindented
18+
/// - second list item
19+
fn bar() {}
20+
21+
#[rustfmt::skip]
22+
/// * first list item
23+
/// overindented line
24+
//~^ ERROR: doc list item overindented
25+
/// this is overindented line too
26+
//~^ ERROR: doc list item overindented
27+
/// * second list item
28+
fn baz() {}

0 commit comments

Comments
 (0)