use oxc_allocator::{TakeIn, Vec};
use oxc_ast::{
AstBuilder, NONE,
ast::{
Expression, FormalParameterKind, FormalParameters, JSXAttributeName, JSXChild, JSXExpression,
ObjectPropertyKind, PropertyKey, PropertyKind,
},
};
use oxc_span::{SPAN, Span};
use vue_compiler_core::parser::Directive;
use crate::parser::{ParserImpl, parse::SourceLocatonSpan};
pub struct VSlotWrapper<'a, 'b> {
ast: &'a AstBuilder<'b>,
key: Option<PropertyKey<'b>>,
params: Option<FormalParameters<'b>>,
is_computed: Option<bool>,
}
impl<'a> ParserImpl<'a> {
pub fn analyze_v_slot(
&mut self,
dir: &Directive<'a>,
wrapper: &mut VSlotWrapper<'_, 'a>,
dir_name: &JSXAttributeName<'a>,
) {
(|| {
let JSXAttributeName::NamespacedName(name_space) = dir_name else {
unreachable!()
};
let key_span = name_space.name.span;
if key_span.is_empty() {
wrapper.set_is_computed(false);
wrapper.set_key(self.ast.property_key_static_identifier(SPAN, "default"));
} else {
let Expression::ObjectExpression(mut object_expression) =
(unsafe { self.parse_expression(key_span, b"({", b":0})")? })
else {
unreachable!()
};
let ObjectPropertyKind::ObjectProperty(object_property) =
object_expression.properties.first_mut().unwrap()
else {
unreachable!()
};
if let PropertyKey::StaticIdentifier(_) = object_property.key {
wrapper.set_is_computed(false);
} else {
wrapper.set_is_computed(true);
}
wrapper.set_key(object_property.key.take_in(self.allocator));
}
if dir.has_empty_expr() {
wrapper.set_params(self.ast.formal_parameters(
SPAN,
FormalParameterKind::ArrowFormalParameters,
self.ast.vec(),
NONE,
));
} else {
let expr = dir.expression.as_ref().unwrap();
let Expression::ArrowFunctionExpression(mut arrow_function_expression) = (unsafe {
let start = expr.location.span().start + 1;
self.parse_expression(
Span::new(start, start + expr.content.raw.len() as u32),
b"((",
b")=>0)",
)?
}) else {
unreachable!()
};
wrapper.set_params(arrow_function_expression.params.take_in(self.allocator));
}
Some(())
})();
}
}
impl<'a, 'b> VSlotWrapper<'a, 'b> {
pub const fn new(ast: &'a AstBuilder<'b>) -> Self {
Self { ast, key: None, params: None, is_computed: None }
}
pub fn wrap(self, children: Vec<'b, JSXChild<'b>>) -> Vec<'b, JSXChild<'b>> {
if self.include_v_slot() {
let Self { ast, key, params, is_computed } = self;
let key = key.unwrap();
let params = params.unwrap();
let is_computed = is_computed.unwrap();
ast.vec1(ast.jsx_child_expression_container(
SPAN,
JSXExpression::ObjectExpression(ast.alloc_object_expression(
SPAN,
ast.vec1(ast.object_property_kind_object_property(
SPAN,
PropertyKind::Init,
key,
ast.expression_arrow_function(
SPAN,
true,
false,
NONE,
params,
NONE,
ast.alloc_function_body(
SPAN,
ast.vec(),
ast.vec1(ast.statement_expression(
SPAN,
ast.expression_jsx_fragment(
SPAN,
ast.jsx_opening_fragment(SPAN),
children,
ast.jsx_closing_fragment(SPAN),
),
)),
),
),
false,
false,
is_computed,
)),
)),
))
} else {
children
}
}
}
impl<'b> VSlotWrapper<'_, 'b> {
const fn include_v_slot(&self) -> bool {
self.key.is_some() && self.params.is_some() && self.is_computed.is_some()
}
const fn set_key(&mut self, key: PropertyKey<'b>) {
self.key = Some(key);
}
const fn set_params(&mut self, params: FormalParameters<'b>) {
self.params = Some(params);
}
const fn set_is_computed(&mut self, is_computed: bool) {
self.is_computed = Some(is_computed);
}
}
#[cfg(test)]
mod tests {
use crate::test_ast;
#[test]
fn v_slot() {
test_ast!("directive/v-slot.vue");
}
}