From 21eaa0425bfa3dbb6e86404a0d73e420bc55ac8a Mon Sep 17 00:00:00 2001 From: Yutaro Ohno Date: Wed, 20 Nov 2024 17:48:17 +0900 Subject: [PATCH] Add lint for overindented list items in docs Add a lint 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. --- README.md | 6 +- clippy_lints/src/doc/lazy_continuation.rs | 78 +++++--- tests/ui/doc/doc_lazy_list.fixed | 40 ++-- tests/ui/doc/doc_lazy_list.rs | 6 + tests/ui/doc/doc_lazy_list.stderr | 218 +++++++++++++++++++++- 5 files changed, 298 insertions(+), 50 deletions(-) diff --git a/README.md b/README.md index cb3a22d4288f..32c1d33e2ed3 100644 --- a/README.md +++ b/README.md @@ -159,11 +159,11 @@ line. (You can swap `clippy::all` with the specific lint category you are target You can add options to your code to `allow`/`warn`/`deny` Clippy lints: * the whole set of `Warn` lints using the `clippy` lint group (`#![deny(clippy::all)]`). - Note that `rustc` has additional [lint groups](https://doc.rust-lang.org/rustc/lints/groups.html). + Note that `rustc` has additional [lint groups](https://doc.rust-lang.org/rustc/lints/groups.html). * all lints using both the `clippy` and `clippy::pedantic` lint groups (`#![deny(clippy::all)]`, - `#![deny(clippy::pedantic)]`). Note that `clippy::pedantic` contains some very aggressive - lints prone to false positives. + `#![deny(clippy::pedantic)]`). Note that `clippy::pedantic` contains some very aggressive + lints prone to false positives. * only some lints (`#![deny(clippy::single_match, clippy::box_vec)]`, etc.) diff --git a/clippy_lints/src/doc/lazy_continuation.rs b/clippy_lints/src/doc/lazy_continuation.rs index f9e4a43c0e7a..61436da72e43 100644 --- a/clippy_lints/src/doc/lazy_continuation.rs +++ b/clippy_lints/src/doc/lazy_continuation.rs @@ -28,41 +28,15 @@ pub(super) fn check( return; } + // Handle blockquotes. let ccount = doc[range.clone()].chars().filter(|c| *c == '>').count(); let blockquote_level = containers .iter() .filter(|c| matches!(c, super::Container::Blockquote)) .count(); - let lcount = doc[range.clone()].chars().filter(|c| *c == ' ').count(); - let list_indentation = containers - .iter() - .map(|c| { - if let super::Container::List(indent) = c { - *indent - } else { - 0 - } - }) - .sum(); - if ccount < blockquote_level || lcount < list_indentation { - let msg = if ccount < blockquote_level { - "doc quote line without `>` marker" - } else { - "doc list item without indentation" - }; + if ccount < blockquote_level { + let msg = "doc quote line without `>` marker"; span_lint_and_then(cx, DOC_LAZY_CONTINUATION, span, msg, |diag| { - if ccount == 0 && blockquote_level == 0 { - // simpler suggestion style for indentation - let indent = list_indentation - lcount; - diag.span_suggestion_verbose( - span.shrink_to_hi(), - "indent this line", - std::iter::repeat(" ").take(indent).join(""), - Applicability::MaybeIncorrect, - ); - diag.help("if this is supposed to be its own paragraph, add a blank line"); - return; - } let mut doc_start_range = &doc[range]; let mut suggested = String::new(); for c in containers { @@ -89,5 +63,51 @@ pub(super) fn check( ); diag.help("if this not intended to be a quote at all, escape it with `\\>`"); }); + return; + } + + if ccount != 0 || blockquote_level != 0 { + // If this doc is a blockquote, we don't go further. + return; + } + + // Handle list items + let lcount = doc[range.clone()].chars().filter(|c| *c == ' ').count(); + let list_indentation = containers + .iter() + .map(|c| { + if let super::Container::List(indent) = c { + *indent + } else { + 0 + } + }) + .sum(); + if lcount != list_indentation { + let msg = if lcount < list_indentation { + "doc list item without indentation" + } else { + "doc list item overindented" + }; + span_lint_and_then(cx, DOC_LAZY_CONTINUATION, span, msg, |diag| { + if lcount < list_indentation { + // simpler suggestion style for indentation + let indent = list_indentation - lcount; + diag.span_suggestion_verbose( + span.shrink_to_hi(), + "indent this line", + std::iter::repeat(" ").take(indent).join(""), + Applicability::MaybeIncorrect, + ); + } else { + diag.span_suggestion_verbose( + span, + "indent this line", + std::iter::repeat(" ").take(list_indentation).join(""), + Applicability::MaybeIncorrect, + ); + } + diag.help("if this is supposed to be its own paragraph, add a blank line"); + }); } } diff --git a/tests/ui/doc/doc_lazy_list.fixed b/tests/ui/doc/doc_lazy_list.fixed index da537518a2b5..982965850498 100644 --- a/tests/ui/doc/doc_lazy_list.fixed +++ b/tests/ui/doc/doc_lazy_list.fixed @@ -52,26 +52,32 @@ fn seven() {} /// /// # Arguments /// * `protocol_descriptors`: A Json Representation of the ProtocolDescriptors -/// to set up. Example: +/// to set up. Example: /// 'protocol_descriptors': [ //~^ ERROR: doc list item without indentation -/// { -/// 'protocol': 25, # u64 Representation of ProtocolIdentifier::AVDTP -/// 'params': [ -/// { -/// 'data': 0x0103 # to indicate 1.3 -/// }, -/// { -/// 'data': 0x0105 # to indicate 1.5 -/// } +/// { +/// 'protocol': 25, # u64 Representation of ProtocolIdentifier::AVDTP +/// 'params': [ +/// { +/// 'data': 0x0103 # to indicate 1.3 +/// }, +/// { +/// 'data': 0x0105 # to indicate 1.5 +/// } /// ] -/// }, -/// { -/// 'protocol': 1, # u64 Representation of ProtocolIdentifier::SDP -/// 'params': [{ -/// 'data': 0x0019 -/// }] -/// } +/// }, +/// { +/// 'protocol': 1, # u64 Representation of ProtocolIdentifier::SDP +/// 'params': [{ +/// 'data': 0x0019 +/// }] +/// } /// ] //~^ ERROR: doc list item without indentation fn eight() {} + +#[rustfmt::skip] +/// - first line +/// second line +//~^ ERROR: doc list item overindented +fn nine() {} diff --git a/tests/ui/doc/doc_lazy_list.rs b/tests/ui/doc/doc_lazy_list.rs index 3cc18e35780a..c8afa102528e 100644 --- a/tests/ui/doc/doc_lazy_list.rs +++ b/tests/ui/doc/doc_lazy_list.rs @@ -75,3 +75,9 @@ fn seven() {} /// ] //~^ ERROR: doc list item without indentation fn eight() {} + +#[rustfmt::skip] +/// - first line +/// second line +//~^ ERROR: doc list item overindented +fn nine() {} diff --git a/tests/ui/doc/doc_lazy_list.stderr b/tests/ui/doc/doc_lazy_list.stderr index b38f43b7555f..43552644f11d 100644 --- a/tests/ui/doc/doc_lazy_list.stderr +++ b/tests/ui/doc/doc_lazy_list.stderr @@ -108,6 +108,18 @@ help: indent this line LL | /// and so should this | ++ +error: doc list item overindented + --> tests/ui/doc/doc_lazy_list.rs:55:5 + | +LL | /// to set up. Example: + | ^^^^ + | + = help: if this is supposed to be its own paragraph, add a blank line +help: indent this line + | +LL | /// to set up. Example: + | ++ + error: doc list item without indentation --> tests/ui/doc/doc_lazy_list.rs:56:5 | @@ -120,6 +132,198 @@ help: indent this line LL | /// 'protocol_descriptors': [ | + +error: doc list item overindented + --> tests/ui/doc/doc_lazy_list.rs:58:5 + | +LL | /// { + | ^^^^^ + | + = help: if this is supposed to be its own paragraph, add a blank line +help: indent this line + | +LL | /// { + | ++ + +error: doc list item overindented + --> tests/ui/doc/doc_lazy_list.rs:59:5 + | +LL | /// 'protocol': 25, # u64 Representation of ProtocolIdentifier::AVDTP + | ^^^^^^^^^ + | + = help: if this is supposed to be its own paragraph, add a blank line +help: indent this line + | +LL | /// 'protocol': 25, # u64 Representation of ProtocolIdentifier::AVDTP + | ++ + +error: doc list item overindented + --> tests/ui/doc/doc_lazy_list.rs:60:5 + | +LL | /// 'params': [ + | ^^^^^^^^^ + | + = help: if this is supposed to be its own paragraph, add a blank line +help: indent this line + | +LL | /// 'params': [ + | ++ + +error: doc list item overindented + --> tests/ui/doc/doc_lazy_list.rs:61:5 + | +LL | /// { + | ^^^^^^^^^^^^^ + | + = help: if this is supposed to be its own paragraph, add a blank line +help: indent this line + | +LL | /// { + | ++ + +error: doc list item overindented + --> tests/ui/doc/doc_lazy_list.rs:62:5 + | +LL | /// 'data': 0x0103 # to indicate 1.3 + | ^^^^^^^^^^^^^^^^ + | + = help: if this is supposed to be its own paragraph, add a blank line +help: indent this line + | +LL | /// 'data': 0x0103 # to indicate 1.3 + | ++ + +error: doc list item overindented + --> tests/ui/doc/doc_lazy_list.rs:63:5 + | +LL | /// }, + | ^^^^^^^^^^^^^ + | + = help: if this is supposed to be its own paragraph, add a blank line +help: indent this line + | +LL | /// }, + | ++ + +error: doc list item overindented + --> tests/ui/doc/doc_lazy_list.rs:64:5 + | +LL | /// { + | ^^^^^^^^^^^^^ + | + = help: if this is supposed to be its own paragraph, add a blank line +help: indent this line + | +LL | /// { + | ++ + +error: doc list item overindented + --> tests/ui/doc/doc_lazy_list.rs:65:5 + | +LL | /// 'data': 0x0105 # to indicate 1.5 + | ^^^^^^^^^^^^^^^^^ + | + = help: if this is supposed to be its own paragraph, add a blank line +help: indent this line + | +LL | /// 'data': 0x0105 # to indicate 1.5 + | ++ + +error: doc list item overindented + --> tests/ui/doc/doc_lazy_list.rs:66:5 + | +LL | /// } + | ^^^^^^^^^^^^^ + | + = help: if this is supposed to be its own paragraph, add a blank line +help: indent this line + | +LL | /// } + | ++ + +error: doc list item overindented + --> tests/ui/doc/doc_lazy_list.rs:68:5 + | +LL | /// }, + | ^^^^^ + | + = help: if this is supposed to be its own paragraph, add a blank line +help: indent this line + | +LL | /// }, + | ++ + +error: doc list item overindented + --> tests/ui/doc/doc_lazy_list.rs:69:5 + | +LL | /// { + | ^^^^^ + | + = help: if this is supposed to be its own paragraph, add a blank line +help: indent this line + | +LL | /// { + | ++ + +error: doc list item overindented + --> tests/ui/doc/doc_lazy_list.rs:70:5 + | +LL | /// 'protocol': 1, # u64 Representation of ProtocolIdentifier::SDP + | ^^^^^^^^^ + | + = help: if this is supposed to be its own paragraph, add a blank line +help: indent this line + | +LL | /// 'protocol': 1, # u64 Representation of ProtocolIdentifier::SDP + | ++ + +error: doc list item overindented + --> tests/ui/doc/doc_lazy_list.rs:71:5 + | +LL | /// 'params': [{ + | ^^^^^^^^^ + | + = help: if this is supposed to be its own paragraph, add a blank line +help: indent this line + | +LL | /// 'params': [{ + | ++ + +error: doc list item overindented + --> tests/ui/doc/doc_lazy_list.rs:72:5 + | +LL | /// 'data': 0x0019 + | ^^^^^^^^^^^^^ + | + = help: if this is supposed to be its own paragraph, add a blank line +help: indent this line + | +LL | /// 'data': 0x0019 + | ++ + +error: doc list item overindented + --> tests/ui/doc/doc_lazy_list.rs:73:5 + | +LL | /// }] + | ^^^^^^^^^ + | + = help: if this is supposed to be its own paragraph, add a blank line +help: indent this line + | +LL | /// }] + | ++ + +error: doc list item overindented + --> tests/ui/doc/doc_lazy_list.rs:74:5 + | +LL | /// } + | ^^^^^ + | + = help: if this is supposed to be its own paragraph, add a blank line +help: indent this line + | +LL | /// } + | ++ + error: doc list item without indentation --> tests/ui/doc/doc_lazy_list.rs:75:5 | @@ -132,5 +336,17 @@ help: indent this line LL | /// ] | + -error: aborting due to 11 previous errors +error: doc list item overindented + --> tests/ui/doc/doc_lazy_list.rs:81:5 + | +LL | /// second line + | ^^^^^ + | + = help: if this is supposed to be its own paragraph, add a blank line +help: indent this line + | +LL | /// second line + | ++ + +error: aborting due to 29 previous errors