swc_ecma_compat_es2015/
spread.rs

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