use crate::file::build::Facts;
use crate::file::vue::{ExposureIssueKind, VueExposure};
use crate::file::{BindingRef, FileArtifact, ScopeId, ScriptUnitId};
use alloc::{boxed::Box, vec::Vec};
use oxc_ast::ast::Program;
use oxc_parser::AdmittedProgram;
use vize_l0::{SourceBlock, Span};
use vize_l1::container::vue::ScriptView;
use vize_l1::embed::Lang;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SetupPrimitiveType {
BigInt,
Boolean,
Null,
Number,
String,
Symbol,
Undefined,
}
pub(crate) struct SetupAnnotationRecord {
pub unit: ScriptUnitId,
pub span: Span,
pub kind: SetupPrimitiveType,
}
#[derive(Default)]
pub(crate) struct SetupAnnotationStorage {
rows: Vec<SetupAnnotationRecord>,
}
impl SetupAnnotationStorage {
pub(crate) fn rows(&self) -> &[SetupAnnotationRecord] {
&self.rows
}
}
pub(super) fn record_annotation(
facts: &mut Facts<'_>,
unit: ScriptUnitId,
span: Span,
kind: SetupPrimitiveType,
) {
facts
.setup_annotations
.get_or_insert_with(|| Box::new(SetupAnnotationStorage::default()))
.rows
.push(SetupAnnotationRecord { unit, span, kind });
}
pub struct SetupAnnotation<'owner, 'arena> {
file: &'owner FileArtifact<'arena>,
original: &'owner SetupAnnotationRecord,
}
impl<'owner, 'arena> SetupAnnotation<'owner, 'arena> {
#[must_use]
pub fn file(&self) -> &'owner FileArtifact<'arena> {
self.file
}
#[must_use]
pub fn span(&self) -> Span {
self.original.span
}
#[must_use]
pub fn kind(&self) -> SetupPrimitiveType {
self.original.kind
}
}
pub(crate) fn initial(program: &Program<'_>) -> bool {
let profile = program.source_type;
profile.is_module()
&& !profile.is_unambiguous()
&& !profile.is_typescript_definition()
&& !profile.is_jsx()
&& program.directives.is_empty()
&& program.hashbang.is_none()
}
pub(super) fn reject(facts: &mut Facts<'_>, id: ScriptUnitId) {
if let Some(unit) = facts.units.last_mut()
&& unit.id == id
{
unit.origin.setup_eligible = false;
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SetupIssueKind {
Exposure(ExposureIssueKind),
Profile,
Scope,
UnsupportedSyntax,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct SetupIssue {
pub span: Span,
pub kind: SetupIssueKind,
}
pub struct VueSetup<'owner, 'descriptor, 'program, 'arena> {
exposure: VueExposure<'owner, 'descriptor, 'program, 'arena>,
}
impl<'owner, 'descriptor, 'program, 'arena> VueSetup<'owner, 'descriptor, 'program, 'arena> {
pub fn checked(
file: &'owner FileArtifact<'arena>,
script: ScriptView<'descriptor, 'arena>,
program: AdmittedProgram<'program, 'arena>,
) -> Result<Self, SetupIssue> {
let span = script.block().span();
let reject = |kind| SetupIssue { span, kind };
let exposure = VueExposure::checked(file, script, program)
.map_err(|issue| reject(SetupIssueKind::Exposure(issue.kind)))?;
if !matches!(exposure.script().lang(), Lang::Js | Lang::Ts) {
return Err(reject(SetupIssueKind::Profile));
}
let unit = file
.units()
.iter()
.find(|unit| unit.id == exposure.unit())
.ok_or_else(|| reject(SetupIssueKind::Scope))?;
if unit.scope.index() == 0
|| !file
.scopes()
.get(unit.scope.index() as usize)
.is_some_and(|scope| {
scope.id == unit.scope && scope.parent == Some(ScopeId(0)) && scope.span == span
})
{
return Err(reject(SetupIssueKind::Scope));
}
if !unit.origin.setup_eligible {
return Err(reject(SetupIssueKind::UnsupportedSyntax));
}
Ok(Self { exposure })
}
#[must_use]
pub fn exposure(&self) -> &VueExposure<'owner, 'descriptor, 'program, 'arena> {
&self.exposure
}
#[must_use]
pub fn source(&self) -> SourceBlock<'arena> {
self.exposure.script().block()
}
pub fn bindings(&self) -> impl Iterator<Item = BindingRef<'owner, 'arena>> + '_ {
self.exposure
.file()
.bindings()
.filter(|binding| self.exposure.binding(*binding).is_ok())
}
pub fn type_annotations(&self) -> impl Iterator<Item = SetupAnnotation<'owner, 'arena>> + '_ {
self.exposure
.file()
.setup_annotations_for_unit(self.exposure.unit())
}
}
impl<'arena> FileArtifact<'arena> {
pub(crate) fn setup_annotations_for_unit(
&self,
unit: ScriptUnitId,
) -> impl Iterator<Item = SetupAnnotation<'_, 'arena>> {
self.setup_annotations()
.iter()
.filter(move |row| row.unit == unit)
.map(move |original| SetupAnnotation {
file: self,
original,
})
}
}