use vize_armature::RootNode;
use vize_relief::ast::*;
use super::{MappingData, SourceMap, SourceMapping, SourceRange, VirtualDocument, VirtualLanguage};
pub struct TemplateCodeGenerator {
output: String,
mappings: Vec<SourceMapping>,
gen_offset: u32,
expr_counter: u32,
block_offset: u32,
}
impl TemplateCodeGenerator {
pub fn new() -> Self {
Self {
output: String::new(),
mappings: Vec::new(),
gen_offset: 0,
expr_counter: 0,
block_offset: 0,
}
}
pub fn set_block_offset(&mut self, offset: u32) {
self.block_offset = offset;
}
pub fn generate<'a>(&mut self, ast: &RootNode<'a>, _source: &str) -> VirtualDocument {
self.output.clear();
self.mappings.clear();
self.gen_offset = 0;
self.expr_counter = 0;
self.write_line("// Virtual TypeScript for template type checking");
self.write_line("// Generated by vize_maestro");
self.write_line("");
self.write_line("declare const __VIZE_ctx: {");
self.write_line(" // Context bindings from <script setup>");
self.write_line(" [key: string]: any;");
self.write_line("};");
self.write_line("");
self.write_line("// Template expressions");
self.visit_children(&ast.children);
let mut source_map = SourceMap::from_mappings(self.mappings.clone());
source_map.set_block_offset(self.block_offset);
VirtualDocument {
uri: String::new(), content: self.output.clone(),
language: VirtualLanguage::Template,
source_map,
}
}
fn visit_children(&mut self, children: &[TemplateChildNode]) {
for child in children {
self.visit_child(child);
}
}
fn visit_child(&mut self, node: &TemplateChildNode) {
match node {
TemplateChildNode::Element(el) => self.visit_element(el),
TemplateChildNode::Text(_) => {}
TemplateChildNode::Comment(_) => {}
TemplateChildNode::Interpolation(interp) => self.visit_interpolation(interp),
TemplateChildNode::If(if_node) => self.visit_if(if_node),
TemplateChildNode::For(for_node) => self.visit_for(for_node),
TemplateChildNode::TextCall(_) => {}
TemplateChildNode::IfBranch(branch) => {
if let Some(ref condition) = branch.condition {
self.emit_expression_node(condition, "if");
}
self.visit_children(&branch.children);
}
TemplateChildNode::CompoundExpression(_) => {}
TemplateChildNode::Hoisted(_) => {}
}
}
fn visit_element(&mut self, element: &ElementNode) {
for prop in &element.props {
if let PropNode::Directive(dir) = prop {
self.visit_directive(dir, &element.tag);
}
}
self.visit_children(&element.children);
}
fn visit_directive(&mut self, directive: &DirectiveNode, _tag: &str) {
let dir_name = &directive.name;
if let Some(ref exp) = directive.exp {
self.emit_expression_node(exp, dir_name);
}
}
fn visit_interpolation(&mut self, interp: &InterpolationNode) {
self.emit_expression_node(&interp.content, "interpolation");
}
fn visit_if(&mut self, if_node: &IfNode) {
for branch in &if_node.branches {
if let Some(ref condition) = branch.condition {
self.emit_expression_node(condition, "if");
}
self.visit_children(&branch.children);
}
}
fn visit_for(&mut self, for_node: &ForNode) {
self.emit_expression_node(&for_node.source, "for");
self.visit_children(&for_node.children);
}
fn emit_expression_node(&mut self, expr: &ExpressionNode, dir_name: &str) {
match expr {
ExpressionNode::Simple(simple) => {
self.emit_simple_expression(simple, dir_name);
}
ExpressionNode::Compound(_compound) => {
let var_name = format!("__VIZE_{}", self.expr_counter);
self.expr_counter += 1;
let line = format!("const {} = void 0; // compound expression\n", var_name);
self.write(&line);
}
}
}
fn emit_simple_expression(&mut self, expr: &SimpleExpressionNode, _dir_name: &str) {
if expr.content.is_empty() {
return;
}
let var_name = format!("__VIZE_{}", self.expr_counter);
self.expr_counter += 1;
let line = format!("const {} = __VIZE_ctx.{};\n", var_name, expr.content);
let expr_start_in_line = format!("const {} = __VIZE_ctx.", var_name).len() as u32;
let gen_start = self.gen_offset + expr_start_in_line;
let gen_end = gen_start + expr.content.len() as u32;
let source_start = expr.loc.start.offset;
let source_end = expr.loc.end.offset;
let mapping = SourceMapping::with_data(
SourceRange::new(source_start, source_end),
SourceRange::new(gen_start, gen_end),
MappingData::Expression {
text: expr.content.to_string(),
},
);
self.mappings.push(mapping);
self.write(&line);
}
fn write(&mut self, s: &str) {
self.output.push_str(s);
self.gen_offset += s.len() as u32;
}
fn write_line(&mut self, s: &str) {
self.output.push_str(s);
self.output.push('\n');
self.gen_offset += s.len() as u32 + 1;
}
}
impl Default for TemplateCodeGenerator {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct TemplateExpression {
pub text: String,
pub range: SourceRange,
pub kind: ExpressionKind,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ExpressionKind {
Interpolation,
VBind,
VOn,
VIf,
VFor,
VModel,
VShow,
VSlot,
Other,
}
pub fn extract_expressions<'a>(ast: &'a RootNode<'a>) -> Vec<TemplateExpression> {
let mut expressions = Vec::new();
extract_from_children(&ast.children, &mut expressions);
expressions
}
fn extract_from_children<'a>(
children: &'a [TemplateChildNode<'a>],
expressions: &mut Vec<TemplateExpression>,
) {
for child in children {
extract_from_child(child, expressions);
}
}
fn extract_from_child<'a>(
node: &'a TemplateChildNode<'a>,
expressions: &mut Vec<TemplateExpression>,
) {
match node {
TemplateChildNode::Element(el) => {
for prop in &el.props {
if let PropNode::Directive(dir) = prop {
extract_from_directive(dir, expressions);
}
}
extract_from_children(&el.children, expressions);
}
TemplateChildNode::Interpolation(interp) => {
if let Some((text, loc)) = get_expression_content(&interp.content) {
expressions.push(TemplateExpression {
text,
range: SourceRange::from(loc),
kind: ExpressionKind::Interpolation,
});
}
}
TemplateChildNode::If(if_node) => {
for branch in &if_node.branches {
if let Some(ref cond) = branch.condition {
if let Some((text, loc)) = get_expression_content(cond) {
expressions.push(TemplateExpression {
text,
range: SourceRange::from(loc),
kind: ExpressionKind::VIf,
});
}
}
extract_from_children(&branch.children, expressions);
}
}
TemplateChildNode::For(for_node) => {
if let Some((text, loc)) = get_expression_content(&for_node.source) {
expressions.push(TemplateExpression {
text,
range: SourceRange::from(loc),
kind: ExpressionKind::VFor,
});
}
extract_from_children(&for_node.children, expressions);
}
TemplateChildNode::IfBranch(branch) => {
if let Some(ref cond) = branch.condition {
if let Some((text, loc)) = get_expression_content(cond) {
expressions.push(TemplateExpression {
text,
range: SourceRange::from(loc),
kind: ExpressionKind::VIf,
});
}
}
extract_from_children(&branch.children, expressions);
}
_ => {}
}
}
fn extract_from_directive<'a>(
dir: &'a DirectiveNode<'a>,
expressions: &mut Vec<TemplateExpression>,
) {
if let Some(ref exp) = dir.exp {
let kind = match dir.name.as_str() {
"bind" => ExpressionKind::VBind,
"on" => ExpressionKind::VOn,
"if" | "else-if" => ExpressionKind::VIf,
"for" => ExpressionKind::VFor,
"model" => ExpressionKind::VModel,
"show" => ExpressionKind::VShow,
"slot" => ExpressionKind::VSlot,
_ => ExpressionKind::Other,
};
if let Some((text, loc)) = get_expression_content(exp) {
expressions.push(TemplateExpression {
text,
range: SourceRange::from(loc),
kind,
});
}
}
}
fn get_expression_content<'a>(
expr: &'a ExpressionNode<'a>,
) -> Option<(String, &'a SourceLocation)> {
match expr {
ExpressionNode::Simple(simple) => {
if simple.content.is_empty() {
None
} else {
Some((simple.content.to_string(), &simple.loc))
}
}
ExpressionNode::Compound(compound) => {
Some(("<compound>".to_string(), &compound.loc))
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_template_code_generator() {
let source = r#"<div>{{ message }}</div>"#;
let allocator = vize_carton::Bump::new();
let (ast, _) = vize_armature::parse(&allocator, source);
let mut gen = TemplateCodeGenerator::new();
let doc = gen.generate(&ast, source);
assert!(doc.content.contains("__VIZE_ctx.message"));
assert!(!doc.source_map.is_empty());
}
#[test]
fn test_generator_with_directives() {
let source = r#"<div v-if="show">test</div>"#;
let allocator = vize_carton::Bump::new();
let (ast, _) = vize_armature::parse(&allocator, source);
let mut gen = TemplateCodeGenerator::new();
let doc = gen.generate(&ast, source);
assert!(doc.content.contains("__VIZE_"));
}
}