swc_ecma_compat_es2018/
object_rest.rs

1use std::{
2    iter,
3    mem::{self, replace},
4};
5
6use swc_common::{util::take::Take, Mark, Spanned, DUMMY_SP};
7use swc_ecma_ast::*;
8use swc_ecma_compat_common::impl_visit_mut_fn;
9use swc_ecma_transforms_base::{helper, helper_expr, perf::Check};
10use swc_ecma_transforms_macros::fast_path;
11use swc_ecma_utils::{
12    alias_ident_for, alias_if_required, find_pat_ids, is_literal, private_ident, quote_ident,
13    var::VarCollector, ExprFactory, StmtLike,
14};
15use swc_ecma_visit::{
16    noop_visit_mut_type, noop_visit_type, Visit, VisitMut, VisitMutWith, VisitWith,
17};
18use swc_trace_macro::swc_trace;
19
20use super::object_rest_spread::Config;
21
22#[derive(Default)]
23pub(super) struct ObjectRest {
24    /// Injected before the original statement.
25    pub vars: Vec<VarDeclarator>,
26    /// Variables which should be declared using `var`
27    pub mutable_vars: Vec<VarDeclarator>,
28    /// Assignment expressions.
29    pub exprs: Vec<Box<Expr>>,
30    pub config: Config,
31}
32
33macro_rules! impl_for_for_stmt {
34    ($name:ident, $T:tt) => {
35        fn $name(&mut self, for_stmt: &mut $T) {
36            if !contains_rest(for_stmt) {
37                return;
38            }
39
40            let stmt;
41
42            let left = match &mut for_stmt.left {
43                ForHead::VarDecl(var_decl) => {
44                    let ref_ident = private_ident!("_ref");
45
46                    // Unpack variables
47                    let mut decls = var_decl
48                        .decls
49                        .take()
50                        .into_iter()
51                        .map(|decl| VarDeclarator {
52                            name: decl.name,
53                            init: Some(Box::new(Expr::Ident(ref_ident.clone()))),
54                            ..decl
55                        })
56                        .collect::<Vec<_>>();
57
58                    // **prepend** decls to self.vars
59                    decls.append(&mut self.vars.take());
60
61                    stmt = Some(Stmt::Decl(
62                        VarDecl {
63                            span: DUMMY_SP,
64                            kind: VarDeclKind::Let,
65                            decls,
66                            ..Default::default()
67                        }
68                        .into(),
69                    ));
70
71                    VarDecl {
72                        decls: vec![VarDeclarator {
73                            span: DUMMY_SP,
74                            name: ref_ident.into(),
75                            init: None,
76                            definite: false,
77                        }],
78                        ..*var_decl.take()
79                    }
80                    .into()
81                }
82                ForHead::Pat(pat) => {
83                    let var_ident = private_ident!("_ref");
84                    let pat = pat.take();
85
86                    // initialize (or destructure)
87                    stmt = Some(
88                        var_ident
89                            .clone()
90                            .make_assign_to(op!("="), pat.try_into().unwrap())
91                            .into_stmt(),
92                    );
93
94                    // `var _ref` in `for (var _ref in foo)`
95                    VarDecl {
96                        span: DUMMY_SP,
97                        kind: VarDeclKind::Var,
98                        decls: vec![VarDeclarator {
99                            span: DUMMY_SP,
100                            name: var_ident.into(),
101                            init: None,
102                            definite: false,
103                        }],
104                        ..Default::default()
105                    }
106                    .into()
107                }
108
109                ForHead::UsingDecl(..) => {
110                    unreachable!("using declaration must be removed by previous pass")
111                }
112
113                #[cfg(swc_ast_unknown)]
114                _ => panic!("unable to access unknown nodes"),
115            };
116            for_stmt.left = left;
117
118            for_stmt.body = Box::new(Stmt::Block(match &mut *for_stmt.body {
119                Stmt::Block(BlockStmt { span, stmts, ctxt }) => BlockStmt {
120                    span: *span,
121                    stmts: stmt.into_iter().chain(stmts.take()).collect(),
122                    ctxt: *ctxt,
123                },
124                body => BlockStmt {
125                    span: DUMMY_SP,
126                    stmts: stmt.into_iter().chain(iter::once(body.take())).collect(),
127                    ..Default::default()
128                },
129            }));
130
131            for_stmt.right.visit_mut_with(self);
132            for_stmt.body.visit_mut_with(self);
133        }
134    };
135}
136
137#[derive(Default)]
138struct RestVisitor {
139    found: bool,
140}
141
142#[swc_trace]
143impl Visit for RestVisitor {
144    noop_visit_type!(fail);
145
146    fn visit_object_pat_prop(&mut self, prop: &ObjectPatProp) {
147        match *prop {
148            ObjectPatProp::Rest(..) => self.found = true,
149            _ => prop.visit_children_with(self),
150        }
151    }
152}
153
154impl Check for RestVisitor {
155    fn should_handle(&self) -> bool {
156        self.found
157    }
158}
159
160fn contains_rest<N>(node: &N) -> bool
161where
162    N: VisitWith<RestVisitor>,
163{
164    let mut v = RestVisitor { found: false };
165    node.visit_with(&mut v);
166    v.found
167}
168
169#[swc_trace]
170#[fast_path(RestVisitor)]
171impl VisitMut for ObjectRest {
172    noop_visit_mut_type!(fail);
173
174    impl_for_for_stmt!(visit_mut_for_in_stmt, ForInStmt);
175
176    impl_for_for_stmt!(visit_mut_for_of_stmt, ForOfStmt);
177
178    impl_visit_mut_fn!();
179
180    /// Handles assign expression
181    fn visit_mut_expr(&mut self, expr: &mut Expr) {
182        // fast path
183        if !contains_rest(expr) {
184            return;
185        }
186
187        expr.visit_mut_children_with(self);
188
189        if let Expr::Assign(AssignExpr {
190            span,
191            left: AssignTarget::Pat(pat),
192            op: op!("="),
193            right,
194        }) = expr
195        {
196            let mut var_ident = alias_ident_for(right, "_tmp");
197            var_ident.ctxt = var_ident.ctxt.apply_mark(Mark::new());
198
199            // println!("Var: var_ident = None");
200            self.mutable_vars.push(VarDeclarator {
201                span: DUMMY_SP,
202                name: var_ident.clone().into(),
203                init: None,
204                definite: false,
205            });
206            // println!("Expr: var_ident = right");
207            self.exprs.push(
208                AssignExpr {
209                    span: DUMMY_SP,
210                    left: var_ident.clone().into(),
211                    op: op!("="),
212                    right: right.take(),
213                }
214                .into(),
215            );
216            let pat = self.fold_rest(
217                &mut 0,
218                pat.take().into(),
219                var_ident.clone().into(),
220                true,
221                true,
222            );
223
224            match pat {
225                Pat::Object(ObjectPat { ref props, .. }) if props.is_empty() => {}
226                _ => self.exprs.push(
227                    AssignExpr {
228                        span: *span,
229                        left: pat.try_into().unwrap(),
230                        op: op!("="),
231                        right: Box::new(var_ident.clone().into()),
232                    }
233                    .into(),
234                ),
235            }
236            self.exprs.push(Box::new(var_ident.into()));
237            *expr = SeqExpr {
238                span: DUMMY_SP,
239                exprs: mem::take(&mut self.exprs),
240            }
241            .into();
242        };
243    }
244
245    /// export var { b, ...c } = asdf2;
246    fn visit_mut_module_decl(&mut self, decl: &mut ModuleDecl) {
247        if !contains_rest(decl) {
248            // fast path
249            return;
250        }
251
252        match decl {
253            ModuleDecl::ExportDecl(ExportDecl {
254                span,
255                decl: Decl::Var(var_decl),
256                ..
257            }) if var_decl.decls.iter().any(|v| v.name.is_object()) => {
258                let specifiers = {
259                    let mut found: Vec<Ident> = Vec::new();
260                    let mut finder = VarCollector { to: &mut found };
261                    var_decl.visit_with(&mut finder);
262                    found
263                        .into_iter()
264                        .map(|ident| ExportNamedSpecifier {
265                            span: DUMMY_SP,
266                            orig: ident.into(),
267                            exported: None,
268                            is_type_only: false,
269                        })
270                        .map(ExportSpecifier::Named)
271                        .collect()
272                };
273
274                let export = NamedExport {
275                    span: *span,
276                    specifiers,
277                    src: None,
278                    type_only: false,
279                    with: None,
280                };
281
282                var_decl.visit_mut_with(self);
283                self.vars.append(&mut var_decl.decls);
284
285                *decl = export.into();
286            }
287            _ => {
288                decl.visit_mut_children_with(self);
289            }
290        };
291    }
292
293    fn visit_mut_module_items(&mut self, n: &mut Vec<ModuleItem>) {
294        self.visit_mut_stmt_like(n);
295    }
296
297    fn visit_mut_stmts(&mut self, n: &mut Vec<Stmt>) {
298        self.visit_mut_stmt_like(n);
299    }
300
301    fn visit_mut_var_declarators(&mut self, decls: &mut Vec<VarDeclarator>) {
302        // fast path
303        if !contains_rest(decls) {
304            return;
305        }
306
307        for mut decl in decls.drain(..) {
308            // fast path
309            if !contains_rest(&decl) {
310                // println!("Var: no rest",);
311                self.vars.push(decl);
312                continue;
313            }
314
315            decl.visit_mut_children_with(self);
316
317            //            if !contains_rest(&decl.name) {
318            //                // println!("Var: no rest",);
319            //                self.vars.push(decl);
320            //                continue;
321            //            }
322
323            let (var_ident, _) = match decl.name {
324                Pat::Ident(ref i) => (Ident::from(i), false),
325
326                _ => match decl.init {
327                    Some(ref e) => alias_if_required(e, "ref"),
328                    _ => (private_ident!("_ref"), true),
329                },
330            };
331
332            let has_init = decl.init.is_some();
333            if let Some(init) = decl.init {
334                match decl.name {
335                    // Optimize { ...props } = this.props
336                    Pat::Object(ObjectPat { props, .. })
337                        if props.len() == 1 && matches!(props[0], ObjectPatProp::Rest(..)) =>
338                    {
339                        let prop = match props.into_iter().next().unwrap() {
340                            ObjectPatProp::Rest(r) => r,
341                            _ => unreachable!(),
342                        };
343
344                        self.vars.push(VarDeclarator {
345                            span: prop.span(),
346                            name: *prop.arg,
347                            init: Some(
348                                CallExpr {
349                                    span: DUMMY_SP,
350                                    callee: helper!(extends),
351                                    args: vec![
352                                        ObjectLit {
353                                            span: DUMMY_SP,
354                                            props: Vec::new(),
355                                        }
356                                        .as_arg(),
357                                        helper_expr!(object_destructuring_empty)
358                                            .as_call(DUMMY_SP, vec![init.as_arg()])
359                                            .as_arg(),
360                                    ],
361                                    ..Default::default()
362                                }
363                                .into(),
364                            ),
365                            definite: false,
366                        });
367                        continue;
368                    }
369                    _ => {}
370                }
371
372                match *init {
373                    // skip `z = z`
374                    Expr::Ident(..) => {}
375                    _ => {
376                        // println!("Var: var_ident = init",);
377                        self.push_var_if_not_empty(VarDeclarator {
378                            span: DUMMY_SP,
379                            name: var_ident.clone().into(),
380                            init: Some(init),
381                            definite: false,
382                        });
383                    }
384                }
385            }
386
387            let mut index = self.vars.len();
388            let mut pat =
389                self.fold_rest(&mut index, decl.name, var_ident.clone().into(), false, true);
390            match pat {
391                // skip `{} = z`
392                Pat::Object(ObjectPat { ref props, .. }) if props.is_empty() => {}
393
394                _ => {
395                    // insert at index to create
396                    // `var { a } = _ref, b = _object_without_properties(_ref, ['a']);`
397                    // instead of
398                    // `var b = _object_without_properties(_ref, ['a']), { a } = _ref;`
399                    // println!("var: simplified pat = var_ident({:?})", var_ident);
400
401                    pat.visit_mut_with(&mut PatSimplifier);
402
403                    self.insert_var_if_not_empty(
404                        index,
405                        VarDeclarator {
406                            name: pat,
407                            // preserve
408                            init: if has_init {
409                                Some(var_ident.clone().into())
410                            } else {
411                                None
412                            },
413                            ..decl
414                        },
415                    )
416                }
417            }
418        }
419
420        *decls = mem::take(&mut self.vars);
421    }
422}
423
424#[swc_trace]
425impl ObjectRest {
426    fn visit_mut_stmt_like<T>(&mut self, stmts: &mut Vec<T>)
427    where
428        T: StmtLike + VisitWith<RestVisitor> + VisitMutWith<ObjectRest>,
429        Vec<T>: VisitMutWith<Self> + VisitWith<RestVisitor>,
430    {
431        if !contains_rest(stmts) {
432            return;
433        }
434
435        let mut buf = Vec::with_capacity(stmts.len());
436
437        for mut stmt in stmts.drain(..) {
438            let mut folder = ObjectRest {
439                config: self.config,
440                ..Default::default()
441            };
442            stmt.visit_mut_with(&mut folder);
443
444            // Add variable declaration
445            // e.g. var ref
446            if !folder.mutable_vars.is_empty() {
447                buf.push(T::from(
448                    VarDecl {
449                        span: DUMMY_SP,
450                        kind: VarDeclKind::Var,
451                        decls: folder.mutable_vars,
452                        ..Default::default()
453                    }
454                    .into(),
455                ));
456            }
457
458            if !folder.vars.is_empty() {
459                buf.push(T::from(
460                    VarDecl {
461                        span: DUMMY_SP,
462                        kind: VarDeclKind::Var,
463                        decls: folder.vars,
464                        ..Default::default()
465                    }
466                    .into(),
467                ));
468            }
469
470            buf.push(stmt);
471
472            buf.extend(folder.exprs.into_iter().map(|v| v.into_stmt()).map(T::from));
473        }
474
475        *stmts = buf;
476    }
477}
478
479impl ObjectRest {
480    fn insert_var_if_not_empty(&mut self, idx: usize, mut decl: VarDeclarator) {
481        if let Some(e1) = decl.init {
482            if let Expr::Ident(ref i1) = *e1 {
483                if let Pat::Ident(ref i2) = decl.name {
484                    if i1.ctxt == i2.ctxt && i1.sym == i2.sym {
485                        return;
486                    }
487                }
488            }
489            decl.init = Some(e1);
490        }
491
492        if let Pat::Object(..) | Pat::Array(..) = decl.name {
493            let ids: Vec<Id> = find_pat_ids(&decl.name);
494            if ids.is_empty() {
495                return;
496            }
497        }
498        self.vars.insert(idx, decl)
499    }
500
501    fn push_var_if_not_empty(&mut self, mut decl: VarDeclarator) {
502        if let Some(e1) = decl.init {
503            if let Expr::Ident(ref i1) = *e1 {
504                if let Pat::Ident(ref i2) = decl.name {
505                    if i1.sym == i2.sym && i1.ctxt == i2.ctxt {
506                        return;
507                    }
508                }
509            }
510            decl.init = Some(e1);
511        }
512
513        if let Pat::Object(ObjectPat { ref props, .. }) = decl.name {
514            if props.is_empty() {
515                return;
516            }
517        }
518        self.vars.push(decl)
519    }
520
521    fn visit_mut_fn_like(
522        &mut self,
523        params: &mut Vec<Param>,
524        body: &mut BlockStmt,
525    ) -> (Vec<Param>, BlockStmt) {
526        if !contains_rest(params) {
527            // fast-path
528            return (params.take(), body.take());
529        }
530
531        let prev_state = replace(
532            self,
533            Self {
534                config: self.config,
535                ..Default::default()
536            },
537        );
538
539        let params = params
540            .drain(..)
541            .map(|mut param| {
542                let var_ident = private_ident!(param.span(), "_param");
543                let mut index = self.vars.len();
544                param.pat =
545                    self.fold_rest(&mut index, param.pat, var_ident.clone().into(), false, true);
546
547                match param.pat {
548                    Pat::Rest(..) | Pat::Ident(..) => param,
549                    Pat::Assign(AssignPat { ref left, .. })
550                        if left.is_ident() || left.is_rest() || left.is_array() =>
551                    {
552                        param
553                    }
554                    Pat::Assign(n) => {
555                        let AssignPat {
556                            span, left, right, ..
557                        } = n;
558                        self.insert_var_if_not_empty(
559                            index,
560                            VarDeclarator {
561                                span,
562                                name: *left,
563                                init: Some(var_ident.clone().into()),
564                                definite: false,
565                            },
566                        );
567                        Param {
568                            span: DUMMY_SP,
569                            decorators: Default::default(),
570                            pat: AssignPat {
571                                span,
572                                left: var_ident.into(),
573                                right,
574                            }
575                            .into(),
576                        }
577                    }
578                    _ => {
579                        // initialize snd destructure
580                        self.insert_var_if_not_empty(
581                            index,
582                            VarDeclarator {
583                                span: DUMMY_SP,
584                                name: param.pat,
585                                init: Some(var_ident.clone().into()),
586                                definite: false,
587                            },
588                        );
589                        Param {
590                            span: DUMMY_SP,
591                            decorators: Default::default(),
592                            pat: var_ident.into(),
593                        }
594                    }
595                }
596            })
597            .collect();
598
599        let ret = (
600            params,
601            BlockStmt {
602                stmts: if self.vars.is_empty() {
603                    None
604                } else {
605                    Some(
606                        VarDecl {
607                            span: DUMMY_SP,
608                            kind: VarDeclKind::Var,
609                            decls: mem::take(&mut self.vars),
610                            ..Default::default()
611                        }
612                        .into(),
613                    )
614                }
615                .into_iter()
616                .chain(body.stmts.take())
617                .collect(),
618                ..body.take()
619            },
620        );
621
622        *self = prev_state;
623
624        ret
625    }
626
627    fn fold_rest(
628        &mut self,
629        index: &mut usize,
630        pat: Pat,
631        obj: Box<Expr>,
632        use_expr_for_assign: bool,
633        use_member_for_array: bool,
634    ) -> Pat {
635        // TODO(kdy1): Optimize when all fields are statically known.
636        //
637        // const { a: { ...bar }, b: { ...baz }, ...foo } = obj;
638        //
639        // can be
640        //
641        // const bar = _extends({}, obj.a), baz = _extends({}, obj.b), foo =
642        // _extends({}, obj);
643
644        if pat.is_ident() {
645            // panic!()
646        }
647
648        let ObjectPat {
649            span,
650            props,
651            type_ann,
652            ..
653        } = match pat {
654            Pat::Object(pat) => pat,
655            Pat::Assign(n) => {
656                let AssignPat {
657                    span, left, right, ..
658                } = n;
659                let left = Box::new(self.fold_rest(
660                    index,
661                    *left,
662                    obj,
663                    use_expr_for_assign,
664                    use_member_for_array,
665                ));
666                return AssignPat { span, left, right }.into();
667            }
668            Pat::Array(n) => {
669                let ArrayPat { span, elems, .. } = n;
670                let elems = elems
671                    .into_iter()
672                    .enumerate()
673                    .map(|(i, elem)| {
674                        elem.map(|elem| {
675                            self.fold_rest(
676                                index,
677                                elem,
678                                if use_member_for_array {
679                                    obj.clone().computed_member(i as f64).into()
680                                } else {
681                                    obj.clone()
682                                },
683                                use_expr_for_assign,
684                                use_member_for_array,
685                            )
686                        })
687                    })
688                    .collect();
689
690                return ArrayPat { span, elems, ..n }.into();
691            }
692            _ => return pat,
693        };
694
695        let mut props: Vec<ObjectPatProp> = props
696            .into_iter()
697            .map(|prop| match prop {
698                ObjectPatProp::Rest(n) => {
699                    let RestPat {
700                        arg, dot3_token, ..
701                    } = n;
702
703                    let pat = self.fold_rest(
704                        index,
705                        *arg,
706                        // TODO: fix this. this is wrong
707                        obj.clone(),
708                        use_expr_for_assign,
709                        true,
710                    );
711                    ObjectPatProp::Rest(RestPat {
712                        dot3_token,
713                        arg: Box::new(pat),
714                        ..n
715                    })
716                }
717                ObjectPatProp::KeyValue(KeyValuePatProp { key, value }) => {
718                    let (key, prop) = match key {
719                        PropName::Ident(ref ident) => {
720                            let ident = ident.clone();
721                            (key, MemberProp::Ident(ident))
722                        }
723                        PropName::Str(Str {
724                            ref value, span, ..
725                        }) => {
726                            let value = value.clone();
727                            (
728                                key,
729                                MemberProp::Computed(ComputedPropName {
730                                    span,
731                                    expr: Lit::Str(Str {
732                                        span,
733                                        raw: None,
734                                        value: value.clone(),
735                                    })
736                                    .into(),
737                                }),
738                            )
739                        }
740                        PropName::Num(Number { span, value, .. }) => (
741                            key,
742                            MemberProp::Computed(ComputedPropName {
743                                span,
744                                expr: Lit::Str(Str {
745                                    span,
746                                    raw: None,
747
748                                    value: format!("{value}").into(),
749                                })
750                                .into(),
751                            }),
752                        ),
753                        PropName::BigInt(BigInt {
754                            span, ref value, ..
755                        }) => {
756                            let value = value.clone();
757                            (
758                                key,
759                                MemberProp::Computed(ComputedPropName {
760                                    span,
761                                    expr: Lit::Str(Str {
762                                        span,
763                                        raw: None,
764                                        value: format!("{value}").into(),
765                                    })
766                                    .into(),
767                                }),
768                            )
769                        }
770                        PropName::Computed(ref c) if is_literal(&c.expr) => {
771                            let c = c.clone();
772                            (key, MemberProp::Computed(c))
773                        }
774                        PropName::Computed(c) => {
775                            let (ident, aliased) = alias_if_required(&c.expr, "key");
776                            if aliased {
777                                *index += 1;
778                                self.vars.push(VarDeclarator {
779                                    span: DUMMY_SP,
780                                    name: ident.clone().into(),
781                                    init: Some(c.expr),
782                                    definite: false,
783                                });
784                            }
785
786                            (
787                                PropName::Computed(ComputedPropName {
788                                    span: c.span,
789                                    expr: ident.clone().into(),
790                                }),
791                                MemberProp::Computed(ComputedPropName {
792                                    span: c.span,
793                                    expr: ident.into(),
794                                }),
795                            )
796                        }
797                        #[cfg(swc_ast_unknown)]
798                        _ => panic!("unable to access unknown nodes"),
799                    };
800
801                    let value = Box::new(
802                        self.fold_rest(
803                            index,
804                            *value,
805                            Box::new(
806                                MemberExpr {
807                                    span: DUMMY_SP,
808                                    obj: obj.clone(),
809                                    prop,
810                                }
811                                .into(),
812                            ),
813                            use_expr_for_assign,
814                            true,
815                        ),
816                    );
817                    ObjectPatProp::KeyValue(KeyValuePatProp { key, value })
818                }
819                _ => prop,
820            })
821            .collect();
822
823        match props.last() {
824            Some(ObjectPatProp::Rest(..)) => {}
825            _ => {
826                return ObjectPat {
827                    span,
828                    props,
829                    optional: false,
830                    type_ann,
831                }
832                .into();
833            }
834        }
835        let last = match props.pop() {
836            Some(ObjectPatProp::Rest(rest)) => rest,
837            _ => unreachable!(),
838        };
839
840        let excluded_props = excluded_props(&props);
841
842        if use_expr_for_assign {
843            // println!("Expr: last.arg = objectWithoutProperties()",);
844            self.exprs.push(
845                AssignExpr {
846                    span: DUMMY_SP,
847                    left: last.arg.try_into().unwrap(),
848                    op: op!("="),
849                    right: Box::new(object_without_properties(
850                        obj,
851                        excluded_props,
852                        self.config.no_symbol,
853                    )),
854                }
855                .into(),
856            );
857        } else {
858            // println!("Var: rest = objectWithoutProperties()",);
859            self.push_var_if_not_empty(VarDeclarator {
860                span: DUMMY_SP,
861                name: *last.arg,
862                init: Some(Box::new(object_without_properties(
863                    obj,
864                    excluded_props,
865                    self.config.no_symbol,
866                ))),
867                definite: false,
868            });
869        }
870
871        ObjectPat {
872            props,
873            span,
874            type_ann,
875            optional: false,
876        }
877        .into()
878    }
879}
880
881#[tracing::instrument(level = "debug", skip_all)]
882fn object_without_properties(
883    obj: Box<Expr>,
884    excluded_props: Vec<Option<ExprOrSpread>>,
885    no_symbol: bool,
886) -> Expr {
887    if excluded_props.is_empty() {
888        return CallExpr {
889            span: DUMMY_SP,
890            callee: helper!(extends),
891            args: vec![
892                ObjectLit {
893                    span: DUMMY_SP,
894                    props: Vec::new(),
895                }
896                .as_arg(),
897                helper_expr!(object_destructuring_empty)
898                    .as_call(DUMMY_SP, vec![obj.as_arg()])
899                    .as_arg(),
900            ],
901            ..Default::default()
902        }
903        .into();
904    }
905
906    let excluded_props = excluded_props
907        .into_iter()
908        .map(|v| {
909            v.map(|v| match *v.expr {
910                Expr::Lit(Lit::Num(Number { span, value, .. })) => ExprOrSpread {
911                    expr: Lit::Str(Str {
912                        span,
913                        raw: None,
914                        value: value.to_string().into(),
915                    })
916                    .into(),
917                    ..v
918                },
919                _ => v,
920            })
921        })
922        .collect();
923
924    CallExpr {
925        span: DUMMY_SP,
926        callee: if no_symbol {
927            helper!(object_without_properties_loose)
928        } else {
929            helper!(object_without_properties)
930        },
931        args: vec![
932            obj.as_arg(),
933            if is_literal(&excluded_props) {
934                ArrayLit {
935                    span: DUMMY_SP,
936                    elems: excluded_props,
937                }
938                .as_arg()
939            } else {
940                CallExpr {
941                    span: DUMMY_SP,
942                    callee: ArrayLit {
943                        span: DUMMY_SP,
944                        elems: excluded_props,
945                    }
946                    .make_member(quote_ident!("map"))
947                    .as_callee(),
948                    args: vec![helper_expr!(to_property_key).as_arg()],
949                    ..Default::default()
950                }
951                .as_arg()
952            },
953        ],
954        ..Default::default()
955    }
956    .into()
957}
958
959#[tracing::instrument(level = "debug", skip_all)]
960fn excluded_props(props: &[ObjectPatProp]) -> Vec<Option<ExprOrSpread>> {
961    props
962        .iter()
963        .map(|prop| match prop {
964            ObjectPatProp::KeyValue(KeyValuePatProp { key, .. }) => match key {
965                PropName::Ident(ident) => Lit::Str(Str {
966                    span: ident.span,
967                    raw: None,
968                    value: ident.sym.clone().into(),
969                })
970                .as_arg(),
971                PropName::Str(s) => Lit::Str(s.clone()).as_arg(),
972                PropName::Num(Number { span, value, .. }) => Lit::Str(Str {
973                    span: *span,
974                    raw: None,
975
976                    value: format!("{value}").into(),
977                })
978                .as_arg(),
979                PropName::BigInt(BigInt { span, value, .. }) => Lit::Str(Str {
980                    span: *span,
981                    raw: None,
982
983                    value: format!("{value}").into(),
984                })
985                .as_arg(),
986                PropName::Computed(c) => c.expr.clone().as_arg(),
987                #[cfg(swc_ast_unknown)]
988                _ => panic!("unable to access unknown nodes"),
989            },
990            ObjectPatProp::Assign(AssignPatProp { key, .. }) => Lit::Str(Str {
991                span: key.span,
992                raw: None,
993                value: key.sym.clone().into(),
994            })
995            .as_arg(),
996            ObjectPatProp::Rest(..) => unreachable!("invalid syntax (multiple rest element)"),
997            #[cfg(swc_ast_unknown)]
998            _ => panic!("unable to access unknown nodes"),
999        })
1000        .map(Some)
1001        .collect()
1002}
1003
1004/// e.g.
1005///
1006///  - `{ x4: {}  }` -> `{}`
1007struct PatSimplifier;
1008
1009#[swc_trace]
1010impl VisitMut for PatSimplifier {
1011    noop_visit_mut_type!(fail);
1012
1013    fn visit_mut_pat(&mut self, pat: &mut Pat) {
1014        pat.visit_mut_children_with(self);
1015
1016        if let Pat::Object(o) = pat {
1017            o.props.retain(|prop| {
1018                if let ObjectPatProp::KeyValue(KeyValuePatProp { value, .. }) = prop {
1019                    match &**value {
1020                        Pat::Object(ObjectPat { props, .. }) if props.is_empty() => {
1021                            return false;
1022                        }
1023                        _ => {}
1024                    }
1025                }
1026
1027                true
1028            });
1029        }
1030    }
1031}