about summary refs log tree commit diff
path: root/clippy_lints/src/doc
diff options
context:
space:
mode:
authorPhilipp Krones <hello@philkrones.com>2025-01-28 18:28:57 +0100
committerPhilipp Krones <hello@philkrones.com>2025-01-28 19:14:45 +0100
commit145d5adf04fecbc48bdf2ab2a356ca4c8df0c149 (patch)
tree1783d720a01e7ef83d143293961fd83200728556 /clippy_lints/src/doc
parentc5196736b27a32b688b957f2937dd4affadbe14f (diff)
parent25509e71359ea0b218309d4b7e94bf40e1ef716e (diff)
downloadrust-145d5adf04fecbc48bdf2ab2a356ca4c8df0c149.tar.gz
rust-145d5adf04fecbc48bdf2ab2a356ca4c8df0c149.zip
Merge remote-tracking branch 'upstream/master' into rustup
Diffstat (limited to 'clippy_lints/src/doc')
-rw-r--r--clippy_lints/src/doc/lazy_continuation.rs110
-rw-r--r--clippy_lints/src/doc/mod.rs34
2 files changed, 105 insertions, 39 deletions
diff --git a/clippy_lints/src/doc/lazy_continuation.rs b/clippy_lints/src/doc/lazy_continuation.rs
index f9e4a43c0e7..2577324f23d 100644
--- a/clippy_lints/src/doc/lazy_continuation.rs
+++ b/clippy_lints/src/doc/lazy_continuation.rs
@@ -1,11 +1,12 @@
-use clippy_utils::diagnostics::span_lint_and_then;
+use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_and_then};
 use itertools::Itertools;
 use rustc_errors::Applicability;
 use rustc_lint::LateContext;
 use rustc_span::{BytePos, Span};
+use std::cmp::Ordering;
 use std::ops::Range;
 
-use super::DOC_LAZY_CONTINUATION;
+use super::{DOC_LAZY_CONTINUATION, DOC_OVERINDENTED_LIST_ITEMS};
 
 fn map_container_to_text(c: &super::Container) -> &'static str {
     match c {
@@ -28,12 +29,57 @@ pub(super) fn check(
         return;
     }
 
+    // Blockquote
     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();
+    if ccount < blockquote_level {
+        span_lint_and_then(
+            cx,
+            DOC_LAZY_CONTINUATION,
+            span,
+            "doc quote line without `>` marker",
+            |diag| {
+                let mut doc_start_range = &doc[range];
+                let mut suggested = String::new();
+                for c in containers {
+                    let text = map_container_to_text(c);
+                    if doc_start_range.starts_with(text) {
+                        doc_start_range = &doc_start_range[text.len()..];
+                        span = span.with_lo(
+                            span.lo() + BytePos(u32::try_from(text.len()).expect("text is not 2**32 or bigger")),
+                        );
+                    } else if matches!(c, super::Container::Blockquote)
+                        && let Some(i) = doc_start_range.find('>')
+                    {
+                        doc_start_range = &doc_start_range[i + 1..];
+                        span = span
+                            .with_lo(span.lo() + BytePos(u32::try_from(i).expect("text is not 2**32 or bigger") + 1));
+                    } else {
+                        suggested.push_str(text);
+                    }
+                }
+                diag.span_suggestion_verbose(
+                    span,
+                    "add markers to start of line",
+                    suggested,
+                    Applicability::MachineApplicable,
+                );
+                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;
+    }
+
+    // List
+    let leading_spaces = doc[range].chars().filter(|c| *c == ' ').count();
     let list_indentation = containers
         .iter()
         .map(|c| {
@@ -44,50 +90,36 @@ pub(super) fn check(
             }
         })
         .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"
-        };
-        span_lint_and_then(cx, DOC_LAZY_CONTINUATION, span, msg, |diag| {
-            if ccount == 0 && blockquote_level == 0 {
+    match leading_spaces.cmp(&list_indentation) {
+        Ordering::Less => span_lint_and_then(
+            cx,
+            DOC_LAZY_CONTINUATION,
+            span,
+            "doc list item without indentation",
+            |diag| {
                 // simpler suggestion style for indentation
-                let indent = list_indentation - lcount;
+                let indent = list_indentation - leading_spaces;
                 diag.span_suggestion_verbose(
                     span.shrink_to_hi(),
                     "indent this line",
-                    std::iter::repeat(" ").take(indent).join(""),
+                    std::iter::repeat_n(" ", 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 {
-                let text = map_container_to_text(c);
-                if doc_start_range.starts_with(text) {
-                    doc_start_range = &doc_start_range[text.len()..];
-                    span = span
-                        .with_lo(span.lo() + BytePos(u32::try_from(text.len()).expect("text is not 2**32 or bigger")));
-                } else if matches!(c, super::Container::Blockquote)
-                    && let Some(i) = doc_start_range.find('>')
-                {
-                    doc_start_range = &doc_start_range[i + 1..];
-                    span =
-                        span.with_lo(span.lo() + BytePos(u32::try_from(i).expect("text is not 2**32 or bigger") + 1));
-                } else {
-                    suggested.push_str(text);
-                }
-            }
-            diag.span_suggestion_verbose(
+            },
+        ),
+        Ordering::Greater => {
+            let sugg = std::iter::repeat_n(" ", list_indentation).join("");
+            span_lint_and_sugg(
+                cx,
+                DOC_OVERINDENTED_LIST_ITEMS,
                 span,
-                "add markers to start of line",
-                suggested,
-                Applicability::MachineApplicable,
+                "doc list item overindented",
+                format!("try using `{sugg}` ({list_indentation} spaces)"),
+                sugg,
+                Applicability::MaybeIncorrect,
             );
-            diag.help("if this not intended to be a quote at all, escape it with `\\>`");
-        });
+        },
+        Ordering::Equal => {},
     }
 }
diff --git a/clippy_lints/src/doc/mod.rs b/clippy_lints/src/doc/mod.rs
index 7561a6cf2a7..15530c3dbc5 100644
--- a/clippy_lints/src/doc/mod.rs
+++ b/clippy_lints/src/doc/mod.rs
@@ -430,6 +430,39 @@ declare_clippy_lint! {
 
 declare_clippy_lint! {
     /// ### What it does
+    ///
+    /// Detects overindented list items in doc comments where the continuation
+    /// lines are indented more than necessary.
+    ///
+    /// ### Why is this bad?
+    ///
+    /// Overindented list items in doc comments can lead to inconsistent and
+    /// poorly formatted documentation when rendered. Excessive indentation may
+    /// cause the text to be misinterpreted as a nested list item or code block,
+    /// affecting readability and the overall structure of the documentation.
+    ///
+    /// ### Example
+    ///
+    /// ```no_run
+    /// /// - This is the first item in a list
+    /// ///      and this line is overindented.
+    /// # fn foo() {}
+    /// ```
+    ///
+    /// Fixes this into:
+    /// ```no_run
+    /// /// - This is the first item in a list
+    /// ///   and this line is overindented.
+    /// # fn foo() {}
+    /// ```
+    #[clippy::version = "1.80.0"]
+    pub DOC_OVERINDENTED_LIST_ITEMS,
+    style,
+    "ensure list items are not overindented"
+}
+
+declare_clippy_lint! {
+    /// ### What it does
     /// Checks if the first paragraph in the documentation of items listed in the module page is too long.
     ///
     /// ### Why is this bad?
@@ -617,6 +650,7 @@ impl_lint_pass!(Documentation => [
     SUSPICIOUS_DOC_COMMENTS,
     EMPTY_DOCS,
     DOC_LAZY_CONTINUATION,
+    DOC_OVERINDENTED_LIST_ITEMS,
     EMPTY_LINE_AFTER_OUTER_ATTR,
     EMPTY_LINE_AFTER_DOC_COMMENTS,
     TOO_LONG_FIRST_DOC_PARAGRAPH,