use vize_l0::config::{VueDialect, VueVersion};
use vize_l0::{Allocator, SourceBlock, SourceRoot, Span, Vec};
use super::super::{Container, ContainerError};
use crate::{embed::Lang, markup::parse::SurfaceParseOptions};
mod policy;
mod template;
pub(super) mod vue1;
pub(super) mod vue2;
pub use template::{NativeTemplateFrameNameRefusal, NativeTemplateFrameNames, TemplateView};
use template::{TemplateNamePair, TemplateSelection};
pub use vue1::{Vue1DescriptorObservation, Vue1DescriptorRefusal, Vue1TemplateView};
pub use vue2::{
Vue2DescriptorExpressionPool, Vue2DescriptorObservation, Vue2DescriptorRefusal,
Vue2RetainedTemplateView, Vue2TemplateView,
};
#[cfg(test)]
mod style_tests;
#[cfg(test)]
mod tests;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct DescriptorOptions {
pub version: VueVersion,
pub dialect: VueDialect,
pub template: SurfaceParseOptions,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum DescriptorIssueCode {
UnsupportedVersion,
UnsupportedDialect,
UnsupportedOptions,
UnsupportedBlock,
UnsupportedBlockSpelling,
UnsupportedAttribute,
DuplicateAttribute,
AmbiguousAttribute,
UnsupportedSetupValue,
ExternalSource,
EncodedLanguage,
UnsupportedLanguage,
LanguageMismatch,
DuplicateRole,
UnsupportedBoundary,
InvalidSourceFrame,
MissingComponentBlock,
UnsupportedScript,
UnsupportedStyle,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct DescriptorIssue {
pub code: DescriptorIssueCode,
pub container_index: Option<usize>,
pub span: Span,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ScriptRole {
Ordinary,
Setup,
}
#[derive(Debug, Clone, Copy)]
struct Selection<'a> {
index: usize,
block: SourceBlock<'a>,
lang: Lang,
}
#[derive(Debug, Clone, Copy)]
struct StyleSelection<'a> {
index: usize,
block: SourceBlock<'a>,
}
#[derive(Debug)]
pub struct DescriptorObservation<'a> {
container: Container<'a>,
root: Option<SourceRoot<'a>>,
options: DescriptorOptions,
issues: Vec<'a, DescriptorIssue>,
ordinary: Option<Selection<'a>>,
setup: Option<Selection<'a>>,
template: Option<TemplateSelection<'a>>,
styles: Vec<'a, StyleSelection<'a>>,
}
impl<'a> DescriptorObservation<'a> {
pub fn source(&self) -> &'a str {
self.container.source
}
pub fn root(&self) -> Option<SourceRoot<'a>> {
self.root
}
pub fn container(&self) -> &Container<'a> {
&self.container
}
pub fn options(&self) -> DescriptorOptions {
self.options
}
pub fn issues(&self) -> &[DescriptorIssue] {
&self.issues
}
pub fn admitted(&self) -> Result<AdmittedDescriptor<'_, 'a>, DescriptorRefusal<'_>> {
if let Some(root) = self.root
&& self.issues.is_empty()
&& self.container.errors.is_empty()
{
Ok(AdmittedDescriptor { owner: self, root })
} else {
Err(DescriptorRefusal {
issues: &self.issues,
errors: &self.container.errors,
})
}
}
}
#[derive(Debug, Clone, Copy)]
pub struct DescriptorRefusal<'o> {
issues: &'o [DescriptorIssue],
errors: &'o [ContainerError],
}
impl DescriptorRefusal<'_> {
pub fn issues(&self) -> &[DescriptorIssue] {
self.issues
}
pub fn errors(&self) -> &[ContainerError] {
self.errors
}
}
#[derive(Debug, Clone, Copy)]
pub struct AdmittedDescriptor<'o, 'a> {
owner: &'o DescriptorObservation<'a>,
root: SourceRoot<'a>,
}
impl<'o, 'a> AdmittedDescriptor<'o, 'a> {
pub fn root(self) -> SourceRoot<'a> {
self.root
}
pub fn source(self) -> &'a str {
self.owner.source()
}
pub fn ordinary(self) -> Option<ScriptView<'o, 'a>> {
self.owner.ordinary.map(|selected| ScriptView {
owner: self.owner,
selected,
role: ScriptRole::Ordinary,
})
}
pub fn setup(self) -> Option<ScriptView<'o, 'a>> {
self.owner.setup.map(|selected| ScriptView {
owner: self.owner,
selected,
role: ScriptRole::Setup,
})
}
pub fn template(self) -> Option<TemplateView<'o, 'a>> {
self.owner.template.as_ref().map(|selected| TemplateView {
owner: self.owner,
selected,
})
}
pub fn styles(self) -> impl ExactSizeIterator<Item = StyleView<'o, 'a>> {
self.owner
.styles
.iter()
.copied()
.map(move |selected| StyleView {
owner: self.owner,
selected,
})
}
pub fn template_lang(self) -> Lang {
self.owner
.ordinary
.or(self.owner.setup)
.map_or(Lang::Js, |selected| selected.lang)
}
}
#[derive(Debug, Clone, Copy)]
pub struct ScriptView<'o, 'a> {
owner: &'o DescriptorObservation<'a>,
selected: Selection<'a>,
role: ScriptRole,
}
impl<'a> ScriptView<'_, 'a> {
pub fn source(&self) -> &'a str {
self.owner.source()
}
pub fn container_index(&self) -> usize {
self.selected.index
}
pub fn block(&self) -> SourceBlock<'a> {
self.selected.block
}
pub fn lang(&self) -> Lang {
self.selected.lang
}
pub fn role(&self) -> ScriptRole {
self.role
}
}
#[derive(Debug, Clone, Copy)]
pub struct StyleView<'o, 'a> {
owner: &'o DescriptorObservation<'a>,
selected: StyleSelection<'a>,
}
impl<'o, 'a> StyleView<'o, 'a> {
pub fn source(&self) -> &'a str {
self.owner.source()
}
pub fn container_index(&self) -> usize {
self.selected.index
}
pub fn block(&self) -> SourceBlock<'a> {
self.selected.block
}
#[expect(
clippy::indexing_slicing,
reason = "Only the original splitter records indices; the owner exposes no mutable blocks"
)]
pub fn original_block(&self) -> &'o crate::container::Block<'a> {
&self.owner.container.blocks[self.selected.index]
}
pub fn attrs(&self) -> &'o [crate::container::BlockAttr<'a>] {
&self.original_block().attrs
}
}
pub(super) fn observe<'a>(
allocator: &'a Allocator,
source: &'a str,
options: DescriptorOptions,
) -> DescriptorObservation<'a> {
let mut state = policy::Policy::new(allocator, source, options);
let container = super::split_with(
allocator,
source,
|index, block, uncertain, self_closing, closing| {
state.record(index, block, uncertain, self_closing, closing);
},
);
state.finish();
DescriptorObservation {
container,
root: state.root,
options,
issues: state.issues,
ordinary: state.ordinary,
setup: state.setup,
template: state.template,
styles: state.styles,
}
}
#[derive(Debug)]
pub struct TemplateOnlyDescriptor<'a> {
block: SourceBlock<'a>,
opening: Span,
closing: Span,
}
impl<'a> TemplateOnlyDescriptor<'a> {
pub fn block(&self) -> SourceBlock<'a> {
self.block
}
pub fn open_tag(&self) -> Span {
self.opening
}
pub fn close_tag(&self) -> Span {
self.closing
}
}
pub(super) fn observe_template_only(source: &str) -> Option<TemplateOnlyDescriptor<'_>> {
let root = SourceRoot::new(source).ok()?;
let envelope = source.trim_ascii();
if !envelope.starts_with("<template>") || !envelope.ends_with("</template>") {
return None;
}
let start = source.len() - source.trim_ascii_start().len();
let content_start = start + "<template>".len();
let (content_end, closing, uncertain) = super::scan::find_template_close(source, content_start);
let closing = closing?;
if uncertain || !source.get(closing.end()..)?.trim_ascii().is_empty() {
return None;
}
let content = source.get(content_start..content_end)?;
Some(TemplateOnlyDescriptor {
block: root.block(content, content_start as u32).ok()?,
opening: Span::new(start as u32, content_start as u32),
closing: Span::new(closing.start() as u32, closing.end() as u32),
})
}
#[cfg(test)]
mod template_only_tests;