use crate::write::{EmitDocument, LinkSink, NoLinks, Recorded, Writer};
use vize_l0::Span;
use vize_l2::file::{FileArtifact, ScriptUnit};
use vize_l2::lang::js::JsxFile;
mod mapping;
pub use mapping::MappingError;
pub mod vue;
const MODULE_SUFFIX: &str = "\n;\nexport {};\n";
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SourceKind {
JavaScript,
TypeScript,
Jsx,
Tsx,
}
impl SourceKind {
#[must_use]
pub const fn extension(self) -> &'static str {
match self {
Self::JavaScript => "mjs",
Self::TypeScript => "ts",
Self::Jsx => "jsx",
Self::Tsx => "tsx",
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ProjectionError {
IncompleteFile,
ProgramCount,
PartialSource,
UnsupportedProfile,
NestedScope,
TemplateOperations,
SourceTooLarge,
}
pub struct ProgramProjection<'file, 'arena> {
file: &'file FileArtifact<'arena>,
unit: &'file ScriptUnit,
kind: SourceKind,
document: EmitDocument,
}
impl<'file, 'arena> ProgramProjection<'file, 'arena> {
#[must_use]
pub fn file(&self) -> &'file FileArtifact<'arena> {
self.file
}
#[must_use]
pub fn unit(&self) -> &'file ScriptUnit {
self.unit
}
#[must_use]
pub const fn source_kind(&self) -> SourceKind {
self.kind
}
#[must_use]
pub fn document(&self) -> &EmitDocument {
&self.document
}
}
pub fn project_program<'file, 'arena>(
file: &'file FileArtifact<'arena>,
) -> Result<ProgramProjection<'file, 'arena>, ProjectionError> {
project::<Recorded, false>(file)
}
pub fn project_program_no_links<'file, 'arena>(
file: &'file FileArtifact<'arena>,
) -> Result<ProgramProjection<'file, 'arena>, ProjectionError> {
project::<NoLinks, false>(file)
}
pub fn project_jsx_program<'file, 'arena>(
owner: &'file JsxFile<'arena>,
) -> Result<ProgramProjection<'file, 'arena>, ProjectionError> {
project::<Recorded, true>(owner.file())
}
pub fn project_jsx_program_no_links<'file, 'arena>(
owner: &'file JsxFile<'arena>,
) -> Result<ProgramProjection<'file, 'arena>, ProjectionError> {
project::<NoLinks, true>(owner.file())
}
fn project<'file, 'arena, L: LinkSink, const JSX: bool>(
file: &'file FileArtifact<'arena>,
) -> Result<ProgramProjection<'file, 'arena>, ProjectionError> {
if !file.is_complete() {
return Err(ProjectionError::IncompleteFile);
}
let [unit] = file.units() else {
return Err(ProjectionError::ProgramCount);
};
let source = file.artifact().source();
let length = u32::try_from(source.len()).map_err(|_| ProjectionError::SourceTooLarge)?;
let capacity = source
.len()
.checked_add(MODULE_SUFFIX.len())
.filter(|length| u32::try_from(*length).is_ok())
.ok_or(ProjectionError::SourceTooLarge)?;
if unit.span != Span::new(0, length) {
return Err(ProjectionError::PartialSource);
}
if !unit.profile.module || unit.profile.jsx != JSX {
return Err(ProjectionError::UnsupportedProfile);
}
if !file
.scopes()
.get(unit.scope.index() as usize)
.is_some_and(|scope| scope.id == unit.scope && scope.parent.is_none())
{
return Err(ProjectionError::NestedScope);
}
if file.artifact().node_count() != 0 || !file.artifact().root().ops.is_empty() {
return Err(ProjectionError::TemplateOperations);
}
let kind = match (unit.profile.typescript, JSX) {
(false, false) => SourceKind::JavaScript,
(true, false) => SourceKind::TypeScript,
(false, true) => SourceKind::Jsx,
(true, true) => SourceKind::Tsx,
};
let mut writer = Writer::<L>::with_capacity(capacity);
writer.push_linked(source, unit.span);
writer.push(MODULE_SUFFIX);
Ok(ProgramProjection {
file,
unit,
kind,
document: writer.finish().into_document(),
})
}