swc_ecma_compat_es2015/
for_of.rs

1use std::mem::take;
2
3use serde::Deserialize;
4use swc_common::{util::take::Take, Mark, Spanned, SyntaxContext, DUMMY_SP};
5use swc_ecma_ast::*;
6use swc_ecma_transforms_base::{
7    helper,
8    perf::{ParExplode, Parallel},
9};
10use swc_ecma_transforms_macros::parallel;
11use swc_ecma_utils::{
12    alias_if_required, member_expr, prepend_stmt, private_ident, quote_ident, ExprFactory,
13};
14use swc_ecma_visit::{noop_visit_mut_type, visit_mut_pass, VisitMut, VisitMutWith};
15use swc_trace_macro::swc_trace;
16
17/// `@babel/plugin-transform-for-of`
18///
19/// ## In
20///
21/// ```js
22/// for (var i of foo) {}
23/// ```
24///
25/// ## Out
26///
27/// ```js
28/// var _iteratorNormalCompletion = true;
29/// var _didIteratorError = false;
30/// var _iteratorError = undefined;
31///
32/// try {
33///   for (var _iterator = foo[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) {
34///     var i = _step.value;
35///   }
36/// } catch (err) {
37///   _didIteratorError = true;
38///   _iteratorError = err;
39/// } finally {
40///   try {
41///     if (!_iteratorNormalCompletion && _iterator.return != null) {
42///       _iterator.return();
43///     }
44///   } finally {
45///     if (_didIteratorError) {
46///       throw _iteratorError;
47///     }
48///   }
49/// }
50/// ```
51pub fn for_of(c: Config) -> impl Pass {
52    visit_mut_pass(ForOf {
53        c,
54        top_level_vars: Default::default(),
55    })
56}
57
58#[derive(Debug, Clone, Copy, Default, Deserialize)]
59#[serde(rename_all = "camelCase")]
60pub struct Config {
61    pub loose: bool,
62    pub assume_array: bool,
63}
64
65struct ForOf {
66    c: Config,
67
68    ///```js
69    /// var _iteratorNormalCompletion = true;
70    /// var _didIteratorError = false;
71    /// var _iteratorError = undefined;
72    /// ```
73    top_level_vars: Vec<VarDeclarator>,
74}
75
76#[swc_trace]
77impl ForOf {
78    fn fold_for_stmt(
79        &mut self,
80        label: Option<Ident>,
81        ForOfStmt {
82            span,
83            left,
84            right,
85            body,
86            ..
87        }: ForOfStmt,
88    ) -> Stmt {
89        if right.is_array() || (self.c.assume_array && !self.c.loose) {
90            // Convert to normal for loop if rhs is array
91            //
92            // babel's output:
93            //
94            //    for(var _i = 0, _t = t; _i < _t.length; _i++){
95            //        let o = _t[_i];
96            //        const t = o;
97            //    }
98
99            let (arr, aliased) = alias_if_required(&right, "_iter");
100
101            let i = private_ident!("_i");
102
103            let test = Some(
104                BinExpr {
105                    span: DUMMY_SP,
106                    left: Box::new(i.clone().into()),
107                    op: op!("<"),
108                    right: arr.clone().make_member(quote_ident!("length")).into(),
109                }
110                .into(),
111            );
112            let update = Some(
113                UpdateExpr {
114                    span: DUMMY_SP,
115                    prefix: false,
116                    op: op!("++"),
117                    arg: Box::new(i.clone().into()),
118                }
119                .into(),
120            );
121
122            let mut decls = Vec::with_capacity(2);
123            decls.push(VarDeclarator {
124                span: DUMMY_SP,
125                name: i.clone().into(),
126                init: Some(0.into()),
127                definite: false,
128            });
129
130            if aliased {
131                decls.push(VarDeclarator {
132                    span: DUMMY_SP,
133                    name: arr.clone().into(),
134                    init: Some(right),
135                    definite: false,
136                });
137            }
138
139            let mut body = match *body {
140                Stmt::Block(b) => b,
141                _ => BlockStmt {
142                    span: DUMMY_SP,
143                    stmts: vec![*body],
144                    ..Default::default()
145                },
146            };
147
148            match left {
149                ForHead::VarDecl(var) => {
150                    assert_eq!(
151                        var.decls.len(),
152                        1,
153                        "Variable declarator of for of loop cannot contain multiple entries"
154                    );
155                    prepend_stmt(
156                        &mut body.stmts,
157                        VarDecl {
158                            span: DUMMY_SP,
159                            kind: var.kind,
160                            declare: false,
161                            decls: vec![VarDeclarator {
162                                span: DUMMY_SP,
163                                name: var.decls.into_iter().next().unwrap().name,
164                                init: Some(arr.computed_member(i).into()),
165                                definite: false,
166                            }],
167                            ..Default::default()
168                        }
169                        .into(),
170                    )
171                }
172
173                ForHead::Pat(pat) => prepend_stmt(
174                    &mut body.stmts,
175                    AssignExpr {
176                        span: DUMMY_SP,
177                        left: pat.try_into().unwrap(),
178                        op: op!("="),
179                        right: arr.computed_member(i).into(),
180                    }
181                    .into_stmt(),
182                ),
183
184                ForHead::UsingDecl(..) => {
185                    unreachable!("using declaration must be removed by previous pass")
186                }
187            }
188
189            let stmt = ForStmt {
190                span,
191                init: Some(
192                    VarDecl {
193                        span: DUMMY_SP,
194                        kind: VarDeclKind::Let,
195                        declare: false,
196                        decls,
197                        ..Default::default()
198                    }
199                    .into(),
200                ),
201                test,
202                update,
203                body: Box::new(Stmt::Block(body)),
204            }
205            .into();
206
207            return match label {
208                Some(label) => LabeledStmt {
209                    span,
210                    label,
211                    body: Box::new(stmt),
212                }
213                .into(),
214                _ => stmt,
215            };
216        }
217
218        // Loose mode
219        if self.c.loose {
220            let iterator = private_ident!("_iterator");
221            let step = private_ident!("_step");
222
223            let decls = vec![
224                VarDeclarator {
225                    span: DUMMY_SP,
226                    name: iterator.clone().into(),
227                    init: Some(Box::new(Expr::Call(CallExpr {
228                        span: DUMMY_SP,
229                        callee: helper!(create_for_of_iterator_helper_loose),
230                        args: vec![right.as_arg()],
231                        ..Default::default()
232                    }))),
233                    definite: Default::default(),
234                },
235                VarDeclarator {
236                    span: DUMMY_SP,
237                    name: step.clone().into(),
238                    init: None,
239                    definite: Default::default(),
240                },
241            ];
242
243            let mut body = match *body {
244                Stmt::Block(b) => b,
245                _ => BlockStmt {
246                    stmts: vec![*body],
247                    ..Default::default()
248                },
249            };
250
251            match left {
252                ForHead::VarDecl(var) => {
253                    assert_eq!(
254                        var.decls.len(),
255                        1,
256                        "Variable declarator of for of loop cannot contain multiple entries"
257                    );
258                    prepend_stmt(
259                        &mut body.stmts,
260                        VarDecl {
261                            kind: var.kind,
262                            decls: vec![VarDeclarator {
263                                span: DUMMY_SP,
264                                name: var.decls.into_iter().next().unwrap().name,
265                                init: Some(step.clone().make_member(quote_ident!("value")).into()),
266                                definite: false,
267                            }],
268                            ..Default::default()
269                        }
270                        .into(),
271                    )
272                }
273
274                ForHead::Pat(pat) => prepend_stmt(
275                    &mut body.stmts,
276                    AssignExpr {
277                        span: DUMMY_SP,
278                        left: pat.try_into().unwrap(),
279                        op: op!("="),
280                        right: step.clone().make_member(quote_ident!("value")).into(),
281                    }
282                    .into_stmt(),
283                ),
284
285                ForHead::UsingDecl(..) => {
286                    unreachable!("using declaration must be removed by previous pass")
287                }
288            }
289
290            // !(_step = _iterator()).done;
291            let test = UnaryExpr {
292                span: DUMMY_SP,
293                op: op!("!"),
294                arg: AssignExpr {
295                    span: DUMMY_SP,
296                    op: op!("="),
297                    left: step.into(),
298                    right: CallExpr {
299                        span: DUMMY_SP,
300                        callee: iterator.as_callee(),
301                        args: Vec::new(),
302                        ..Default::default()
303                    }
304                    .into(),
305                }
306                .make_member(quote_ident!("done"))
307                .into(),
308            }
309            .into();
310
311            let stmt = ForStmt {
312                span,
313                init: Some(
314                    VarDecl {
315                        kind: VarDeclKind::Var,
316                        decls,
317                        ..Default::default()
318                    }
319                    .into(),
320                ),
321                test: Some(test),
322                update: None,
323                body: Box::new(Stmt::Block(body)),
324            }
325            .into();
326            return match label {
327                Some(label) => LabeledStmt {
328                    span,
329                    label,
330                    body: Box::new(stmt),
331                }
332                .into(),
333                _ => stmt,
334            };
335        }
336
337        let var_span = left.span();
338        let var_ctxt = SyntaxContext::empty().apply_mark(Mark::fresh(Mark::root()));
339
340        let mut body = match *body {
341            Stmt::Block(block) => block,
342            body => BlockStmt {
343                span: DUMMY_SP,
344                stmts: vec![body],
345                ..Default::default()
346            },
347        };
348
349        let step = quote_ident!(var_ctxt, var_span, "_step");
350        let step_value = step.clone().make_member(quote_ident!("value"));
351        body.stmts.insert(
352            0,
353            match left {
354                ForHead::VarDecl(mut var) => {
355                    assert_eq!(var.decls.len(), 1);
356                    VarDecl {
357                        span: var.span,
358                        kind: var.kind,
359                        decls: vec![VarDeclarator {
360                            init: Some(step_value.into()),
361                            ..var.decls.pop().unwrap()
362                        }],
363                        declare: false,
364                        ..Default::default()
365                    }
366                    .into()
367                }
368                ForHead::Pat(pat) => AssignExpr {
369                    span: DUMMY_SP,
370                    left: pat.try_into().unwrap(),
371                    op: op!("="),
372                    right: step_value.into(),
373                }
374                .into_stmt(),
375
376                ForHead::UsingDecl(..) => {
377                    unreachable!("using declaration must be removed by previous pass")
378                }
379            },
380        );
381
382        let iterator = quote_ident!(var_ctxt, var_span, "_iterator");
383        // `_iterator.return`
384        let iterator_return = iterator.clone().make_member(quote_ident!("return")).into();
385
386        let normal_completion_ident =
387            Ident::new("_iteratorNormalCompletion".into(), var_span, var_ctxt);
388        self.top_level_vars.push(VarDeclarator {
389            span: DUMMY_SP,
390            name: normal_completion_ident.clone().into(),
391            init: Some(true.into()),
392            definite: false,
393        });
394        let error_flag_ident = Ident::new("_didIteratorError".into(), var_span, var_ctxt);
395        self.top_level_vars.push(VarDeclarator {
396            span: DUMMY_SP,
397            name: error_flag_ident.clone().into(),
398            init: Some(false.into()),
399            definite: false,
400        });
401        let error_ident = Ident::new("_iteratorError".into(), var_span, var_ctxt);
402        self.top_level_vars.push(VarDeclarator {
403            span: DUMMY_SP,
404            name: error_ident.clone().into(),
405            init: Some(Ident::new_no_ctxt("undefined".into(), DUMMY_SP).into()),
406            definite: false,
407        });
408
409        let for_stmt = ForStmt {
410            span,
411            init: Some(
412                VarDecl {
413                    span: DUMMY_SP,
414                    kind: VarDeclKind::Var,
415                    declare: false,
416                    decls: vec![
417                        VarDeclarator {
418                            span: DUMMY_SP,
419                            name: iterator.clone().into(),
420                            init: Some(Box::new(Expr::Call(CallExpr {
421                                span: DUMMY_SP,
422                                callee: right
423                                    .computed_member(member_expr!(
424                                        Default::default(),
425                                        Default::default(),
426                                        Symbol.iterator
427                                    ))
428                                    .as_callee(),
429                                args: Vec::new(),
430                                ..Default::default()
431                            }))),
432                            definite: false,
433                        },
434                        VarDeclarator {
435                            span: DUMMY_SP,
436                            name: step.clone().into(),
437                            init: None,
438                            definite: false,
439                        },
440                    ],
441                    ..Default::default()
442                }
443                .into(),
444            ),
445            // !(_iteratorNormalCompletion = (_step = _iterator.next()).done)
446            test: Some(
447                UnaryExpr {
448                    span: DUMMY_SP,
449                    op: op!("!"),
450                    arg: {
451                        let step_expr: Expr = AssignExpr {
452                            span: DUMMY_SP,
453                            left: step.into(),
454                            op: op!("="),
455                            // `_iterator.next()`
456                            right: Box::new(Expr::Call(CallExpr {
457                                span: DUMMY_SP,
458                                // `_iterator.next`
459                                callee: iterator.make_member(quote_ident!("next")).as_callee(),
460                                args: Vec::new(),
461                                ..Default::default()
462                            })),
463                        }
464                        .into();
465
466                        Box::new(
467                            AssignExpr {
468                                span: DUMMY_SP,
469                                left: normal_completion_ident.clone().into(),
470                                op: op!("="),
471                                right: step_expr.make_member(quote_ident!("done")).into(),
472                            }
473                            .into(),
474                        )
475                    },
476                }
477                .into(),
478            ),
479
480            // `_iteratorNormalCompletion = true`
481            update: Some(
482                AssignExpr {
483                    span: DUMMY_SP,
484                    left: normal_completion_ident.clone().into(),
485                    op: op!("="),
486                    right: true.into(),
487                }
488                .into(),
489            ),
490            body: Box::new(body.into()),
491        }
492        .into();
493
494        let for_stmt = match label {
495            Some(label) => LabeledStmt {
496                span,
497                label,
498                body: Box::new(for_stmt),
499            }
500            .into(),
501            None => for_stmt,
502        };
503
504        TryStmt {
505            span: DUMMY_SP,
506            block: BlockStmt {
507                span: DUMMY_SP,
508                stmts: vec![for_stmt],
509                ..Default::default()
510            },
511            handler: Some(CatchClause {
512                span: DUMMY_SP,
513                param: Some(quote_ident!("err").into()),
514                // _didIteratorError = true;
515                // _iteratorError = err;
516                body: BlockStmt {
517                    stmts: vec![
518                        // _didIteratorError = true;
519                        AssignExpr {
520                            span: DUMMY_SP,
521                            left: error_flag_ident.clone().into(),
522                            op: op!("="),
523                            right: true.into(),
524                        }
525                        .into_stmt(),
526                        // _iteratorError = err;
527                        AssignExpr {
528                            span: DUMMY_SP,
529                            left: error_ident.clone().into(),
530                            op: op!("="),
531                            right: Box::new(Expr::Ident(quote_ident!("err").into())),
532                        }
533                        .into_stmt(),
534                    ],
535                    ..Default::default()
536                },
537            }),
538            finalizer: Some(BlockStmt {
539                stmts: vec![make_finally_block(
540                    iterator_return,
541                    &normal_completion_ident,
542                    error_flag_ident,
543                    error_ident,
544                )],
545                ..Default::default()
546            }),
547        }
548        .into()
549    }
550}
551
552/// ```js
553///   try {
554///     if (!_iteratorNormalCompletion && _iterator.return != null) {
555///       _iterator.return();
556///     }
557///   } finally {
558///     if (_didIteratorError) {
559///       throw _iteratorError;
560///     }
561///   }
562/// ```
563#[tracing::instrument(level = "info", skip_all)]
564fn make_finally_block(
565    iterator_return: Box<Expr>,
566    normal_completion_ident: &Ident,
567    error_flag_ident: Ident,
568    error_ident: Ident,
569) -> Stmt {
570    TryStmt {
571        span: DUMMY_SP,
572        block: BlockStmt {
573            span: DUMMY_SP,
574            stmts: vec![
575                // if (!_iteratorNormalCompletion && _iterator.return !=
576                // null) {
577                //   _iterator.return();
578                // }
579                Stmt::If(IfStmt {
580                    span: DUMMY_SP,
581                    test: Box::new(Expr::Bin(BinExpr {
582                        span: DUMMY_SP,
583                        left: Box::new(Expr::Unary(UnaryExpr {
584                            span: DUMMY_SP,
585                            op: op!("!"),
586                            arg: Box::new(Expr::Ident(normal_completion_ident.clone())),
587                        })),
588                        op: op!("&&"),
589                        right: Box::new(Expr::Bin(BinExpr {
590                            span: DUMMY_SP,
591                            left: iterator_return.clone(),
592                            op: op!("!="),
593                            right: Null { span: DUMMY_SP }.into(),
594                        })),
595                    })),
596                    cons: Box::new(Stmt::Block(BlockStmt {
597                        span: DUMMY_SP,
598                        stmts: vec![CallExpr {
599                            span: DUMMY_SP,
600                            callee: iterator_return.as_callee(),
601                            args: Vec::new(),
602                            ..Default::default()
603                        }
604                        .into_stmt()],
605                        ..Default::default()
606                    })),
607                    alt: None,
608                }),
609            ],
610            ..Default::default()
611        },
612        handler: None,
613        finalizer: Some(BlockStmt {
614            stmts: vec![
615                // if (_didIteratorError) {
616                //   throw _iteratorError;
617                // }
618                Stmt::If(IfStmt {
619                    span: DUMMY_SP,
620                    test: Box::new(Expr::Ident(error_flag_ident)),
621                    cons: Box::new(Stmt::Block(BlockStmt {
622                        stmts: vec![Stmt::Throw(ThrowStmt {
623                            span: DUMMY_SP,
624                            arg: Box::new(Expr::Ident(error_ident)),
625                        })],
626                        ..Default::default()
627                    })),
628                    alt: None,
629                }),
630            ],
631            ..Default::default()
632        }),
633    }
634    .into()
635}
636
637impl Parallel for ForOf {
638    fn create(&self) -> Self {
639        ForOf {
640            c: self.c,
641            top_level_vars: Default::default(),
642        }
643    }
644
645    fn merge(&mut self, other: Self) {
646        self.top_level_vars.extend(other.top_level_vars);
647    }
648}
649
650#[swc_trace]
651impl ParExplode for ForOf {
652    fn after_one_stmt(&mut self, stmts: &mut Vec<Stmt>) {
653        // Add variable declaration
654        // e.g. var ref
655        if !self.top_level_vars.is_empty() {
656            stmts.push(
657                VarDecl {
658                    span: DUMMY_SP,
659                    kind: VarDeclKind::Var,
660                    decls: take(&mut self.top_level_vars),
661                    declare: false,
662                    ..Default::default()
663                }
664                .into(),
665            );
666        }
667    }
668
669    fn after_one_module_item(&mut self, stmts: &mut Vec<ModuleItem>) {
670        // Add variable declaration
671        // e.g. var ref
672        if !self.top_level_vars.is_empty() {
673            stmts.push(
674                VarDecl {
675                    span: DUMMY_SP,
676                    kind: VarDeclKind::Var,
677                    decls: take(&mut self.top_level_vars),
678                    declare: false,
679                    ..Default::default()
680                }
681                .into(),
682            );
683        }
684    }
685}
686
687#[swc_trace]
688#[parallel(explode)]
689impl VisitMut for ForOf {
690    noop_visit_mut_type!(fail);
691
692    fn visit_mut_stmt(&mut self, s: &mut Stmt) {
693        match s {
694            Stmt::Labeled(LabeledStmt { label, body, .. }) => {
695                // Handle label
696                match &mut **body {
697                    Stmt::ForOf(stmt) => {
698                        stmt.visit_mut_children_with(self);
699
700                        *s = self.fold_for_stmt(Some(label.clone()), stmt.take());
701                    }
702                    _ => {
703                        body.visit_mut_with(self);
704                    }
705                }
706            }
707            Stmt::ForOf(stmt) => {
708                stmt.visit_mut_children_with(self);
709
710                *s = self.fold_for_stmt(None, stmt.take())
711            }
712            _ => s.visit_mut_children_with(self),
713        }
714    }
715}