Repository: swc-project/swc (rust)

Issue:
False "Cannot reassign to a variable declared with `const`" error with destructured function arguments in Typescript
### Describe the bug

The code below will admittedly seem a bit contrived but it is a boiled down version of something we have in our codebase. 

What seems to trip the compiler is the `const fn3 = ...` assignment. 

Looks like it mistakens the `({x,y} ... => string)` type for a destructured variable declaration.

The code parses and compiles correctly with tsc, ts-node and esbuild.

### Input code

```typescript
function fn1({x,y}: {x: string, y: string}): string {
    return x + y
}

function fn2({x,y}: {x: string, y: string}): string {
   const fn3: ({x,y}: {x: string, y: string}) => string = fn1
   return fn3({x,y})
}
```


### Config

```json
{
  "jsc": {
    "parser": {
      "syntax": "typescript",
      "tsx": false
    },
    "target": "es5",
    "loose": false,
    "minify": {
      "compress": false,
      "mangle": false
    }
  },
  "module": {
    "type": "es6"
  },
  "minify": false,
  "isModule": true
}
```


### Playground link

https://play.swc.rs/?version=1.2.151&code=H4sIAAAAAAAAA0srzUsuyczPU0jLM9SortCprLVSqK6wUiguKcrMS9dRqIQxazVhLIVqLgUgKEotKS3KU6hQ0Fao5Krl4kpDmGREkknJ%2BXnFJUBdxlYKBPQp2NrBdNqCHMyFcAZQO0SzJsgtAFg82JHWAAAA&config=H4sIAAAAAAAAA0WNSwrDMAxE76J1ts0id%2BghjKsEF%2F%2FQKBBjfPfawSU7aebpqdIXlrZK2QhYxoQS1Vy0kZbMsOKy0kKKHu3Gg1tfjBysHWG8eudTAs92oeCi28sw2RSyMPBUJh7%2BT7YuCulzjqDez27hSu1xzDuH9wRVTm4%2FOkwQUrUAAAA%3D

### Expected behavior

The code should compile to:
```
function fn1({ x, y }) {
  return x + y;
}
function fn2({ x, y }) {
  const fn3 = fn1;
  return fn3({ x, y });
}
```

### Actual behavior

```
error: Cannot reassign to a variable declared with `const`
 --> input.ts:5:15
  |
5 | function fn2({x,y}: {x: string, y: string}): string {
  |               ^
  |
note: x was declared here
 --> input.ts:6:17
  |
6 |    const fn3: ({x,y}: {x: string, y: string}) => string = fn1
  |                 ^

error: Cannot reassign to a variable declared with `const`
 --> input.ts:5:17
  |
5 | function fn2({x,y}: {x: string, y: string}): string {
  |                 ^
  |
note: y was declared here
 --> input.ts:6:19
  |
6 |    const fn3: ({x,y}: {x: string, y: string}) => string = fn1
  |                   ^
```

### Version

1.2.151

### Additional context

_No response_

Pull request:
fix(wasm): Fix handling of `swc_common::GLOBALS`
**Description:**


**Related issue (if exists):**


 - Closes #3929

Patch:
diff --git a/crates/wasm/src/lib.rs b/crates/wasm/src/lib.rs
index 25faec30fa..99755cabdd 100644
--- a/crates/wasm/src/lib.rs
+++ b/crates/wasm/src/lib.rs
@@ -21,14 +21,16 @@ pub fn minify_sync(s: &str, opts: JsValue) -> Result<JsValue, JsValue> {
     let c = compiler();
 
     try_with_handler(c.cm.clone(), false, |handler| {
-        let opts: JsMinifyOptions = opts.into_serde().context("failed to parse options")?;
+        c.run(|| {
+            let opts: JsMinifyOptions = opts.into_serde().context("failed to parse options")?;
 
-        let fm = c.cm.new_source_file(FileName::Anon, s.into());
-        let program = c
-            .minify(fm, handler, &opts)
-            .context("failed to minify file")?;
+            let fm = c.cm.new_source_file(FileName::Anon, s.into());
+            let program = c
+                .minify(fm, handler, &opts)
+                .context("failed to minify file")?;
 
-        JsValue::from_serde(&program).context("failed to serialize json")
+            JsValue::from_serde(&program).context("failed to serialize json")
+        })
     })
     .map_err(convert_err)
 }
@@ -40,29 +42,31 @@ pub fn parse_sync(s: &str, opts: JsValue) -> Result<JsValue, JsValue> {
     let c = compiler();
 
     try_with_handler(c.cm.clone(), false, |handler| {
-        let opts: ParseOptions = opts.into_serde().context("failed to parse options")?;
-
-        let fm = c.cm.new_source_file(FileName::Anon, s.into());
-
-        let cmts = c.comments().clone();
-        let comments = if opts.comments {
-            Some(&cmts as &dyn Comments)
-        } else {
-            None
-        };
-
-        let program = c
-            .parse_js(
-                fm,
-                handler,
-                opts.target,
-                opts.syntax,
-                opts.is_module,
-                comments,
-            )
-            .context("failed to parse code")?;
-
-        JsValue::from_serde(&program).context("failed to serialize json")
+        c.run(|| {
+            let opts: ParseOptions = opts.into_serde().context("failed to parse options")?;
+
+            let fm = c.cm.new_source_file(FileName::Anon, s.into());
+
+            let cmts = c.comments().clone();
+            let comments = if opts.comments {
+                Some(&cmts as &dyn Comments)
+            } else {
+                None
+            };
+
+            let program = c
+                .parse_js(
+                    fm,
+                    handler,
+                    opts.target,
+                    opts.syntax,
+                    opts.is_module,
+                    comments,
+                )
+                .context("failed to parse code")?;
+
+            JsValue::from_serde(&program).context("failed to serialize json")
+        })
     })
     .map_err(convert_err)
 }
@@ -74,28 +78,30 @@ pub fn print_sync(s: JsValue, opts: JsValue) -> Result<JsValue, JsValue> {
     let c = compiler();
 
     try_with_handler(c.cm.clone(), false, |_handler| {
-        let opts: Options = opts.into_serde().context("failed to parse options")?;
-
-        let program: Program = s.into_serde().context("failed to deserialize program")?;
-
-        let s = c
-            .print(
-                &program,
-                None,
-                None,
-                true,
-                opts.codegen_target().unwrap_or(EsVersion::Es2020),
-                opts.source_maps
-                    .clone()
-                    .unwrap_or(SourceMapsConfig::Bool(false)),
-                &Default::default(),
-                None,
-                opts.config.minify,
-                None,
-            )
-            .context("failed to print code")?;
-
-        JsValue::from_serde(&s).context("failed to serialize json")
+        c.run(|| {
+            let opts: Options = opts.into_serde().context("failed to parse options")?;
+
+            let program: Program = s.into_serde().context("failed to deserialize program")?;
+
+            let s = c
+                .print(
+                    &program,
+                    None,
+                    None,
+                    true,
+                    opts.codegen_target().unwrap_or(EsVersion::Es2020),
+                    opts.source_maps
+                        .clone()
+                        .unwrap_or(SourceMapsConfig::Bool(false)),
+                    &Default::default(),
+                    None,
+                    opts.config.minify,
+                    None,
+                )
+                .context("failed to print code")?;
+
+            JsValue::from_serde(&s).context("failed to serialize json")
+        })
     })
     .map_err(convert_err)
 }
@@ -107,21 +113,23 @@ pub fn transform_sync(s: &str, opts: JsValue) -> Result<JsValue, JsValue> {
     let c = compiler();
 
     try_with_handler(c.cm.clone(), false, |handler| {
-        let opts: Options = opts.into_serde().context("failed to parse options")?;
-
-        let fm = c.cm.new_source_file(
-            if opts.filename.is_empty() {
-                FileName::Anon
-            } else {
-                FileName::Real(opts.filename.clone().into())
-            },
-            s.into(),
-        );
-        let out = c
-            .process_js_file(fm, handler, &opts)
-            .context("failed to process js file")?;
-
-        JsValue::from_serde(&out).context("failed to serialize json")
+        c.run(|| {
+            let opts: Options = opts.into_serde().context("failed to parse options")?;
+
+            let fm = c.cm.new_source_file(
+                if opts.filename.is_empty() {
+                    FileName::Anon
+                } else {
+                    FileName::Real(opts.filename.clone().into())
+                },
+                s.into(),
+            );
+            let out = c
+                .process_js_file(fm, handler, &opts)
+                .context("failed to process js file")?;
+
+            JsValue::from_serde(&out).context("failed to serialize json")
+        })
     })
     .map_err(convert_err)
 }


Test patch:
diff --git a/crates/swc/tests/fixture/issue-3929/input/.swcrc b/crates/swc/tests/fixture/issue-3929/input/.swcrc
new file mode 100644
index 0000000000..e4d496ade7
--- /dev/null
+++ b/crates/swc/tests/fixture/issue-3929/input/.swcrc
@@ -0,0 +1,18 @@
+{
+    "jsc": {
+        "parser": {
+            "syntax": "typescript",
+            "tsx": false
+        },
+        "target": "es5",
+        "loose": false,
+        "minify": {
+            "compress": false,
+            "mangle": false
+        }
+    },
+    "module": {
+        "type": "es6"
+    },
+    "minify": false
+}
\ No newline at end of file
diff --git a/crates/swc/tests/fixture/issue-3929/input/index.ts b/crates/swc/tests/fixture/issue-3929/input/index.ts
new file mode 100644
index 0000000000..2289fd6508
--- /dev/null
+++ b/crates/swc/tests/fixture/issue-3929/input/index.ts
@@ -0,0 +1,11 @@
+export { }
+
+function fn1({ x, y }: { x: string, y: string }): string {
+    return x + y
+}
+
+function fn2({ x, y }: { x: string, y: string }): string {
+    const fn3: ({ x, y }: { x: string, y: string }) => string = fn1
+    return fn3({ x, y })
+}
+
diff --git a/crates/swc/tests/fixture/issue-3929/output/index.js b/crates/swc/tests/fixture/issue-3929/output/index.js
new file mode 100644
index 0000000000..d99720714e
--- /dev/null
+++ b/crates/swc/tests/fixture/issue-3929/output/index.js
@@ -0,0 +1,12 @@
+function fn1(param) {
+    var x = param.x, y = param.y;
+    return x + y;
+}
+function fn2(param) {
+    var x = param.x, y = param.y;
+    var fn3 = fn1;
+    return fn3({
+        x: x,
+        y: y
+    });
+}
diff --git a/crates/swc/tests/fixture/issue-3929/output/index.ts b/crates/swc/tests/fixture/issue-3929/output/index.ts
new file mode 100644
index 0000000000..e2d4de6e38
--- /dev/null
+++ b/crates/swc/tests/fixture/issue-3929/output/index.ts
@@ -0,0 +1,13 @@
+function fn1(param) {
+    var x = param.x, y = param.y;
+    return x + y;
+}
+function fn2(param) {
+    var x = param.x, y = param.y;
+    var fn3 = fn1;
+    return fn3({
+        x: x,
+        y: y
+    });
+}
+export { };
diff --git a/crates/swc_ecma_lints/tests/pass/issue-3929/1/input.ts b/crates/swc_ecma_lints/tests/pass/issue-3929/1/input.ts
new file mode 100644
index 0000000000..d08b8d05a6
--- /dev/null
+++ b/crates/swc_ecma_lints/tests/pass/issue-3929/1/input.ts
@@ -0,0 +1,8 @@
+function fn1({ x, y }: { x: string, y: string }): string {
+    return x + y
+}
+
+function fn2({ x, y }: { x: string, y: string }): string {
+    const fn3: ({ x, y }: { x: string, y: string }) => string = fn1
+    return fn3({ x, y })
+}
\ No newline at end of file