Repository: swc-project/swc (rust)

Issue:
import/export should obey binding rule
### Describe the bug

[es6 import/export is binding](https://stackoverflow.com/a/46937590/13763537), [babel obey this rule](https://babeljs.io/repl#?browsers=defaults%2C%20not%20ie%2011%2C%20not%20ie_mob%2011&build=&builtIns=false&corejs=3.6&spec=false&loose=false&code_lz=KYDwDg9gTgLgBAG2PAygeQKoCUDCBRAWQEEAFEgSQDkBxAfWwBk4BeOAcgGcIBXKAY2AEAhmwDcAKHTZ8xMlTqM4AalZswYAJYA7AOaMx4gPSG4GgGZwA7sFMBbSLDgAiKbkKkKNelgZOrNyw0EBDgdZGcuXgFhdW09HycgA&debug=false&forceAllTransforms=false&shippedProposals=false&circleciRepo=&evaluate=true&fileSize=false&timeTravel=false&sourceType=module&lineWrap=true&presets=env%2Creact%2Cstage-2&prettier=false&targets=&version=7.17.6&externalPlugins=&assumptions=%7B%7D), [but swc does not](https://play.swc.rs/?version=1.2.146&code=H4sIAAAAAAAAA0utKMgvKlHISS1RCPYPDXJ29XUMCPD0c48PDfJRsFVQL84vLUpO9U1Ut%2BbClNcGKigoyMxLB3KACvT1sZhRnpmTo5CUqqAEM0kJALAfEw5zAAAA&config=H4sIAAAAAAAAA0WMSwrDMAxE76K1t93kDj2EcJXg4B8aBWqM714bUrKSmPdmOp3wtHWqrBBdH1o2%2FtJG4hPDa6hGbmoz2jlChiNjPcSWgtdksRTITR2lkMPe1pIvqaoAD%2BJ8xL855lAqn2sFnazVeVcllXyCxjN0lwPet216yfgBCKaI9boAAAA%3D).
code snippet come from rollup https://github.com/rollup/rollup/blob/511e9ae226869606b0a03071787d358901ed968a/src/utils/sourceMappingURL.ts#L3
please see these links

### Input code

```typescript
export let SOURCEMAPPING_URL = 'sourceMa';
SOURCEMAPPING_URL += 'ppingURL';
// SOURCEMAPPING_URL will be "sourceMa"
```


### Config

```json
{
  "jsc": {
    "parser": {
      "syntax": "ecmascript",
      "jsx": false
    },
    "target": "es5",
    "loose": false,
    "minify": {
      "compress": false,
      "mangle": false
    }
  },
  "module": {
    "type": "commonjs"
  },
  "minify": false,
  "isModule": true
}
```


### Playground link

https://play.swc.rs/?version=1.2.146&code=H4sIAAAAAAAAA0utKMgvKlHISS1RCPYPDXJ29XUMCPD0c48PDfJRsFVQL84vLUpO9U1Ut%2BbClNcGKigoyMxLB3KACvT1sZhRnpmTo5CUqqAEM0kJALAfEw5zAAAA&config=H4sIAAAAAAAAA0WMSwrDMAxE76K1t93kDj2EcJXg4B8aBWqM714bUrKSmPdmOp3wtHWqrBBdH1o2%2FtJG4hPDa6hGbmoz2jlChiNjPcSWgtdksRTITR2lkMPe1pIvqaoAD%2BJ8xL855lAqn2sFnazVeVcllXyCxjN0lwPet216yfgBCKaI9boAAAA%3D

### Expected behavior

if we import "SOURCEMAPPING_URL" we should get "sourceMappingURL".

### Actual behavior

if we import "SOURCEMAPPING_URL" we get "sourceMa".

### Version

1.2.146

### Additional context

_No response_

Pull request:
fix(es/module): binding export for assign expr
<!-- 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:**

Some fun facts:

```javascript
ident = expr;
```
the LHS is parsed as `PatOrExpr::Pat`

```javascript
ident += expr;
```

the LHS is parsed as `PatOrExpr::Expr`


**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):**
- fix #3898 

Patch:
diff --git a/crates/swc_ecma_transforms_module/src/util.rs b/crates/swc_ecma_transforms_module/src/util.rs
index 513fa98679..867e81ac96 100644
--- a/crates/swc_ecma_transforms_module/src/util.rs
+++ b/crates/swc_ecma_transforms_module/src/util.rs
@@ -672,7 +672,13 @@ impl Scope {
 
                 let mut found: Vec<(JsWord, Span)> = vec![];
                 let mut v = DestructuringFinder { found: &mut found };
-                expr.left.visit_with(&mut v);
+
+                if let PatOrExpr::Expr(e) = &expr.left {
+                    e.visit_children_with(&mut v);
+                } else {
+                    expr.left.visit_with(&mut v);
+                }
+
                 if v.found.is_empty() {
                     return Expr::Assign(AssignExpr {
                         left: expr.left,
@@ -759,63 +765,52 @@ impl Scope {
                     }
                 }
 
-                match expr.left {
-                    PatOrExpr::Pat(pat) if pat.is_ident() => {
-                        let i = pat.expect_ident();
-                        let mut scope = folder.scope_mut();
-                        let entry = scope
-                            .exported_bindings
-                            .entry((i.id.sym.clone(), i.id.span.ctxt()));
-
-                        match entry {
-                            Entry::Occupied(entry) => {
-                                let expr = Expr::Assign(AssignExpr {
-                                    left: PatOrExpr::Pat(i.into()),
-                                    ..expr
-                                });
-                                let e = chain_assign!(entry, Box::new(expr));
-
-                                *e
-                            }
-                            _ => Expr::Assign(AssignExpr {
-                                left: PatOrExpr::Pat(i.into()),
-                                ..expr
-                            }),
+                if let Some(ident) = expr.left.as_ident() {
+                    let mut scope = folder.scope_mut();
+                    let entry = scope
+                        .exported_bindings
+                        .entry((ident.sym.clone(), ident.span.ctxt()));
+
+                    match entry {
+                        Entry::Occupied(entry) => {
+                            let expr = Expr::Assign(expr);
+
+                            *chain_assign!(entry, Box::new(expr))
                         }
+                        _ => expr.into(),
                     }
-                    _ => {
-                        let mut exprs = iter::once(Box::new(Expr::Assign(expr)))
-                            .chain(
-                                found
-                                    .into_iter()
-                                    .map(|var| Ident::new(var.0, var.1))
-                                    .filter_map(|i| {
-                                        let mut scope = folder.scope_mut();
-                                        let entry = match scope
-                                            .exported_bindings
-                                            .entry((i.sym.clone(), i.span.ctxt()))
-                                        {
-                                            Entry::Occupied(entry) => entry,
-                                            _ => {
-                                                return None;
-                                            }
-                                        };
-                                        let e = chain_assign!(entry, Box::new(Expr::Ident(i)));
-
-                                        // exports.name = x
-                                        Some(e)
-                                    }),
-                            )
-                            .collect::<Vec<_>>();
-                        if exprs.len() == 1 {
-                            return *exprs.pop().unwrap();
-                        }
-
-                        Expr::Seq(SeqExpr {
-                            span: DUMMY_SP,
-                            exprs,
-                        })
+                } else {
+                    let mut exprs = iter::once(Box::new(Expr::Assign(expr)))
+                        .chain(
+                            found
+                                .into_iter()
+                                .map(|var| Ident::new(var.0, var.1))
+                                .filter_map(|i| {
+                                    let mut scope = folder.scope_mut();
+                                    let entry = match scope
+                                        .exported_bindings
+                                        .entry((i.sym.clone(), i.span.ctxt()))
+                                    {
+                                        Entry::Occupied(entry) => entry,
+                                        _ => {
+                                            return None;
+                                        }
+                                    };
+                                    let e = chain_assign!(entry, Box::new(Expr::Ident(i)));
+
+                                    // exports.name = x
+                                    Some(e)
+                                }),
+                        )
+                        .collect::<Vec<_>>();
+                    if exprs.len() == 1 {
+                        return *exprs.pop().unwrap();
                     }
+
+                    Expr::Seq(SeqExpr {
+                        span: DUMMY_SP,
+                        exprs,
+                    })
                 }
             }
             _ => expr.fold_children_with(folder),


Test patch:
diff --git a/crates/swc_ecma_transforms_module/tests/fixture/commonjs/issue-3898/1/input.js b/crates/swc_ecma_transforms_module/tests/fixture/commonjs/issue-3898/1/input.js
new file mode 100644
index 0000000000..5b9eaec4a1
--- /dev/null
+++ b/crates/swc_ecma_transforms_module/tests/fixture/commonjs/issue-3898/1/input.js
@@ -0,0 +1,21 @@
+export let foo = 1;
+foo = 2;
+foo += 2;
+foo -= 2;
+foo *= 2;
+foo /= 2;
+foo %= 2;
+foo <<= 2;
+foo >>= 2;
+foo >>>= 2;
+foo |= 2;
+foo ^= 2;
+foo &= 2;
+foo **= 2;
+foo &&= 2;
+foo ||= 2;
+foo ??= 2;
+
+[foo] = [2];
+[foo = 3] = [];
+({ bar: foo } = { bar: 2 });
diff --git a/crates/swc_ecma_transforms_module/tests/fixture/commonjs/issue-3898/1/output.js b/crates/swc_ecma_transforms_module/tests/fixture/commonjs/issue-3898/1/output.js
new file mode 100644
index 0000000000..eb3c41dab7
--- /dev/null
+++ b/crates/swc_ecma_transforms_module/tests/fixture/commonjs/issue-3898/1/output.js
@@ -0,0 +1,30 @@
+"use strict";
+Object.defineProperty(exports, "__esModule", {
+    value: true
+});
+exports.foo = void 0;
+let foo = 1;
+exports.foo = foo;
+exports.foo = foo = 2;
+exports.foo = foo += 2;
+exports.foo = foo -= 2;
+exports.foo = foo *= 2;
+exports.foo = foo /= 2;
+exports.foo = foo %= 2;
+exports.foo = foo <<= 2;
+exports.foo = foo >>= 2;
+exports.foo = foo >>>= 2;
+exports.foo = foo |= 2;
+exports.foo = foo ^= 2;
+exports.foo = foo &= 2;
+exports.foo = foo **= 2;
+exports.foo = foo &&= 2;
+exports.foo = foo ||= 2;
+exports.foo = foo ??= 2;
+[foo] = [
+    2
+], exports.foo = foo;
+[foo = 3] = [], exports.foo = foo;
+({ bar: foo  } = {
+    bar: 2
+}), exports.foo = foo;