#![forbid(unsafe_code)]
#[cfg(feature = "native-parser")]
use crate::parser::{lexer::Lexer, span::Span, token::Token};
#[cfg(feature = "native-parser")]
pub(crate) struct CommentEntry {
pub span: Span,
pub text: String,
}
#[cfg(feature = "native-parser")]
pub(crate) struct CommentMap {
entries: Vec<CommentEntry>,
}
#[cfg(feature = "native-parser")]
impl CommentMap {
pub fn build(mut entries: Vec<CommentEntry>) -> Self {
entries.sort_by_key(|e| e.span.start);
Self { entries }
}
pub fn extract(src: &str) -> Self {
let mut entries = Vec::new();
for result in Lexer::new(src) {
let spanned = match result {
Ok(s) => s,
Err(_) => continue,
};
match spanned.value {
Token::LineComment(text) => {
let formatted = format!("{text}\n");
entries.push(CommentEntry {
span: spanned.span,
text: formatted,
});
}
Token::BlockComment(text) => {
let formatted = format!("{text}\n");
entries.push(CommentEntry {
span: spanned.span,
text: formatted,
});
}
Token::Eof => break,
_ => {}
}
}
Self::build(entries)
}
fn last_before(&self, pos: usize) -> Option<usize> {
let mut result = None;
for (i, e) in self.entries.iter().enumerate() {
if e.span.end <= pos {
result = Some(i);
} else {
break;
}
}
result
}
pub fn leading_for(&self, decl_start: usize, src: &[u8]) -> (Option<String>, Vec<String>) {
let last_idx = match self.last_before(decl_start) {
None => return (None, Vec::new()),
Some(i) => i,
};
let mut groups: Vec<Vec<String>> = Vec::new();
let mut current_group: Vec<String> = Vec::new();
let mut boundary = decl_start;
let mut idx = last_idx as isize;
while idx >= 0 {
let entry = &self.entries[idx as usize];
let gap_start = entry.span.end;
let gap_end = boundary.min(src.len());
let gap = if gap_end > gap_start {
&src[gap_start..gap_end]
} else {
&[]
};
let newlines = gap.iter().filter(|&&b| b == b'\n').count();
if newlines >= 2 {
if !current_group.is_empty() {
current_group.reverse();
groups.push(current_group);
current_group = Vec::new();
}
}
current_group.push(entry.text.clone());
boundary = entry.span.start;
idx -= 1;
}
if !current_group.is_empty() {
current_group.reverse();
groups.push(current_group);
}
groups.reverse();
if groups.is_empty() {
return (None, Vec::new());
}
let leading_group = groups.pop().expect("non-empty groups");
let leading = if leading_group.is_empty() {
None
} else {
Some(leading_group.concat())
};
let detached: Vec<String> = groups.into_iter().map(|g| g.concat()).collect();
(leading, detached)
}
pub fn trailing_for(&self, decl_end: usize, src: &[u8]) -> Option<String> {
let line_of_end = src[..decl_end.min(src.len())]
.iter()
.filter(|&&b| b == b'\n')
.count();
for entry in &self.entries {
if entry.span.start < decl_end {
continue;
}
let line_of_comment = src[..entry.span.start.min(src.len())]
.iter()
.filter(|&&b| b == b'\n')
.count();
if line_of_comment == line_of_end {
return Some(entry.text.clone());
}
if line_of_comment > line_of_end {
break;
}
}
None
}
}