Repository: swc-project/swc (rust)

Issue:
preset_env with bugfixes for Safari 15 messes up syntax contexts
Running preset_env for Safari 15 and bugfixes enabled messes up syntax contexts (the example adds the context to each identifier, but the actual usecase renamed variables incorrectly)

Version: cfe6e5fe1bb24fd5fe7e7c7f232100fd3cc2ad7f

### Input code

```js
export default function _typeof() {
  if (Date.now() > 0) {
    _typeof = function _typeof() {
      console.log(0);
    };
  } else {
    _typeof = function _typeof() {
      console.log(2);
    };
  }

  return _typeof();
}
```

### Expected behaviour

```js
export default function _typeof_1() {
    if (Date_2.now_0() > 0) {
        _typeof_1 = function _typeof_3() {
            console_2.log_0(0);
        };
    } else {
        _typeof_1 = function _typeof_4() {
            console_2.log_0(2);
        };
    }
    return _typeof_1();
};
```

### Actual behaviour

```js
export default function _typeof_1(obj_2) {
    if (obj_2 != null) {
        __typeof_0 = function _typeof_3(obj_3) { // <-- left hand side should be _typeof_1
            console_4.log_0(0);
        };
    } else {
        __typeof_0 = function _typeof_5(obj_5) { // <-- left hand side should be _typeof_1
            console_4.log_0(2);
        };
    }
    return __typeof_0(obj_2);
};
```

### Reproduction

```rust
use swc::{self, config::Options};
use swc_atoms::JsWord;
use swc_common::{
    chain, comments::SingleThreadedComments, sync::Lrc, FileName, Globals, Mark, SourceMap,
    SyntaxContext, DUMMY_SP,
};
use swc_ecma_ast::{
    Decl, DefaultDecl, ExportDefaultDecl, Ident, ImportSpecifier, Invalid, Module, ModuleDecl,
    ModuleItem,
};
use swc_ecma_codegen::{text_writer::JsWriter, Config, Emitter};
use swc_ecma_parser::{lexer::Lexer, EsConfig, PResult, Parser, StringInput, Syntax, TsConfig};
use swc_ecma_preset_env::{preset_env, BrowserData, Mode, Targets, Version};
use swc_ecma_transforms::{
    compat::reserved_words::reserved_words,
    fixer,
    fixer::paren_remover,
    helpers, hygiene,
    optimization::simplify::{dead_branch_remover, expr_simplifier},
    react, resolver,
};
use swc_ecma_transforms_compat::es2015;
use swc_ecma_visit::{Fold, FoldWith};

fn main() {
    let cm = Lrc::<SourceMap>::default();
    let src = r#"
    export default function _typeof() {
        if (Date.now() > 0) {
          _typeof = function _typeof() {
            console.log(0);
          };
        } else {
          _typeof = function _typeof() {
            console.log(2);
          };
        }
      
        return _typeof();
      }
"#;
    let (module, comments) = parse(src, "test.js", &cm).unwrap();

    swc_common::GLOBALS.set(&Globals::new(), || {
        helpers::HELPERS.set(&helpers::Helpers::default(), || {
            let unresolved_mark = Mark::fresh(Mark::root());
            let global_mark = Mark::fresh(Mark::root());
            let module = module.fold_with(&mut resolver(unresolved_mark, global_mark, false));

            let module = module.fold_with(&mut preset_env(
                global_mark,
                Some(&comments),
                swc_ecma_preset_env::Config {
                    targets: Some(Targets::Versions(BrowserData {
                        safari: Some(Version {
                            major: 15,
                            minor: 1,
                            patch: 0,
                        }),
                        ..Default::default()
                    })),
                    bugfixes: true,
                    ..Default::default()
                },
                Default::default(),
            ));

            let module = module.fold_with(&mut Test {});

            let module = module.fold_with(&mut chain!(
                reserved_words(),
                hygiene(),
                fixer(Some(&comments))
            ));

            let code = emit(&module, &comments, cm);
            println!("{}", code);
        });
    });
}

struct Test {
}
impl Fold for Test {
    fn fold_ident(&mut self, node: Ident) -> Ident {
        let new_name: JsWord = format!("{}_{}", node.sym, node.span.ctxt().as_u32()).into();
        Ident::new(new_name, node.span)
    }
}

fn parse(
    code: &str,
    filename: &str,
    cm: &Lrc<SourceMap>,
) -> PResult<(Module, SingleThreadedComments)> {
    let source_file = cm.new_source_file(FileName::Real(filename.into()), code.into());
    let comments = SingleThreadedComments::default();

    let lexer = Lexer::new(
        Syntax::Es(EsConfig {
            jsx: true,
            ..Default::default()
        }),
        Default::default(),
        StringInput::from(&*source_file),
        Some(&comments),
    );
    let mut parser = Parser::new_from(lexer);
    match parser.parse_module() {
        Err(err) => Err(err),
        Ok(module) => Ok((module, comments)),
    }
}

fn emit(module: &Module, comments: &SingleThreadedComments, cm: Lrc<SourceMap>) -> String {
    let mut buf = vec![];
    {
        let writer = Box::new(JsWriter::new(cm.clone(), "\n", &mut buf, None));
        let config = Config { minify: false };
        let mut emitter = Emitter {
            cfg: config,
            comments: Some(&comments),
            cm,
            wr: writer,
        };
        emitter.emit_module(&module).unwrap();
    }

    String::from_utf8(buf).unwrap()
}
```

Pull request:
fix(es/bugfixes): Fix handling of syntax context
**Description:**

**Related issue (if exists):**

 - Closes #4488

Patch:
diff --git a/crates/swc_ecma_transforms_compat/src/bugfixes/async_arrows_in_class.rs b/crates/swc_ecma_transforms_compat/src/bugfixes/async_arrows_in_class.rs
index 131b09afbc..d786dea104 100644
--- a/crates/swc_ecma_transforms_compat/src/bugfixes/async_arrows_in_class.rs
+++ b/crates/swc_ecma_transforms_compat/src/bugfixes/async_arrows_in_class.rs
@@ -98,13 +98,22 @@ impl Fold for AsyncArrowsInClass {
 
 #[cfg(test)]
 mod tests {
+    use swc_common::{chain, Mark};
+    use swc_ecma_transforms_base::resolver;
     use swc_ecma_transforms_testing::test;
 
     use super::*;
 
+    fn tr() -> impl Fold {
+        chain!(
+            resolver(Mark::new(), Mark::new(), false),
+            async_arrows_in_class()
+        )
+    }
+
     test!(
         ::swc_ecma_parser::Syntax::default(),
-        |_| async_arrows_in_class(),
+        |_| tr(),
         async_arrows,
         r#"
         class Foo {
@@ -131,7 +140,7 @@ mod tests {
 
     test!(
         ::swc_ecma_parser::Syntax::default(),
-        |_| async_arrows_in_class(),
+        |_| tr(),
         callback,
         r#"
         class Foo {
@@ -151,7 +160,7 @@ mod tests {
 
     test!(
         ::swc_ecma_parser::Syntax::default(),
-        |_| async_arrows_in_class(),
+        |_| tr(),
         this,
         r#"
         class Foo {
@@ -175,7 +184,7 @@ mod tests {
 
     test!(
         ::swc_ecma_parser::Syntax::default(),
-        |_| async_arrows_in_class(),
+        |_| tr(),
         non_async_arrow,
         r#"
         class Foo {
@@ -193,7 +202,7 @@ mod tests {
 
     test!(
         ::swc_ecma_parser::Syntax::default(),
-        |_| async_arrows_in_class(),
+        |_| tr(),
         non_class_async_arrow,
         "let x = async () => await 1;",
         "let x = async () => await 1;"
diff --git a/crates/swc_ecma_transforms_compat/src/bugfixes/edge_default_param.rs b/crates/swc_ecma_transforms_compat/src/bugfixes/edge_default_param.rs
index ebef63275e..adb8d43a5d 100644
--- a/crates/swc_ecma_transforms_compat/src/bugfixes/edge_default_param.rs
+++ b/crates/swc_ecma_transforms_compat/src/bugfixes/edge_default_param.rs
@@ -61,13 +61,22 @@ impl VisitMut for EdgeDefaultParam {
 
 #[cfg(test)]
 mod tests {
+    use swc_common::{chain, Mark};
+    use swc_ecma_transforms_base::resolver;
     use swc_ecma_transforms_testing::test;
 
     use super::*;
 
+    fn tr() -> impl Fold {
+        chain!(
+            resolver(Mark::new(), Mark::new(), false),
+            edge_default_param()
+        )
+    }
+
     test!(
         ::swc_ecma_parser::Syntax::default(),
-        |_| edge_default_param(),
+        |_| tr(),
         destructured_default_value,
         "const f = ({ a = 1 }) => a;",
         "const f = ({ a: a = 1 }) => a;"
@@ -75,7 +84,7 @@ mod tests {
 
     test!(
         ::swc_ecma_parser::Syntax::default(),
-        |_| edge_default_param(),
+        |_| tr(),
         destructured_no_default_value,
         "const f = ({ a }) => a;",
         "const f = ({ a }) => a;"
@@ -83,7 +92,7 @@ mod tests {
 
     test!(
         ::swc_ecma_parser::Syntax::default(),
-        |_| edge_default_param(),
+        |_| tr(),
         nested_default_value,
         "const f = ({ a: { b = 1 } }) => [a, b];",
         "const f = ({ a: { b: b = 1 } }) => [a, b];"
@@ -91,7 +100,7 @@ mod tests {
 
     test!(
         ::swc_ecma_parser::Syntax::default(),
-        |_| edge_default_param(),
+        |_| tr(),
         non_arguments,
         "const f = () => { const { a = 1 } = {}; };",
         "const f = () => { const { a = 1 } = {}; };"
diff --git a/crates/swc_ecma_transforms_compat/src/bugfixes/safari_id_destructuring_collision_in_function_expression.rs b/crates/swc_ecma_transforms_compat/src/bugfixes/safari_id_destructuring_collision_in_function_expression.rs
index a404dc2302..3675b1da18 100644
--- a/crates/swc_ecma_transforms_compat/src/bugfixes/safari_id_destructuring_collision_in_function_expression.rs
+++ b/crates/swc_ecma_transforms_compat/src/bugfixes/safari_id_destructuring_collision_in_function_expression.rs
@@ -1,8 +1,7 @@
+use std::collections::HashMap;
+
 use swc_atoms::JsWord;
-use swc_common::{
-    collections::{AHashMap, AHashSet},
-    Span,
-};
+use swc_common::{collections::AHashSet, Span};
 use swc_ecma_ast::*;
 use swc_ecma_transforms_base::hygiene::rename;
 use swc_ecma_visit::{as_folder, noop_visit_mut_type, Fold, VisitMut, VisitMutWith};
@@ -17,35 +16,52 @@ pub fn safari_id_destructuring_collision_in_function_expression() -> impl Fold +
 struct SafariIdDestructuringCollisionInFunctionExpression {
     fn_expr_name: JsWord,
     destructured_id_span: Option<Span>,
-    other_ident_syms: AHashSet<JsWord>,
+    other_ident_symbols: AHashSet<JsWord>,
     in_body: bool,
 }
 
+impl SafariIdDestructuringCollisionInFunctionExpression {
+    fn visit_mut_pat_id(&mut self, id: &Ident) {
+        if !self.in_body && self.fn_expr_name == id.sym {
+            self.destructured_id_span = Some(id.span);
+        } else {
+            self.other_ident_symbols.insert(id.sym.clone());
+        }
+    }
+}
+
 #[swc_trace]
 impl VisitMut for SafariIdDestructuringCollisionInFunctionExpression {
     noop_visit_mut_type!();
 
+    fn visit_mut_assign_pat_prop(&mut self, n: &mut AssignPatProp) {
+        self.visit_mut_pat_id(&n.key);
+
+        n.value.visit_mut_with(self);
+    }
+
     fn visit_mut_binding_ident(&mut self, binding_ident: &mut BindingIdent) {
-        if !self.in_body && self.fn_expr_name.eq(&binding_ident.id.sym) {
-            self.destructured_id_span = Some(binding_ident.id.span);
-        } else {
-            self.other_ident_syms.insert(binding_ident.id.sym.clone());
-        }
+        self.visit_mut_pat_id(&binding_ident.id)
     }
 
     fn visit_mut_fn_expr(&mut self, n: &mut FnExpr) {
         if let Some(ident) = &n.ident {
+            let old_span = self.destructured_id_span.take();
+            let old_fn_expr_name = self.fn_expr_name.clone();
+            let old_in_body = self.in_body;
+
             self.fn_expr_name = ident.sym.clone();
+            self.in_body = false;
             n.function.params.visit_mut_children_with(self);
             self.in_body = true;
             n.function.body.visit_mut_children_with(self);
-            self.in_body = false;
-            if let Some(id_span) = &self.destructured_id_span {
-                let mut rename_map = AHashMap::default();
+
+            if let Some(id_span) = self.destructured_id_span.take() {
+                let mut rename_map = HashMap::default();
                 let new_id: JsWord = {
                     let mut id_value: JsWord = format!("_{}", self.fn_expr_name).into();
                     let mut count = 0;
-                    while self.other_ident_syms.contains(&id_value) {
+                    while self.other_ident_symbols.contains(&id_value) {
                         count += 1;
                         id_value = format!("_{}{}", self.fn_expr_name, count).into();
                     }
@@ -55,25 +71,51 @@ impl VisitMut for SafariIdDestructuringCollisionInFunctionExpression {
                 rename_map.insert(id, new_id);
                 n.function.visit_mut_children_with(&mut rename(&rename_map));
             }
+
+            self.in_body = old_in_body;
+            self.fn_expr_name = old_fn_expr_name;
+            self.destructured_id_span = old_span;
         }
     }
 
     fn visit_mut_ident(&mut self, ident: &mut Ident) {
-        if self.in_body && !self.fn_expr_name.eq(&ident.sym) {
-            self.other_ident_syms.insert(ident.sym.clone());
+        if self.in_body && self.fn_expr_name != ident.sym {
+            self.other_ident_symbols.insert(ident.sym.clone());
+        }
+    }
+
+    fn visit_mut_member_prop(&mut self, p: &mut MemberProp) {
+        if let MemberProp::Computed(..) = p {
+            p.visit_mut_children_with(self)
+        }
+    }
+
+    fn visit_mut_prop_name(&mut self, p: &mut PropName) {
+        if let PropName::Computed(..) = p {
+            p.visit_mut_children_with(self)
         }
     }
 }
 
 #[cfg(test)]
 mod tests {
-    use swc_ecma_transforms_testing::test;
+    use swc_common::{chain, Mark};
+    use swc_ecma_parser::Syntax;
+    use swc_ecma_transforms_base::resolver;
+    use swc_ecma_transforms_testing::{test, HygieneTester};
 
     use super::*;
 
+    fn tr() -> impl Fold {
+        chain!(
+            resolver(Mark::new(), Mark::new(), false),
+            safari_id_destructuring_collision_in_function_expression()
+        )
+    }
+
     test!(
-        ::swc_ecma_parser::Syntax::default(),
-        |_| safari_id_destructuring_collision_in_function_expression(),
+        Syntax::default(),
+        |_| tr(),
         basic,
         "(function a ([a]) { a });
          (function a({ ...a }) { a });
@@ -84,8 +126,8 @@ mod tests {
     );
 
     test!(
-        ::swc_ecma_parser::Syntax::default(),
-        |_| safari_id_destructuring_collision_in_function_expression(),
+        Syntax::default(),
+        |_| tr(),
         avoid_collision_1,
         "(function a([a, _a]) { a + _a })",
         "(function a([_a1, _a]) {
@@ -94,24 +136,24 @@ mod tests {
     );
 
     test!(
-        ::swc_ecma_parser::Syntax::default(),
-        |_| safari_id_destructuring_collision_in_function_expression(),
+        Syntax::default(),
+        |_| tr(),
         use_duplicated_id,
         "(function a([a]) { console.log(_a); })",
         "(function a([_a1]) { console.log(_a); });"
     );
 
     test!(
-        ::swc_ecma_parser::Syntax::default(),
-        |_| safari_id_destructuring_collision_in_function_expression(),
+        Syntax::default(),
+        |_| tr(),
         avoid_collision_2,
         "(function _a([_a]) { console.log(_a); })",
         "(function _a([__a]) { console.log(__a); });"
     );
 
     test!(
-        ::swc_ecma_parser::Syntax::default(),
-        |_| safari_id_destructuring_collision_in_function_expression(),
+        Syntax::default(),
+        |_| tr(),
         assign_outside_var,
         "let _a;
         (function a([a]) {
@@ -124,10 +166,45 @@ mod tests {
     );
 
     test!(
-        ::swc_ecma_parser::Syntax::default(),
-        |_| safari_id_destructuring_collision_in_function_expression(),
+        Syntax::default(),
+        |_| tr(),
         assignment_expr_in_default_value,
         "(function a([a = a = 3]) {})",
         "(function a([_a = _a = 3]) {})"
     );
+
+    test!(
+        Syntax::default(),
+        |_| chain!(tr(), HygieneTester),
+        issue_4488_1,
+        "
+        export default function _typeof() {
+            if (Date.now() > 0) {
+                _typeof = function _typeof() {
+                    console.log(0);
+                };
+            } else {
+                _typeof = function _typeof() {
+                    console.log(2);
+                };
+            }
+        
+            return _typeof();
+        }
+        ",
+        "
+        export default function _typeof__1() {
+            if (Date__2.now() > 0) {
+                _typeof__1 = function _typeof__3() {
+                    console__2.log(0);
+                };
+            } else {
+                _typeof__1 = function _typeof__4() {
+                    console__2.log(2);
+                };
+            }
+            return _typeof__1();
+        }
+        "
+    );
 }
diff --git a/crates/swc_ecma_transforms_compat/src/bugfixes/template_literal_caching.rs b/crates/swc_ecma_transforms_compat/src/bugfixes/template_literal_caching.rs
index 0cfd7ae94d..dd6eea874f 100644
--- a/crates/swc_ecma_transforms_compat/src/bugfixes/template_literal_caching.rs
+++ b/crates/swc_ecma_transforms_compat/src/bugfixes/template_literal_caching.rs
@@ -153,13 +153,22 @@ impl Fold for TemplateLiteralCaching {
 
 #[cfg(test)]
 mod tests {
+    use swc_common::{chain, Mark};
+    use swc_ecma_transforms_base::resolver;
     use swc_ecma_transforms_testing::test;
 
     use super::*;
 
+    fn tr() -> impl Fold {
+        chain!(
+            resolver(Mark::new(), Mark::new(), false),
+            template_literal_caching()
+        )
+    }
+
     test!(
         ::swc_ecma_parser::Syntax::default(),
-        |_| template_literal_caching(),
+        |_| tr(),
         single_tag,
         "t`a`;",
         r#"
@@ -170,7 +179,7 @@ mod tests {
 
     test!(
         ::swc_ecma_parser::Syntax::default(),
-        |_| template_literal_caching(),
+        |_| tr(),
         single_tag_empty,
         "x``;",
         r#"
@@ -181,7 +190,7 @@ mod tests {
 
     test!(
         ::swc_ecma_parser::Syntax::default(),
-        |_| template_literal_caching(),
+        |_| tr(),
         multiple_tags,
         r#"
         t`a`;
@@ -196,7 +205,7 @@ mod tests {
 
     test!(
         ::swc_ecma_parser::Syntax::default(),
-        |_| template_literal_caching(),
+        |_| tr(),
         function_scoped_tag,
         "const f = t => t`a`;",
         r#"
@@ -207,7 +216,7 @@ mod tests {
 
     test!(
         ::swc_ecma_parser::Syntax::default(),
-        |_| template_literal_caching(),
+        |_| tr(),
         dynamic_tag,
         "fn()``;",
         r#"
@@ -218,7 +227,7 @@ mod tests {
 
     test!(
         ::swc_ecma_parser::Syntax::default(),
-        |_| template_literal_caching(),
+        |_| tr(),
         dynamic_expressions,
         "const f = t => t`a${1}b${t}${[\"hello\"]}`;",
         r#"
@@ -229,7 +238,7 @@ mod tests {
 
     test!(
         ::swc_ecma_parser::Syntax::default(),
-        |_| template_literal_caching(),
+        |_| tr(),
         same_tag_safari_11,
         "x`a` === x`a`;",
         r#"
@@ -240,7 +249,7 @@ mod tests {
 
     test!(
         ::swc_ecma_parser::Syntax::default(),
-        |_| template_literal_caching(),
+        |_| tr(),
         shared_strings_safari_11,
         "x`a` === y`a`;",
         r#"
@@ -251,7 +260,7 @@ mod tests {
 
     test!(
         ::swc_ecma_parser::Syntax::default(),
-        |_| template_literal_caching(),
+        |_| tr(),
         template_literals,
         r#"
         `a`;
@@ -269,7 +278,7 @@ mod tests {
 
     test!(
         ::swc_ecma_parser::Syntax::default(),
-        |_| template_literal_caching(),
+        |_| tr(),
         prevent_tag_collision,
         r#"
         const _ = 1;
@@ -285,7 +294,7 @@ mod tests {
 
     test!(
         ::swc_ecma_parser::Syntax::default(),
-        |_| template_literal_caching(),
+        |_| tr(),
         block_scoped_tag,
         "for (let t of []) t`a`;",
         r#"


Test patch:
diff --git a/crates/swc_ecma_transforms_testing/src/lib.rs b/crates/swc_ecma_transforms_testing/src/lib.rs
index 66049f4e75..825bfe970d 100644
--- a/crates/swc_ecma_transforms_testing/src/lib.rs
+++ b/crates/swc_ecma_transforms_testing/src/lib.rs
@@ -616,6 +616,31 @@ impl VisitMut for Normalizer {
     }
 }
 
+/// Converts `foo#1` to `foo__1` so it can be verified by the test.
+pub struct HygieneTester;
+impl Fold for HygieneTester {
+    fn fold_ident(&mut self, ident: Ident) -> Ident {
+        Ident {
+            sym: format!("{}__{}", ident.sym, ident.span.ctxt.as_u32()).into(),
+            ..ident
+        }
+    }
+
+    fn fold_member_prop(&mut self, p: MemberProp) -> MemberProp {
+        match p {
+            MemberProp::Computed(..) => p.fold_children_with(self),
+            _ => p,
+        }
+    }
+
+    fn fold_prop_name(&mut self, p: PropName) -> PropName {
+        match p {
+            PropName::Computed(..) => p.fold_children_with(self),
+            _ => p,
+        }
+    }
+}
+
 pub struct HygieneVisualizer;
 impl Fold for HygieneVisualizer {
     fn fold_ident(&mut self, ident: Ident) -> Ident {