swc_vue_jsx_visitor/
lib.rs

1use directive::{is_directive, parse_directive, Directive, NormalDirective};
2use fnv::FnvHashMap;
3use indexmap::IndexSet;
4pub use options::{Options, Regex};
5use patch_flags::PatchFlags;
6use slot_flag::SlotFlag;
7use std::{borrow::Cow, collections::BTreeMap, mem};
8use swc_core::{
9    common::{comments::Comments, Mark, Span, Spanned, SyntaxContext, DUMMY_SP},
10    ecma::{
11        ast::*,
12        atoms::JsWord,
13        utils::{private_ident, quote_ident, quote_str},
14        visit::{VisitMut, VisitMutWith},
15    },
16    plugin::errors::HANDLER,
17};
18
19mod directive;
20mod options;
21mod patch_flags;
22mod resolve_type;
23mod slot_flag;
24mod util;
25
26const FRAGMENT: &str = "Fragment";
27const KEEP_ALIVE: &str = "KeepAlive";
28
29struct AttrsTransformationResult<'a> {
30    attrs: Expr,
31    patch_flags: PatchFlags,
32    dynamic_props: Option<IndexSet<Cow<'a, str>>>,
33    slots: Option<Box<Expr>>,
34}
35
36pub struct VueJsxTransformVisitor<C>
37where
38    C: Comments,
39{
40    options: Options,
41    vue_imports: BTreeMap<&'static str, Ident>,
42    transform_on_helper: Option<Ident>,
43
44    define_component: Option<SyntaxContext>,
45    interfaces: FnvHashMap<(JsWord, SyntaxContext), TsInterfaceDecl>,
46    type_aliases: FnvHashMap<(JsWord, SyntaxContext), TsType>,
47
48    unresolved_mark: Mark,
49    comments: Option<C>,
50
51    pragma: Option<String>,
52    slot_helper_ident: Option<Ident>,
53    injecting_vars: Vec<VarDeclarator>,
54    slot_counter: usize,
55    slot_flag_stack: Vec<SlotFlag>,
56
57    assignment_left: Option<Ident>,
58    injecting_consts: Vec<VarDeclarator>,
59}
60
61impl<C> VueJsxTransformVisitor<C>
62where
63    C: Comments,
64{
65    pub fn new(options: Options, unresolved_mark: Mark, comments: Option<C>) -> Self {
66        Self {
67            options,
68            vue_imports: Default::default(),
69            transform_on_helper: None,
70
71            define_component: None,
72            interfaces: Default::default(),
73            type_aliases: Default::default(),
74
75            unresolved_mark,
76            comments,
77
78            pragma: None,
79            slot_helper_ident: None,
80            injecting_vars: Default::default(),
81            slot_counter: 1,
82            slot_flag_stack: Default::default(),
83
84            assignment_left: None,
85            injecting_consts: Default::default(),
86        }
87    }
88
89    fn import_from_vue(&mut self, item: &'static str) -> Ident {
90        self.vue_imports
91            .entry(item)
92            .or_insert_with_key(|name| private_ident!(format!("_{name}")))
93            .clone()
94    }
95
96    fn generate_slot_helper(&mut self) -> Ident {
97        self.slot_helper_ident
98            .get_or_insert_with(|| private_ident!("_isSlot"))
99            .clone()
100    }
101
102    fn transform_jsx_element(&mut self, jsx_element: &JSXElement) -> Expr {
103        if self.options.optimize {
104            self.slot_flag_stack.push(SlotFlag::Stable);
105        }
106
107        let is_component = self.is_component(&jsx_element.opening.name);
108        let mut directives = vec![];
109        let AttrsTransformationResult {
110            attrs,
111            patch_flags,
112            dynamic_props,
113            slots,
114        } = self.transform_attrs(&jsx_element.opening.attrs, is_component, &mut directives);
115        let mut vnode_call_args = vec![
116            ExprOrSpread {
117                spread: None,
118                expr: Box::new(self.transform_tag(&jsx_element.opening.name)),
119            },
120            ExprOrSpread {
121                spread: None,
122                expr: Box::new(attrs),
123            },
124            ExprOrSpread {
125                spread: None,
126                expr: Box::new(self.transform_children(&jsx_element.children, is_component, slots)),
127            },
128        ];
129        if self.options.optimize {
130            if !patch_flags.is_empty() {
131                vnode_call_args.push(ExprOrSpread {
132                    spread: None,
133                    expr: Box::new(Expr::Lit(Lit::Num(Number {
134                        span: DUMMY_SP,
135                        value: patch_flags.bits() as f64,
136                        raw: None,
137                    }))),
138                });
139            }
140            match dynamic_props {
141                Some(dynamic_props) if !dynamic_props.is_empty() => {
142                    vnode_call_args.push(ExprOrSpread {
143                        spread: None,
144                        expr: Box::new(Expr::Array(ArrayLit {
145                            span: DUMMY_SP,
146                            elems: dynamic_props
147                                .into_iter()
148                                .map(|prop| {
149                                    Some(ExprOrSpread {
150                                        spread: None,
151                                        expr: Box::new(Expr::Lit(Lit::Str(quote_str!(prop)))),
152                                    })
153                                })
154                                .collect(),
155                        })),
156                    })
157                }
158                _ => {}
159            }
160        }
161
162        let create_vnode_call = Expr::Call(CallExpr {
163            span: DUMMY_SP,
164            callee: Callee::Expr(Box::new(Expr::Ident(self.get_pragma()))),
165            args: vnode_call_args,
166            ..Default::default()
167        });
168
169        if directives.is_empty() {
170            create_vnode_call
171        } else {
172            Expr::Call(CallExpr {
173                span: DUMMY_SP,
174                callee: Callee::Expr(Box::new(Expr::Ident(
175                    self.import_from_vue("withDirectives"),
176                ))),
177                args: vec![
178                    ExprOrSpread {
179                        spread: None,
180                        expr: Box::new(create_vnode_call),
181                    },
182                    ExprOrSpread {
183                        spread: None,
184                        expr: Box::new(Expr::Array(ArrayLit {
185                            span: DUMMY_SP,
186                            elems: directives
187                                .into_iter()
188                                .map(|directive| {
189                                    let mut elems =
190                                        vec![
191                                            Some(ExprOrSpread {
192                                                spread: None,
193                                                expr: Box::new(self.resolve_directive(
194                                                    &directive.name,
195                                                    jsx_element,
196                                                )),
197                                            }),
198                                            Some(ExprOrSpread {
199                                                spread: None,
200                                                expr: Box::new(directive.value),
201                                            }),
202                                        ];
203                                    if let Some(argument) = directive.argument {
204                                        elems.push(Some(ExprOrSpread {
205                                            spread: None,
206                                            expr: Box::new(argument),
207                                        }));
208                                    }
209                                    if let Some(modifiers) = directive.modifiers {
210                                        elems.push(Some(ExprOrSpread {
211                                            spread: None,
212                                            expr: Box::new(modifiers),
213                                        }));
214                                    }
215                                    Some(ExprOrSpread {
216                                        spread: None,
217                                        expr: Box::new(Expr::Array(ArrayLit {
218                                            span: DUMMY_SP,
219                                            elems,
220                                        })),
221                                    })
222                                })
223                                .collect(),
224                        })),
225                    },
226                ],
227                ..Default::default()
228            })
229        }
230    }
231
232    fn transform_jsx_fragment(&mut self, jsx_fragment: &JSXFragment) -> Expr {
233        if self.options.optimize {
234            self.slot_flag_stack.push(SlotFlag::Stable);
235        }
236
237        Expr::Call(CallExpr {
238            span: DUMMY_SP,
239            callee: Callee::Expr(Box::new(Expr::Ident(self.get_pragma()))),
240            args: vec![
241                ExprOrSpread {
242                    spread: None,
243                    expr: Box::new(Expr::Ident(self.import_from_vue(FRAGMENT))),
244                },
245                ExprOrSpread {
246                    spread: None,
247                    expr: Box::new(Expr::Lit(Lit::Null(Null { span: DUMMY_SP }))),
248                },
249                ExprOrSpread {
250                    spread: None,
251                    expr: Box::new(self.transform_children(&jsx_fragment.children, false, None)),
252                },
253            ],
254            ..Default::default()
255        })
256    }
257
258    fn transform_tag(&mut self, jsx_element_name: &JSXElementName) -> Expr {
259        match jsx_element_name {
260            JSXElementName::Ident(ident) => {
261                let name = &*ident.sym;
262                if name.as_bytes()[0].is_ascii_lowercase()
263                    && (css_dataset::tags::STANDARD_HTML_TAGS.contains(name)
264                        || css_dataset::tags::SVG_TAGS.contains(name))
265                {
266                    Expr::Lit(Lit::Str(quote_str!(name)))
267                } else if name == FRAGMENT {
268                    Expr::Ident(self.import_from_vue(FRAGMENT))
269                } else if self
270                    .options
271                    .custom_element_patterns
272                    .iter()
273                    .any(|pattern| pattern.is_match(name))
274                {
275                    Expr::Lit(Lit::Str(quote_str!(name)))
276                } else if ident.to_id().1.has_mark(self.unresolved_mark) {
277                    // for components that can't be resolved from current file
278                    Expr::Call(CallExpr {
279                        span: DUMMY_SP,
280                        callee: Callee::Expr(Box::new(Expr::Ident(
281                            self.import_from_vue("resolveComponent"),
282                        ))),
283                        args: vec![ExprOrSpread {
284                            spread: None,
285                            expr: Box::new(Expr::Lit(Lit::Str(quote_str!(name)))),
286                        }],
287                        ..Default::default()
288                    })
289                } else {
290                    Expr::Ident(ident.clone())
291                }
292            }
293            JSXElementName::JSXMemberExpr(expr) => Expr::JSXMember(expr.clone()),
294            JSXElementName::JSXNamespacedName(name) => Expr::JSXNamespacedName(name.clone()),
295        }
296    }
297
298    fn transform_attrs<'a>(
299        &mut self,
300        attrs: &'a [JSXAttrOrSpread],
301        is_component: bool,
302        directives: &mut Vec<NormalDirective>,
303    ) -> AttrsTransformationResult<'a> {
304        let mut slots = None;
305
306        if attrs.is_empty() {
307            return AttrsTransformationResult {
308                attrs: Expr::Lit(Lit::Null(Null { span: DUMMY_SP })),
309                patch_flags: PatchFlags::empty(),
310                dynamic_props: None,
311                slots,
312            };
313        }
314
315        let mut dynamic_props = IndexSet::new();
316
317        // patch flags analysis
318        let mut has_ref = false;
319        let mut has_class_binding = false;
320        let mut has_style_binding = false;
321        let mut has_hydration_event_binding = false;
322        let mut has_dynamic_keys = false;
323
324        let (mut props, mut merge_args) = attrs.iter().fold(
325            (
326                Vec::with_capacity(attrs.len()),
327                Vec::with_capacity(attrs.len()),
328            ),
329            |(mut props, mut merge_args), jsx_attr_or_spread| {
330                match jsx_attr_or_spread {
331                    JSXAttrOrSpread::JSXAttr(jsx_attr) if is_directive(jsx_attr) => {
332                        match parse_directive(jsx_attr, is_component) {
333                            Directive::Normal(directive) => directives.push(directive),
334                            Directive::Html(expr) => {
335                                props.push(PropOrSpread::Prop(Box::new(Prop::KeyValue(
336                                    KeyValueProp {
337                                        key: PropName::Str(quote_str!("innerHTML")),
338                                        value: Box::new(expr),
339                                    },
340                                ))));
341                                dynamic_props.insert("innerHTML".into());
342                            }
343                            Directive::Text(expr) => {
344                                props.push(PropOrSpread::Prop(Box::new(Prop::KeyValue(
345                                    KeyValueProp {
346                                        key: PropName::Str(quote_str!("textContent")),
347                                        value: Box::new(expr),
348                                    },
349                                ))));
350                                dynamic_props.insert("textContent".into());
351                            }
352                            Directive::VModel(directive) => {
353                                if is_component {
354                                    props.push(PropOrSpread::Prop(Box::new(Prop::KeyValue(
355                                        KeyValueProp {
356                                            key: match &directive.argument {
357                                                Some(Expr::Lit(Lit::Null(..))) | None => {
358                                                    dynamic_props.insert("modelValue".into());
359                                                    PropName::Str(quote_str!("modelValue"))
360                                                }
361                                                Some(Expr::Lit(Lit::Str(Str {
362                                                    value, ..
363                                                }))) => {
364                                                    dynamic_props
365                                                        .insert(Cow::from(value.to_string()));
366                                                    PropName::Str(quote_str!(&**value))
367                                                }
368                                                Some(expr) => {
369                                                    PropName::Computed(ComputedPropName {
370                                                        span: DUMMY_SP,
371                                                        expr: Box::new(expr.clone()),
372                                                    })
373                                                }
374                                            },
375                                            value: Box::new(directive.value.clone()),
376                                        },
377                                    ))));
378                                    if let Some(modifiers) = directive.modifiers {
379                                        props.push(PropOrSpread::Prop(Box::new(Prop::KeyValue(
380                                            KeyValueProp {
381                                                key: match &directive.argument {
382                                                    Some(Expr::Lit(Lit::Null(..))) | None => {
383                                                        PropName::Str(quote_str!("modelModifiers"))
384                                                    }
385                                                    Some(Expr::Lit(Lit::Str(Str {
386                                                        value,
387                                                        ..
388                                                    }))) => PropName::Str(quote_str!(format!(
389                                                        "{value}Modifiers"
390                                                    ))),
391                                                    Some(expr) => {
392                                                        PropName::Computed(ComputedPropName {
393                                                            span: DUMMY_SP,
394                                                            expr: Box::new(Expr::Bin(BinExpr {
395                                                                span: DUMMY_SP,
396                                                                op: op!(bin, "+"),
397                                                                left: Box::new(expr.clone()),
398                                                                right: Box::new(Expr::Lit(
399                                                                    Lit::Str(quote_str!(
400                                                                        "Modifiers"
401                                                                    )),
402                                                                )),
403                                                            })),
404                                                        })
405                                                    }
406                                                },
407                                                value: Box::new(modifiers),
408                                            },
409                                        ))))
410                                    }
411                                } else {
412                                    directives.push(NormalDirective {
413                                        name: JsWord::from("model"),
414                                        argument: directive.transformed_argument,
415                                        modifiers: directive.modifiers.clone(),
416                                        value: directive.value.clone(),
417                                    });
418                                }
419
420                                props.push(PropOrSpread::Prop(Box::new(Prop::KeyValue(
421                                    KeyValueProp {
422                                        key: match directive.argument {
423                                            Some(Expr::Lit(Lit::Null(..))) | None => {
424                                                dynamic_props.insert("onUpdate:modelValue".into());
425                                                PropName::Str(quote_str!("onUpdate:modelValue"))
426                                            }
427                                            Some(Expr::Lit(Lit::Str(Str { value, .. }))) => {
428                                                let name = format!("onUpdate:{value}");
429                                                let prop_name = PropName::Str(quote_str!(&*name));
430                                                dynamic_props.insert(name.into());
431                                                prop_name
432                                            }
433                                            Some(expr) => {
434                                                has_dynamic_keys = true;
435                                                PropName::Computed(ComputedPropName {
436                                                    span: DUMMY_SP,
437                                                    expr: Box::new(Expr::Bin(BinExpr {
438                                                        span: DUMMY_SP,
439                                                        op: op!(bin, "+"),
440                                                        left: Box::new(Expr::Lit(Lit::Str(
441                                                            quote_str!("onUpdate"),
442                                                        ))),
443                                                        right: Box::new(expr),
444                                                    })),
445                                                })
446                                            }
447                                        },
448                                        value: Box::new(Expr::Arrow(ArrowExpr {
449                                            span: DUMMY_SP,
450                                            params: vec![Pat::Ident(BindingIdent {
451                                                id: quote_ident!("$event").into(),
452                                                type_ann: None,
453                                            })],
454                                            body: Box::new(BlockStmtOrExpr::Expr(Box::new(
455                                                Expr::Assign(AssignExpr {
456                                                    span: DUMMY_SP,
457                                                    op: op!("="),
458                                                    left: AssignTarget::Simple(
459                                                        SimpleAssignTarget::Paren(ParenExpr {
460                                                            span: DUMMY_SP,
461                                                            expr: Box::new(directive.value),
462                                                        }),
463                                                    ),
464                                                    right: Box::new(Expr::Ident(
465                                                        quote_ident!("$event").into(),
466                                                    )),
467                                                }),
468                                            ))),
469                                            is_async: false,
470                                            is_generator: false,
471                                            ..Default::default()
472                                        })),
473                                    },
474                                ))));
475                            }
476                            Directive::Slots(expr) => slots = expr,
477                        }
478                    }
479                    JSXAttrOrSpread::JSXAttr(jsx_attr) => {
480                        let attr_name = match &jsx_attr.name {
481                            JSXAttrName::Ident(ident) => Cow::from(&*ident.sym),
482                            JSXAttrName::JSXNamespacedName(name) => {
483                                Cow::from(format!("{}:{}", name.ns.sym, name.name.sym))
484                            }
485                        };
486                        let attr_value = jsx_attr
487                            .value
488                            .as_ref()
489                            .map(|value| match value {
490                                JSXAttrValue::Lit(Lit::Str(str)) => Box::new(Expr::Lit(Lit::Str(
491                                    quote_str!(util::transform_text(&str.value)),
492                                ))),
493                                JSXAttrValue::Lit(..) => {
494                                    unreachable!("JSX attribute value literal must be string")
495                                }
496                                JSXAttrValue::JSXExprContainer(JSXExprContainer {
497                                    expr: JSXExpr::Expr(expr),
498                                    ..
499                                }) => expr.clone(),
500                                JSXAttrValue::JSXExprContainer(JSXExprContainer {
501                                    expr: JSXExpr::JSXEmptyExpr(expr),
502                                    ..
503                                }) => Box::new(Expr::JSXEmpty(*expr)),
504                                JSXAttrValue::JSXElement(element) => {
505                                    Box::new(Expr::JSXElement(element.clone()))
506                                }
507                                JSXAttrValue::JSXFragment(fragment) => {
508                                    Box::new(Expr::JSXFragment(fragment.clone()))
509                                }
510                            })
511                            .unwrap_or_else(|| {
512                                Box::new(Expr::Lit(Lit::Bool(Bool {
513                                    span: DUMMY_SP,
514                                    value: true,
515                                })))
516                            });
517
518                        if attr_name == "ref" {
519                            has_ref = true;
520                        } else if !jsx_attr
521                            .value
522                            .as_ref()
523                            .map(util::is_jsx_attr_value_constant)
524                            .unwrap_or_default()
525                        {
526                            if !is_component && util::is_on(&attr_name)
527                                // omit the flag for click handlers becaues hydration gives click
528                                // dedicated fast path.
529                                && !attr_name.eq_ignore_ascii_case("onclick")
530                                // omit v-model handlers
531                                && attr_name != "onUpdate:modelValue"
532                            {
533                                has_hydration_event_binding = true;
534                            }
535                            match &*attr_name {
536                                "class" if !is_component => has_class_binding = true,
537                                "style" if !is_component => has_style_binding = true,
538                                "key" | "on" | "ref" => {}
539                                _ => {
540                                    dynamic_props.insert(attr_name.clone());
541                                }
542                            }
543                        }
544
545                        if self.options.transform_on
546                            && (attr_name == "on" || attr_name == "nativeOn")
547                        {
548                            merge_args.push(Expr::Call(CallExpr {
549                                span: DUMMY_SP,
550                                callee: Callee::Expr(Box::new(Expr::Ident(
551                                    self.transform_on_helper
552                                        .get_or_insert_with(|| private_ident!("_transformOn"))
553                                        .clone(),
554                                ))),
555                                args: vec![ExprOrSpread {
556                                    spread: None,
557                                    expr: attr_value,
558                                }],
559                                ..Default::default()
560                            }));
561                        } else {
562                            props.push(PropOrSpread::Prop(Box::new(Prop::KeyValue(
563                                KeyValueProp {
564                                    key: PropName::Str(quote_str!(attr_name)),
565                                    value: attr_value,
566                                },
567                            ))));
568                        }
569                    }
570                    JSXAttrOrSpread::SpreadElement(spread) => {
571                        has_dynamic_keys = true;
572
573                        if !props.is_empty() && self.options.merge_props {
574                            merge_args.push(Expr::Object(ObjectLit {
575                                span: DUMMY_SP,
576                                props: util::dedupe_props(mem::take(&mut props)),
577                            }));
578                        }
579
580                        if let Expr::Object(object) = &*spread.expr {
581                            if self.options.merge_props {
582                                merge_args.push(Expr::Object(object.clone()));
583                            } else {
584                                props.extend_from_slice(&object.props);
585                            }
586                        } else if self.options.merge_props {
587                            merge_args.push(*spread.expr.clone());
588                        } else {
589                            props.push(PropOrSpread::Spread(spread.clone()));
590                        }
591                    }
592                }
593                (props, merge_args)
594            },
595        );
596
597        let expr = if !merge_args.is_empty() {
598            if !props.is_empty() {
599                merge_args.push(Expr::Object(ObjectLit {
600                    span: DUMMY_SP,
601                    props: if self.options.merge_props {
602                        util::dedupe_props(mem::take(&mut props))
603                    } else {
604                        mem::take(&mut props)
605                    },
606                }));
607            }
608            match merge_args.as_slice() {
609                [expr] => expr.clone(),
610                _ => Expr::Call(CallExpr {
611                    span: DUMMY_SP,
612                    callee: Callee::Expr(Box::new(Expr::Ident(self.import_from_vue("mergeProps")))),
613                    args: merge_args
614                        .into_iter()
615                        .map(|expr| ExprOrSpread {
616                            spread: None,
617                            expr: Box::new(expr),
618                        })
619                        .collect(),
620                    ..Default::default()
621                }),
622            }
623        } else if !props.is_empty() {
624            if let [PropOrSpread::Spread(SpreadElement { expr, .. })] = props.as_slice() {
625                *expr.clone()
626            } else {
627                Expr::Object(ObjectLit {
628                    span: DUMMY_SP,
629                    props: if self.options.merge_props {
630                        util::dedupe_props(props)
631                    } else {
632                        props
633                    },
634                })
635            }
636        } else {
637            Expr::Lit(Lit::Null(Null { span: DUMMY_SP }))
638        };
639
640        let mut patch_flags = PatchFlags::empty();
641        if has_dynamic_keys {
642            patch_flags.insert(PatchFlags::FULL_PROPS);
643        } else {
644            if has_class_binding {
645                patch_flags.insert(PatchFlags::CLASS);
646            }
647            if has_style_binding {
648                patch_flags.insert(PatchFlags::STYLE);
649            }
650            if !dynamic_props.is_empty() {
651                patch_flags.insert(PatchFlags::PROPS);
652            }
653            if has_hydration_event_binding {
654                patch_flags.insert(PatchFlags::HYDRATE_EVENTS);
655            }
656        }
657        if (patch_flags.is_empty() || patch_flags == PatchFlags::HYDRATE_EVENTS)
658            && (has_ref || !directives.is_empty())
659        {
660            patch_flags.insert(PatchFlags::NEED_PATCH);
661        }
662
663        AttrsTransformationResult {
664            attrs: expr,
665            patch_flags,
666            dynamic_props: Some(dynamic_props),
667            slots,
668        }
669    }
670
671    fn transform_children(
672        &mut self,
673        children: &[JSXElementChild],
674        is_component: bool,
675        slots: Option<Box<Expr>>,
676    ) -> Expr {
677        let elems = children
678            .iter()
679            .filter_map(|child| match child {
680                JSXElementChild::JSXText(jsx_text) => {
681                    self.transform_jsx_text(jsx_text).map(|expr| ExprOrSpread {
682                        spread: None,
683                        expr: Box::new(expr),
684                    })
685                }
686                JSXElementChild::JSXExprContainer(JSXExprContainer {
687                    expr: JSXExpr::JSXEmptyExpr(..),
688                    ..
689                }) => None,
690                JSXElementChild::JSXExprContainer(JSXExprContainer {
691                    expr: JSXExpr::Expr(expr),
692                    ..
693                }) => {
694                    if self.options.optimize {
695                        match &**expr {
696                            Expr::Ident(ident)
697                                if !ident.to_id().1.has_mark(self.unresolved_mark) =>
698                            {
699                                self.slot_flag_stack.fill(SlotFlag::Dynamic);
700                            }
701                            _ => {}
702                        }
703                    }
704                    Some(ExprOrSpread {
705                        spread: None,
706                        expr: expr.clone(),
707                    })
708                }
709                JSXElementChild::JSXSpreadChild(JSXSpreadChild { expr, .. }) => {
710                    if self.options.optimize {
711                        match &**expr {
712                            Expr::Ident(ident)
713                                if !ident.to_id().1.has_mark(self.unresolved_mark) =>
714                            {
715                                self.slot_flag_stack.fill(SlotFlag::Dynamic);
716                            }
717                            _ => {}
718                        }
719                    }
720                    Some(ExprOrSpread {
721                        spread: Some(DUMMY_SP),
722                        expr: expr.clone(),
723                    })
724                }
725                JSXElementChild::JSXElement(jsx_element) => Some(ExprOrSpread {
726                    spread: None,
727                    expr: Box::new(self.transform_jsx_element(jsx_element)),
728                }),
729                JSXElementChild::JSXFragment(jsx_fragment) => Some(ExprOrSpread {
730                    spread: None,
731                    expr: Box::new(self.transform_jsx_fragment(jsx_fragment)),
732                }),
733            })
734            .map(Some)
735            .collect::<Vec<_>>();
736
737        let slot_flag = if self.options.optimize {
738            self.slot_flag_stack.pop().unwrap_or(SlotFlag::Stable)
739        } else {
740            SlotFlag::Stable
741        };
742
743        match elems.as_slice() {
744            [] => {
745                if let Some(slots) = slots {
746                    *slots
747                } else {
748                    Expr::Lit(Lit::Null(Null { span: DUMMY_SP }))
749                }
750            }
751            [Some(ExprOrSpread { spread: None, expr })] => match &**expr {
752                expr @ Expr::Ident(..) if is_component => {
753                    let elems = self.build_iife(elems.clone());
754                    if self.options.enable_object_slots {
755                        Expr::Cond(CondExpr {
756                            span: DUMMY_SP,
757                            test: Box::new(Expr::Call(CallExpr {
758                                span: DUMMY_SP,
759                                callee: Callee::Expr(Box::new(Expr::Ident(
760                                    self.generate_slot_helper(),
761                                ))),
762                                args: vec![ExprOrSpread {
763                                    spread: None,
764                                    expr: Box::new(expr.clone()),
765                                }],
766                                ..Default::default()
767                            })),
768                            cons: Box::new(expr.clone()),
769                            alt: Box::new(self.wrap_children(elems, slot_flag, slots)),
770                        })
771                    } else {
772                        self.wrap_children(elems, slot_flag, slots)
773                    }
774                }
775                expr @ Expr::Call(..) if expr.span() != DUMMY_SP && is_component => {
776                    // the element was generated and doesn't have location information
777                    if self.options.enable_object_slots {
778                        let slot_ident = self.generate_unique_slot_ident();
779                        Expr::Cond(CondExpr {
780                            span: DUMMY_SP,
781                            test: Box::new(Expr::Call(CallExpr {
782                                span: DUMMY_SP,
783                                callee: Callee::Expr(Box::new(Expr::Ident(
784                                    self.generate_slot_helper(),
785                                ))),
786                                args: vec![ExprOrSpread {
787                                    spread: None,
788                                    expr: Box::new(Expr::Assign(AssignExpr {
789                                        span: DUMMY_SP,
790                                        op: op!("="),
791                                        left: AssignTarget::Simple(SimpleAssignTarget::Paren(
792                                            ParenExpr {
793                                                span: DUMMY_SP,
794                                                expr: Box::new(Expr::Ident(slot_ident.clone())),
795                                            },
796                                        )),
797                                        right: Box::new(expr.clone()),
798                                    })),
799                                }],
800                                ..Default::default()
801                            })),
802                            cons: Box::new(Expr::Ident(slot_ident.clone())),
803                            alt: {
804                                let elems = self.build_iife(vec![Some(ExprOrSpread {
805                                    spread: None,
806                                    expr: Box::new(Expr::Ident(slot_ident)),
807                                })]);
808                                Box::new(self.wrap_children(elems, slot_flag, slots))
809                            },
810                        })
811                    } else {
812                        self.wrap_children(elems, slot_flag, slots)
813                    }
814                }
815                expr @ Expr::Fn(..) | expr @ Expr::Arrow(..) => Expr::Object(ObjectLit {
816                    span: DUMMY_SP,
817                    props: vec![PropOrSpread::Prop(Box::new(Prop::KeyValue(KeyValueProp {
818                        key: PropName::Ident(quote_ident!("default")),
819                        value: Box::new(expr.clone()),
820                    })))],
821                }),
822                Expr::Object(ObjectLit { props, .. }) => {
823                    let mut props = props.clone();
824                    if self.options.optimize {
825                        props.push(PropOrSpread::Prop(Box::new(Prop::KeyValue(KeyValueProp {
826                            key: PropName::Ident(quote_ident!("_")),
827                            value: Box::new(Expr::Lit(Lit::Num(Number {
828                                span: DUMMY_SP,
829                                value: slot_flag as u8 as f64,
830                                raw: None,
831                            }))),
832                        }))));
833                    }
834                    Expr::Object(ObjectLit {
835                        span: DUMMY_SP,
836                        props,
837                    })
838                }
839                _ => {
840                    if is_component {
841                        self.wrap_children(elems, slot_flag, slots)
842                    } else {
843                        Expr::Array(ArrayLit {
844                            span: DUMMY_SP,
845                            elems,
846                        })
847                    }
848                }
849            },
850            _ => {
851                if is_component {
852                    self.wrap_children(elems, slot_flag, slots)
853                } else {
854                    Expr::Array(ArrayLit {
855                        span: DUMMY_SP,
856                        elems,
857                    })
858                }
859            }
860        }
861    }
862
863    fn wrap_children(
864        &self,
865        elems: Vec<Option<ExprOrSpread>>,
866        slot_flag: SlotFlag,
867        slots: Option<Box<Expr>>,
868    ) -> Expr {
869        let mut props = vec![PropOrSpread::Prop(Box::new(Prop::KeyValue(KeyValueProp {
870            key: PropName::Ident(quote_ident!("default")),
871            value: Box::new(Expr::Arrow(ArrowExpr {
872                span: DUMMY_SP,
873                params: vec![],
874                body: Box::new(BlockStmtOrExpr::Expr(Box::new(Expr::Array(ArrayLit {
875                    span: DUMMY_SP,
876                    elems,
877                })))),
878                is_async: false,
879                is_generator: false,
880                ..Default::default()
881            })),
882        })))];
883
884        if let Some(expr) = slots {
885            match *expr {
886                Expr::Object(ObjectLit {
887                    props: slot_props, ..
888                }) => props.extend_from_slice(&slot_props),
889                _ => props.push(PropOrSpread::Spread(SpreadElement {
890                    dot3_token: DUMMY_SP,
891                    expr,
892                })),
893            }
894        }
895
896        if self.options.optimize {
897            props.push(PropOrSpread::Prop(Box::new(Prop::KeyValue(KeyValueProp {
898                key: PropName::Ident(quote_ident!("_")),
899                value: Box::new(Expr::Lit(Lit::Num(Number {
900                    span: DUMMY_SP,
901                    value: slot_flag as u8 as f64,
902                    raw: None,
903                }))),
904            }))));
905        }
906
907        Expr::Object(ObjectLit {
908            span: DUMMY_SP,
909            props,
910        })
911    }
912
913    fn generate_unique_slot_ident(&mut self) -> Ident {
914        let ident = if self.slot_counter == 1 {
915            private_ident!("_slot")
916        } else {
917            private_ident!(format!("_slot{}", self.slot_counter))
918        };
919        self.injecting_vars.push(VarDeclarator {
920            span: DUMMY_SP,
921            name: Pat::Ident(BindingIdent {
922                id: ident.clone(),
923                type_ann: None,
924            }),
925            init: None,
926            definite: false,
927        });
928
929        self.slot_counter += 1;
930        ident
931    }
932
933    fn transform_jsx_text(&mut self, jsx_text: &JSXText) -> Option<Expr> {
934        let text = util::transform_text(&jsx_text.value);
935        if text.is_empty() {
936            None
937        } else {
938            Some(Expr::Call(CallExpr {
939                span: DUMMY_SP,
940                callee: Callee::Expr(Box::new(Expr::Ident(
941                    self.import_from_vue("createTextVNode"),
942                ))),
943                args: vec![ExprOrSpread {
944                    spread: None,
945                    expr: Box::new(Expr::Lit(Lit::Str(quote_str!(text)))),
946                }],
947                ..Default::default()
948            }))
949        }
950    }
951
952    fn resolve_directive(&mut self, directive_name: &str, jsx_element: &JSXElement) -> Expr {
953        match directive_name {
954            "show" => Expr::Ident(self.import_from_vue("vShow")),
955            "model" => match &jsx_element.opening.name {
956                JSXElementName::Ident(ident) if &ident.sym == "select" => {
957                    Expr::Ident(self.import_from_vue("vModelSelect"))
958                }
959                JSXElementName::Ident(ident) if &ident.sym == "textarea" => {
960                    Expr::Ident(self.import_from_vue("vModelText"))
961                }
962                _ => {
963                    let typ = jsx_element
964                        .opening
965                        .attrs
966                        .iter()
967                        .find_map(|jsx_attr_or_spread| match jsx_attr_or_spread {
968                            JSXAttrOrSpread::JSXAttr(JSXAttr {
969                                name: JSXAttrName::Ident(ident),
970                                value,
971                                ..
972                            }) if &ident.sym == "type" => value.as_ref(),
973                            _ => None,
974                        });
975                    match typ {
976                        Some(JSXAttrValue::Lit(Lit::Str(str))) if &str.value == "checkbox" => {
977                            Expr::Ident(self.import_from_vue("vModelCheckbox"))
978                        }
979                        Some(JSXAttrValue::Lit(Lit::Str(str))) if &str.value == "radio" => {
980                            Expr::Ident(self.import_from_vue("vModelRadio"))
981                        }
982                        Some(JSXAttrValue::Lit(Lit::Str(..))) | None => {
983                            Expr::Ident(self.import_from_vue("vModelText"))
984                        }
985                        Some(..) => Expr::Ident(self.import_from_vue("vModelDynamic")),
986                    }
987                }
988            },
989            _ => Expr::Call(CallExpr {
990                span: DUMMY_SP,
991                callee: Callee::Expr(Box::new(Expr::Ident(
992                    self.import_from_vue("resolveDirective"),
993                ))),
994                args: vec![ExprOrSpread {
995                    spread: None,
996                    expr: Box::new(Expr::Lit(Lit::Str(quote_str!(directive_name)))),
997                }],
998                ..Default::default()
999            }),
1000        }
1001    }
1002
1003    fn is_component(&self, element_name: &JSXElementName) -> bool {
1004        let name = match element_name {
1005            JSXElementName::Ident(Ident { sym, .. }) => sym,
1006            JSXElementName::JSXMemberExpr(JSXMemberExpr { prop, .. }) => &*prop.sym,
1007            JSXElementName::JSXNamespacedName(JSXNamespacedName { name, .. }) => &*name.sym,
1008        };
1009        let should_transformed_to_slots = !self
1010            .vue_imports
1011            .get(FRAGMENT)
1012            .map(|ident| &*ident.sym == name)
1013            .unwrap_or_default()
1014            && name != KEEP_ALIVE;
1015
1016        if matches!(element_name, JSXElementName::JSXMemberExpr(..)) {
1017            should_transformed_to_slots
1018        } else {
1019            self.options
1020                .custom_element_patterns
1021                .iter()
1022                .all(|pattern| !pattern.is_match(name))
1023                && should_transformed_to_slots
1024                && !(name.as_bytes()[0].is_ascii_lowercase()
1025                    && (css_dataset::tags::STANDARD_HTML_TAGS.contains(name)
1026                        || css_dataset::tags::SVG_TAGS.contains(name)))
1027        }
1028    }
1029
1030    fn get_pragma(&mut self) -> Ident {
1031        self.pragma
1032            .as_ref()
1033            .or(self.options.pragma.as_ref())
1034            .map(|name| quote_ident!(name.as_str()).into())
1035            .unwrap_or_else(|| self.import_from_vue("createVNode"))
1036    }
1037
1038    fn search_jsx_pragma(&mut self, span: Span) {
1039        if let Some(comments) = &self.comments {
1040            comments.with_leading(span.lo, |comments| {
1041                let pragma = comments.iter().find_map(|comment| {
1042                    let trimmed = comment.text.trim();
1043                    trimmed
1044                        .strip_prefix('*')
1045                        .unwrap_or(trimmed)
1046                        .trim()
1047                        .strip_prefix("@jsx")
1048                        .map(str::trim)
1049                });
1050                if let Some(pragma) = pragma {
1051                    self.pragma = Some(pragma.to_string());
1052                }
1053            });
1054        }
1055    }
1056
1057    fn build_iife(&mut self, elems: Vec<Option<ExprOrSpread>>) -> Vec<Option<ExprOrSpread>> {
1058        let left = self.assignment_left.take();
1059        if let Some(left) = left {
1060            elems
1061                .into_iter()
1062                .map(|elem| match elem {
1063                    Some(ExprOrSpread { spread: None, expr }) => match *expr {
1064                        Expr::Ident(ident) if ident.sym == left.sym => {
1065                            let name = private_ident!(format!("_{}", ident.sym));
1066                            self.injecting_consts.push(VarDeclarator {
1067                                span: DUMMY_SP,
1068                                name: Pat::Ident(BindingIdent {
1069                                    id: name.clone(),
1070                                    type_ann: None,
1071                                }),
1072                                init: Some(Box::new(Expr::Call(CallExpr {
1073                                    span: DUMMY_SP,
1074                                    callee: Callee::Expr(Box::new(Expr::Fn(FnExpr {
1075                                        ident: None,
1076                                        function: Box::new(Function {
1077                                            params: vec![],
1078                                            decorators: vec![],
1079                                            span: DUMMY_SP,
1080                                            body: Some(BlockStmt {
1081                                                span: DUMMY_SP,
1082                                                stmts: vec![Stmt::Return(ReturnStmt {
1083                                                    span: DUMMY_SP,
1084                                                    arg: Some(Box::new(Expr::Ident(ident))),
1085                                                })],
1086                                                ..Default::default()
1087                                            }),
1088                                            is_generator: false,
1089                                            is_async: false,
1090                                            ..Default::default()
1091                                        }),
1092                                    }))),
1093                                    args: vec![],
1094                                    ..Default::default()
1095                                }))),
1096                                definite: false,
1097                            });
1098                            Some(ExprOrSpread {
1099                                spread: None,
1100                                expr: Box::new(Expr::Ident(name)),
1101                            })
1102                        }
1103                        expr => Some(ExprOrSpread {
1104                            spread: None,
1105                            expr: Box::new(expr),
1106                        }),
1107                    },
1108                    _ => elem,
1109                })
1110                .collect()
1111        } else {
1112            elems
1113        }
1114    }
1115
1116    fn is_define_component_call(&self, CallExpr { callee, .. }: &CallExpr) -> bool {
1117        callee
1118            .as_expr()
1119            .and_then(|expr| expr.as_ident())
1120            .and_then(|ident| {
1121                self.define_component
1122                    .map(|ctxt| ctxt == ident.ctxt && ident.sym == "defineComponent")
1123            })
1124            .unwrap_or_default()
1125    }
1126}
1127
1128impl<C> VisitMut for VueJsxTransformVisitor<C>
1129where
1130    C: Comments,
1131{
1132    fn visit_mut_module(&mut self, module: &mut Module) {
1133        self.search_jsx_pragma(module.span);
1134        module
1135            .body
1136            .iter()
1137            .for_each(|item| self.search_jsx_pragma(item.span()));
1138
1139        module.visit_mut_children_with(self);
1140
1141        if !self.injecting_consts.is_empty() {
1142            module.body.insert(
1143                0,
1144                ModuleItem::Stmt(Stmt::Decl(Decl::Var(Box::new(VarDecl {
1145                    span: DUMMY_SP,
1146                    kind: VarDeclKind::Const,
1147                    decls: mem::take(&mut self.injecting_consts),
1148                    ..Default::default()
1149                })))),
1150            );
1151        }
1152
1153        if !self.injecting_vars.is_empty() {
1154            module.body.insert(
1155                0,
1156                ModuleItem::Stmt(Stmt::Decl(Decl::Var(Box::new(VarDecl {
1157                    span: DUMMY_SP,
1158                    kind: VarDeclKind::Let,
1159                    decls: mem::take(&mut self.injecting_vars),
1160                    ..Default::default()
1161                })))),
1162            );
1163            self.slot_counter = 1;
1164        }
1165
1166        if let Some(slot_helper) = &self.slot_helper_ident {
1167            module.body.insert(
1168                0,
1169                ModuleItem::Stmt(Stmt::Decl(Decl::Fn(util::build_slot_helper(
1170                    slot_helper.clone(),
1171                    self.import_from_vue("isVNode"),
1172                )))),
1173            )
1174        }
1175
1176        if let Some(helper) = &self.transform_on_helper {
1177            module.body.insert(
1178                0,
1179                ModuleItem::ModuleDecl(ModuleDecl::Import(ImportDecl {
1180                    span: DUMMY_SP,
1181                    specifiers: vec![ImportSpecifier::Default(ImportDefaultSpecifier {
1182                        span: DUMMY_SP,
1183                        local: helper.clone(),
1184                    })],
1185                    src: Box::new(quote_str!("@vue/babel-helper-vue-transform-on")),
1186                    type_only: false,
1187                    with: None,
1188                    phase: Default::default(),
1189                })),
1190            )
1191        }
1192
1193        if !self.vue_imports.is_empty() {
1194            module.body.insert(
1195                0,
1196                ModuleItem::ModuleDecl(ModuleDecl::Import(ImportDecl {
1197                    span: DUMMY_SP,
1198                    specifiers: self
1199                        .vue_imports
1200                        .iter()
1201                        .map(|(imported, local)| {
1202                            ImportSpecifier::Named(ImportNamedSpecifier {
1203                                span: DUMMY_SP,
1204                                local: local.clone(),
1205                                imported: Some(ModuleExportName::Ident(
1206                                    quote_ident!(*imported).into(),
1207                                )),
1208                                is_type_only: false,
1209                            })
1210                        })
1211                        .collect(),
1212                    src: Box::new(quote_str!("vue")),
1213                    type_only: false,
1214                    with: None,
1215                    phase: Default::default(),
1216                })),
1217            );
1218        }
1219    }
1220
1221    fn visit_mut_stmts(&mut self, stmts: &mut Vec<Stmt>) {
1222        stmts.visit_mut_children_with(self);
1223
1224        if !self.injecting_consts.is_empty() {
1225            stmts.insert(
1226                0,
1227                Stmt::Decl(Decl::Var(Box::new(VarDecl {
1228                    span: DUMMY_SP,
1229                    kind: VarDeclKind::Const,
1230                    decls: mem::take(&mut self.injecting_consts),
1231                    ..Default::default()
1232                }))),
1233            );
1234        }
1235
1236        if !self.injecting_vars.is_empty() {
1237            stmts.insert(
1238                0,
1239                Stmt::Decl(Decl::Var(Box::new(VarDecl {
1240                    span: DUMMY_SP,
1241                    kind: VarDeclKind::Let,
1242                    decls: mem::take(&mut self.injecting_vars),
1243                    ..Default::default()
1244                }))),
1245            );
1246            self.slot_counter = 1;
1247        }
1248    }
1249
1250    fn visit_mut_arrow_expr(&mut self, arrow_expr: &mut ArrowExpr) {
1251        arrow_expr.visit_mut_children_with(self);
1252
1253        if !self.injecting_consts.is_empty() || !self.injecting_vars.is_empty() {
1254            if let BlockStmtOrExpr::Expr(ret) = &*arrow_expr.body {
1255                let mut stmts = Vec::with_capacity(3);
1256
1257                if !self.injecting_consts.is_empty() {
1258                    stmts.push(Stmt::Decl(Decl::Var(Box::new(VarDecl {
1259                        span: DUMMY_SP,
1260                        kind: VarDeclKind::Const,
1261                        decls: mem::take(&mut self.injecting_consts),
1262                        ..Default::default()
1263                    }))));
1264                }
1265
1266                if !self.injecting_vars.is_empty() {
1267                    stmts.push(Stmt::Decl(Decl::Var(Box::new(VarDecl {
1268                        span: DUMMY_SP,
1269                        kind: VarDeclKind::Let,
1270                        decls: mem::take(&mut self.injecting_vars),
1271                        ..Default::default()
1272                    }))));
1273                    self.slot_counter = 1;
1274                }
1275
1276                stmts.push(Stmt::Return(ReturnStmt {
1277                    span: DUMMY_SP,
1278                    arg: Some(ret.clone()),
1279                }));
1280
1281                arrow_expr.body = Box::new(BlockStmtOrExpr::BlockStmt(BlockStmt {
1282                    span: DUMMY_SP,
1283                    stmts,
1284                    ..Default::default()
1285                }));
1286            }
1287        }
1288    }
1289
1290    fn visit_mut_expr(&mut self, expr: &mut Expr) {
1291        expr.visit_mut_children_with(self);
1292
1293        match expr {
1294            Expr::JSXElement(jsx_element) => *expr = self.transform_jsx_element(jsx_element),
1295            Expr::JSXFragment(jsx_fragment) => *expr = self.transform_jsx_fragment(jsx_fragment),
1296            Expr::Assign(AssignExpr {
1297                left: AssignTarget::Simple(SimpleAssignTarget::Ident(binding_ident)),
1298                ..
1299            }) => self.assignment_left = Some(binding_ident.id.clone()),
1300            _ => {}
1301        }
1302    }
1303
1304    // decouple `v-models`
1305    fn visit_mut_jsx_opening_element(&mut self, jsx_opening_element: &mut JSXOpeningElement) {
1306        jsx_opening_element.visit_mut_children_with(self);
1307
1308        let Some(index) =
1309            jsx_opening_element
1310                .attrs
1311                .iter()
1312                .enumerate()
1313                .find_map(|(i, jsx_attr_or_spread)| match jsx_attr_or_spread {
1314                    JSXAttrOrSpread::JSXAttr(JSXAttr {
1315                        name: JSXAttrName::Ident(IdentName { sym, .. }),
1316                        ..
1317                    }) if sym == "v-models" => Some(i),
1318                    _ => None,
1319                })
1320        else {
1321            return;
1322        };
1323
1324        let JSXAttrOrSpread::JSXAttr(JSXAttr { value, .. }) =
1325            jsx_opening_element.attrs.remove(index)
1326        else {
1327            unreachable!()
1328        };
1329
1330        let Some(JSXAttrValue::JSXExprContainer(JSXExprContainer {
1331            expr: JSXExpr::Expr(expr),
1332            ..
1333        })) = value
1334        else {
1335            HANDLER.with(|handler| {
1336                handler.span_err(
1337                    value.span(),
1338                    "you should pass a Two-dimensional Arrays to v-models",
1339                )
1340            });
1341            return;
1342        };
1343        let Expr::Array(ArrayLit { elems, .. }) = *expr else {
1344            HANDLER.with(|handler| {
1345                handler.span_err(
1346                    expr.span(),
1347                    "you should pass a Two-dimensional Arrays to v-models",
1348                )
1349            });
1350            return;
1351        };
1352
1353        jsx_opening_element
1354            .attrs
1355            .splice(index..index, util::decouple_v_models(elems));
1356    }
1357
1358    fn visit_mut_import_decl(&mut self, import_decl: &mut ImportDecl) {
1359        import_decl.visit_mut_children_with(self);
1360
1361        if import_decl.src.value != "vue" {
1362            return;
1363        }
1364
1365        let ctxt = import_decl.specifiers.iter().find_map(|specifier| {
1366            if let ImportSpecifier::Named(ImportNamedSpecifier {
1367                local,
1368                imported: None,
1369                ..
1370            }) = specifier
1371            {
1372                (local.sym == "defineComponent").then_some(local.ctxt)
1373            } else {
1374                None
1375            }
1376        });
1377        if let Some(ctxt) = ctxt {
1378            self.define_component = Some(ctxt);
1379        }
1380    }
1381
1382    fn visit_mut_ts_interface_decl(&mut self, ts_interface_decl: &mut TsInterfaceDecl) {
1383        ts_interface_decl.visit_mut_children_with(self);
1384        if self.options.resolve_type {
1385            let key = (ts_interface_decl.id.sym.clone(), ts_interface_decl.id.ctxt);
1386            if let Some(interface) = self.interfaces.get_mut(&key) {
1387                interface
1388                    .body
1389                    .body
1390                    .extend_from_slice(&ts_interface_decl.body.body);
1391            } else {
1392                self.interfaces.insert(key, ts_interface_decl.clone());
1393            }
1394        }
1395    }
1396
1397    fn visit_mut_ts_type_alias_decl(&mut self, ts_type_alias_decl: &mut TsTypeAliasDecl) {
1398        ts_type_alias_decl.visit_mut_children_with(self);
1399        if self.options.resolve_type {
1400            self.type_aliases.insert(
1401                (
1402                    ts_type_alias_decl.id.sym.clone(),
1403                    ts_type_alias_decl.id.ctxt,
1404                ),
1405                (*ts_type_alias_decl.type_ann).clone(),
1406            );
1407        }
1408    }
1409
1410    fn visit_mut_call_expr(&mut self, call_expr: &mut CallExpr) {
1411        call_expr.visit_mut_children_with(self);
1412
1413        if !self.options.resolve_type {
1414            return;
1415        }
1416
1417        if !self.is_define_component_call(call_expr) {
1418            return;
1419        }
1420
1421        let Some(maybe_setup) = call_expr.args.first() else {
1422            return;
1423        };
1424
1425        let props_types = self.extract_props_type(maybe_setup);
1426        let emits_types = self.extract_emits_type(maybe_setup);
1427        if let Some(prop_types) = props_types {
1428            inject_define_component_option(call_expr, "props", prop_types);
1429        }
1430        if let Some(emits_type) = emits_types {
1431            inject_define_component_option(call_expr, "emits", Expr::Array(emits_type));
1432        }
1433    }
1434
1435    fn visit_mut_var_declarator(&mut self, var_declarator: &mut VarDeclarator) {
1436        var_declarator.visit_mut_children_with(self);
1437
1438        if !self.options.resolve_type {
1439            return;
1440        }
1441        let Pat::Ident(name) = &var_declarator.name else {
1442            return;
1443        };
1444        let Some(Expr::Call(call)) = var_declarator.init.as_deref_mut() else {
1445            return;
1446        };
1447        if !self.is_define_component_call(call) {
1448            return;
1449        }
1450
1451        inject_define_component_option(
1452            call,
1453            "name",
1454            Expr::Lit(Lit::Str(quote_str!(name.sym.clone()))),
1455        );
1456    }
1457}
1458
1459fn inject_define_component_option(call: &mut CallExpr, name: &'static str, value: Expr) {
1460    let options = call.args.get_mut(1);
1461    if options
1462        .as_ref()
1463        .and_then(|options| options.spread)
1464        .is_some()
1465    {
1466        return;
1467    }
1468
1469    match options.map(|options| &mut *options.expr) {
1470        Some(Expr::Object(object)) => {
1471            if !object.props.iter().any(|prop| {
1472                prop.as_prop()
1473                    .and_then(|prop| prop.as_key_value())
1474                    .and_then(|key_value| key_value.key.as_ident())
1475                    .map(|ident| ident.sym == name)
1476                    .unwrap_or_default()
1477            }) {
1478                object
1479                    .props
1480                    .push(PropOrSpread::Prop(Box::new(Prop::KeyValue(KeyValueProp {
1481                        key: PropName::Ident(quote_ident!(name)),
1482                        value: Box::new(value),
1483                    }))));
1484            }
1485        }
1486        Some(..) => {
1487            let expr = Expr::Object(ObjectLit {
1488                props: vec![
1489                    PropOrSpread::Prop(Box::new(Prop::KeyValue(KeyValueProp {
1490                        key: PropName::Ident(quote_ident!(name)),
1491                        value: Box::new(value),
1492                    }))),
1493                    PropOrSpread::Spread(SpreadElement {
1494                        expr: call.args.remove(1).expr,
1495                        dot3_token: DUMMY_SP,
1496                    }),
1497                ],
1498                span: DUMMY_SP,
1499            });
1500            call.args.insert(
1501                1,
1502                ExprOrSpread {
1503                    expr: Box::new(expr),
1504                    spread: None,
1505                },
1506            );
1507        }
1508        None => {
1509            call.args.push(ExprOrSpread {
1510                expr: Box::new(Expr::Object(ObjectLit {
1511                    props: vec![PropOrSpread::Prop(Box::new(Prop::KeyValue(KeyValueProp {
1512                        key: PropName::Ident(quote_ident!(name)),
1513                        value: Box::new(value),
1514                    })))],
1515                    span: DUMMY_SP,
1516                })),
1517                spread: None,
1518            });
1519        }
1520    }
1521}