swc_ecma_compat_es2015/
spread.rs

1use std::mem;
2
3use serde::Deserialize;
4use swc_atoms::atom;
5use swc_common::{util::take::Take, Mark, Span, Spanned, SyntaxContext, DUMMY_SP};
6use swc_ecma_ast::*;
7use swc_ecma_transforms_base::{ext::ExprRefExt, helper, perf::Check};
8use swc_ecma_transforms_macros::fast_path;
9use swc_ecma_utils::{
10    alias_ident_for, member_expr, prepend_stmt, quote_ident, ExprFactory, StmtLike,
11};
12use swc_ecma_visit::{
13    noop_visit_mut_type, noop_visit_type, visit_mut_pass, Visit, VisitMut, VisitMutWith, VisitWith,
14};
15use swc_trace_macro::swc_trace;
16
17pub fn spread(c: Config, unresolved_mark: Mark) -> impl Pass {
18    let unresolved_ctxt = SyntaxContext::empty().apply_mark(unresolved_mark);
19    visit_mut_pass(Spread {
20        c,
21        unresolved_ctxt,
22        vars: Default::default(),
23    })
24}
25
26#[derive(Debug, Clone, Copy, Default, Deserialize)]
27#[serde(rename_all = "camelCase")]
28pub struct Config {
29    pub loose: bool,
30}
31
32/// es2015 - `SpreadElement`
33struct Spread {
34    c: Config,
35    unresolved_ctxt: SyntaxContext,
36    vars: Vec<VarDeclarator>,
37}
38
39#[swc_trace]
40#[fast_path(SpreadFinder)]
41impl VisitMut for Spread {
42    noop_visit_mut_type!(fail);
43
44    fn visit_mut_module_items(&mut self, n: &mut Vec<ModuleItem>) {
45        self.visit_mut_stmt_like(n);
46    }
47
48    fn visit_mut_stmts(&mut self, n: &mut Vec<Stmt>) {
49        self.visit_mut_stmt_like(n);
50    }
51
52    fn visit_mut_expr(&mut self, e: &mut Expr) {
53        e.visit_mut_children_with(self);
54
55        match e {
56            Expr::Array(ArrayLit { span, elems }) => {
57                if !elems.iter().any(|e| {
58                    matches!(
59                        e,
60                        Some(ExprOrSpread {
61                            spread: Some(_),
62                            ..
63                        })
64                    )
65                }) {
66                    return;
67                }
68
69                *e = self.concat_args(*span, elems.take().into_iter(), true);
70            }
71
72            // super(...spread) should be removed by es2015::classes pass
73            Expr::Call(CallExpr {
74                callee: Callee::Expr(callee),
75                args,
76                span,
77                ..
78            }) => {
79                let has_spread = args
80                    .iter()
81                    .any(|ExprOrSpread { spread, .. }| spread.is_some());
82                if !has_spread {
83                    return;
84                }
85
86                let (this, callee_updated) = match &**callee {
87                    Expr::SuperProp(SuperPropExpr {
88                        obj: Super { span, .. },
89                        ..
90                    }) => (ThisExpr { span: *span }.into(), None),
91
92                    Expr::Member(MemberExpr { obj, .. }) if obj.is_this() => (obj.clone(), None),
93
94                    // Injected variables can be accessed without any side effect
95                    Expr::Member(MemberExpr { obj, .. })
96                        if obj.as_ident().is_some() && obj.as_ident().unwrap().span.is_dummy() =>
97                    {
98                        (obj.as_ident().unwrap().clone().into(), None)
99                    }
100
101                    Expr::Ident(Ident { span, .. }) => (Expr::undefined(*span), None),
102
103                    Expr::Member(MemberExpr { span, obj, prop }) => {
104                        let ident = alias_ident_for(obj, "_instance");
105                        self.vars.push(VarDeclarator {
106                            span: DUMMY_SP,
107                            definite: false,
108                            // Initialized by paren expression.
109                            name: ident.clone().into(),
110                            // Initialized by paren expression.
111                            init: None,
112                        });
113
114                        let this = ident.clone().into();
115                        let callee: Expr = AssignExpr {
116                            span: DUMMY_SP,
117                            left: ident.into(),
118                            op: op!("="),
119                            right: obj.clone(),
120                        }
121                        .into();
122                        (
123                            this,
124                            Some(
125                                MemberExpr {
126                                    span: *span,
127                                    obj: callee.into(),
128                                    prop: prop.clone(),
129                                }
130                                .into(),
131                            ),
132                        )
133                    }
134
135                    // https://github.com/swc-project/swc/issues/400
136                    // _ => (undefined(callee.span()), callee),
137                    _ => (
138                        ThisExpr {
139                            span: callee.span(),
140                        }
141                        .into(),
142                        None,
143                    ),
144                };
145
146                let args_array = if args.iter().all(|e| {
147                    matches!(e, ExprOrSpread { spread: None, .. })
148                        || matches!(e, ExprOrSpread { expr, .. } if expr.is_array())
149                }) {
150                    ArrayLit {
151                        span: *span,
152                        elems: expand_literal_args(args.take().into_iter().map(Some)),
153                    }
154                    .into()
155                } else {
156                    self.concat_args(*span, args.take().into_iter().map(Some), false)
157                };
158
159                let apply = MemberExpr {
160                    span: DUMMY_SP,
161                    obj: callee_updated.unwrap_or_else(|| callee.take()),
162                    prop: quote_ident!("apply").into(),
163                };
164
165                *e = CallExpr {
166                    span: *span,
167                    callee: apply.as_callee(),
168                    args: vec![this.as_arg(), args_array.as_arg()],
169                    ..Default::default()
170                }
171                .into()
172            }
173            Expr::New(NewExpr {
174                callee,
175                args: Some(args),
176                span,
177                ..
178            }) => {
179                let has_spread = args
180                    .iter()
181                    .any(|ExprOrSpread { spread, .. }| spread.is_some());
182                if !has_spread {
183                    return;
184                }
185
186                let args = self.concat_args(*span, args.take().into_iter().map(Some), true);
187
188                *e = CallExpr {
189                    span: *span,
190                    callee: helper!(construct),
191                    args: vec![callee.take().as_arg(), args.as_arg()],
192                    ..Default::default()
193                }
194                .into();
195            }
196            _ => {}
197        };
198    }
199}
200
201#[swc_trace]
202impl Spread {
203    fn visit_mut_stmt_like<T>(&mut self, items: &mut Vec<T>)
204    where
205        T: StmtLike,
206        Vec<T>: VisitMutWith<Self>,
207    {
208        let orig = self.vars.take();
209
210        items.visit_mut_children_with(self);
211
212        if !self.vars.is_empty() {
213            prepend_stmt(
214                items,
215                T::from(
216                    VarDecl {
217                        kind: VarDeclKind::Var,
218                        decls: self.vars.take(),
219                        ..Default::default()
220                    }
221                    .into(),
222                ),
223            );
224        }
225
226        self.vars = orig;
227    }
228}
229
230#[swc_trace]
231impl Spread {
232    fn concat_args(
233        &self,
234        span: Span,
235        args: impl ExactSizeIterator<Item = Option<ExprOrSpread>>,
236        need_array: bool,
237    ) -> Expr {
238        //
239        // []
240        //
241        let mut first_arr = None;
242
243        let mut tmp_arr = Vec::new();
244        let mut buf = Vec::new();
245        let args_len = args.len();
246
247        macro_rules! make_arr {
248            () => {
249                let elems = mem::take(&mut tmp_arr);
250                match first_arr {
251                    Some(_) => {
252                        if !elems.is_empty() {
253                            buf.push(ArrayLit { span, elems }.as_arg());
254                        }
255                    }
256                    None => {
257                        first_arr = Some(Expr::Array(ArrayLit { span, elems }));
258                    }
259                }
260            };
261        }
262
263        // Shorthand [].concat(arr1, arr2) should be used under loose mode.
264        // Array.prototype.concat has the lovely feature by which arrays passed
265        // to it are depth-1 flattened. This effectively implements the loose
266        // spread. But if any arrays that are not being spread are present, they
267        // will be incorrectly flattened. The solution is to wrap every
268        // contiguous slice of non-spread args in an array, which will protect
269        // array args from being flattened.
270        if self.c.loose {
271            let mut arg_list = Vec::new();
272            let mut current_elems = Vec::new();
273            for arg in args.flatten() {
274                let expr = arg.expr;
275                match arg.spread {
276                    Some(span) => {
277                        if !current_elems.is_empty() {
278                            arg_list.push(
279                                ArrayLit {
280                                    span: DUMMY_SP,
281                                    elems: current_elems,
282                                }
283                                .as_arg(),
284                            );
285                            current_elems = Vec::new();
286                        }
287                        // Special handling for `arguments` to call Array.prototype.slice
288                        // https://github.com/babel/babel/blob/61ad8555b875cb0c0996f18f803b6bf1d2150173/packages/babel-plugin-transform-spread/src/index.ts#L43-L47
289                        let expr = match *expr {
290                            Expr::Ident(Ident { ref sym, ctxt, .. })
291                                if &**sym == "arguments" && ctxt == self.unresolved_ctxt =>
292                            {
293                                CallExpr {
294                                    span,
295                                    callee: member_expr!(
296                                        Default::default(),
297                                        DUMMY_SP,
298                                        Array.prototype.slice.call
299                                    )
300                                    .as_callee(),
301                                    args: vec![expr.as_arg()],
302                                    ..Default::default()
303                                }
304                                .into()
305                            }
306                            _ => *expr,
307                        };
308                        arg_list.push(expr.as_arg());
309                    }
310                    None => {
311                        current_elems.push(Some(expr.as_arg()));
312                    }
313                }
314            }
315            if !current_elems.is_empty() {
316                arg_list.push(
317                    ArrayLit {
318                        span: DUMMY_SP,
319                        elems: current_elems,
320                    }
321                    .as_arg(),
322                );
323            }
324
325            return CallExpr {
326                span: DUMMY_SP,
327                callee: ArrayLit {
328                    span: DUMMY_SP,
329                    elems: Vec::new(),
330                }
331                .make_member(quote_ident!("concat"))
332                .as_callee(),
333                args: arg_list,
334                ..Default::default()
335            }
336            .into();
337        }
338
339        for arg in args {
340            if let Some(arg) = arg {
341                let ExprOrSpread { expr, spread } = arg;
342
343                fn to_consumable_array(expr: Box<Expr>, span: Span) -> CallExpr {
344                    if matches!(*expr, Expr::Lit(Lit::Str(..))) {
345                        CallExpr {
346                            span,
347                            callee: quote_ident!("Array")
348                                .make_member(quote_ident!("from"))
349                                .as_callee(),
350                            args: vec![expr.as_arg()],
351                            ..Default::default()
352                        }
353                    } else {
354                        CallExpr {
355                            span,
356                            callee: helper!(to_consumable_array),
357                            args: vec![expr.as_arg()],
358                            ..Default::default()
359                        }
360                    }
361                }
362
363                match spread {
364                    // ...b -> toConsumableArray(b)
365                    Some(span) => {
366                        //
367                        make_arr!();
368
369                        buf.push(match *expr {
370                            Expr::Ident(Ident { ref sym, .. }) if &**sym == "arguments" => {
371                                if args_len == 1 {
372                                    if need_array {
373                                        return CallExpr {
374                                            span,
375                                            callee: member_expr!(
376                                                Default::default(),
377                                                DUMMY_SP,
378                                                Array.prototype.slice.call
379                                            )
380                                            .as_callee(),
381                                            args: vec![expr.as_arg()],
382                                            ..Default::default()
383                                        }
384                                        .into();
385                                    } else {
386                                        return *expr;
387                                    }
388                                } else {
389                                    CallExpr {
390                                        span,
391                                        callee: member_expr!(
392                                            Default::default(),
393                                            DUMMY_SP,
394                                            Array.prototype.slice.call
395                                        )
396                                        .as_callee(),
397                                        args: vec![expr.as_arg()],
398                                        ..Default::default()
399                                    }
400                                    .as_arg()
401                                }
402                            }
403                            _ => {
404                                if args_len == 1 && !need_array {
405                                    return if self.c.loose {
406                                        *expr
407                                    } else {
408                                        to_consumable_array(expr, span).into()
409                                    };
410                                }
411                                // [].concat(arr) is shorter than _to_consumable_array(arr)
412                                if args_len == 1 {
413                                    return if self.c.loose {
414                                        CallExpr {
415                                            span: DUMMY_SP,
416                                            callee: ArrayLit {
417                                                span: DUMMY_SP,
418                                                elems: Vec::new(),
419                                            }
420                                            .make_member(quote_ident!("concat"))
421                                            .as_callee(),
422                                            args: vec![expr.as_arg()],
423                                            ..Default::default()
424                                        }
425                                        .into()
426                                    } else {
427                                        to_consumable_array(expr, span).into()
428                                    };
429                                }
430                                to_consumable_array(expr, span).as_arg()
431                            }
432                        });
433                    }
434                    None => tmp_arr.push(Some(expr.as_arg())),
435                }
436            } else {
437                tmp_arr.push(None);
438            }
439        }
440        make_arr!();
441
442        if !buf.is_empty()
443            && match first_arr {
444                None => true,
445                Some(Expr::Array(ref arr)) if arr.elems.is_empty() => true,
446                _ => false,
447            }
448        {
449            let callee = buf
450                .remove(0)
451                .expr
452                .make_member(IdentName::new(atom!("concat"), DUMMY_SP))
453                .as_callee();
454
455            return CallExpr {
456                span,
457                callee,
458                args: buf,
459                ..Default::default()
460            }
461            .into();
462        }
463
464        CallExpr {
465            // TODO
466            span,
467
468            callee: first_arr
469                .take()
470                .unwrap_or_else(|| {
471                    // No arg
472
473                    // assert!(args.is_empty());
474                    Expr::Array(ArrayLit {
475                        span,
476                        elems: Vec::new(),
477                    })
478                })
479                .make_member(IdentName::new(atom!("concat"), span))
480                .as_callee(),
481
482            args: buf,
483            ..Default::default()
484        }
485        .into()
486    }
487}
488
489#[tracing::instrument(level = "debug", skip_all)]
490fn expand_literal_args(
491    args: impl ExactSizeIterator<Item = Option<ExprOrSpread>>,
492) -> Vec<Option<ExprOrSpread>> {
493    fn expand(
494        buf: &mut Vec<Option<ExprOrSpread>>,
495        args: impl ExactSizeIterator<Item = Option<ExprOrSpread>>,
496    ) {
497        for mut arg in args {
498            if let Some(ExprOrSpread {
499                spread: Some(spread_span),
500                expr,
501            }) = arg
502            {
503                match *expr {
504                    Expr::Array(arr) => {
505                        expand(buf, arr.elems.into_iter());
506                        continue;
507                    }
508                    _ => {
509                        arg = Some(ExprOrSpread {
510                            spread: Some(spread_span),
511                            expr,
512                        })
513                    }
514                }
515            }
516
517            buf.push(arg)
518        }
519    }
520
521    let mut buf = Vec::with_capacity(args.len() + 4);
522    expand(&mut buf, args);
523    buf
524}
525
526#[derive(Default)]
527struct SpreadFinder {
528    found: bool,
529}
530
531impl Visit for SpreadFinder {
532    noop_visit_type!(fail);
533
534    fn visit_expr_or_spread(&mut self, n: &ExprOrSpread) {
535        n.visit_children_with(self);
536
537        self.found |= n.spread.is_some();
538    }
539}
540
541impl Check for SpreadFinder {
542    fn should_handle(&self) -> bool {
543        self.found
544    }
545}