swc_ecma_compat_bugfixes/
safari_id_destructuring_collision_in_function_expression.rs1use 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 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}