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 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 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 && !attr_name.eq_ignore_ascii_case("onclick")
530 && 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 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 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}