1
Fork 0

Rollup merge of #93038 - GuillaumeGomez:block-doc-comments, r=notriddle

Fix star handling in block doc comments

Fixes #92872.

Some extra explanation about this PR and why https://github.com/rust-lang/rust/pull/92357 created this regression: when we merge doc comment kinds for example in:

```rust
/// he
/**
* hello
*/
#[doc = "boom"]
```

We don't want to remove the empty lines between them. However, to correctly compute the "horizontal trim", we still need it, so instead, I put back a part of the "vertical trim" directly in the "horizontal trim" computation so it doesn't impact the output buffer but allows us to correctly handle the stars.

r? `@camelid`
This commit is contained in:
Matthias Krüger 2022-01-20 17:10:41 +01:00 committed by GitHub
commit 6c627d25f6
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 60 additions and 15 deletions

View file

@ -242,6 +242,17 @@ impl Attribute {
} }
} }
pub fn doc_str_and_comment_kind(&self) -> Option<(Symbol, CommentKind)> {
match self.kind {
AttrKind::DocComment(kind, data) => Some((data, kind)),
AttrKind::Normal(ref item, _) if item.path == sym::doc => item
.meta_kind()
.and_then(|kind| kind.value_str())
.map(|data| (data, CommentKind::Line)),
_ => None,
}
}
pub fn doc_str(&self) -> Option<Symbol> { pub fn doc_str(&self) -> Option<Symbol> {
match self.kind { match self.kind {
AttrKind::DocComment(.., data) => Some(data), AttrKind::DocComment(.., data) => Some(data),

View file

@ -1,3 +1,4 @@
use crate::token::CommentKind;
use rustc_span::source_map::SourceMap; use rustc_span::source_map::SourceMap;
use rustc_span::{BytePos, CharPos, FileName, Pos, Symbol}; use rustc_span::{BytePos, CharPos, FileName, Pos, Symbol};
@ -25,7 +26,7 @@ pub struct Comment {
/// Makes a doc string more presentable to users. /// Makes a doc string more presentable to users.
/// Used by rustdoc and perhaps other tools, but not by rustc. /// Used by rustdoc and perhaps other tools, but not by rustc.
pub fn beautify_doc_string(data: Symbol) -> Symbol { pub fn beautify_doc_string(data: Symbol, kind: CommentKind) -> Symbol {
fn get_vertical_trim(lines: &[&str]) -> Option<(usize, usize)> { fn get_vertical_trim(lines: &[&str]) -> Option<(usize, usize)> {
let mut i = 0; let mut i = 0;
let mut j = lines.len(); let mut j = lines.len();
@ -42,10 +43,28 @@ pub fn beautify_doc_string(data: Symbol) -> Symbol {
if i != 0 || j != lines.len() { Some((i, j)) } else { None } if i != 0 || j != lines.len() { Some((i, j)) } else { None }
} }
fn get_horizontal_trim(lines: &[&str]) -> Option<usize> { fn get_horizontal_trim(lines: &[&str], kind: CommentKind) -> Option<usize> {
let mut i = usize::MAX; let mut i = usize::MAX;
let mut first = true; let mut first = true;
// In case we have doc comments like `/**` or `/*!`, we want to remove stars if they are
// present. However, we first need to strip the empty lines so they don't get in the middle
// when we try to compute the "horizontal trim".
let lines = if kind == CommentKind::Block {
let mut i = 0;
let mut j = lines.len();
while i < j && lines[i].trim().is_empty() {
i += 1;
}
while j > i && lines[j - 1].trim().is_empty() {
j -= 1;
}
&lines[i..j]
} else {
lines
};
for line in lines { for line in lines {
for (j, c) in line.chars().enumerate() { for (j, c) in line.chars().enumerate() {
if j > i || !"* \t".contains(c) { if j > i || !"* \t".contains(c) {
@ -79,11 +98,13 @@ pub fn beautify_doc_string(data: Symbol) -> Symbol {
} else { } else {
&mut lines &mut lines
}; };
if let Some(horizontal) = get_horizontal_trim(&lines) { if let Some(horizontal) = get_horizontal_trim(&lines, kind) {
changes = true; changes = true;
// remove a "[ \t]*\*" block from each line, if possible // remove a "[ \t]*\*" block from each line, if possible
for line in lines.iter_mut() { for line in lines.iter_mut() {
*line = &line[horizontal + 1..]; if horizontal + 1 < line.len() {
*line = &line[horizontal + 1..];
}
} }
} }
if changes { if changes {

View file

@ -5,7 +5,7 @@ use rustc_span::create_default_session_globals_then;
fn test_block_doc_comment_1() { fn test_block_doc_comment_1() {
create_default_session_globals_then(|| { create_default_session_globals_then(|| {
let comment = "\n * Test \n ** Test\n * Test\n"; let comment = "\n * Test \n ** Test\n * Test\n";
let stripped = beautify_doc_string(Symbol::intern(comment)); let stripped = beautify_doc_string(Symbol::intern(comment), CommentKind::Block);
assert_eq!(stripped.as_str(), " Test \n* Test\n Test"); assert_eq!(stripped.as_str(), " Test \n* Test\n Test");
}) })
} }
@ -14,7 +14,7 @@ fn test_block_doc_comment_1() {
fn test_block_doc_comment_2() { fn test_block_doc_comment_2() {
create_default_session_globals_then(|| { create_default_session_globals_then(|| {
let comment = "\n * Test\n * Test\n"; let comment = "\n * Test\n * Test\n";
let stripped = beautify_doc_string(Symbol::intern(comment)); let stripped = beautify_doc_string(Symbol::intern(comment), CommentKind::Block);
assert_eq!(stripped.as_str(), " Test\n Test"); assert_eq!(stripped.as_str(), " Test\n Test");
}) })
} }
@ -23,7 +23,7 @@ fn test_block_doc_comment_2() {
fn test_block_doc_comment_3() { fn test_block_doc_comment_3() {
create_default_session_globals_then(|| { create_default_session_globals_then(|| {
let comment = "\n let a: *i32;\n *a = 5;\n"; let comment = "\n let a: *i32;\n *a = 5;\n";
let stripped = beautify_doc_string(Symbol::intern(comment)); let stripped = beautify_doc_string(Symbol::intern(comment), CommentKind::Block);
assert_eq!(stripped.as_str(), " let a: *i32;\n *a = 5;"); assert_eq!(stripped.as_str(), " let a: *i32;\n *a = 5;");
}) })
} }
@ -31,13 +31,13 @@ fn test_block_doc_comment_3() {
#[test] #[test]
fn test_line_doc_comment() { fn test_line_doc_comment() {
create_default_session_globals_then(|| { create_default_session_globals_then(|| {
let stripped = beautify_doc_string(Symbol::intern(" test")); let stripped = beautify_doc_string(Symbol::intern(" test"), CommentKind::Line);
assert_eq!(stripped.as_str(), " test"); assert_eq!(stripped.as_str(), " test");
let stripped = beautify_doc_string(Symbol::intern("! test")); let stripped = beautify_doc_string(Symbol::intern("! test"), CommentKind::Line);
assert_eq!(stripped.as_str(), "! test"); assert_eq!(stripped.as_str(), "! test");
let stripped = beautify_doc_string(Symbol::intern("test")); let stripped = beautify_doc_string(Symbol::intern("test"), CommentKind::Line);
assert_eq!(stripped.as_str(), "test"); assert_eq!(stripped.as_str(), "test");
let stripped = beautify_doc_string(Symbol::intern("!test")); let stripped = beautify_doc_string(Symbol::intern("!test"), CommentKind::Line);
assert_eq!(stripped.as_str(), "!test"); assert_eq!(stripped.as_str(), "!test");
}) })
} }

View file

@ -821,9 +821,9 @@ impl<'tcx> SaveContext<'tcx> {
let mut result = String::new(); let mut result = String::new();
for attr in attrs { for attr in attrs {
if let Some(val) = attr.doc_str() { if let Some((val, kind)) = attr.doc_str_and_comment_kind() {
// FIXME: Should save-analysis beautify doc strings itself or leave it to users? // FIXME: Should save-analysis beautify doc strings itself or leave it to users?
result.push_str(beautify_doc_string(val).as_str()); result.push_str(beautify_doc_string(val, kind).as_str());
result.push('\n'); result.push('\n');
} }
} }

View file

@ -1040,9 +1040,9 @@ impl Attributes {
) -> Attributes { ) -> Attributes {
let mut doc_strings: Vec<DocFragment> = vec![]; let mut doc_strings: Vec<DocFragment> = vec![];
let clean_attr = |(attr, parent_module): (&ast::Attribute, Option<DefId>)| { let clean_attr = |(attr, parent_module): (&ast::Attribute, Option<DefId>)| {
if let Some(value) = attr.doc_str() { if let Some((value, kind)) = attr.doc_str_and_comment_kind() {
trace!("got doc_str={:?}", value); trace!("got doc_str={:?}", value);
let value = beautify_doc_string(value); let value = beautify_doc_string(value, kind);
let kind = if attr.is_doc_comment() { let kind = if attr.is_doc_comment() {
DocFragmentKind::SugaredDoc DocFragmentKind::SugaredDoc
} else { } else {

View file

@ -0,0 +1,2 @@
<div class="docblock"><p>a</p>
</div>

View file

@ -0,0 +1,11 @@
#![crate_name = "foo"]
// The goal of this test is to answer that it won't be generated as a list because
// block doc comments can have their lines starting with a star.
// @has foo/fn.foo.html
// @snapshot docblock - '//*[@class="rustdoc-toggle top-doc"]//*[@class="docblock"]'
/**
* a
*/
pub fn foo() {}