Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions crates/oxc_angular_compiler/src/ir/enums.rs
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,10 @@ pub enum ExpressionKind {
Void,
/// Template literal with resolved expressions (used after name resolution).
ResolvedTemplateLiteral,
/// Tagged template literal with resolved tag and template expressions.
TaggedTemplateLiteral,
/// Spread element (...expr) inside a call or other expression list.
SpreadElement,
/// Arrow function expression.
ArrowFunction,
/// Parenthesized expression.
Expand Down
124 changes: 116 additions & 8 deletions crates/oxc_angular_compiler/src/ir/expression.rs
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,12 @@ pub enum IrExpression<'a> {
/// Used when a template literal like `\`bwi ${menuItem.icon}\`` has expressions
/// that need to be resolved to variables in scope.
ResolvedTemplateLiteral(Box<'a, ResolvedTemplateLiteralExpr<'a>>),
/// Tagged template literal (tag`...`) with IR tag and template expressions.
/// Used to preserve pipes and arrow functions inside tagged templates during ingest.
TaggedTemplateLiteral(Box<'a, IrTaggedTemplateLiteralExpr<'a>>),
/// Spread element (...expr) with an IR inner expression.
/// Used to preserve pipes and arrow functions inside spread positions during ingest.
SpreadElement(Box<'a, IrSpreadElementExpr<'a>>),
/// Arrow function expression.
/// Created by the generateArrowFunctions phase to wrap user-defined arrow functions.
ArrowFunction(Box<'a, ArrowFunctionExpr<'a>>),
Expand Down Expand Up @@ -267,6 +273,10 @@ impl<'a> IrExpression<'a> {
IrExpression::Void(_) => ExpressionKind::Void,
// ResolvedTemplateLiteral is a template literal with resolved expressions
IrExpression::ResolvedTemplateLiteral(_) => ExpressionKind::ResolvedTemplateLiteral,
// TaggedTemplateLiteral is a tagged template literal with IR sub-expressions
IrExpression::TaggedTemplateLiteral(_) => ExpressionKind::TaggedTemplateLiteral,
// SpreadElement is a spread element with an IR inner expression
IrExpression::SpreadElement(_) => ExpressionKind::SpreadElement,
// ArrowFunction is an arrow function expression
IrExpression::ArrowFunction(_) => ExpressionKind::ArrowFunction,
IrExpression::Parenthesized(_) => ExpressionKind::Parenthesized,
Expand Down Expand Up @@ -780,6 +790,35 @@ impl<'a> IrExpression<'a> {
&allocator,
))
}
IrExpression::TaggedTemplateLiteral(e) => {
let mut elements = Vec::with_capacity_in(e.elements.len(), &allocator);
for elem in e.elements.iter() {
elements.push(IrTemplateLiteralElement {
text: elem.text.clone(),
source_span: elem.source_span,
});
}
let mut expressions = Vec::with_capacity_in(e.expressions.len(), &allocator);
for expr in e.expressions.iter() {
expressions.push(expr.clone_in(allocator));
}
IrExpression::TaggedTemplateLiteral(Box::new_in(
IrTaggedTemplateLiteralExpr {
tag: Box::new_in(e.tag.clone_in(allocator), &allocator),
elements,
expressions,
source_span: e.source_span,
},
&allocator,
))
}
IrExpression::SpreadElement(e) => IrExpression::SpreadElement(Box::new_in(
IrSpreadElementExpr {
expr: Box::new_in(e.expr.clone_in(allocator), &allocator),
source_span: e.source_span,
},
&allocator,
)),
IrExpression::ArrowFunction(e) => {
let mut params = Vec::with_capacity_in(e.params.len(), &allocator);
for param in e.params.iter() {
Expand Down Expand Up @@ -1073,6 +1112,34 @@ pub struct IrTemplateLiteralElement<'a> {
pub source_span: Option<Span>,
}

/// Tagged template literal (tag`...`) with IR sub-expressions.
///
/// Used during ingest to preserve pipes and arrow functions inside the tag and
/// the template's interpolated expressions.
#[derive(Debug)]
pub struct IrTaggedTemplateLiteralExpr<'a> {
/// The tag function expression.
pub tag: Box<'a, IrExpression<'a>>,
/// Template literal text elements (the static parts between expressions).
pub elements: Vec<'a, IrTemplateLiteralElement<'a>>,
/// The dynamic parts inside ${...}.
pub expressions: Vec<'a, IrExpression<'a>>,
/// Source span.
pub source_span: Option<Span>,
}

/// Spread element (...expr) with an IR inner expression.
///
/// Used during ingest to preserve pipes and arrow functions inside spread
/// positions (e.g. call arguments).
#[derive(Debug)]
pub struct IrSpreadElementExpr<'a> {
/// The expression being spread.
pub expr: Box<'a, IrExpression<'a>>,
/// Source span.
pub source_span: Option<Span>,
}

/// Derived literal array for pure function bodies.
/// This is used when a literal array contains non-constant entries that need
/// to be replaced with PureFunctionParameter references.
Expand Down Expand Up @@ -1714,11 +1781,29 @@ pub fn transform_expressions_in_expression<'a, F>(
transform_expressions_in_expression(expr, transform, flags);
}
}
IrExpression::TaggedTemplateLiteral(e) => {
transform_expressions_in_expression(&mut e.tag, transform, flags);
for expr in e.expressions.iter_mut() {
transform_expressions_in_expression(expr, transform, flags);
}
}
IrExpression::SpreadElement(e) => {
transform_expressions_in_expression(&mut e.expr, transform, flags);
}
IrExpression::ArrowFunction(e) => {
// Transform body with InChildOperation and InArrowFunctionOperation flags set
let child_flags = flags
.union(VisitorContextFlag::IN_CHILD_OPERATION)
.union(VisitorContextFlag::IN_ARROW_FUNCTION_OPERATION);
// Mirror ir.ArrowFunctionExpr.transformInternalExpressions: ops are
// transformed as child operations inside an arrow function. The
// InArrowFunctionOperation flag only applies to top-level (collected)
// arrows; arrows preserved in place inside child operations (listener
// handlers, nested arrows) get only InChildOperation, matching the
// o.ArrowFunctionExpr branch of transformExpressionsInExpression.
let mut child_flags = flags.union(VisitorContextFlag::IN_CHILD_OPERATION);
if !flags.contains(VisitorContextFlag::IN_CHILD_OPERATION) {
child_flags = child_flags.union(VisitorContextFlag::IN_ARROW_FUNCTION_OPERATION);
}
for op in e.ops.iter_mut() {
transform_expressions_in_update_op(op, transform, child_flags);
}
transform_expressions_in_expression(&mut e.body, transform, child_flags);
}
IrExpression::Parenthesized(e) => {
Expand Down Expand Up @@ -1901,11 +1986,25 @@ pub fn visit_expressions_in_expression<'a, F>(
visit_expressions_in_expression(expr, visitor, flags);
}
}
IrExpression::TaggedTemplateLiteral(e) => {
visit_expressions_in_expression(&e.tag, visitor, flags);
for expr in e.expressions.iter() {
visit_expressions_in_expression(expr, visitor, flags);
}
}
IrExpression::SpreadElement(e) => {
visit_expressions_in_expression(&e.expr, visitor, flags);
}
IrExpression::ArrowFunction(e) => {
// Visit body with InChildOperation and InArrowFunctionOperation flags set
let child_flags = flags
.union(VisitorContextFlag::IN_CHILD_OPERATION)
.union(VisitorContextFlag::IN_ARROW_FUNCTION_OPERATION);
// See transform_expressions_in_expression for the flag rationale:
// InArrowFunctionOperation applies only to top-level (collected) arrows.
let mut child_flags = flags.union(VisitorContextFlag::IN_CHILD_OPERATION);
if !flags.contains(VisitorContextFlag::IN_CHILD_OPERATION) {
child_flags = child_flags.union(VisitorContextFlag::IN_ARROW_FUNCTION_OPERATION);
}
for op in e.ops.iter() {
visit_expressions_in_update_op(op, visitor, child_flags);
}
visit_expressions_in_expression(&e.body, visitor, child_flags);
}
IrExpression::Parenthesized(e) => {
Expand Down Expand Up @@ -3003,6 +3102,15 @@ pub fn vars_used_by_ir_expression(expr: &IrExpression<'_>) -> u32 {
rtl.expressions.iter().map(vars_used_by_ir_expression).sum()
}

// TaggedTemplateLiteral: vars used by tag and template expressions
IrExpression::TaggedTemplateLiteral(ttl) => {
vars_used_by_ir_expression(&ttl.tag)
+ ttl.expressions.iter().map(vars_used_by_ir_expression).sum::<u32>()
}

// SpreadElement: vars used by the inner expression
IrExpression::SpreadElement(spread) => vars_used_by_ir_expression(&spread.expr),

// All other expressions don't directly consume variable slots
IrExpression::LexicalRead(_)
| IrExpression::Reference(_)
Expand Down
6 changes: 6 additions & 0 deletions crates/oxc_angular_compiler/src/pipeline/compilation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -806,6 +806,11 @@ pub struct HostBindingCompilationUnit<'a> {
pub vars: Option<u32>,
/// Generated function name.
pub fn_name: Option<Ident<'a>>,
/// Arrow functions found in this unit.
///
/// Populated by the generateArrowFunctions phase. Only non-listener,
/// top-level arrows are collected, matching upstream `unit.functions`.
pub functions: Vec<'a, *mut crate::ir::expression::ArrowFunctionExpr<'a>>,
}

impl<'a> HostBindingCompilationUnit<'a> {
Expand All @@ -820,6 +825,7 @@ impl<'a> HostBindingCompilationUnit<'a> {
attributes: None,
vars: None,
fn_name: None,
functions: Vec::new_in(&allocator),
}
}
}
Expand Down
44 changes: 44 additions & 0 deletions crates/oxc_angular_compiler/src/pipeline/emit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1320,6 +1320,50 @@ fn convert_pure_function_body<'a>(
&allocator,
))
}
// TaggedTemplateLiteral: convert tag and template expressions
IrExpression::TaggedTemplateLiteral(ttl) => {
let tag = convert_pure_function_body(allocator, &ttl.tag, params);
let mut elements = OxcVec::new_in(&allocator);
let mut expressions = OxcVec::new_in(&allocator);
for elem in ttl.elements.iter() {
elements.push(crate::output::ast::TemplateLiteralElement {
text: elem.text.clone(),
raw_text: elem.text.clone(),
source_span: elem.source_span,
});
}
for expr in ttl.expressions.iter() {
expressions.push(convert_pure_function_body(allocator, expr, params));
}
OutputExpression::TaggedTemplateLiteral(Box::new_in(
crate::output::ast::TaggedTemplateLiteralExpr {
tag: Box::new_in(tag, &allocator),
template: Box::new_in(
crate::output::ast::TemplateLiteralExpr {
elements,
expressions,
source_span: ttl.source_span,
},
&allocator,
),
source_span: ttl.source_span,
},
&allocator,
))
}

// SpreadElement: convert the inner expression
IrExpression::SpreadElement(spread) => {
let inner = convert_pure_function_body(allocator, &spread.expr, params);
OutputExpression::SpreadElement(Box::new_in(
crate::output::ast::SpreadElementExpr {
expr: Box::new_in(inner, &allocator),
source_span: spread.source_span,
},
&allocator,
))
}

// Parenthesized expression - convert inner and wrap
IrExpression::Parenthesized(paren) => {
let inner = convert_pure_function_body(allocator, &paren.expr, params);
Expand Down
Loading
Loading