Repository: swc-project/swc (rust)

Issue:
minifier: invalid handling of function scope
### Describe the bug

According to the [MDN: function](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/function#description)
> Like let, in strict mode, [function declarations are scoped to the most closely containing block](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/function#block-level_function_declaration).

However, It seems that swc handle function scope incorrectly.

### Input code

```typescript
const k = (() => {
  function x() { console.log("hi") }
  {
    function x() { console.log("merong") }
  }
  return x;
})();
k();
```


### Config

_No response_

### Playground link (or link to the minimal reproduction)

https://play.swc.rs/?version=1.4.13&code=H4sIAAAAAAAAA0utKMgvKlFIK81LLsnMz1PI1tBUUKjmUkCIVABFqhWS8%2FOK83NS9XLy0zWUMjKVNBVquSAK8SvNTS3Kz0uHKgfhotSS0iKgUmuuWi6gZdYAlBBbpoEAAAA%3D&config=H4sIAAAAAAAAA32UO3LjMAyG%2B5zCozrFjostcoDtcgYOTYIyvXxoCNCxJuO7L0TJj40hdRI%2B%2FAAJgPh%2B2%2B26E5ruY%2FfNn%2Fwz6IJQ7v9swTGRvrClAxM1muIH6t5v9IQTcjogNNN1Jh3p0gM1Fe5%2F7feLogs5I9wUiy365N34nNPkOBRAfLKxlUPWCInwf%2F3CSv6aAJX6bD%2FkHECnDaI0Kp8IeihSYJND0AOCOusiRJlOqovHLKWYYCWwaih5EHmynnxOnPOVWtBWmWxBQL6AIX8GSca5WJaQryfcp2ELh9r3rc8%2F1HDWoWoScsKltYRPK0Q9Zo%2BkXE1SCWe4UoMZLsX9qfROFaBa0qvulH1a6clfAK5A0IhJR5DiNg%2FH87SmdptKnxyPLI0C5%2FmWbpmg56Iq751Q2akyUMhL3Sxgq4GpskY6zoJXyofeggLneFaE0PjlyRylpDQOkJ0AuL%2FaSVM1A3V%2FhSt8ehAb%2BA%2FfkuQBWzyipuM6xTEecthIEIGO2W44cCsor%2BPCW%2BIyrPOaLPBogBVdKjbwugT4AVBWoe3Ll9ng58ERVR%2Fy4bEmFofrfQ9HnfrHe59X8dvi0MVsa4PLkp%2F6O6%2Fm393D6baF7wfvPH7elC3p9R%2BoYzQdMAYAAA%3D%3D

### SWC Info output

_No response_

### Expected behavior

```javascript
let k = (()=>{
    function x() {
        console.log("hi");
    }
    return x;
})();
k();
```
stdout: "hi"

### Actual behavior

```javascript
let k = (()=>{
    function x() {
        console.log("hi");
    }
    function x() {
        console.log("merong");
    }
    return x;
})();
k();
```
stdout: "merong"

### Version

1.4.13

### Additional context

I don't know how swc handles strict mode script internally, but inserting pragma inside the body of the anonymous function resolves this. (sadly, prepending pragma in a first line of the script file or changing source type to "module" and mark k as a exported variable or function doesn't work)

Pull request:
fix(es/resolver): Correctly check strict mode
<!-- Note: CI script will automatically rebase your PR so please do not rebase unless required -->

<!-- Thanks for submitting a pull request! Please provide enough information so that others can review your pull request. -->

**Description:**

<!-- Explain the **motivation** for making this change. What existing problem does the pull request solve? -->

**BREAKING CHANGE:**

<!--
If this PR introduces a breaking change, it must contain a notice for it to be included in the CHANGELOG. Add description or remove entirely if not breaking.

You may need to update `MIGRATION.md` for the breaking changes.
-->

**Related issue (if exists):**
Closes #8842 

Patch:
diff --git a/crates/swc_ecma_transforms_base/src/resolver/mod.rs b/crates/swc_ecma_transforms_base/src/resolver/mod.rs
index ee704fb36a..5b3ae1cd17 100644
--- a/crates/swc_ecma_transforms_base/src/resolver/mod.rs
+++ b/crates/swc_ecma_transforms_base/src/resolver/mod.rs
@@ -558,11 +558,14 @@ impl<'a> VisitMut for Resolver<'a> {
                     child.mark_block(&mut s.span);
 
                     let old_strict_mode = child.strict_mode;
-                    child.strict_mode = s
-                        .stmts
-                        .first()
-                        .map(|stmt| stmt.is_use_strict())
-                        .unwrap_or(false);
+
+                    if !child.strict_mode {
+                        child.strict_mode = s
+                            .stmts
+                            .first()
+                            .map(|stmt| stmt.is_use_strict())
+                            .unwrap_or(false);
+                    }
                     // Prevent creating new scope.
                     s.stmts.visit_mut_with(child);
                     child.strict_mode = old_strict_mode;
@@ -901,11 +904,13 @@ impl<'a> VisitMut for Resolver<'a> {
             Some(body) => {
                 self.mark_block(&mut body.span);
                 let old_strict_mode = self.strict_mode;
-                self.strict_mode = body
-                    .stmts
-                    .first()
-                    .map(|stmt| stmt.is_use_strict())
-                    .unwrap_or(false);
+                if !self.strict_mode {
+                    self.strict_mode = body
+                        .stmts
+                        .first()
+                        .map(|stmt| stmt.is_use_strict())
+                        .unwrap_or(false);
+                }
                 // Prevent creating new scope.
                 body.visit_mut_children_with(self);
                 self.strict_mode = old_strict_mode;


Test patch:
diff --git a/crates/swc_ecma_minifier/tests/terser/compress/dead_code/dead_code_2_should_warn_strict/output.js b/crates/swc_ecma_minifier/tests/terser/compress/dead_code/dead_code_2_should_warn_strict/output.js
index dc2a7837ae..32add590e3 100644
--- a/crates/swc_ecma_minifier/tests/terser/compress/dead_code/dead_code_2_should_warn_strict/output.js
+++ b/crates/swc_ecma_minifier/tests/terser/compress/dead_code/dead_code_2_should_warn_strict/output.js
@@ -1,6 +1,6 @@
 "use strict";
 function f() {
-    var x, g;
+    var x, g1;
     g();
     x = 10;
     throw Error("foo");
diff --git a/crates/swc_ecma_minifier/tests/terser/compress/dead_code/dead_code_2_should_warn_strict/output.mangleOnly.js b/crates/swc_ecma_minifier/tests/terser/compress/dead_code/dead_code_2_should_warn_strict/output.mangleOnly.js
index 2800760b4f..e6063df3a8 100644
--- a/crates/swc_ecma_minifier/tests/terser/compress/dead_code/dead_code_2_should_warn_strict/output.mangleOnly.js
+++ b/crates/swc_ecma_minifier/tests/terser/compress/dead_code/dead_code_2_should_warn_strict/output.mangleOnly.js
@@ -1,6 +1,6 @@
 "use strict";
 function n() {
-    o();
+    g();
     n = 10;
     throw new Error("foo");
     if (n) {
diff --git a/crates/swc_ecma_transforms_base/tests/fixture.rs b/crates/swc_ecma_transforms_base/tests/fixture.rs
index 6b79194a1f..d5ef8a4328 100644
--- a/crates/swc_ecma_transforms_base/tests/fixture.rs
+++ b/crates/swc_ecma_transforms_base/tests/fixture.rs
@@ -10,7 +10,7 @@ use swc_ecma_visit::{
 };
 use testing::{fixture, run_test2, NormalizedOutput};
 
-pub fn print(cm: Lrc<SourceMap>, module: &Module) -> String {
+pub fn print(cm: Lrc<SourceMap>, program: &Program) -> String {
     let mut buf = vec![];
     {
         let mut emitter = Emitter {
@@ -23,7 +23,7 @@ pub fn print(cm: Lrc<SourceMap>, module: &Module) -> String {
         };
 
         // println!("Emitting: {:?}", module);
-        emitter.emit_module(module).unwrap();
+        emitter.emit_program(program).unwrap();
     }
 
     let s = String::from_utf8_lossy(&buf);
@@ -47,15 +47,15 @@ where
         let lexer = Lexer::new(syntax, EsVersion::latest(), StringInput::from(&*fm), None);
         let mut parser = Parser::new_from(lexer);
 
-        let module = parser
-            .parse_module()
+        let program = parser
+            .parse_program()
             .map_err(|err| err.into_diagnostic(&handler).emit())?;
 
         let mut folder = op();
 
-        let module = module.fold_with(&mut folder);
+        let program = program.fold_with(&mut folder);
 
-        let actual = print(cm, &module);
+        let actual = print(cm, &program);
         let actual = NormalizedOutput::from(actual);
 
         actual.compare_to_file(&output).unwrap();
diff --git a/crates/swc_ecma_transforms_base/tests/resolver/function/block/input.js b/crates/swc_ecma_transforms_base/tests/resolver/function/block/input.js
new file mode 100644
index 0000000000..5f97fb21ee
--- /dev/null
+++ b/crates/swc_ecma_transforms_base/tests/resolver/function/block/input.js
@@ -0,0 +1,12 @@
+export function k() {
+    function x() {
+        console.log("hi");
+    }
+    {
+        function x() {
+            console.log("merong");
+        }
+    }
+    return x;
+}
+k();
diff --git a/crates/swc_ecma_transforms_base/tests/resolver/function/block/output.js b/crates/swc_ecma_transforms_base/tests/resolver/function/block/output.js
new file mode 100644
index 0000000000..ce93ac3cf1
--- /dev/null
+++ b/crates/swc_ecma_transforms_base/tests/resolver/function/block/output.js
@@ -0,0 +1,12 @@
+export function k__2() {
+    function x__3() {
+        console.log("hi");
+    }
+    {
+        function x__5() {
+            console.log("merong");
+        }
+    }
+    return x__3;
+}
+k__2();
diff --git a/crates/swc_ecma_transforms_base/tests/resolver/issues/6310/output.js b/crates/swc_ecma_transforms_base/tests/resolver/issues/6310/output.js
index ce7f15e220..fd64c79f66 100644
--- a/crates/swc_ecma_transforms_base/tests/resolver/issues/6310/output.js
+++ b/crates/swc_ecma_transforms_base/tests/resolver/issues/6310/output.js
@@ -1,4 +1,4 @@
 switch(0){
-    case x__3:
-        function x__3() {}
+    case x__2:
+        function x__2() {}
 }
diff --git a/crates/swc_ecma_transforms_base/tests/resolver/issues/7685/output.js b/crates/swc_ecma_transforms_base/tests/resolver/issues/7685/output.js
index fcfeaf2b43..a31e62c38b 100644
--- a/crates/swc_ecma_transforms_base/tests/resolver/issues/7685/output.js
+++ b/crates/swc_ecma_transforms_base/tests/resolver/issues/7685/output.js
@@ -2,4 +2,4 @@ var NaN__2 = 1;
 {
     let NaN__3 = 1;
     console.log(NaN__3);
-}console.log(NaN__2);
+}console.log(NaN);
diff --git a/crates/swc_ecma_transforms_base/tests/resolver/minifier/10/output.js b/crates/swc_ecma_transforms_base/tests/resolver/minifier/10/output.js
index 1c7a9c4c44..ef647e6cd5 100644
--- a/crates/swc_ecma_transforms_base/tests/resolver/minifier/10/output.js
+++ b/crates/swc_ecma_transforms_base/tests/resolver/minifier/10/output.js
@@ -1,2 +1,2 @@
 var NaN__2;
-console.log(NaN__2.toString());
+console.log(NaN.toString());
diff --git a/crates/swc_ecma_transforms_base/tests/resolver/minifier/11/output.js b/crates/swc_ecma_transforms_base/tests/resolver/minifier/11/output.js
index 9e66877fa2..3c827c9dce 100644
--- a/crates/swc_ecma_transforms_base/tests/resolver/minifier/11/output.js
+++ b/crates/swc_ecma_transforms_base/tests/resolver/minifier/11/output.js
@@ -1,2 +1,2 @@
 var NaN__2 = 5;
-console.log(NaN__2.toString());
+console.log(NaN.toString());