swc_ecma_compat_bugfixes/
async_arrows_in_class.rs

1use swc_common::{util::take::Take, Mark, DUMMY_SP};
2use swc_ecma_ast::*;
3use swc_ecma_compat_es2015::arrow;
4use swc_ecma_utils::prepend_stmt;
5use swc_ecma_visit::{fold_pass, standard_only_fold, Fold, FoldWith, InjectVars, VisitMutWith};
6use swc_trace_macro::swc_trace;
7
8/// A bugfix pass for Safari 10.3.
9///
10/// Safari 10.3 had an issue where async arrow function expressions within any
11/// class method would throw. After an initial fix, any references to the
12/// instance via `this` within those methods would also throw. This is fixed by
13/// converting arrow functions in class methods into equivalent function
14/// expressions. See https://bugs.webkit.org/show_bug.cgi?id=166879
15pub fn async_arrows_in_class(unresolved_mark: Mark) -> impl Pass {
16    fold_pass(AsyncArrowsInClass {
17        unresolved_mark,
18        ..Default::default()
19    })
20}
21#[derive(Default, Clone)]
22struct AsyncArrowsInClass {
23    in_class_method: bool,
24    unresolved_mark: Mark,
25    vars: Vec<VarDeclarator>,
26}
27
28/// TODO: VisitMut
29#[swc_trace]
30impl Fold for AsyncArrowsInClass {
31    standard_only_fold!();
32
33    fn fold_class_method(&mut self, n: ClassMethod) -> ClassMethod {
34        self.in_class_method = true;
35        let res = n.fold_children_with(self);
36        self.in_class_method = false;
37        res
38    }
39
40    fn fold_constructor(&mut self, n: Constructor) -> Constructor {
41        self.in_class_method = true;
42        let res = n.fold_children_with(self);
43        self.in_class_method = false;
44        res
45    }
46
47    fn fold_expr(&mut self, n: Expr) -> Expr {
48        let mut n = n.fold_children_with(self);
49        if !self.in_class_method {
50            return n;
51        }
52
53        match n {
54            Expr::Arrow(ref a) => {
55                if a.is_async {
56                    let mut v = arrow(self.unresolved_mark);
57                    n.visit_mut_with(&mut v);
58                    self.vars.extend(v.take_vars());
59                    n
60                } else {
61                    n
62                }
63            }
64            _ => n,
65        }
66    }
67
68    fn fold_module_items(&mut self, stmts: Vec<ModuleItem>) -> Vec<ModuleItem> {
69        let mut stmts = stmts.fold_children_with(self);
70        if !self.vars.is_empty() {
71            prepend_stmt(
72                &mut stmts,
73                VarDecl {
74                    span: DUMMY_SP,
75                    kind: VarDeclKind::Var,
76                    declare: false,
77                    decls: self.vars.take(),
78                    ..Default::default()
79                }
80                .into(),
81            );
82        }
83
84        stmts
85    }
86
87    fn fold_stmts(&mut self, stmts: Vec<Stmt>) -> Vec<Stmt> {
88        let mut stmts = stmts.fold_children_with(self);
89        if !self.vars.is_empty() {
90            prepend_stmt(
91                &mut stmts,
92                VarDecl {
93                    span: DUMMY_SP,
94                    kind: VarDeclKind::Var,
95                    declare: false,
96                    decls: self.vars.take(),
97                    ..Default::default()
98                }
99                .into(),
100            );
101        }
102
103        stmts
104    }
105}
106
107#[cfg(test)]
108mod tests {
109    use swc_ecma_transforms_base::resolver;
110    use swc_ecma_transforms_testing::test;
111
112    use super::*;
113
114    fn tr() -> impl Pass {
115        let unresolved = Mark::new();
116        (
117            resolver(unresolved, Mark::new(), false),
118            async_arrows_in_class(unresolved),
119        )
120    }
121
122    test!(
123        ::swc_ecma_parser::Syntax::default(),
124        |_| tr(),
125        async_arrows,
126        r#"
127        class Foo {
128            constructor() {
129                this.x = async () => await 1;
130            }
131            bar() {
132                (async () => { })();
133            }
134        }"#
135    );
136
137    test!(
138        ::swc_ecma_parser::Syntax::default(),
139        |_| tr(),
140        callback,
141        r#"
142        class Foo {
143            foo() {
144                bar(async () => await 1);
145            }
146        }"#
147    );
148
149    test!(
150        ::swc_ecma_parser::Syntax::default(),
151        |_| tr(),
152        this,
153        r#"
154        class Foo {
155            constructor() {
156                this.x = () => async () => await this;
157            }
158        }"#
159    );
160
161    // TODO: handle arguments and super. This isn't handled in general for arrow
162    // functions atm...
163
164    test!(
165        ::swc_ecma_parser::Syntax::default(),
166        |_| tr(),
167        non_async_arrow,
168        r#"
169        class Foo {
170            constructor() {
171                this.x = () => {};
172            }
173        }"#
174    );
175
176    test!(
177        ::swc_ecma_parser::Syntax::default(),
178        |_| tr(),
179        non_class_async_arrow,
180        "let x = async () => await 1;"
181    );
182}