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, 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 { 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::>(); 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 }