diff --git a/CHANGELOG.md b/CHANGELOG.md
index 02ae34164731..8e1af31d6cd5 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -103,18 +103,33 @@ our [guidelines for writing a good changelog entry](https://github.com/biomejs/b
Contributed by @Conaclos
-- Fixes [#4059](https://github.com/biomejs/biome/issues/4059), the rule [noUselessFragments](https://biomejs.dev/linter/rules/no-useless-fragments/) now correctly handles fragments containing HTML escapes (e.g. ` `) inside expression escapes `{ ... }`.
-The following code is no longer reported:
+- [noUselessFragments](https://biomejs.dev/linter/rules/no-useless-fragments/) now correctly handles fragments containing HTML escapes (e.g. ` `) inside expression escapes `{ ... }` ([#4059](https://github.com/biomejs/biome/issues/4059)).
-```jsx
-function Component() {
- return (
-
{line || <> >}
- )
-}
-```
+ The following code is no longer reported:
+
+ ```jsx
+ function Component() {
+ return (
+ {line || <> >}
+ )
+ }
+ ```
-Contributed by @fireairforce
+ Contributed by @fireairforce
+
+- [noUnusedFunctionParameters](https://biomejs.dev/linter/rules/no-unused-function-parameters/) and [noUnusedVariables](https://biomejs.dev/linter/rules/no-unused-variables/) no longer reports a parameter as unused when another parameter has a constructor type with the same parameter name ([#4227](https://github.com/biomejs/biome/issues/4227)).
+
+ In the following code, the `name` parameter is no longer reported as unused.
+
+ ```ts
+ export class Foo {
+ bar(name: string, _class: new (name: string) => any) {
+ return name
+ }
+ }
+ ```
+
+ Contributed by @Conaclos
### Parser
diff --git a/crates/biome_js_analyze/tests/specs/correctness/noUnusedFunctionParameters/issue4227.ts b/crates/biome_js_analyze/tests/specs/correctness/noUnusedFunctionParameters/issue4227.ts
new file mode 100644
index 000000000000..8be4d8d9baa5
--- /dev/null
+++ b/crates/biome_js_analyze/tests/specs/correctness/noUnusedFunctionParameters/issue4227.ts
@@ -0,0 +1,5 @@
+class Foo {
+ bar(name: string, _class: new (name: string) => any) {
+ return name
+ }
+}
\ No newline at end of file
diff --git a/crates/biome_js_analyze/tests/specs/correctness/noUnusedFunctionParameters/issue4227.ts.snap b/crates/biome_js_analyze/tests/specs/correctness/noUnusedFunctionParameters/issue4227.ts.snap
new file mode 100644
index 000000000000..f92663464864
--- /dev/null
+++ b/crates/biome_js_analyze/tests/specs/correctness/noUnusedFunctionParameters/issue4227.ts.snap
@@ -0,0 +1,12 @@
+---
+source: crates/biome_js_analyze/tests/spec_tests.rs
+expression: issue4227.ts
+---
+# Input
+```ts
+class Foo {
+ bar(name: string, _class: new (name: string) => any) {
+ return name
+ }
+}
+```
diff --git a/crates/biome_js_semantic/src/events.rs b/crates/biome_js_semantic/src/events.rs
index bed7ce3f0efb..f2eef59b50e9 100644
--- a/crates/biome_js_semantic/src/events.rs
+++ b/crates/biome_js_semantic/src/events.rs
@@ -439,7 +439,9 @@ impl SemanticEventExtractor {
let node = node.syntax();
if matches!(
node.kind(),
- JsSyntaxKind::TS_FUNCTION_TYPE | JsSyntaxKind::TS_MAPPED_TYPE
+ JsSyntaxKind::TS_CONSTRUCTOR_TYPE
+ | JsSyntaxKind::TS_FUNCTION_TYPE
+ | JsSyntaxKind::TS_MAPPED_TYPE
) {
self.push_scope(
node.text_trimmed_range(),
@@ -811,7 +813,9 @@ impl SemanticEventExtractor {
let node = node.syntax();
if matches!(
node.kind(),
- JsSyntaxKind::TS_FUNCTION_TYPE | JsSyntaxKind::TS_MAPPED_TYPE
+ JsSyntaxKind::TS_CONSTRUCTOR_TYPE
+ | JsSyntaxKind::TS_FUNCTION_TYPE
+ | JsSyntaxKind::TS_MAPPED_TYPE
) {
self.pop_scope(node.text_trimmed_range());
}
diff --git a/crates/biome_js_semantic/src/semantic_model/builder.rs b/crates/biome_js_semantic/src/semantic_model/builder.rs
index ac8c76dba880..753232abab9f 100644
--- a/crates/biome_js_semantic/src/semantic_model/builder.rs
+++ b/crates/biome_js_semantic/src/semantic_model/builder.rs
@@ -100,6 +100,7 @@ impl SemanticModelBuilder {
| JS_FOR_IN_STATEMENT
| JS_SWITCH_STATEMENT
| JS_CATCH_CLAUSE
+ | TS_CONSTRUCTOR_TYPE
| TS_FUNCTION_TYPE
| TS_MAPPED_TYPE => {
self.scope_node_by_range