vize_atelier_core 0.235.0

Atelier Core - The core workshop for Vize Vue template parsing, transform lanes, and code generation
Documentation
//! Element helper functions and predicates.
//!
//! Utility functions for checking element properties like directives,
//! renderable props, and special element types.

use crate::{
    DirectiveNode, ElementNode, ElementType, ExpressionNode, Namespace, PropNode, TemplateChildNode,
};
use vize_carton::is_builtin_directive;

use super::super::{
    context::CodegenContext, node::generate_node, props::is_supported_directive,
    v_for::generate_for, v_if::generate_if,
};

/// Check if element has v-once directive
pub fn has_v_once(el: &ElementNode<'_>) -> bool {
    el.props.iter().any(|prop| {
        if let PropNode::Directive(dir) = prop {
            dir.name.as_str() == "once"
        } else {
            false
        }
    })
}

/// Check if a template child node is whitespace-only text or a comment.
/// Used to skip generating empty default slots for components.
pub(crate) fn is_whitespace_or_comment(child: &TemplateChildNode<'_>) -> bool {
    match child {
        TemplateChildNode::Text(t) => t.content.trim().is_empty(),
        TemplateChildNode::Comment(_) => true,
        _ => false,
    }
}

/// Check if element has v-show directive
pub fn has_vshow_directive(el: &ElementNode<'_>) -> bool {
    el.props.iter().any(|prop| {
        if let PropNode::Directive(dir) = prop {
            dir.name.as_str() == "show" && dir.exp.is_some()
        } else {
            false
        }
    })
}

/// Check if element has custom directives
pub fn has_custom_directives(el: &ElementNode<'_>) -> bool {
    el.props.iter().any(|prop| {
        if let PropNode::Directive(dir) = prop {
            !is_builtin_directive(&dir.name)
        } else {
            false
        }
    })
}

/// Get custom directives from element
pub fn get_custom_directives<'a, 'b>(el: &'b ElementNode<'a>) -> Vec<&'b DirectiveNode<'a>> {
    el.props
        .iter()
        .filter_map(|prop| {
            if let PropNode::Directive(dir) = prop
                && !is_builtin_directive(&dir.name)
            {
                return Some(dir.as_ref());
            }
            None
        })
        .collect()
}

/// Check if native element has v-model directive
pub fn has_vmodel_directive(el: &ElementNode<'_>) -> bool {
    // Only native elements use withDirectives for v-model
    if el.tag_type != ElementType::Element {
        return false;
    }
    // Only input, textarea, select support v-model
    if !matches!(el.tag.as_str(), "input" | "textarea" | "select") {
        return false;
    }
    el.props.iter().any(|prop| {
        if let PropNode::Directive(dir) = prop {
            dir.name.as_str() == "model"
        } else {
            false
        }
    })
}

/// Get v-model directive from element
pub(crate) fn get_vmodel_directive<'a, 'b>(
    el: &'b ElementNode<'a>,
) -> Option<&'b DirectiveNode<'a>> {
    el.props.iter().find_map(|prop| {
        if let PropNode::Directive(dir) = prop
            && dir.name.as_str() == "model"
        {
            return Some(dir.as_ref());
        }
        None
    })
}

/// Check if a prop is the `is` attribute or `:is` binding (used by dynamic components)
pub(crate) fn is_is_prop(p: &PropNode<'_>) -> bool {
    match p {
        PropNode::Attribute(attr) => attr.name == "is",
        PropNode::Directive(dir) => {
            if dir.name == "bind"
                && let Some(ExpressionNode::Simple(arg)) = &dir.arg
            {
                return arg.content == "is";
            }
            false
        }
    }
}

/// Check if a tag is Vue's lowercase dynamic component special tag.
pub(crate) fn is_dynamic_component_tag(tag: &str) -> bool {
    tag == "component"
}

/// Check if an element should compile through resolveDynamicComponent().
pub(crate) fn is_dynamic_component(el: &ElementNode<'_>) -> bool {
    is_dynamic_component_tag(&el.tag) || (el.tag == "Component" && el.props.iter().any(is_is_prop))
}

/// Check if a single prop is renderable (not v-show or unsupported directive)
pub(crate) fn is_renderable_prop(prop: &PropNode<'_>) -> bool {
    match prop {
        PropNode::Attribute(_) => true,
        PropNode::Directive(dir) => is_supported_directive(dir),
    }
}

/// Check if element has any renderable props
pub fn has_renderable_props(el: &ElementNode<'_>) -> bool {
    el.props.iter().any(|prop| is_renderable_prop(prop))
}

/// Check if an element has a static-arg dynamic key binding (`:key="..."`).
pub(crate) fn has_dynamic_key_binding(el: &ElementNode<'_>) -> bool {
    el.props.iter().any(|prop| {
        matches!(
            prop,
            PropNode::Directive(dir)
                if dir.name == "bind"
                    && dir.exp.is_some()
                    && matches!(
                        dir.arg.as_ref(),
                        Some(ExpressionNode::Simple(arg))
                            if arg.is_static && arg.content.as_str() == "key"
                    )
        )
    })
}

/// Check whether a native element needs its own block to enter or leave an
/// SVG/MathML namespace boundary. Descendants already inside the same
/// namespace can stay as inline VNodes, matching Vue's DOM compiler output.
pub(crate) fn crosses_namespace_boundary(ctx: &CodegenContext, el: &ElementNode<'_>) -> bool {
    el.tag_type == ElementType::Element
        && (el.ns != ctx.parent_ns || children_cross_namespace_boundary(el.ns, &el.children))
}

pub(crate) fn child_namespace(el: &ElementNode<'_>) -> Namespace {
    match el.ns {
        Namespace::Svg if matches!(el.tag.as_str(), "foreignObject" | "desc" | "title") => {
            Namespace::Html
        }
        Namespace::MathMl
            if matches!(
                el.tag.as_str(),
                "annotation-xml" | "mi" | "mo" | "mn" | "ms" | "mtext"
            ) =>
        {
            Namespace::Html
        }
        _ => el.ns,
    }
}

fn children_cross_namespace_boundary(ns: Namespace, children: &[TemplateChildNode<'_>]) -> bool {
    children
        .iter()
        .any(|child| child_crosses_namespace_boundary(ns, child))
}

fn child_crosses_namespace_boundary(ns: Namespace, child: &TemplateChildNode<'_>) -> bool {
    match child {
        TemplateChildNode::Element(el) => match el.tag_type {
            ElementType::Element => el.ns != ns,
            ElementType::Template => children_cross_namespace_boundary(ns, &el.children),
            _ => false,
        },
        TemplateChildNode::If(if_node) => if_node
            .branches
            .iter()
            .any(|branch| children_cross_namespace_boundary(ns, &branch.children)),
        TemplateChildNode::For(for_node) => {
            children_cross_namespace_boundary(ns, &for_node.children)
        }
        _ => false,
    }
}

/// Generate root node (wrapped in block)
pub fn generate_root_node(ctx: &mut CodegenContext, node: &TemplateChildNode<'_>) {
    match node {
        TemplateChildNode::Element(el) => super::block::generate_element_block(ctx, el),
        TemplateChildNode::If(if_node) => generate_if(ctx, if_node),
        TemplateChildNode::For(for_node) => generate_for(ctx, for_node),
        _ => generate_node(ctx, node),
    }
}