vize_l4 0.578.0

L4 - the Davinci emission level (codename Stesura): writer, expression rewriting, module assembly and targets
Documentation
use super::{Vue2Access, Vue2AccessError, Vue2ErrorKind, emit};
use crate::{
    expr::{
        ResolvedExpressions,
        vue::{Access, Binding},
    },
    write::{NoLinks, Recorded},
};
use alloc::vec::Vec;
use vize_l0::{Allocator, String};
use vize_l1::dialect::vue2::surface::parse_component;
use vize_l1_to_l2::native_vue2::lower_vue2_component;
use vize_l2::{
    artifact::Artifact,
    expr::ExprRef,
    resolution::{BindingId, BindingLookup, ResolutionTable, resolve_expression},
};
use vize_l3::decision::{
    build_decisions, build_dom_decisions, dom::ContextOnly, policy::TargetPolicy,
};

const CONTEXT: &[&str] = &[
    "count",
    "label",
    "Math",
    "_vm",
    "value",
    "render",
    "staticRenderFns",
    "_vm1",
    "render1",
    "staticRenderFns1",
];
struct Lookup;
impl BindingLookup for Lookup {
    fn lookup(&self, name: &str) -> Option<BindingId> {
        CONTEXT
            .iter()
            .position(|known| *known == name)
            .map(|index| BindingId::new(index as u32))
    }
}
fn tables<'a>(artifact: &Artifact<'a>) -> Vec<ResolutionTable<'a>> {
    let mut tables = Vec::new();
    artifact
        .visit_nodes(&mut |_, node| {
            node.for_each_expression(&mut |expression| {
                let ExprRef::Js(expression) = expression else {
                    panic!("original JS")
                };
                tables.push(resolve_expression(expression, &Lookup).unwrap());
            });
        })
        .unwrap();
    tables.sort_by_key(|row| (row.expression().span.start, row.expression().span.end));
    tables
}
fn bindings() -> Vec<Binding<'static>> {
    CONTEXT
        .iter()
        .enumerate()
        .map(|(index, name)| Binding {
            id: BindingId::new(index as u32),
            access: if *name == "Math" {
                Access::Global
            } else {
                Access::Context
            },
        })
        .collect()
}
fn module(source: &str) -> String {
    module_with_bindings(source, &bindings())
}

fn module_with_bindings(source: &str, bindings: &[Binding<'_>]) -> String {
    let allocator = Allocator::default();
    let original = parse_component(&allocator, source)
        .unwrap()
        .into_expression_pool();
    let observation = lower_vue2_component(&allocator, original);
    let artifact = observation.admitted().unwrap();
    let tables = tables(artifact);
    let ids = bindings
        .iter()
        .map(|binding| binding.id)
        .collect::<Vec<_>>();
    let policy = ContextOnly::new(&tables, &ids);
    let analysis = build_dom_decisions(artifact, &policy).unwrap();
    let access = Vue2Access::checked(bindings).unwrap();
    emit::<NoLinks>(
        &analysis,
        ResolvedExpressions::checked(&tables).unwrap(),
        &access,
    )
    .unwrap()
    .finish()
    .text
}

#[test]
fn actual_retained_vue2_source_uses_instance_helpers_and_complete_vue2_exports() {
    assert_eq!(
        module("<div id='a'>Hello {{ count }}</div>"),
        "export function render() {\n  const _vm = this;\n  return _vm._c(\"div\", {attrs: {\"id\": \"a\"}}, [_vm._v(\"Hello \" + _vm._s(_vm.count))]);\n}\nrender._withStripped = true;\nexport const staticRenderFns = [];\n"
    );
}

#[test]
fn empty_authored_class_keeps_data_without_a_static_class_field() {
    assert_eq!(
        module("<div class=''>{{ count }}</div>"),
        "export function render() {\n  const _vm = this;\n  return _vm._c(\"div\", {}, [_vm._v(_vm._s(_vm.count))]);\n}\nrender._withStripped = true;\nexport const staticRenderFns = [];\n"
    );
}

#[test]
fn explicit_context_global_and_shorthand_access_preserve_authored_meaning() {
    assert_eq!(
        module("<div>{{ Math.round(count) }}</div>"),
        "export function render() {\n  const _vm = this;\n  return _vm._c(\"div\", void 0, [_vm._v(_vm._s(Math.round(_vm.count)))]);\n}\nrender._withStripped = true;\nexport const staticRenderFns = [];\n"
    );
    assert_eq!(
        module("<div>{{ ({count}) }}</div>"),
        "export function render() {\n  const _vm = this;\n  return _vm._c(\"div\", void 0, [_vm._v(_vm._s(({count: _vm.count})))]);\n}\nrender._withStripped = true;\nexport const staticRenderFns = [];\n"
    );
    assert_eq!(
        module("<div>{{ _vm }}</div>"),
        "export function render() {\n  const _vm = this;\n  return _vm._c(\"div\", void 0, [_vm._v(_vm._s(_vm._vm))]);\n}\nrender._withStripped = true;\nexport const staticRenderFns = [];\n"
    );
}

#[test]
fn missing_real_resolution_or_runtime_binding_is_a_typed_refusal() {
    let arena = Allocator::default();
    let original = parse_component(&arena, "<div>{{ count }}</div>")
        .unwrap()
        .into_expression_pool();
    let observation = lower_vue2_component(&arena, original);
    let artifact = observation.admitted().unwrap();
    let tables = tables(artifact);
    let ids = [BindingId::new(0)];
    let analysis = build_dom_decisions(artifact, &ContextOnly::new(&tables, &ids)).unwrap();
    let access = Vue2Access::checked(&bindings()).unwrap();
    let error = emit::<NoLinks>(
        &analysis,
        ResolvedExpressions::checked(&[]).unwrap(),
        &access,
    )
    .unwrap_err();
    assert_eq!(
        error.kind,
        Vue2ErrorKind::Expression(crate::expr::EmitError {
            span: tables.first().unwrap().expression().span,
            kind: crate::expr::EmitErrorKind::MissingResolution
        })
    );
    let missing = Vue2Access::checked(&[]).unwrap();
    let error = emit::<NoLinks>(
        &analysis,
        ResolvedExpressions::checked(&tables).unwrap(),
        &missing,
    )
    .unwrap_err();
    assert_eq!(
        error.kind,
        Vue2ErrorKind::Expression(crate::expr::EmitError {
            span: tables.first().unwrap().expression().span,
            kind: crate::expr::EmitErrorKind::Access(crate::expr::AccessError::MissingBinding)
        })
    );
}

#[test]
fn target_refuses_fragment_roots_and_unrelated_target_decisions() {
    let arena = Allocator::default();
    let original = parse_component(&arena, "<div>a</div><p>b</p>")
        .unwrap()
        .into_expression_pool();
    let observation = lower_vue2_component(&arena, original);
    let artifact = observation.admitted().unwrap();
    let access = Vue2Access::checked(&[]).unwrap();
    let analysis = build_decisions(artifact, TargetPolicy::Dom).unwrap();
    assert_eq!(
        emit::<NoLinks>(
            &analysis,
            ResolvedExpressions::checked(&[]).unwrap(),
            &access
        )
        .unwrap_err()
        .kind,
        Vue2ErrorKind::Root
    );
    let analysis = build_decisions(artifact, TargetPolicy::Ssr).unwrap();
    assert_eq!(
        emit::<NoLinks>(
            &analysis,
            ResolvedExpressions::checked(&[]).unwrap(),
            &access
        )
        .unwrap_err()
        .kind,
        Vue2ErrorKind::WrongPolicy
    );
}

#[test]
fn unsupported_setup_identities_never_receive_an_instance_fallback() {
    assert_eq!(
        Vue2Access::checked(&[Binding {
            id: BindingId::new(0),
            access: Access::SetupRef
        }])
        .err(),
        Some(Vue2AccessError::UnsupportedBinding)
    );
    assert_eq!(
        Vue2Access::checked(&[
            Binding {
                id: BindingId::new(0),
                access: Access::Context
            },
            Binding {
                id: BindingId::new(0),
                access: Access::Context
            }
        ])
        .err(),
        Some(Vue2AccessError::UnorderedOrDuplicateIdentity)
    );
}

#[test]
fn named_expression_links_still_point_to_the_original_utf8_source() {
    let arena = Allocator::default();
    let source = "<div>雪😀 {{ count }}</div>";
    let original = parse_component(&arena, source)
        .unwrap()
        .into_expression_pool();
    let observation = lower_vue2_component(&arena, original);
    let artifact = observation.admitted().unwrap();
    let tables = tables(artifact);
    let bindings = bindings();
    let ids = bindings
        .iter()
        .map(|binding| binding.id)
        .collect::<Vec<_>>();
    let analysis = build_dom_decisions(artifact, &ContextOnly::new(&tables, &ids)).unwrap();
    let access = Vue2Access::checked(&bindings).unwrap();
    let emitted = emit::<Recorded>(
        &analysis,
        ResolvedExpressions::checked(&tables).unwrap(),
        &access,
    )
    .unwrap()
    .finish();
    assert_eq!(emitted.text, module(source));
    let link = emitted
        .links
        .links()
        .iter()
        .find(|link| link.name.as_deref() == Some("count"))
        .unwrap();
    assert_eq!(
        source.get(link.authored.start as usize..link.authored.end as usize),
        Some("count")
    );
    assert_eq!(
        emitted
            .text
            .get(link.generated.start as usize..link.generated.end as usize),
        Some("_vm.count")
    );
}

fn raw_bindings(role: Access<'static>) -> Vec<Binding<'static>> {
    bindings()
        .into_iter()
        .map(|mut binding| {
            if matches!(binding.id.index(), 3 | 5..=9) {
                binding.access = role;
            }
            binding
        })
        .collect()
}

#[test]
fn verbatim_receiver_names_include_semantic_escaped_identifiers() {
    for role in [Access::Global, Access::Local] {
        for (expression, emitted) in [("_vm", "_vm"), (r"_\u0076m", r"_\u0076m")] {
            let source = vize_l0::cstr!("<div>{{{{ {expression} }}}}</div>");
            assert_eq!(
                module_with_bindings(&source, &raw_bindings(role)),
                vize_l0::cstr!(
                    "export function render() {{\n  const _vm1 = this;\n  return _vm1._c(\"div\", void 0, [_vm1._v(_vm1._s({emitted}))]);\n}}\nrender._withStripped = true;\nexport const staticRenderFns = [];\n"
                )
            );
        }
    }
}

#[test]
fn verbatim_module_names_use_private_declarations_with_stable_public_exports() {
    for role in [Access::Global, Access::Local] {
        assert_eq!(
            module_with_bindings("<div>{{ render }}</div>", &raw_bindings(role)),
            "function render1() {\n  const _vm = this;\n  return _vm._c(\"div\", void 0, [_vm._v(_vm._s(render))]);\n}\nrender1._withStripped = true;\nexport { render1 as render };\nexport const staticRenderFns = [];\n"
        );
        assert_eq!(
            module_with_bindings("<div>{{ staticRenderFns }}</div>", &raw_bindings(role)),
            "export function render() {\n  const _vm = this;\n  return _vm._c(\"div\", void 0, [_vm._v(_vm._s(staticRenderFns))]);\n}\nrender._withStripped = true;\nconst staticRenderFns1 = [];\nexport { staticRenderFns1 as staticRenderFns };\n"
        );
    }
}

#[test]
fn fresh_names_reserve_only_used_verbatim_names_and_choose_smallest_free_suffix() {
    let source = "<div>{{ [_vm,_vm1,render,render1,staticRenderFns,staticRenderFns1] }}</div>";
    for role in [Access::Global, Access::Local] {
        assert_eq!(
            module_with_bindings(source, &raw_bindings(role)),
            "function render2() {\n  const _vm2 = this;\n  return _vm2._c(\"div\", void 0, [_vm2._v(_vm2._s([_vm,_vm1,render,render1,staticRenderFns,staticRenderFns1]))]);\n}\nrender2._withStripped = true;\nexport { render2 as render };\nconst staticRenderFns2 = [];\nexport { staticRenderFns2 as staticRenderFns };\n"
        );
    }
    let mut mixed = bindings();
    for binding in &mut mixed {
        if matches!(binding.id.index(), 3 | 5 | 6) {
            binding.access = Access::Global;
        }
    }
    assert_eq!(
        module_with_bindings(source, &mixed),
        "function render1() {\n  const _vm1 = this;\n  return _vm1._c(\"div\", void 0, [_vm1._v(_vm1._s([_vm,_vm1._vm1,render,_vm1.render1,staticRenderFns,_vm1.staticRenderFns1]))]);\n}\nrender1._withStripped = true;\nexport { render1 as render };\nconst staticRenderFns1 = [];\nexport { staticRenderFns1 as staticRenderFns };\n"
    );
    assert_eq!(
        module_with_bindings(
            "<div>{{ ({_vm:count,render:label,staticRenderFns:value}) }}</div>",
            &raw_bindings(Access::Global)
        ),
        "export function render() {\n  const _vm = this;\n  return _vm._c(\"div\", void 0, [_vm._v(_vm._s(({_vm:_vm.count,render:_vm.label,staticRenderFns:_vm.value})))]);\n}\nrender._withStripped = true;\nexport const staticRenderFns = [];\n"
    );
}