use crate::file::build::Facts;
use crate::file::{
Export, FileArtifact, Namespace, ScopeId, ScriptProfile, ScriptUnit, ScriptUnitId,
};
use oxc_parser::{AdmittedProgram, OriginalDefaultExport, ParseOptions};
use vize_l0::{SourceBlock, Span};
use vize_l1::container::vue::{ScriptRole, ScriptView};
use vize_l1::embed::Lang;
pub(crate) fn initial(admitted: &AdmittedProgram<'_, '_>) -> bool {
let program = admitted.program();
let profile = admitted.source_type();
profile.is_module()
&& !profile.is_unambiguous()
&& !profile.is_typescript_definition()
&& !profile.is_jsx()
&& program.source_type == profile
&& admitted.options() == ParseOptions::default()
&& program.hashbang.is_none()
&& !admitted.has_legacy_literals()
&& admitted.sole_default_export().is_some()
}
pub(super) fn reject(facts: &mut Facts<'_>, id: ScriptUnitId) {
if let Some(unit) = facts.units.last_mut()
&& unit.id == id
{
unit.origin.ordinary_empty_eligible = false;
}
}
pub struct OrdinaryEmptyScript<'f> {
unit: &'f ScriptUnit,
export: &'f Export,
}
impl<'f> OrdinaryEmptyScript<'f> {
pub(crate) fn from_facts(facts: &'f Facts<'_>) -> Option<Self> {
let [unit] = facts.units.as_slice() else {
return None;
};
let [export] = facts.exports.as_slice() else {
return None;
};
let root = facts.scopes.first()?;
if !facts.issues.is_empty()
|| !unit.walk_completed()
|| !unit.origin.ordinary_empty_eligible
|| !unit.origin.has_export
|| unit.origin.has_call
|| unit.origin.reserved_binding
|| !unit.profile.module
|| unit.profile.jsx
|| unit.scope != ScopeId(0)
|| root.id != ScopeId(0)
|| root.parent.is_some()
|| !facts.declarations.is_empty()
|| !facts.imports.is_empty()
|| !facts.references.is_empty()
|| export.unit != unit.id
|| export.name.as_str() != "default"
|| export.namespace != Namespace::Value
|| export.local.is_some()
|| export.source.is_some()
|| export.local_reference.is_some()
{
return None;
}
Some(Self { unit, export })
}
#[must_use]
pub fn unit(&self) -> ScriptUnitId {
self.unit.id
}
#[must_use]
pub fn scope(&self) -> ScopeId {
self.unit.scope
}
#[must_use]
pub fn script_span(&self) -> Span {
self.unit.span
}
#[must_use]
pub fn statement_span(&self) -> Span {
self.export.span
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum OrdinaryIssueKind {
Source,
Profile,
Role,
MissingUnit,
ProgramOrigin,
Scope,
IncompleteFile,
UnsupportedSyntax,
Span,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct OrdinaryIssue {
pub span: Span,
pub kind: OrdinaryIssueKind,
}
pub struct VueOrdinaryEmpty<'f, 'd, 'p, 'a> {
file: &'f FileArtifact<'a>,
script: ScriptView<'d, 'a>,
original: OriginalDefaultExport<'p, 'a>,
unit: ScriptUnitId,
}
impl<'f, 'd, 'p, 'a> VueOrdinaryEmpty<'f, 'd, 'p, 'a> {
pub fn checked(
file: &'f FileArtifact<'a>,
script: ScriptView<'d, 'a>,
admitted: AdmittedProgram<'p, 'a>,
) -> Result<Self, OrdinaryIssue> {
let reject = |kind| OrdinaryIssue {
span: script.block().span(),
kind,
};
if script.role() != ScriptRole::Ordinary {
return Err(reject(OrdinaryIssueKind::Role));
}
if !core::ptr::eq(file.artifact().source(), script.source())
|| !core::ptr::eq(admitted.source(), script.block().source())
{
return Err(reject(OrdinaryIssueKind::Source));
}
let profile = admitted.source_type();
if !profile.is_module()
|| profile.is_unambiguous()
|| profile.is_jsx()
|| profile.is_typescript_definition()
|| admitted.program().source_type != profile
|| admitted.options() != ParseOptions::default()
|| profile.is_typescript() != (script.lang() == Lang::Ts)
|| !matches!(script.lang(), Lang::Js | Lang::Ts)
{
return Err(reject(OrdinaryIssueKind::Profile));
}
let [unit] = file.units() else {
return Err(reject(OrdinaryIssueKind::MissingUnit));
};
if unit.id.index() as usize != script.container_index() {
return Err(reject(OrdinaryIssueKind::MissingUnit));
}
if unit.span != script.block().span()
|| unit.profile
!= (ScriptProfile {
typescript: profile.is_typescript(),
jsx: false,
module: true,
})
{
return Err(reject(OrdinaryIssueKind::Profile));
}
if unit.origin.body != admitted.program().body.as_ptr() as usize
|| unit.origin.length != admitted.program().body.len()
{
return Err(reject(OrdinaryIssueKind::ProgramOrigin));
}
if unit.scope != ScopeId(0)
|| !file.scopes().first().is_some_and(|root| {
root.id == ScopeId(0)
&& root.parent.is_none()
&& root.span == Span::new(0, script.source().len() as u32)
})
{
return Err(reject(OrdinaryIssueKind::Scope));
}
if !file.is_complete() || !unit.walk_completed() {
return Err(reject(OrdinaryIssueKind::IncompleteFile));
}
let family = file
.ordinary_empty_script()
.ok_or_else(|| reject(OrdinaryIssueKind::UnsupportedSyntax))?;
let original = admitted
.sole_default_export()
.ok_or_else(|| reject(OrdinaryIssueKind::UnsupportedSyntax))?;
let statement = authored(script.block(), original.statement_span());
let keyword = authored(script.block(), original.default_keyword_span());
let object = authored(script.block(), original.declaration_span());
if !matches!((statement, keyword, object), (Some(statement), Some(keyword), Some(object))
if statement == family.statement_span()
&& statement.start.checked_add(6).is_some_and(|end| end <= keyword.start)
&& keyword.end.checked_sub(keyword.start) == Some(7)
&& keyword.end <= object.start && object.start < object.end
&& object.end <= statement.end)
{
return Err(reject(OrdinaryIssueKind::Span));
}
Ok(Self {
file,
script,
original,
unit: unit.id,
})
}
#[must_use]
pub fn file(&self) -> &'f FileArtifact<'a> {
self.file
}
#[must_use]
pub fn unit(&self) -> ScriptUnitId {
self.unit
}
#[must_use]
pub fn script(&self) -> ScriptView<'d, 'a> {
self.script
}
#[must_use]
pub fn source(&self) -> SourceBlock<'a> {
self.script.block()
}
#[must_use]
pub fn program(&self) -> &AdmittedProgram<'p, 'a> {
self.original.program()
}
#[must_use]
pub fn original(&self) -> &OriginalDefaultExport<'p, 'a> {
&self.original
}
#[must_use]
pub fn statement_span(&self) -> Span {
self.project(self.original.statement_span())
}
#[must_use]
pub fn export_keyword_span(&self) -> Span {
let start = self.statement_span().start;
Span::new(start, start + 6)
}
#[must_use]
pub fn default_keyword_span(&self) -> Span {
self.project(self.original.default_keyword_span())
}
#[must_use]
pub fn object_span(&self) -> Span {
self.project(self.original.declaration_span())
}
fn project(&self, span: oxc_span::Span) -> Span {
let base = self.source().span().start;
Span::new(base + span.start, base + span.end)
}
}
fn authored(block: SourceBlock<'_>, span: oxc_span::Span) -> Option<Span> {
block.source().get(span.start as usize..span.end as usize)?;
Some(Span::new(
block.span().start.checked_add(span.start)?,
block.span().start.checked_add(span.end)?,
))
}