use oxc_css_parser::ast::*;
use oxc_css_parser::pos::Span;
use super::Compiler;
use super::Layer;
use crate::doc::{Comment, Item};
pub(super) fn collect_definition_spans(stmts: &[Statement], out: &mut Vec<Span>) {
for st in stmts {
match st {
Statement::LessMixinDefinition(d) => {
out.push(d.block.span);
collect_definition_spans(&d.block.statements, out);
}
Statement::LessVariableDeclaration(d) => {
if let ComponentValue::LessDetachedRuleset(dr) = &d.value {
out.push(dr.block.span);
collect_definition_spans(&dr.block.statements, out);
}
}
Statement::QualifiedRule(r) => collect_definition_spans(&r.block.statements, out),
Statement::LessConditionalQualifiedRule(r) => {
collect_definition_spans(&r.block.statements, out);
}
Statement::AtRule(a) => {
if let Some(block) = &a.block {
collect_definition_spans(&block.statements, out);
}
}
_ => {}
}
}
}
pub(super) fn strip_block_comments(text: &str) -> String {
let mut out = String::with_capacity(text.len());
let mut cursor = 0;
for (start, end) in block_comments(text) {
out.push_str(&text[cursor..start]);
cursor = end;
}
out.push_str(&text[cursor.min(text.len())..]);
out.split_whitespace()
.collect::<Vec<_>>()
.join(" ")
.replace(" ,", ",")
}
pub(super) fn block_comments(text: &str) -> Vec<(usize, usize)> {
let mut out = Vec::new();
let bytes = text.as_bytes();
let mut i = 0;
while i + 1 < bytes.len() {
if bytes[i] == b'/' && bytes[i + 1] == b'*' {
match text[i + 2..].find("*/") {
Some(j) => {
out.push((i, i + 2 + j + 2));
i += 2 + j + 2;
}
None => break,
}
} else {
i += 1;
}
}
out
}
pub(super) fn child_header_span(st: &Statement) -> Option<Span> {
match st {
Statement::QualifiedRule(r) => Some(Span {
start: r.span.start,
end: r.block.span.start,
}),
Statement::LessConditionalQualifiedRule(r) => Some(Span {
start: r.span.start,
end: r.block.span.start,
}),
Statement::AtRule(a) => Some(Span {
start: a.name.span().start,
end: a.block.as_ref().map_or(a.span.end, |b| b.span.start),
}),
_ => None,
}
}
pub(super) fn child_block_span(st: &Statement) -> Option<Span> {
match st {
Statement::QualifiedRule(r) => Some(r.block.span),
Statement::LessConditionalQualifiedRule(r) => Some(r.block.span),
Statement::LessMixinDefinition(d) => Some(d.block.span),
Statement::LessVariableDeclaration(d) => match &d.value {
ComponentValue::LessDetachedRuleset(dr) => Some(dr.block.span),
_ => None,
},
Statement::AtRule(a) => a.block.as_ref().map(|b| b.span),
Statement::KeyframeBlock(k) => Some(k.block.span),
_ => None,
}
}
impl<'a> Compiler<'a> {
pub(super) fn mixin_comments(&mut self, block: &'a SimpleBlock<'a>) -> Vec<Comment> {
let children: Vec<Span> = block
.statements
.iter()
.filter_map(child_block_span)
.collect();
let mut out = Vec::new();
for (i, &(start, end)) in self.comments.iter().enumerate() {
if start < block.span.start
|| start >= block.span.end
|| children.iter().any(|c| c.start <= start && start < c.end)
{
continue;
}
self.comment_done[i] = true;
out.push(self.comment(start, end));
}
out
}
pub(super) fn own_comments(
&mut self,
block_span: Span,
stmts: &'a [Statement<'a>],
) -> Vec<Comment> {
let children: Vec<Span> = stmts.iter().filter_map(child_block_span).collect();
let headers: Vec<Span> = stmts.iter().filter_map(child_header_span).collect();
let values: Vec<Span> = stmts
.iter()
.filter_map(|st| {
let (start, from) = match st {
Statement::Declaration(d) => (d.span.start, d.colon_span.end),
Statement::LessVariableDeclaration(d) => (d.span.start, d.colon_span.end),
_ => return None,
};
let (_, end, _) = self.region_from(from)?;
Some(Span { start, end })
})
.collect();
let mut out = Vec::new();
for (i, &(start, end)) in self.comments.iter().enumerate() {
if self.comment_done[i]
|| start < block_span.start
|| start >= block_span.end
|| children.iter().any(|c| c.start <= start && start < c.end)
|| headers.iter().any(|h| h.start <= start && start < h.end)
|| values.iter().any(|v| v.start <= start && start < v.end)
{
continue;
}
self.comment_done[i] = true;
out.push(self.comment(start, end));
}
out
}
pub(super) fn trailing_comment(&self, end: usize) -> Option<String> {
let rest = self.src.get(end..)?;
let start = rest
.char_indices()
.find(|(_, c)| !matches!(c, ' ' | '\t' | '\r' | '\n'))
.map(|(i, _)| i)?;
let tail = &rest[start..];
let close = tail.strip_prefix("/*")?.find("*/")? + 4;
Some(tail[..close].to_string())
}
pub(super) fn mark_comments(&mut self, span: Span) {
for (i, &(start, _)) in self.comments.iter().enumerate() {
if start >= span.start && start < span.end {
self.comment_done[i] = true;
}
}
}
pub(super) fn take_comments(&mut self, limit: usize) -> Vec<Comment> {
let mut out = Vec::new();
while self.comment_idx < self.comments.len() {
let i = self.comment_idx;
let (start, end) = self.comments[i];
if start >= limit {
break;
}
self.comment_idx += 1;
if self.comment_done[i]
|| self
.def_spans
.iter()
.any(|s| s.start <= start && start < s.end)
{
continue;
}
self.comment_done[i] = true;
out.push(self.comment(start, end));
}
out
}
pub(super) fn flush_comments(&mut self, limit: usize, layer: &mut Layer) {
for comment in self.take_comments(limit) {
layer.items.push(Item::Comment(comment));
}
}
pub(super) fn comment(&self, start: usize, end: usize) -> Comment {
let column = self.src[..start]
.rfind('\n')
.map_or(start, |i| start - i - 1);
Comment {
text: self.src[start + 2..end.saturating_sub(2)].to_string(),
span: Span { start, end },
column,
}
}
}