use vize_l0::{Allocator, Namespace, Vec, is_void_tag};
use super::{HeaderPolicy, SurfacePolicy};
use crate::build::scope::{Frame, Recovery};
use crate::event::{EventKind, Recorder};
use crate::markup::entity::DecodedEntity;
use crate::markup::token::{LexErrorCode, LexMode, QuoteType, Sink};
use crate::surface::LintHeaderFact;
use core::marker::PhantomData;
struct ModeFrame<'a> {
tag: &'a str,
ns: Namespace,
verbatim: bool,
exact_pre: bool,
table_context: bool,
recovery_context: bool,
}
impl<'a> Frame<'a> for ModeFrame<'a> {
fn tag(&self) -> &'a str {
self.tag
}
fn namespace(&self) -> Namespace {
self.ns
}
}
pub(crate) struct VueSink<'a, 'v, P: SurfacePolicy> {
source: &'a str,
recorder: Recorder<'a, 'v>,
unsupported: &'v mut Vec<'a, P::Boundary>,
stack: Vec<'a, ModeFrame<'a>>,
recovery: Recovery<'a>,
tag: Option<&'a str>,
tag_events: usize,
policy: PhantomData<P>,
}
impl<'a, 'v, P: SurfacePolicy> VueSink<'a, 'v, P> {
pub(crate) fn new(
allocator: &'a Allocator,
source: &'a str,
recorder: Recorder<'a, 'v>,
unsupported: &'v mut Vec<'a, P::Boundary>,
) -> Self {
Self {
source,
recorder,
unsupported,
stack: Vec::new_in(&allocator),
recovery: Recovery::new(allocator, true),
tag: None,
tag_events: 0,
policy: PhantomData,
}
}
fn finish_tag(&mut self, self_closing: bool) -> (bool, LintHeaderFact) {
let Some(tag) = self.tag.take() else {
return (false, LintHeaderFact::default());
};
let header_literal = P::LINT_TAGS && self.mode() == LexMode::Verbatim;
let entry_recovery = P::LINT_TAGS
&& (self.stack.len() >= 4096
|| self
.stack
.last()
.is_some_and(|frame| frame.recovery_context));
let (ns, implicit_depth) = self.recovery.open(&self.stack, tag, self_closing);
if let Some(depth) = implicit_depth {
self.stack.truncate(depth);
}
let inherited = self.mode() == LexMode::Verbatim;
let inherited_exact =
P::LINT_TAGS && self.stack.last().is_some_and(|frame| frame.exact_pre);
let table_context =
P::LINT_TAGS && self.stack.last().is_some_and(|frame| frame.table_context);
let inherited_recovery = P::LINT_TAGS
&& self
.stack
.last()
.is_some_and(|frame| frame.recovery_context);
let recovery_context = entry_recovery || inherited_recovery;
let (heads, exact_head) = if inherited {
(HeaderPolicy::default(), false)
} else {
self.resolve_heads()
};
let verbatim = inherited || heads.pre;
let exact_pre = inherited_exact || exact_head;
let lint_tag = if P::LINT_TAGS {
P::lint_tag(
tag,
heads.structural_template,
verbatim,
exact_pre,
inherited,
)
} else {
None
};
if !self_closing && !is_void_tag(tag) {
let frame_recovery = P::LINT_TAGS
&& (inherited_recovery || recovery_spelling(tag) || self.stack.len() >= 4096);
self.stack.push(ModeFrame {
tag,
ns,
verbatim,
exact_pre,
table_context: P::LINT_TAGS && (table_context || tag.eq_ignore_ascii_case("table")),
recovery_context: frame_recovery,
});
}
(
verbatim,
LintHeaderFact {
kind: lint_tag,
literal: header_literal,
table_context,
recovery_context,
},
)
}
fn resolve_heads(&mut self) -> (HeaderPolicy, bool) {
let unsupported_start = self.unsupported.len();
let mut name_start = None;
let mut policy = HeaderPolicy::default();
let mut exact_pre = false;
for event in self.recorder.events.iter().skip(self.tag_events) {
match event.kind {
EventKind::AttrName => {
name_start.get_or_insert(event.start);
}
EventKind::AttrNameEnd => {
if let Some(start) = name_start.take()
&& let Some(raw) = self.source.get(start as usize..event.start as usize)
{
let header = if P::LINT_TAGS {
P::lint_header(raw, start, self.source)
} else {
P::pre(raw, start, self.source).map(|pre| HeaderPolicy {
pre,
structural_template: false,
})
};
match header {
Ok(header) => {
policy.pre |= header.pre;
policy.structural_template |= header.structural_template;
exact_pre |= P::LINT_TAGS && raw == "v-pre";
}
Err(boundary) => self.unsupported.push(boundary),
}
}
}
_ => {}
}
}
if policy.pre {
self.unsupported.truncate(unsupported_start);
}
(policy, exact_pre)
}
}
macro_rules! forward {
($($name:ident($($arg:ident: $ty:ty),*);)*) => {
$(fn $name(&mut self, $($arg: $ty),*) {
self.recorder.$name($($arg),*);
})*
};
}
impl<P: SurfacePolicy> Sink for VueSink<'_, '_, P> {
forward! {
on_text(start: usize, end: usize);
on_text_entity(ch: char, start: usize, end: usize);
on_text_entity_value(value: DecodedEntity, start: usize, end: usize);
on_attrib_data(start: usize, end: usize);
on_attrib_entity(ch: char, start: usize, end: usize);
on_attrib_entity_value(value: DecodedEntity, start: usize, end: usize);
on_attrib_end(quote: QuoteType, end: usize);
on_attrib_name(start: usize, end: usize);
on_attrib_name_end(end: usize);
on_dir_name(start: usize, end: usize);
on_dir_arg(start: usize, end: usize);
on_dir_modifier(start: usize, end: usize);
on_comment(start: usize, end: usize);
on_cdata(start: usize, end: usize);
on_processing_instruction(start: usize, end: usize);
on_end();
on_error(code: LexErrorCode, index: usize);
}
fn on_interpolation(&mut self, start: usize, end: usize) {
P::interpolation(
self.source,
&mut self.recorder,
self.unsupported,
start,
end,
false,
);
}
fn on_raw_interpolation(&mut self, start: usize, end: usize) {
P::interpolation(
self.source,
&mut self.recorder,
self.unsupported,
start,
end,
true,
);
}
fn on_open_tag_name(&mut self, start: usize, end: usize) {
self.tag = self.source.get(start..end);
self.tag_events = self.recorder.events.len();
self.recorder.on_open_tag_name(start, end);
}
fn svg_title_children(&self) -> bool {
self.stack.last().is_some_and(|parent| {
parent.ns == Namespace::Svg && !matches!(parent.tag, "foreignObject" | "desc" | "title")
})
}
fn on_open_tag_end(&mut self, end: usize) {
let (verbatim, facts) = self.finish_tag(false);
if P::LINT_TAGS {
self.recorder
.opening_end_with_lint(EventKind::OpenTagEnd, end, verbatim, facts);
} else {
self.recorder
.opening_end(EventKind::OpenTagEnd, end, verbatim);
}
}
fn on_self_closing_tag(&mut self, end: usize) {
let (verbatim, facts) = self.finish_tag(true);
if P::LINT_TAGS {
self.recorder
.opening_end_with_lint(EventKind::SelfClosingTag, end, verbatim, facts);
} else {
self.recorder
.opening_end(EventKind::SelfClosingTag, end, verbatim);
}
}
fn on_close_tag(&mut self, start: usize, end: usize) {
if let Some(tag) = self.source.get(start..end)
&& let Some(depth) = self.recovery.close(&self.stack, tag)
{
self.stack.truncate(depth);
}
self.recorder.on_close_tag(start, end);
}
fn mode(&self) -> LexMode {
if self.stack.last().is_some_and(|frame| frame.verbatim) {
LexMode::Verbatim
} else {
LexMode::Normal
}
}
}
fn recovery_spelling(tag: &str) -> bool {
[
"p", "form", "a", "button", "li", "dt", "dd", "option", "optgroup", "b", "big", "code",
"em", "font", "i", "nobr", "s", "small", "strike", "strong", "tt", "u",
]
.iter()
.any(|name| tag.eq_ignore_ascii_case(name))
}