swc_ecma_compat_bugfixes/
safari_id_destructuring_collision_in_function_expression.rs

1use std::collections::HashMap;
2
3use rustc_hash::FxHashSet;
4use swc_atoms::{atom, Atom};
5use swc_common::SyntaxContext;
6use swc_ecma_ast::*;
7use swc_ecma_transforms_base::hygiene::rename;
8use swc_ecma_visit::{noop_visit_mut_type, visit_mut_pass, VisitMut, VisitMutWith};
9use swc_trace_macro::swc_trace;
10
11pub fn safari_id_destructuring_collision_in_function_expression() -> impl Pass {
12    visit_mut_pass(SafariIdDestructuringCollisionInFunctionExpression::default())
13}
14
15#[derive(Default, Clone)]
16struct SafariIdDestructuringCollisionInFunctionExpression {
17    fn_expr_name: Atom,
18    destructured_id_span: Option<SyntaxContext>,
19    other_ident_symbols: FxHashSet<Atom>,
20    in_body: bool,
21}
22
23impl SafariIdDestructuringCollisionInFunctionExpression {
24    fn visit_mut_pat_id(&mut self, id: &Ident) {
25        if !self.in_body && self.fn_expr_name == id.sym {
26            self.destructured_id_span = Some(id.ctxt);
27        } else {
28            self.other_ident_symbols.insert(id.sym.clone());
29        }
30    }
31}
32
33#[swc_trace]
34impl VisitMut for SafariIdDestructuringCollisionInFunctionExpression {
35    noop_visit_mut_type!(fail);
36
37    fn visit_mut_assign_pat_prop(&mut self, n: &mut AssignPatProp) {
38        self.visit_mut_pat_id(&Ident::from(&n.key));
39
40        n.value.visit_mut_with(self);
41    }
42
43    fn visit_mut_binding_ident(&mut self, binding_ident: &mut BindingIdent) {
44        self.visit_mut_pat_id(&Ident::from(&*binding_ident))
45    }
46
47    fn visit_mut_fn_expr(&mut self, n: &mut FnExpr) {
48        let old_in_body = self.in_body;
49        if let Some(ident) = &n.ident {
50            let old_span = self.destructured_id_span.take();
51            let old_fn_expr_name = self.fn_expr_name.clone();
52
53            self.fn_expr_name = ident.sym.clone();
54            self.in_body = false;
55            n.function.params.visit_mut_children_with(self);
56            self.in_body = true;
57            n.function.body.visit_mut_children_with(self);
58
59            if let Some(id_ctxt) = self.destructured_id_span.take() {
60                let mut rename_map = HashMap::default();
61                let new_id: Atom = {
62                    let mut id_value: Atom = format!("_{}", self.fn_expr_name).into();
63                    let mut count = 0;
64                    while self.other_ident_symbols.contains(&id_value) {
65                        count += 1;
66                        id_value = format!("_{}{}", self.fn_expr_name, count).into();
67                    }
68                    id_value
69                };
70                let id = (self.fn_expr_name.clone(), id_ctxt);
71                rename_map.insert(id, new_id);
72                n.function.visit_mut_children_with(&mut rename(&rename_map));
73            }
74
75            self.fn_expr_name = old_fn_expr_name;
76            self.destructured_id_span = old_span;
77        } else {
78            // fn_expr_name assgin empty to express that it is a non-ident-function
79            // Otherwise, it will be treated as a function with an ident name due to the
80            // previous function
81            self.fn_expr_name = atom!("");
82            self.in_body = false;
83            n.function.params.visit_mut_children_with(self);
84            self.in_body = true;
85            n.function.body.visit_mut_children_with(self);
86        }
87        self.in_body = old_in_body;
88    }
89
90    fn visit_mut_ident(&mut self, ident: &mut Ident) {
91        if self.in_body && self.fn_expr_name != ident.sym {
92            self.other_ident_symbols.insert(ident.sym.clone());
93        }
94    }
95
96    fn visit_mut_member_prop(&mut self, p: &mut MemberProp) {
97        if let MemberProp::Computed(..) = p {
98            p.visit_mut_children_with(self)
99        }
100    }
101
102    fn visit_mut_prop_name(&mut self, p: &mut PropName) {
103        if let PropName::Computed(..) = p {
104            p.visit_mut_children_with(self)
105        }
106    }
107}
108
109#[cfg(test)]
110mod tests {
111    use swc_common::Mark;
112    use swc_ecma_parser::Syntax;
113    use swc_ecma_transforms_base::resolver;
114    use swc_ecma_transforms_testing::{test, HygieneTester};
115    use swc_ecma_visit::fold_pass;
116
117    use super::*;
118
119    fn tr() -> impl Pass {
120        (
121            resolver(Mark::new(), Mark::new(), false),
122            safari_id_destructuring_collision_in_function_expression(),
123        )
124    }
125
126    test!(
127        Syntax::default(),
128        |_| tr(),
129        basic,
130        "(function a ([a]) { a });
131         (function a({ ...a }) { a });
132         (function a({ a }) { a });"
133    );
134
135    test!(
136        Syntax::default(),
137        |_| tr(),
138        avoid_collision_1,
139        "(function a([a, _a]) { a + _a })"
140    );
141
142    test!(
143        Syntax::default(),
144        |_| tr(),
145        issue_10242_1,
146        r#"
147[
148  {
149    37287: function (e, t, n) {
150      !function (t, n) {
151      }(0, (function () {
152        return function (e) {}([
153          function (e, t, n) {
154          o.fromDer = function (e, t) {
155            var a = function e(t, n, r, a) {
156              var d, f, v = function (e, t) {}(t, n);
157            }(e, e.length(), 0, t);
158          }
159          }
160        ])
161      }))
162    },
163    31922: function (e, t, n) {
164      var L = (0, w.Z)(function () {
165        var e = (0, i.Z)((0, o.Z)().mark((function e(t) {
166          return (0, o.Z)().wrap((function (e) {
167
168          }), e)
169        })));
170
171      }(), 1e3);
172      var $ = function (e) {
173        var _e = ye[0],
174          $e = function () {
175            var e = (0, i.Z)((0, o.Z)().mark((function e() {
176              return (0, o.Z)().wrap((function (e) {
177
178              }), e)
179            })));
180
181          }();
182      },
183        ee = $,
184
185        _e = n(36750);
186
187      var zt = function () {
188        var e = (0, i.Z)((0, o.Z)().mark((
189          function e(t) {
190            return (0, o.Z)().wrap((function (e) {
191            console.log(e);
192            console.log(_e);
193          }), e)
194        })));
195       
196      }();
197
198    }
199  }
200];
201"#
202    );
203
204    test!(
205        Syntax::default(),
206        |_| tr(),
207        use_duplicated_id,
208        "(function a([a]) { console.log(_a); })"
209    );
210
211    test!(
212        Syntax::default(),
213        |_| tr(),
214        avoid_collision_2,
215        "(function _a([_a]) { console.log(_a); })"
216    );
217
218    test!(
219        Syntax::default(),
220        |_| tr(),
221        assign_outside_var,
222        "let _a;
223        (function a([a]) {
224            _a = 3;
225        })"
226    );
227
228    test!(
229        Syntax::default(),
230        |_| tr(),
231        assignment_expr_in_default_value,
232        "(function a([a = a = 3]) {})"
233    );
234
235    test!(
236        Syntax::default(),
237        |_| (tr(), fold_pass(HygieneTester)),
238        issue_4488_1,
239        "
240        export default function _type_of() {
241            if (Date.now() > 0) {
242                _type_of = function _type_of() {
243                    console.log(0);
244                };
245            } else {
246                _type_of = function _type_of() {
247                    console.log(2);
248                };
249            }
250        
251            return _type_of();
252        }
253        "
254    );
255
256    test!(
257        Syntax::default(),
258        |_| tr(),
259        in_nameless_fn,
260        "(function () {
261          (function a(a) {a});
262        });
263        "
264    );
265
266    test!(
267        Syntax::default(),
268        |_| tr(),
269        in_nameless_fn_multiple,
270        "// nameless iife
271        var x = function() {
272            // not transformed
273            var b = function a(a) {
274                return a;
275            };
276        }();
277        // nameless iife
278        var x = function x() {
279            var b = function a(_a) {
280                return _a;
281            };
282        }();
283        // nameless function
284        (function() {
285            // not transformed
286            var b = function a(a) {
287                return a;
288            };
289        });
290        // named function
291        (function x() {
292            var b = function a(_a) {
293                return _a;
294            };
295        });"
296    );
297}