use std::mem::take;
use oxc_ast::{
AstBuilder,
ast::{Expression, JSXChild},
};
use oxc_span::{GetSpan, SPAN};
use crate::parser::{ParserImpl, error};
pub enum VIf<'a> {
If(Expression<'a>),
ElseIf(Expression<'a>),
Else,
}
#[allow(clippy::from_over_into)]
impl<'a> Into<Expression<'a>> for VIf<'a> {
fn into(self) -> Expression<'a> {
match self {
VIf::If(e) | VIf::ElseIf(e) => e,
VIf::Else => panic!("VIf::Else::into() called. v-else has no expression"),
}
}
}
pub struct VIfManager<'a, 'b> {
ast: &'a AstBuilder<'b>,
chain: Vec<(JSXChild<'b>, VIf<'b>)>, }
impl<'a> ParserImpl<'a> {
pub fn add_v_if(
&mut self,
child: JSXChild<'a>,
v_if: VIf<'a>,
manager: &mut VIfManager<'_, 'a>,
) -> Option<JSXChild<'a>> {
if matches!(v_if, VIf::If(_)) {
if manager.chain.is_empty() {
manager.chain.push((child, v_if));
None
} else {
let result = manager.take_chain();
manager.chain.push((child, v_if));
result
}
} else if manager.chain.is_empty() {
error::v_else_without_adjacent_if(&mut self.errors, child.span());
Some(child)
} else if matches!(v_if, VIf::Else) {
manager.chain.push((child, v_if));
manager.take_chain()
} else {
manager.chain.push((child, v_if));
None
}
}
}
impl<'a, 'b> VIfManager<'a, 'b> {
pub const fn new(ast: &'a AstBuilder<'b>) -> Self {
Self { ast, chain: vec![] }
}
pub fn take_chain(&mut self) -> Option<JSXChild<'b>> {
if self.chain.is_empty() {
return None;
}
let ast = self.ast;
let mut chain_stack = take(&mut self.chain);
let last = if matches!(chain_stack.last().unwrap().1, VIf::Else) {
self.build_jsx_fragment_expression(chain_stack.pop().unwrap().0)
} else {
ast.expression_identifier(SPAN, "undefined")
};
let mut result = last;
while let Some((child, v_if)) = chain_stack.pop() {
result = ast.expression_conditional(
SPAN,
v_if.into(),
self.build_jsx_fragment_expression(child),
result,
);
}
Some(ast.jsx_child_expression_container(SPAN, result.into()))
}
fn build_jsx_fragment_expression(&self, child: JSXChild<'b>) -> Expression<'b> {
Expression::JSXFragment(self.ast.alloc_jsx_fragment(
SPAN,
self.ast.jsx_opening_fragment(SPAN),
self.ast.vec1(child),
self.ast.jsx_closing_fragment(SPAN),
))
}
}
#[cfg(test)]
mod tests {
use crate::test_ast;
#[test]
fn v_if() {
test_ast!("directive/v-if.vue");
test_ast!("directive/v-if-error.vue", true, false);
}
}