Repository: swc-project/swc (rust)

Issue:
Duplicate Declaration Function should be Overwritten but get Duplicate binding Error
### Describe the bug

The function repeatedly declared in the JavaScript scope will be overwritten by the last function, however, the program can still run correctly, but the error of duplicate binding is reported during SWC conversion.

### Input code

```typescript
function sum ( i, j) {
  return i + j;
}

function sum(){
  return 0;
}

console.log(sum());
console.log(sum(1, 2));
```


### Config

```json
{
  "jsc": {
    "parser": {
      "syntax": "ecmascript",
      "jsx": false,
      "dynamicImport": false,
      "privateMethod": false,
      "functionBind": false,
      "exportDefaultFrom": false,
      "exportNamespaceFrom": false,
      "decorators": false,
      "decoratorsBeforeExport": false,
      "topLevelAwait": false,
      "importMeta": false
    },
    "transform": null,
    "target": "es5",
    "loose": false,
    "externalHelpers": false
  }
}
```


### Playground link

https://play.swc.rs/?version=1.2.148&code=H4sIAAAAAAAAA0srzUsuyczPUyguzVXQUMjUUcjSVKjmUlAoSi0pLcpTyFTQVsiy5qrl4kpDUqmhiaTEACydnJ9XnJ%2BTqpeTn64BVqFpjSFmqKNgBBQHAM2STN90AAAA&config=H4sIAAAAAAAAAy2LQQqAMAwE%2F5KzVy%2F9g48INUpL05YkgkX6dw142t0Z9oGsEcIDHUVJvOmohjcEoMioUVI3mAsYyknmWFff3ParkB9s9C8hNuZWs7pMuv36wKI0X3tLUmhnAAAA

### Expected behavior

After SWC conversion, the code does not change the semantics, and the last function with the same name overwrites the previous function

### Actual behavior

_No response_

### Version

1.2.148

### Additional context

I run swc use cmd as follows
```bash
npx swc --config-file=.swcrc poc.js -o poc.js
```
The environment is ubuntu 20.04 amd64.

Pull request:
feat(es/lint): support module/script mode in DuplicateBindings 
<!-- 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):**
- fix #3852 

Patch:
diff --git a/.github/workflows/integration.yml b/.github/workflows/integration.yml
index a73864dfb2..54f30c3e90 100644
--- a/.github/workflows/integration.yml
+++ b/.github/workflows/integration.yml
@@ -94,9 +94,7 @@ jobs:
           # Download three.js
           git clone --depth 1 https://github.com/mrdoob/three.js.git -b r117 tests/integration/three-js/repo
 
-          swc --sync tests/integration/three-js/repo/ -d tests/integration/three-js/build/
-          # swc tests/integration/three-js/repo/src/ -d tests/integration/three-js/repo/build/
-          # swc tests/integration/three-js/repo/test/unit/**/*.js -d tests/integration/three-js/repo/test/unit/build/
+          swc -C isModule=unknown --sync tests/integration/three-js/repo/ -d tests/integration/three-js/build/
 
           (cd tests/integration/three-js/build/test && qunit -r failonlyreporter unit/three.source.unit.js)
 
diff --git a/crates/swc_ecma_lints/src/rules/duplicate_bindings.rs b/crates/swc_ecma_lints/src/rules/duplicate_bindings.rs
index 68ea7e561a..3118fefb31 100644
--- a/crates/swc_ecma_lints/src/rules/duplicate_bindings.rs
+++ b/crates/swc_ecma_lints/src/rules/duplicate_bindings.rs
@@ -1,7 +1,10 @@
 use std::collections::hash_map::Entry;
 
-use ahash::AHashSet;
-use swc_common::{collections::AHashMap, errors::HANDLER, Span};
+use swc_common::{
+    collections::{AHashMap, AHashSet},
+    errors::HANDLER,
+    Span,
+};
 use swc_ecma_ast::*;
 use swc_ecma_utils::ident::IdentLike;
 use swc_ecma_visit::{noop_visit_type, Visit, VisitWith};
@@ -12,41 +15,61 @@ pub fn duplicate_bindings() -> Box<dyn Rule> {
     visitor_rule(DuplicateBindings::default())
 }
 
+#[derive(Debug, Default, Clone, Copy)]
+struct BindingInfo {
+    span: Span,
+    unique: bool,
+}
+
 #[derive(Debug, Default)]
 struct DuplicateBindings {
-    bindings: AHashMap<Id, Span>,
+    bindings: AHashMap<Id, BindingInfo>,
     type_bindings: AHashSet<Id>,
 
     var_decl_kind: Option<VarDeclKind>,
     is_pat_decl: bool,
+
+    is_module: bool,
 }
 
 impl DuplicateBindings {
     /// Add a binding.
-    fn add(&mut self, id: &Ident, check_for_var_kind: bool) {
-        if check_for_var_kind {
-            if let Some(VarDeclKind::Var) = self.var_decl_kind {
-                return;
-            }
-        }
-
+    fn add(&mut self, id: &Ident, unique: bool) {
         match self.bindings.entry(id.to_id()) {
             Entry::Occupied(mut prev) => {
-                HANDLER.with(|handler| {
-                    handler
-                        .struct_span_err(id.span, "Duplicate binding")
-                        .span_note(*prev.get(), &format!("{} was declared at here", id.sym))
-                        .emit();
-                });
+                if unique || prev.get().unique {
+                    HANDLER.with(|handler| {
+                        handler
+                            .struct_span_err(id.span, "Duplicate binding")
+                            .span_note(prev.get().span, &format!("{} was declared at here", id.sym))
+                            .emit();
+                    });
+                }
 
                 // Next span.
-                *prev.get_mut() = id.span;
+                if unique || !prev.get().unique {
+                    *prev.get_mut() = BindingInfo {
+                        span: id.span,
+                        unique,
+                    }
+                }
             }
             Entry::Vacant(e) => {
-                e.insert(id.span);
+                e.insert(BindingInfo {
+                    span: id.span,
+                    unique,
+                });
             }
         }
     }
+
+    /// `const` or `let`
+    fn is_unique_var_kind(&self) -> bool {
+        matches!(
+            self.var_decl_kind,
+            Some(VarDeclKind::Const) | Some(VarDeclKind::Let)
+        )
+    }
 }
 
 impl Visit for DuplicateBindings {
@@ -57,6 +80,7 @@ impl Visit for DuplicateBindings {
             type_bindings: &mut self.type_bindings,
         });
 
+        self.is_module = true;
         m.visit_children_with(self);
     }
 
@@ -72,12 +96,12 @@ impl Visit for DuplicateBindings {
         p.visit_children_with(self);
 
         if self.is_pat_decl {
-            self.add(&p.key, true);
+            self.add(&p.key, self.is_unique_var_kind());
         }
     }
 
     fn visit_class_decl(&mut self, d: &ClassDecl) {
-        self.add(&d.ident, false);
+        self.add(&d.ident, true);
 
         d.visit_children_with(self);
     }
@@ -97,7 +121,7 @@ impl Visit for DuplicateBindings {
 
     fn visit_fn_decl(&mut self, d: &FnDecl) {
         if d.function.body.is_some() {
-            self.add(&d.ident, false);
+            self.add(&d.ident, self.is_module);
         }
 
         d.visit_children_with(self);
@@ -107,7 +131,7 @@ impl Visit for DuplicateBindings {
         s.visit_children_with(self);
 
         if !self.type_bindings.contains(&s.local.to_id()) {
-            self.add(&s.local, false);
+            self.add(&s.local, true);
         }
     }
 
@@ -115,7 +139,7 @@ impl Visit for DuplicateBindings {
         s.visit_children_with(self);
 
         if !s.is_type_only && !self.type_bindings.contains(&s.local.to_id()) {
-            self.add(&s.local, false);
+            self.add(&s.local, true);
         }
     }
 
@@ -123,7 +147,7 @@ impl Visit for DuplicateBindings {
         s.visit_children_with(self);
 
         if !self.type_bindings.contains(&s.local.to_id()) {
-            self.add(&s.local, false);
+            self.add(&s.local, true);
         }
     }
 
@@ -132,7 +156,7 @@ impl Visit for DuplicateBindings {
 
         if let Pat::Ident(p) = p {
             if self.is_pat_decl {
-                self.add(&p.id, true);
+                self.add(&p.id, self.is_unique_var_kind());
             }
         }
     }


Test patch:
diff --git a/crates/swc/tests/error_msg.rs b/crates/swc/tests/error_msg.rs
index 168c16dce2..de342d7d16 100644
--- a/crates/swc/tests/error_msg.rs
+++ b/crates/swc/tests/error_msg.rs
@@ -62,7 +62,7 @@ fn fixture(input: PathBuf) {
             handler,
             &Options {
                 swcrc: true,
-                is_module: IsModule::Bool(true),
+                is_module: IsModule::Unknown,
 
                 ..Default::default()
             },
diff --git a/crates/swc/tests/errors/lints/duplicate-bindings/const-let/1/input.js b/crates/swc/tests/errors/lints/duplicate-bindings/const-let/1/input.js
deleted file mode 100644
index 51661e5153..0000000000
--- a/crates/swc/tests/errors/lints/duplicate-bindings/const-let/1/input.js
+++ /dev/null
@@ -1,2 +0,0 @@
-const a = 5;
-let a = 2;
\ No newline at end of file
diff --git a/crates/swc/tests/errors/lints/duplicate-bindings/const-let/1/output.swc-stderr b/crates/swc/tests/errors/lints/duplicate-bindings/const-let/1/output.swc-stderr
deleted file mode 100644
index 3ddc1545c2..0000000000
--- a/crates/swc/tests/errors/lints/duplicate-bindings/const-let/1/output.swc-stderr
+++ /dev/null
@@ -1,12 +0,0 @@
-error: Duplicate binding
- 
-  |
-2 | let a = 2;
-  |     ^
-  |
-note: a was declared at here
- 
-  |
-1 | const a = 5;
-  |       ^
-
diff --git a/crates/swc/tests/errors/lints/duplicate-bindings/module/export/input.js b/crates/swc/tests/errors/lints/duplicate-bindings/module/export/input.js
new file mode 100644
index 0000000000..12b86a72fb
--- /dev/null
+++ b/crates/swc/tests/errors/lints/duplicate-bindings/module/export/input.js
@@ -0,0 +1,10 @@
+function foo() {}
+const foo = 1; // error
+
+function bar() {}
+var bar; // error
+
+function baz() {}
+function baz() {} // error
+
+export {};
diff --git a/crates/swc/tests/errors/lints/duplicate-bindings/module/export/output.swc-stderr b/crates/swc/tests/errors/lints/duplicate-bindings/module/export/output.swc-stderr
new file mode 100644
index 0000000000..21cda3edc9
--- /dev/null
+++ b/crates/swc/tests/errors/lints/duplicate-bindings/module/export/output.swc-stderr
@@ -0,0 +1,36 @@
+error: Duplicate binding
+ 
+  |
+2 | const foo = 1; // error
+  |       ^^^
+  |
+note: foo was declared at here
+ 
+  |
+1 | function foo() {}
+  |          ^^^
+
+error: Duplicate binding
+ 
+  |
+5 | var bar; // error
+  |     ^^^
+  |
+note: bar was declared at here
+ 
+  |
+4 | function bar() {}
+  |          ^^^
+
+error: Duplicate binding
+ 
+  |
+8 | function baz() {} // error
+  |          ^^^
+  |
+note: baz was declared at here
+ 
+  |
+7 | function baz() {}
+  |          ^^^
+
diff --git a/crates/swc/tests/errors/lints/duplicate-bindings/module/import/input.js b/crates/swc/tests/errors/lints/duplicate-bindings/module/import/input.js
new file mode 100644
index 0000000000..a81975e4cc
--- /dev/null
+++ b/crates/swc/tests/errors/lints/duplicate-bindings/module/import/input.js
@@ -0,0 +1,4 @@
+import { hi } from "foo";
+
+function foo() {}
+function foo() {}
diff --git a/crates/swc/tests/errors/lints/duplicate-bindings/module/import/output.swc-stderr b/crates/swc/tests/errors/lints/duplicate-bindings/module/import/output.swc-stderr
new file mode 100644
index 0000000000..c0284a2617
--- /dev/null
+++ b/crates/swc/tests/errors/lints/duplicate-bindings/module/import/output.swc-stderr
@@ -0,0 +1,12 @@
+error: Duplicate binding
+ 
+  |
+4 | function foo() {}
+  |          ^^^
+  |
+note: foo was declared at here
+ 
+  |
+3 | function foo() {}
+  |          ^^^
+
diff --git a/crates/swc/tests/errors/lints/duplicate-bindings/var/1/input.js b/crates/swc/tests/errors/lints/duplicate-bindings/var/1/input.js
new file mode 100644
index 0000000000..3a6bc678a9
--- /dev/null
+++ b/crates/swc/tests/errors/lints/duplicate-bindings/var/1/input.js
@@ -0,0 +1,11 @@
+const a = 5;
+let a = 2; // error
+
+const b = 1;
+var b = 2; // error
+
+let c = 3;
+var c = 4; // error
+
+var d = 1;
+var d = 1; // ok
diff --git a/crates/swc/tests/errors/lints/duplicate-bindings/var/1/output.swc-stderr b/crates/swc/tests/errors/lints/duplicate-bindings/var/1/output.swc-stderr
new file mode 100644
index 0000000000..f0f80a2f1a
--- /dev/null
+++ b/crates/swc/tests/errors/lints/duplicate-bindings/var/1/output.swc-stderr
@@ -0,0 +1,36 @@
+error: Duplicate binding
+ 
+  |
+2 | let a = 2; // error
+  |     ^
+  |
+note: a was declared at here
+ 
+  |
+1 | const a = 5;
+  |       ^
+
+error: Duplicate binding
+ 
+  |
+5 | var b = 2; // error
+  |     ^
+  |
+note: b was declared at here
+ 
+  |
+4 | const b = 1;
+  |       ^
+
+error: Duplicate binding
+ 
+  |
+8 | var c = 4; // error
+  |     ^
+  |
+note: c was declared at here
+ 
+  |
+7 | let c = 3;
+  |     ^
+
diff --git a/crates/swc/tests/errors/lints/duplicate-bindings/var/2/input.js b/crates/swc/tests/errors/lints/duplicate-bindings/var/2/input.js
new file mode 100644
index 0000000000..53852b78db
--- /dev/null
+++ b/crates/swc/tests/errors/lints/duplicate-bindings/var/2/input.js
@@ -0,0 +1,8 @@
+function foo() {}
+const foo = 1; // error
+
+function bar() {}
+var bar; // ok
+
+function baz() {}
+function baz() {} // ok
diff --git a/crates/swc/tests/errors/lints/duplicate-bindings/var/2/output.swc-stderr b/crates/swc/tests/errors/lints/duplicate-bindings/var/2/output.swc-stderr
new file mode 100644
index 0000000000..794008fd6f
--- /dev/null
+++ b/crates/swc/tests/errors/lints/duplicate-bindings/var/2/output.swc-stderr
@@ -0,0 +1,12 @@
+error: Duplicate binding
+ 
+  |
+2 | const foo = 1; // error
+  |       ^^^
+  |
+note: foo was declared at here
+ 
+  |
+1 | function foo() {}
+  |          ^^^
+
diff --git a/crates/swc/tests/errors/lints/no-loop-func/default/output.swc-stderr b/crates/swc/tests/errors/lints/no-loop-func/default/output.swc-stderr
index a47920a575..9b1631b0fc 100644
--- a/crates/swc/tests/errors/lints/no-loop-func/default/output.swc-stderr
+++ b/crates/swc/tests/errors/lints/no-loop-func/default/output.swc-stderr
@@ -1,3 +1,15 @@
+error: Duplicate binding
+  
+   |
+56 |     var a = 0;
+   |         ^
+   |
+note: a was declared at here
+  
+   |
+36 | let a; for (let i in {}) { (function() { a; }); a = 1; }
+   |     ^
+
 error: Duplicate binding
   
    |
diff --git a/crates/swc_ecma_lints/tests/fixture.rs b/crates/swc_ecma_lints/tests/fixture.rs
index 50f02fe799..d76f3e0578 100644
--- a/crates/swc_ecma_lints/tests/fixture.rs
+++ b/crates/swc_ecma_lints/tests/fixture.rs
@@ -40,21 +40,19 @@ fn pass(input: PathBuf) {
 
         let mut parser = Parser::new_from(lexer);
 
-        let mut m = parser.parse_module().unwrap();
+        let mut program = parser.parse_program().unwrap();
         let top_level_mark = Mark::fresh(Mark::root());
 
         if input.extension().unwrap() == "ts" || input.extension().unwrap() == "tsx" {
-            m.visit_mut_with(&mut ts_resolver(top_level_mark));
+            program.visit_mut_with(&mut ts_resolver(top_level_mark));
         } else {
-            m.visit_mut_with(&mut resolver_with_mark(top_level_mark));
+            program.visit_mut_with(&mut resolver_with_mark(top_level_mark));
         }
 
         let top_level_ctxt = SyntaxContext::empty().apply_mark(top_level_mark);
 
         let config = LintConfig::default();
 
-        let program = Program::Module(m);
-
         let rules = all(LintParams {
             program: &program,
             lint_config: &config,
diff --git a/crates/swc_ecma_lints/tests/pass/issue-3852/1/input.js b/crates/swc_ecma_lints/tests/pass/issue-3852/1/input.js
new file mode 100644
index 0000000000..3e69106fb6
--- /dev/null
+++ b/crates/swc_ecma_lints/tests/pass/issue-3852/1/input.js
@@ -0,0 +1,10 @@
+function sum(i, j) {
+    return i + j;
+}
+
+function sum() {
+    return 0;
+}
+
+console.log(sum());
+console.log(sum(1, 2));