swc_ecma_compat_es2015/
spread.rs

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