From 87147f6c554aa00e04859f145d23cd2586201888 Mon Sep 17 00:00:00 2001 From: Noah Lev Date: Tue, 21 May 2024 22:47:39 -0700 Subject: [PATCH] Simplify logic for unindenting doc comments This has almost the same behavior, except that it completely ignores raw doc comments (i.e., `#[doc = "..."]`) for the purposes of computing indentation. --- compiler/rustc_resolve/src/rustdoc.rs | 77 +++++---------------- tests/rustdoc-ui/unescaped_backticks.stderr | 28 ++++---- tests/rustdoc/unindent.rs | 2 +- 3 files changed, 34 insertions(+), 73 deletions(-) diff --git a/compiler/rustc_resolve/src/rustdoc.rs b/compiler/rustc_resolve/src/rustdoc.rs index 66b4981eb55ba..4102e6e032f4c 100644 --- a/compiler/rustc_resolve/src/rustdoc.rs +++ b/compiler/rustc_resolve/src/rustdoc.rs @@ -84,84 +84,44 @@ pub enum MalformedGenerics { /// Removes excess indentation on comments in order for the Markdown /// to be parsed correctly. This is necessary because the convention for -/// writing documentation is to provide a space between the /// or //! marker +/// writing documentation is to provide a space between the `///` or `//!` marker /// and the doc text, but Markdown is whitespace-sensitive. For example, /// a block of text with four-space indentation is parsed as a code block, /// so if we didn't unindent comments, these list items /// +/// ``` /// /// A list: /// /// /// /// - Foo /// /// - Bar +/// # fn foo() {} +/// ``` /// /// would be parsed as if they were in a code block, which is likely not what the user intended. pub fn unindent_doc_fragments(docs: &mut [DocFragment]) { - // `add` is used in case the most common sugared doc syntax is used ("/// "). The other - // fragments kind's lines are never starting with a whitespace unless they are using some - // markdown formatting requiring it. Therefore, if the doc block have a mix between the two, - // we need to take into account the fact that the minimum indent minus one (to take this - // whitespace into account). - // - // For example: - // - // /// hello! - // #[doc = "another"] - // - // In this case, you want "hello! another" and not "hello! another". - let add = if docs.windows(2).any(|arr| arr[0].kind != arr[1].kind) - && docs.iter().any(|d| d.kind == DocFragmentKind::SugaredDoc) - { - // In case we have a mix of sugared doc comments and "raw" ones, we want the sugared one to - // "decide" how much the minimum indent will be. - 1 - } else { - 0 - }; - - // `min_indent` is used to know how much whitespaces from the start of each lines must be - // removed. Example: - // - // /// hello! - // #[doc = "another"] - // - // In here, the `min_indent` is 1 (because non-sugared fragment are always counted with minimum - // 1 whitespace), meaning that "hello!" will be considered a codeblock because it starts with 4 - // (5 - 1) whitespaces. - let Some(min_indent) = docs + // Only sugared docs have the concept of indentation. + // We assume the docs overall are indented by the amount that the least-indented line is indented. + // Raw docs are taken literally. + let min_indent = docs .iter() - .map(|fragment| { - fragment - .doc - .as_str() - .lines() - .filter(|line| line.chars().any(|c| !c.is_whitespace())) - .map(|line| { - // Compare against either space or tab, ignoring whether they are - // mixed or not. - let whitespace = line.chars().take_while(|c| *c == ' ' || *c == '\t').count(); - whitespace - + (if fragment.kind == DocFragmentKind::SugaredDoc { 0 } else { add }) - }) - .min() - .unwrap_or(usize::MAX) - }) + .filter(|frag| frag.kind == DocFragmentKind::SugaredDoc) + .flat_map(|frag| frag.doc.as_str().lines()) + .filter(|line| line.chars().any(|c| !c.is_whitespace())) + // FIXME: we should be more careful when spaces and tabs are mixed + .map(|line| line.chars().take_while(|c| *c == ' ' || *c == '\t').count()) .min() - else { - return; - }; + .unwrap_or(0); for fragment in docs { if fragment.doc == kw::Empty { continue; } - let indent = if fragment.kind != DocFragmentKind::SugaredDoc && min_indent > 0 { - min_indent - add - } else { - min_indent + fragment.indent = match fragment.kind { + // Raw docs are taken literally. + DocFragmentKind::RawDoc => 0, + DocFragmentKind::SugaredDoc => min_indent, }; - - fragment.indent = indent; } } @@ -171,6 +131,7 @@ pub fn unindent_doc_fragments(docs: &mut [DocFragment]) { /// /// Note: remove the trailing newline where appropriate pub fn add_doc_fragment(out: &mut String, frag: &DocFragment) { + debug!("add_doc_fragment: {:?}", frag); if frag.doc == kw::Empty { out.push('\n'); return; diff --git a/tests/rustdoc-ui/unescaped_backticks.stderr b/tests/rustdoc-ui/unescaped_backticks.stderr index 67b87f353a153..22662dbbc5918 100644 --- a/tests/rustdoc-ui/unescaped_backticks.stderr +++ b/tests/rustdoc-ui/unescaped_backticks.stderr @@ -280,11 +280,11 @@ LL | | /// level changes. | |______________________^ | = help: the opening backtick of a previous inline code may be missing - change: The Subscriber` may be accessed by calling [`WeakDispatch::upgrade`], - to this: The `Subscriber` may be accessed by calling [`WeakDispatch::upgrade`], + change: The Subscriber` may be accessed by calling [`WeakDispatch::upgrade`], + to this: The `Subscriber` may be accessed by calling [`WeakDispatch::upgrade`], = help: if you meant to use a literal backtick, escape it - change: `None`. Otherwise, it will return `Some(Dispatch)`. - to this: `None`. Otherwise, it will return `Some(Dispatch)\`. + change: `None`. Otherwise, it will return `Some(Dispatch)`. + to this: `None`. Otherwise, it will return `Some(Dispatch)\`. error: unescaped backtick --> $DIR/unescaped_backticks.rs:323:5 @@ -300,8 +300,8 @@ LL | | /// level changes. | = help: the opening or closing backtick of an inline code may be missing = help: if you meant to use a literal backtick, escape it - change: or `None` if it isn't. - to this: or `None\` if it isn't. + change: or `None` if it isn't. + to this: or `None\` if it isn't. error: unescaped backtick --> $DIR/unescaped_backticks.rs:323:5 @@ -316,11 +316,11 @@ LL | | /// level changes. | |______________________^ | = help: a previous inline code might be longer than expected - change: Called before the filtered [`Layer]'s [`on_event`], to determine if - to this: Called before the filtered [`Layer`]'s [`on_event`], to determine if + change: Called before the filtered [`Layer]'s [`on_event`], to determine if + to this: Called before the filtered [`Layer`]'s [`on_event`], to determine if = help: if you meant to use a literal backtick, escape it - change: `on_event` should be called. - to this: `on_event\` should be called. + change: `on_event` should be called. + to this: `on_event\` should be called. error: unescaped backtick --> $DIR/unescaped_backticks.rs:323:5 @@ -335,11 +335,11 @@ LL | | /// level changes. | |______________________^ | = help: a previous inline code might be longer than expected - change: Therefore, if the `Filter will change the value returned by this - to this: Therefore, if the `Filter` will change the value returned by this + change: Therefore, if the `Filter will change the value returned by this + to this: Therefore, if the `Filter` will change the value returned by this = help: if you meant to use a literal backtick, escape it - change: [`rebuild_interest_cache`][rebuild] is called after the value of the max - to this: [`rebuild_interest_cache\`][rebuild] is called after the value of the max + change: [`rebuild_interest_cache`][rebuild] is called after the value of the max + to this: [`rebuild_interest_cache\`][rebuild] is called after the value of the max error: unescaped backtick --> $DIR/unescaped_backticks.rs:349:56 diff --git a/tests/rustdoc/unindent.rs b/tests/rustdoc/unindent.rs index 372af5f4672a7..d25ea82b78c4d 100644 --- a/tests/rustdoc/unindent.rs +++ b/tests/rustdoc/unindent.rs @@ -40,7 +40,7 @@ pub struct G; pub struct H; // @has foo/struct.I.html -// @matches - '//pre[@class="rust rust-example-rendered"]' '(?m)4 whitespaces!\Z' +// @has - '//div[@class="docblock"]/p' '4 whitespaces! something' /// 4 whitespaces! #[doc = "something"] pub struct I;