use std::cell::{Cell, RefCell};
use super::CommentMode;
use tsv_lang::{Comment, JsonWriter, Span, WirePositions};
pub struct IslandComments<'a> {
pub queue: Vec<&'a Comment>,
pub root_parent_end: Option<u32>,
pub root_fallback: bool,
pub html_leading: Option<&'a str>,
pub removes_parens: bool,
pub root_parens: Vec<Span>,
}
struct Frame {
#[cfg(debug_assertions)]
node_type: &'static str,
span: Span,
lead_start: u32,
lead_end: u32,
is_last_in_body: bool,
}
#[derive(Default)]
struct State {
head: usize,
next_is_last_body: bool,
frames: Vec<Frame>,
virtual_roots: usize,
attached: Vec<u32>,
front_floor: Option<(usize, u32)>,
#[cfg(debug_assertions)]
skipped: Vec<SkippedFrame>,
#[cfg(debug_assertions)]
skipped_mark_pending: bool,
}
#[cfg(debug_assertions)]
struct SkippedFrame {
node_type: &'static str,
span: Span,
is_last_in_body: Option<bool>,
}
pub struct CommentAttach<'a> {
source: &'a str,
island: IslandComments<'a>,
skip_depth: Cell<u32>,
state: RefCell<State>,
}
impl<'a> CommentAttach<'a> {
#[must_use]
pub fn new(source: &'a str, island: IslandComments<'a>) -> Self {
let attach = Self {
source,
island,
skip_depth: Cell::new(0),
state: RefCell::new(State::default()),
};
if !attach.island.root_parens.is_empty() {
let st = &mut *attach.state.borrow_mut();
for &span in &attach.island.root_parens {
attach.push_frame(st, "ParenthesizedExpression", span, false);
}
st.virtual_roots = st.frames.len();
}
attach
}
#[must_use]
pub(super) fn removes_parens(&self) -> bool {
self.island.removes_parens
}
#[must_use]
pub fn mode(&self) -> CommentMode<'_> {
if self.island.queue.is_empty() && self.island.html_leading.is_none() {
CommentMode::Off
} else {
CommentMode::Attach(self)
}
}
#[cold]
#[inline]
pub(super) fn open(&self, node_type: &'static str, span: Span) {
let depth = self.skip_depth.get();
if depth > 0 {
self.skip_depth.set(depth + 1);
#[cfg(debug_assertions)]
self.debug_open_skipped(node_type, span);
return;
}
self.open_attached(node_type, span);
}
#[cold]
fn open_attached(&self, node_type: &'static str, span: Span) {
let st = &mut *self.state.borrow_mut();
let is_last_in_body = std::mem::take(&mut st.next_is_last_body);
#[cfg(debug_assertions)]
self.debug_check_open(st, node_type, span);
if st.frames.len() > st.virtual_roots
&& let Some(parent_end) = st.frames.last().map(|f| f.span.end)
&& self.subtree_takes_nothing(st, span, parent_end, is_last_in_body)
{
self.skip_depth.set(1);
#[cfg(debug_assertions)]
st.skipped.push(SkippedFrame {
node_type,
span,
is_last_in_body: Some(is_last_in_body),
});
return;
}
self.push_frame(st, node_type, span, is_last_in_body);
}
#[cfg_attr(not(debug_assertions), expect(unused_variables))]
fn push_frame(
&self,
st: &mut State,
node_type: &'static str,
span: Span,
is_last_in_body: bool,
) {
let lead_start = st.attached.len() as u32;
while self.front(st).is_some_and(|c| c.span.start < span.start) {
st.attached.push(st.head as u32);
st.head += 1;
}
let lead_end = st.attached.len() as u32;
st.frames.push(Frame {
#[cfg(debug_assertions)]
node_type,
span,
lead_start,
lead_end,
is_last_in_body,
});
}
fn subtree_takes_nothing(
&self,
st: &mut State,
span: Span,
parent_end: u32,
is_last_in_body: bool,
) -> bool {
let Some(front) = self.front(st) else {
return true;
};
span.end < self.front_floor(st, front)
&& (!is_last_in_body || front.span.start >= parent_end)
}
fn front_floor(&self, st: &mut State, front: &Comment) -> u32 {
if let Some((head, floor)) = st.front_floor
&& head == st.head
{
return floor;
}
let before = &self.source.as_bytes()[..front.span.start as usize];
let floor = before
.iter()
.rposition(|&b| !is_trailing_gap_byte(b))
.map_or(0, |i| i + 1) as u32;
st.front_floor = Some((st.head, floor));
floor
}
#[cold]
pub(super) fn mark_last_body_element(&self) {
#[cfg(debug_assertions)]
self.debug_mark_skipped();
if self.skip_depth.get() == 0 {
self.state.borrow_mut().next_is_last_body = true;
}
}
#[cold]
#[inline(never)]
pub(super) fn close_and_emit(
&self,
w: &mut JsonWriter,
node_type: &'static str,
span: Span,
positions: WirePositions<'_>,
) {
let depth = self.skip_depth.get();
if depth > 0 {
self.skip_depth.set(depth - 1);
#[cfg(debug_assertions)]
self.debug_close_skipped(node_type, span);
return;
}
self.close_attached(w, node_type, span, positions);
}
#[cold]
fn close_attached(
&self,
w: &mut JsonWriter,
node_type: &'static str,
span: Span,
positions: WirePositions<'_>,
) {
let st = &mut *self.state.borrow_mut();
let Some(frame) = self.pop_frame(st, node_type, span) else {
return;
};
let is_root = st.frames.is_empty();
self.claim_at_close(st, &frame, node_type, span);
let leading = &st.attached[frame.lead_start as usize..frame.lead_end as usize];
let trailing = &st.attached[frame.lead_end as usize..];
let html = if is_root {
self.island.html_leading
} else {
None
};
self.emit(w, leading, trailing, html, positions);
st.attached.truncate(frame.lead_start as usize);
if st.virtual_roots != 0 {
self.close_root_parens(st);
}
}
#[cold]
pub(super) fn paren_open(&self, span: Span) {
self.open("ParenthesizedExpression", span);
}
#[cold]
#[inline(never)]
pub(super) fn paren_close(&self, span: Span) {
const NODE_TYPE: &str = "ParenthesizedExpression";
let depth = self.skip_depth.get();
if depth > 0 {
self.skip_depth.set(depth - 1);
#[cfg(debug_assertions)]
self.debug_close_skipped(NODE_TYPE, span);
return;
}
let st = &mut *self.state.borrow_mut();
let Some(frame) = self.pop_frame(st, NODE_TYPE, span) else {
return;
};
self.claim_at_close(st, &frame, NODE_TYPE, span);
st.attached.truncate(frame.lead_start as usize);
if st.virtual_roots != 0 {
self.close_root_parens(st);
}
}
#[expect(clippy::inline_always)]
#[inline(always)]
#[cfg_attr(not(debug_assertions), expect(unused_variables))]
fn pop_frame(&self, st: &mut State, node_type: &'static str, span: Span) -> Option<Frame> {
debug_assert!(
!st.next_is_last_body,
"{node_type} closed with an unconsumed last-body mark — the marked element \
emitted no node, so the mark would land on an unrelated one"
);
let Some(frame) = st.frames.pop() else {
debug_assert!(false, "attach close without open: {node_type}");
return None;
};
#[cfg(debug_assertions)]
assert!(
frame.node_type == node_type && frame.span == span,
"attach open/close mismatch: opened {} ({},{}), closed {} ({},{})",
frame.node_type,
frame.span.start,
frame.span.end,
node_type,
span.start,
span.end,
);
debug_assert_eq!(
frame.lead_end as usize,
st.attached.len(),
"a closing node's descendants left entries behind — its trailing run must \
start exactly at the end of its own leading run"
);
Some(frame)
}
#[expect(clippy::inline_always)]
#[inline(always)]
fn claim_at_close(&self, st: &mut State, frame: &Frame, node_type: &'static str, span: Span) {
let is_root = st.frames.is_empty();
let parent_end = if is_root {
self.island.root_parent_end
} else {
st.frames.last().map(|f| f.span.end)
};
self.attach_trailing(st, span, parent_end, frame.is_last_in_body);
if is_root && self.island.root_fallback {
self.attach_root_fallback(st, node_type, span);
}
}
#[cold]
#[inline(never)]
fn close_root_parens(&self, st: &mut State) {
if st.frames.len() != st.virtual_roots {
return;
}
st.virtual_roots = 0;
while let Some(frame) = st.frames.pop() {
debug_assert_eq!(frame.lead_end as usize, st.attached.len());
self.claim_at_close(st, &frame, "ParenthesizedExpression", frame.span);
st.attached.truncate(frame.lead_start as usize);
}
}
#[inline]
fn front(&self, st: &State) -> Option<&'a Comment> {
(st.head < self.island.queue.len()).then(|| self.island.queue[st.head])
}
fn attach_trailing(
&self,
st: &mut State,
span: Span,
parent_end: Option<u32>,
is_last_in_body: bool,
) {
for _ in 0..self.trailing_claim(st, span, parent_end, is_last_in_body) {
st.attached.push(st.head as u32);
st.head += 1;
}
}
fn trailing_claim(
&self,
st: &State,
span: Span,
parent_end: Option<u32>,
is_last_in_body: bool,
) -> usize {
let Some(first) = self.front(st) else {
return 0;
};
if parent_end == Some(span.end) {
return 0;
}
if is_last_in_body {
let rest = &self.island.queue[st.head..];
match parent_end {
Some(pe) => rest.iter().take_while(|c| c.span.start < pe).count(),
None => rest.len(),
}
} else if span.end <= first.span.start {
let gap = &self.source.as_bytes()[span.end as usize..first.span.start as usize];
usize::from(gap.iter().all(|&b| is_trailing_gap_byte(b)))
} else {
0
}
}
#[cfg(debug_assertions)]
fn debug_check_open(&self, st: &State, node_type: &'static str, span: Span) {
let parent = match st.skipped.last() {
Some(skipped) => Some((skipped.node_type, skipped.span)),
None if st.frames.len() >= st.virtual_roots + 2 => {
st.frames.last().map(|f| (f.node_type, f.span))
}
None => None,
};
if let Some((parent_type, parent_span)) = parent {
assert!(
span.end <= parent_span.end,
"{node_type} ({},{}) ends past its non-root parent {parent_type} ({},{}) — \
the comment attach's subtree skip assumes it cannot",
span.start,
span.end,
parent_span.start,
parent_span.end,
);
}
if self.skip_depth.get() > 0 {
assert!(
self.front(st).is_none_or(|c| c.span.start >= span.start),
"skipped {node_type} ({},{}) would have taken a leading comment",
span.start,
span.end,
);
}
}
#[cfg(debug_assertions)]
pub(super) fn debug_assert_opens_island_root(&self, node_type: &'static str, span: Span) {
assert!(
self.skip_depth.get() == 0 && self.state.borrow().frames.is_empty(),
"{node_type} ({},{}) has a child ending past it but is not its island's root — \
the comment attach's subtree skip assumes only a root can",
span.start,
span.end,
);
}
#[cfg(debug_assertions)]
fn debug_mark_skipped(&self) {
if self.skip_depth.get() > 0 {
self.state.borrow_mut().skipped_mark_pending = true;
}
}
#[cfg(debug_assertions)]
fn debug_open_skipped(&self, node_type: &'static str, span: Span) {
let st = &mut *self.state.borrow_mut();
assert!(
!st.next_is_last_body,
"a last-body mark is recorded inside a skipped subtree"
);
st.skipped_mark_pending = false;
self.debug_check_open(st, node_type, span);
st.skipped.push(SkippedFrame {
node_type,
span,
is_last_in_body: None,
});
}
#[cfg(debug_assertions)]
fn debug_close_skipped(&self, node_type: &'static str, span: Span) {
let st = &mut *self.state.borrow_mut();
assert!(
!st.next_is_last_body && !st.skipped_mark_pending,
"{node_type} closed with an unconsumed last-body mark in a skipped subtree — \
the marked element emitted no node, so outside a skip the mark would land on \
an unrelated one"
);
let Some(frame) = st.skipped.pop() else {
debug_assert!(false, "a skipped close without a skipped open: {node_type}");
return;
};
assert!(
frame.node_type == node_type && frame.span == span,
"attach open/close mismatch in a skipped subtree: opened {} ({},{}), closed \
{node_type} ({},{})",
frame.node_type,
frame.span.start,
frame.span.end,
span.start,
span.end,
);
let Some(parent_end) = st
.skipped
.last()
.map(|parent| parent.span.end)
.or_else(|| st.frames.last().map(|f| f.span.end))
else {
debug_assert!(false, "a skipped subtree's root is never the island root");
return;
};
let readings: &[bool] = match frame.is_last_in_body {
Some(is_last_in_body) => &[is_last_in_body][..],
None => &[false, true],
};
for &is_last_in_body in readings {
assert_eq!(
self.trailing_claim(st, span, Some(parent_end), is_last_in_body),
0,
"skipped {node_type} ({},{}) would have taken a trailing comment \
(last in body: {is_last_in_body})",
span.start,
span.end,
);
}
}
fn attach_root_fallback(&self, st: &mut State, node_type: &'static str, span: Span) {
let Some(first) = self.front(st) else {
return;
};
if first.span.start < span.end && node_type != "Program" {
return;
}
while st.head < self.island.queue.len() {
st.attached.push(st.head as u32);
st.head += 1;
}
}
fn emit(
&self,
w: &mut JsonWriter,
leading: &[u32],
trailing: &[u32],
html: Option<&str>,
positions: WirePositions<'_>,
) {
if leading.is_empty() && trailing.is_empty() && html.is_none() {
return;
}
if leading.is_empty() && !trailing.is_empty() {
self.emit_trailing(w, trailing, positions);
self.emit_leading(w, leading, html, positions);
} else {
self.emit_leading(w, leading, html, positions);
self.emit_trailing(w, trailing, positions);
}
}
fn emit_leading(
&self,
w: &mut JsonWriter,
leading: &[u32],
html: Option<&str>,
positions: WirePositions<'_>,
) {
if leading.is_empty() && html.is_none() {
return;
}
w.raw(",\"leadingComments\":[");
if let Some(value) = html {
w.raw("{\"type\":\"Line\",\"value\":");
w.string(value);
w.raw("}");
if !leading.is_empty() {
w.raw(",");
}
}
self.write_run(w, leading, positions);
w.raw("]");
}
fn emit_trailing(&self, w: &mut JsonWriter, trailing: &[u32], positions: WirePositions<'_>) {
if trailing.is_empty() {
return;
}
w.raw(",\"trailingComments\":[");
self.write_run(w, trailing, positions);
w.raw("]");
}
fn write_run(&self, w: &mut JsonWriter, run: &[u32], positions: WirePositions<'_>) {
for (i, &idx) in run.iter().enumerate() {
if i > 0 {
w.raw(",");
}
let comment = self.island.queue[idx as usize];
w.raw("{\"type\":\"");
w.raw(if comment.is_block { "Block" } else { "Line" });
w.raw("\",\"value\":");
w.string(&comment.wire_value(self.source));
w.span_start_end_field(positions, comment.span.start, comment.span.end);
w.raw("}");
}
}
}
#[inline]
fn is_trailing_gap_byte(b: u8) -> bool {
matches!(b, b',' | b')' | b' ' | b'\t')
}
#[cfg(debug_assertions)]
impl Drop for CommentAttach<'_> {
fn drop(&mut self) {
if std::thread::panicking() {
return;
}
let st = self.state.borrow();
let skipped = self.skip_depth.get() as usize;
assert!(
st.frames.is_empty() && st.attached.is_empty() && skipped == 0,
"an island finished with {} unclosed node(s) ({skipped} of them skipped) and {} \
unemitted comment(s)",
st.frames.len() + skipped,
st.attached.len(),
);
debug_assert_eq!(st.skipped.len(), skipped);
}
}