use super::{NativeTemplateFile, NativeTemplateIssue, NativeTemplateView};
use crate::file::FileArtifact;
use vize_l0::{
Span,
config::{VueDialect, VueVersion},
};
use vize_l1::{
SurfaceParseOptions,
container::vue::{AdmittedDescriptor, DescriptorObservation},
css::{EmptyClassStyle, StyleIssue, StyleSyntax},
markup::NativeTemplateGrammar,
};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum NativeScopedTemplateIssueKind {
Descriptor,
Profile,
Script,
StyleCount,
StyleProfile,
StyleSyntax(StyleIssue),
Template,
Source,
Completion(NativeTemplateIssue),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct NativeScopedTemplateIssue {
pub span: Span,
pub kind: NativeScopedTemplateIssueKind,
}
pub struct NativeScopedTemplateView<'owner, 'arena> {
template: NativeTemplateView<'owner, 'arena>,
file: &'owner FileArtifact<'arena>,
descriptor: AdmittedDescriptor<'owner, 'arena>,
empty: EmptyClassStyle<'owner, 'arena>,
}
impl<'owner, 'arena> NativeScopedTemplateView<'owner, 'arena> {
#[must_use]
pub fn owner(&self) -> &'owner NativeTemplateFile<'arena> {
self.template.owner()
}
#[must_use]
pub fn file(&self) -> &'owner FileArtifact<'arena> {
self.file
}
#[must_use]
pub fn descriptor(&self) -> AdmittedDescriptor<'owner, 'arena> {
self.descriptor
}
#[must_use]
pub fn style_syntax(&self) -> &'owner StyleSyntax<'arena> {
self.empty.syntax()
}
#[must_use]
pub fn empty_style(&self) -> EmptyClassStyle<'owner, 'arena> {
self.empty
}
}
impl<'arena> NativeTemplateFile<'arena> {
pub fn scoped_view<'owner>(
&'owner self,
original: &'owner DescriptorObservation<'arena>,
syntax: &'owner StyleSyntax<'arena>,
) -> Result<NativeScopedTemplateView<'owner, 'arena>, NativeScopedTemplateIssue> {
use NativeScopedTemplateIssueKind as Kind;
let whole = Span::new(0, original.source().len() as u32);
let reject = |kind| NativeScopedTemplateIssue { span: whole, kind };
let options = original.options();
if options.version != VueVersion::V3
|| options.dialect != VueDialect::Vue
|| options.template != SurfaceParseOptions::default()
{
return Err(reject(Kind::Profile));
}
let descriptor = original.admitted().map_err(|_| reject(Kind::Descriptor))?;
let template = self.view().map_err(|issue| NativeScopedTemplateIssue {
span: issue.span,
kind: Kind::Completion(issue),
})?;
let file = template.file().ok_or_else(|| reject(Kind::Template))?;
let selected = self.selected();
if descriptor.ordinary().is_some()
|| descriptor.setup().is_some()
|| selected.ordinary().is_some()
|| selected.setup().is_some()
|| !file.units().is_empty()
{
return Err(reject(Kind::Script));
}
if descriptor.styles().len() != 1 {
return Err(reject(Kind::StyleCount));
}
let style = descriptor
.styles()
.next()
.ok_or_else(|| reject(Kind::StyleCount))?;
if !style
.attrs()
.iter()
.any(|attr| attr.name == "scoped" && attr.value.is_none())
|| style.attrs().iter().any(|attr| {
!matches!(
(attr.name, attr.value),
("scoped", None) | ("lang", Some("css"))
)
})
{
return Err(NativeScopedTemplateIssue {
span: style.block().span(),
kind: Kind::StyleProfile,
});
}
let original_template = descriptor
.template()
.ok_or_else(|| reject(Kind::Template))?;
let block = selected.component().block();
if !selected.has_styles()
|| selected.grammar() != NativeTemplateGrammar::JavaScriptModule
|| selected.template_index() != original_template.container_index()
|| block.span() != original_template.block().span()
|| !core::ptr::eq(block.source(), original_template.block().source())
|| !core::ptr::eq(block.root_source(), descriptor.source())
|| !core::ptr::eq(file.artifact().source(), descriptor.source())
|| syntax.container_index() != style.container_index()
|| syntax.source().span() != style.block().span()
|| !core::ptr::eq(syntax.source().source(), style.block().source())
|| !core::ptr::eq(syntax.source().root_source(), descriptor.source())
{
return Err(reject(Kind::Source));
}
let empty = syntax
.empty_class()
.map_err(|issue| NativeScopedTemplateIssue {
span: issue.span,
kind: Kind::StyleSyntax(issue),
})?;
Ok(NativeScopedTemplateView {
template,
file,
descriptor,
empty,
})
}
}