vize_l4 0.474.0

L4 - the Davinci emission level (codename Stesura): writer, expression rewriting, module assembly and targets
Documentation
//! Recorded standalone modules retain the original selected completion owner.

use alloc::boxed::Box;
use core::fmt;
use vize_l0::{SourceBlock, String};
use vize_l3::decision::native::NativeTemplateDomAnalysis;

use super::{DomError, emit_template};
use crate::{
    module::{AssemblyError, assemble_template},
    runtime::{HelperSet, Runtime, vocabulary},
    write::{EmitDocument, Emitted, LinkSink, Recorded, SpanLink},
};

/// A complete recorded module with its original analysis and source frame.
///
/// Only [`emit_template_output`] constructs this non-cloneable owner. The map
/// source comes from its retained selected Component, never a caller pairing.
/// This standalone render export does not authorize whole-SFC assembly or
/// claim upstream raw-map/subspan parity.
///
/// Neither a neutral document nor an arbitrary writer can be promoted:
/// ```compile_fail
/// use vize_l4::{targets::dom::emit_template_output, write::EmitDocument};
/// fn promote(document: EmitDocument) { let _ = emit_template_output(document); }
/// ```
/// ```compile_fail
/// use vize_l4::{targets::dom::emit_template_output, write::{Recorded, Writer}};
/// fn promote(writer: Writer<Recorded>) { let _ = emit_template_output(writer); }
/// ```
/// ```compile_fail
/// use vize_l0::SourceBlock;
/// use vize_l4::targets::dom::emit_template_output;
/// fn promote(block: SourceBlock<'_>) { let _ = emit_template_output(block); }
/// ```
/// Its storage and completion authority are private:
/// ```compile_fail
/// use vize_l4::targets::dom::NativeTemplateDomOutput;
/// fn forge() { let _ = NativeTemplateDomOutput {}; }
/// ```
/// ```compile_fail
/// use vize_l4::targets::dom::NativeTemplateDomOutput;
/// fn clone(output: NativeTemplateDomOutput<'_, '_>) { let _ = output.clone(); }
/// ```
/// A caller cannot substitute even byte-equal foreign source:
/// ```compile_fail
/// use vize_l4::targets::dom::NativeTemplateDomOutput;
/// fn replace(output: &NativeTemplateDomOutput<'_, '_>, foreign: &str) {
///     let _ = output.source_map("Foreign.vue", foreign);
/// }
/// ```
/// The actual lower owner must remain alive:
/// ```compile_fail
/// use vize_l2::lang::js::NativeTemplateFile;
/// use vize_l3::decision::native::build_native_dom_file_decisions;
/// use vize_l4::targets::dom::emit_template_output;
/// fn discard(owner: NativeTemplateFile<'_>) {
///     let analysis = build_native_dom_file_decisions(owner.view().unwrap()).unwrap();
///     let output = emit_template_output(analysis).unwrap();
///     drop(owner);
///     let _ = output.source_map("Original.vue");
/// }
/// ```
/// Borrowed generated bytes cannot outlive the output:
/// ```compile_fail
/// use vize_l4::targets::dom::NativeTemplateDomOutput;
/// fn discard(output: NativeTemplateDomOutput<'_, '_>) {
///     let code = output.code();
///     drop(output);
///     let _ = code.len();
/// }
/// ```
/// The retained analysis cannot escape a borrow of the output:
/// ```compile_fail
/// use vize_l3::decision::native::NativeTemplateDomAnalysis;
/// use vize_l4::targets::dom::NativeTemplateDomOutput;
/// fn escape<'o, 'a>(output: &NativeTemplateDomOutput<'o, 'a>)
///     -> &'o NativeTemplateDomAnalysis<'o, 'a> { output.analysis() }
/// ```
pub struct NativeTemplateDomOutput<'owner, 'arena> {
    analysis: NativeTemplateDomAnalysis<'owner, 'arena>,
    block: SourceBlock<'arena>,
    document: EmitDocument,
    helpers: HelperSet,
}

impl<'owner, 'arena> NativeTemplateDomOutput<'owner, 'arena> {
    /// The same moved analysis; this borrow cannot outlive the output.
    #[must_use]
    pub fn analysis(&self) -> &NativeTemplateDomAnalysis<'owner, 'arena> {
        &self.analysis
    }

    /// Original selected block, derived from the retained owner.
    #[must_use]
    pub const fn source_block(&self) -> SourceBlock<'arena> {
        self.block
    }

    #[must_use]
    pub fn code(&self) -> &str {
        self.document.as_str()
    }

    #[must_use]
    pub fn links(&self) -> &[SpanLink] {
        self.document.links()
    }

    #[must_use]
    pub fn helpers(&self) -> &HelperSet {
        &self.helpers
    }

    /// Serialize existing links against the original complete authored root.
    /// `filename` is presentation metadata and cannot replace source custody.
    #[must_use]
    pub fn source_map(&self, filename: &str) -> String {
        self.document.source_map(filename, self.block.root_source())
    }
}

/// Typed refusal before a recorded native output can be constructed.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum NativeTemplateDomOutputError {
    Dom(DomError),
    Assembly(AssemblyError),
    SourceOwnerMismatch,
    InvalidGeneratedRange,
    InvalidAuthoredRange,
}

/// Refusal retains the same completed File, source and original L3 diagnostics.
pub struct NativeTemplateDomOutputFailure<'owner, 'arena> {
    analysis: Box<NativeTemplateDomAnalysis<'owner, 'arena>>,
    error: NativeTemplateDomOutputError,
}

impl<'owner, 'arena> NativeTemplateDomOutputFailure<'owner, 'arena> {
    #[must_use]
    pub fn analysis(&self) -> &NativeTemplateDomAnalysis<'owner, 'arena> {
        &self.analysis
    }

    #[must_use]
    pub const fn error(&self) -> NativeTemplateDomOutputError {
        self.error
    }
}

impl fmt::Debug for NativeTemplateDomOutputFailure<'_, '_> {
    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
        formatter
            .debug_struct("NativeTemplateDomOutputFailure")
            .field("error", &self.error)
            .finish_non_exhaustive()
    }
}

/// Consume genuine selected-template analysis through the existing encoder and
/// standalone assembler. No new AST walk, target decision or source parse runs.
///
/// The analysis cannot be reused after transfer:
/// ```compile_fail
/// use vize_l3::decision::native::NativeTemplateDomAnalysis;
/// use vize_l4::targets::dom::emit_template_output;
/// fn reuse(analysis: NativeTemplateDomAnalysis<'_, '_>) {
///     let _ = emit_template_output(analysis);
///     let _ = analysis.file();
/// }
/// ```
pub fn emit_template_output<'owner, 'arena>(
    analysis: NativeTemplateDomAnalysis<'owner, 'arena>,
) -> Result<NativeTemplateDomOutput<'owner, 'arena>, NativeTemplateDomOutputFailure<'owner, 'arena>>
{
    match record(&analysis) {
        Ok((block, document, helpers)) => Ok(NativeTemplateDomOutput {
            analysis,
            block,
            document,
            helpers,
        }),
        Err(error) => Err(NativeTemplateDomOutputFailure {
            analysis: Box::new(analysis),
            error,
        }),
    }
}

fn record<'arena>(
    analysis: &NativeTemplateDomAnalysis<'_, 'arena>,
) -> Result<(SourceBlock<'arena>, EmitDocument, HelperSet), NativeTemplateDomOutputError> {
    let block = analysis.owner().selected().component().block();
    if !core::ptr::eq(block.root_source(), analysis.artifact().source()) {
        return Err(NativeTemplateDomOutputError::SourceOwnerMismatch);
    }
    let render = emit_template::<Recorded>(analysis).map_err(NativeTemplateDomOutputError::Dom)?;
    let Emitted {
        text,
        links,
        helpers,
    } = assemble_template(render, vocabulary(Runtime::VueDom))
        .map_err(NativeTemplateDomOutputError::Assembly)?;
    let document = links.into_document(text);
    if u32::try_from(document.len()).is_err() {
        return Err(NativeTemplateDomOutputError::InvalidGeneratedRange);
    }
    for link in document.links() {
        if document
            .as_str()
            .get(link.generated.start as usize..link.generated.end as usize)
            .is_none()
        {
            return Err(NativeTemplateDomOutputError::InvalidGeneratedRange);
        }
        if !block.contains_block_span(link.authored) {
            return Err(NativeTemplateDomOutputError::InvalidAuthoredRange);
        }
    }
    Ok((block, document, helpers))
}