pub(super) struct Comment {
pub(super) text: String,
pub(super) delete_start: usize,
pub(super) delete_end: usize,
pub(super) diag_start: usize,
pub(super) diag_end: usize,
}
pub(super) fn find_trailing_comment(source: &str, attr_hi: usize) -> Option<Comment> {
let bytes = source.as_bytes();
let mut cursor = attr_hi;
while cursor < bytes.len() && is_horizontal_whitespace(bytes[cursor]) {
cursor += 1;
}
if cursor + 2 > bytes.len() || &bytes[cursor..cursor + 2] != b"//" {
return None;
}
if is_doc_comment_prefix(&bytes[cursor + 2..]) {
return None;
}
let comment_start = cursor;
let mut end = comment_start;
while end < bytes.len() && bytes[end] != b'\n' {
end += 1;
}
let text_end = if end > comment_start && bytes[end - 1] == b'\r' {
end - 1
} else {
end
};
let text = normalise_comment_text(&source[comment_start..text_end]);
if text.is_empty() {
return None;
}
Some(Comment {
text,
delete_start: attr_hi,
delete_end: text_end,
diag_start: comment_start,
diag_end: text_end,
})
}
fn is_horizontal_whitespace(byte: u8) -> bool {
matches!(byte, b' ' | b'\t')
}
fn is_doc_comment_prefix(rest_after_slashes: &[u8]) -> bool {
match rest_after_slashes {
[b'!', ..] => true,
[b'/'] => true,
[b'/', next, ..] if *next != b'/' => true,
_ => false,
}
}
fn normalise_comment_text(content: &str) -> String {
let after_slashes = content.strip_prefix("//").unwrap_or(content);
let trimmed = after_slashes.trim_matches([' ', '\t', '\r']);
let bytes = trimmed.as_bytes();
let mut run = 0;
while run < bytes.len() && matches!(bytes[run], b'-' | b'=' | b'*') {
run += 1;
}
if run > 0 {
if run == bytes.len() {
return String::new();
}
if bytes
.get(run)
.is_some_and(|byte| is_horizontal_whitespace(*byte))
{
let after = &trimmed[run..];
return after.trim_start_matches([' ', '\t']).to_owned();
}
}
trimmed.to_owned()
}
#[cfg(test)]
mod tests;