| use rustc_span::{BytePos, Symbol}; |
| |
| use crate::token::CommentKind; |
| |
| #[cfg(test)] |
| mod tests; |
| |
| #[derive(Clone, Copy, PartialEq, Debug)] |
| pub enum CommentStyle { |
| /// No code on either side of each line of the comment |
| Isolated, |
| /// Code exists to the left of the comment |
| Trailing, |
| /// Code before /* foo */ and after the comment |
| Mixed, |
| /// Just a manual blank line "\n\n", for layout |
| BlankLine, |
| } |
| |
| #[derive(Clone)] |
| pub struct Comment { |
| pub style: CommentStyle, |
| pub lines: Vec<String>, |
| pub pos: BytePos, |
| } |
| |
| /// A fast conservative estimate on whether the string can contain documentation links. |
| /// A pair of square brackets `[]` must exist in the string, but we only search for the |
| /// opening bracket because brackets always go in pairs in practice. |
| #[inline] |
| pub fn may_have_doc_links(s: &str) -> bool { |
| s.contains('[') |
| } |
| |
| /// Makes a doc string more presentable to users. |
| /// Used by rustdoc and perhaps other tools, but not by rustc. |
| pub fn beautify_doc_string(data: Symbol, kind: CommentKind) -> Symbol { |
| fn get_vertical_trim(lines: &[&str]) -> Option<(usize, usize)> { |
| let mut i = 0; |
| let mut j = lines.len(); |
| // first line of all-stars should be omitted |
| if lines.first().is_some_and(|line| line.chars().all(|c| c == '*')) { |
| i += 1; |
| } |
| |
| // like the first, a last line of all stars should be omitted |
| if j > i && !lines[j - 1].is_empty() && lines[j - 1].chars().all(|c| c == '*') { |
| j -= 1; |
| } |
| |
| if i != 0 || j != lines.len() { Some((i, j)) } else { None } |
| } |
| |
| fn get_horizontal_trim(lines: &[&str], kind: CommentKind) -> Option<String> { |
| let mut i = usize::MAX; |
| 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 = match kind { |
| CommentKind::Block => { |
| // Whatever happens, we skip the first line. |
| let mut i = lines |
| .first() |
| .map(|l| if l.trim_start().starts_with('*') { 0 } else { 1 }) |
| .unwrap_or(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] |
| } |
| CommentKind::Line => lines, |
| }; |
| |
| for line in lines { |
| for (j, c) in line.chars().enumerate() { |
| if j > i || !"* \t".contains(c) { |
| return None; |
| } |
| if c == '*' { |
| if first { |
| i = j; |
| first = false; |
| } else if i != j { |
| return None; |
| } |
| break; |
| } |
| } |
| if i >= line.len() { |
| return None; |
| } |
| } |
| Some(lines.first()?[..i].to_string()) |
| } |
| |
| let data_s = data.as_str(); |
| if data_s.contains('\n') { |
| let mut lines = data_s.lines().collect::<Vec<&str>>(); |
| let mut changes = false; |
| let lines = if let Some((i, j)) = get_vertical_trim(&lines) { |
| changes = true; |
| // remove whitespace-only lines from the start/end of lines |
| &mut lines[i..j] |
| } else { |
| &mut lines |
| }; |
| if let Some(horizontal) = get_horizontal_trim(lines, kind) { |
| changes = true; |
| // remove a "[ \t]*\*" block from each line, if possible |
| for line in lines.iter_mut() { |
| if let Some(tmp) = line.strip_prefix(&horizontal) { |
| *line = tmp; |
| if kind == CommentKind::Block |
| && (*line == "*" || line.starts_with("* ") || line.starts_with("**")) |
| { |
| *line = &line[1..]; |
| } |
| } |
| } |
| } |
| if changes { |
| return Symbol::intern(&lines.join("\n")); |
| } |
| } |
| data |
| } |