swc_ecma_compat_es2015/
for_of.rs

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