Thanks to visit codestin.com
Credit goes to github.com

Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions .changeset/rotten-knives-brush.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
"@biomejs/biome": patch
---

Fixed [#7101](https://github.com/biomejs/biome/issues/7101): [`noUnusedPrivateClassMembers`](https://biomejs.dev/linter/rules/no-unused-private-class-members/) now handles members declared as part of constructor arguments:

1. If a class member defined in a constructor argument is only used within the constructor, it removes the `private` modifier and makes it a plain method argument.
1. If it is not used at all, it will prefix it with an underscore, similar to `noUnusedFunctionParameter`.
Original file line number Diff line number Diff line change
@@ -1,8 +1,14 @@
use crate::{
JsRuleAction,
services::semantic::Semantic,
utils::{is_node_equal, rename::RenameSymbolExtensions},
};
use biome_analyze::{
Ast, FixKind, Rule, RuleDiagnostic, RuleSource, context::RuleContext, declare_lint_rule,
FixKind, Rule, RuleDiagnostic, RuleSource, context::RuleContext, declare_lint_rule,
};
use biome_console::markup;
use biome_diagnostics::Severity;
use biome_js_semantic::ReferencesExtensions;
use biome_js_syntax::{
AnyJsClassMember, AnyJsClassMemberName, AnyJsFormalParameter, AnyJsName,
JsAssignmentExpression, JsAssignmentOperator, JsClassDeclaration, JsSyntaxKind, JsSyntaxNode,
Expand All @@ -15,8 +21,6 @@ use biome_rowan::{
use biome_rule_options::no_unused_private_class_members::NoUnusedPrivateClassMembersOptions;
use rustc_hash::FxHashSet;

use crate::{JsRuleAction, utils::is_node_equal};

declare_lint_rule! {
/// Disallow unused private class members
///
Expand Down Expand Up @@ -76,9 +80,27 @@ declare_node_union! {
pub AnyMember = AnyJsClassMember | TsPropertyParameter
}

#[derive(Debug, Clone)]
pub enum UnusedMemberAction {
RemoveMember(AnyMember),
RemovePrivateModifier {
member: AnyMember,
rename_with_underscore: bool,
},
}

impl UnusedMemberAction {
fn property_range(&self) -> Option<TextRange> {
match self {
Self::RemoveMember(member) => member.property_range(),
Self::RemovePrivateModifier { member, .. } => member.property_range(),
}
}
}

impl Rule for NoUnusedPrivateClassMembers {
type Query = Ast<JsClassDeclaration>;
type State = AnyMember;
type Query = Semantic<JsClassDeclaration>;
type State = UnusedMemberAction;
type Signals = Box<[Self::State]>;
type Options = NoUnusedPrivateClassMembersOptions;

Expand All @@ -88,32 +110,123 @@ impl Rule for NoUnusedPrivateClassMembers {
if private_members.is_empty() {
Vec::new()
} else {
traverse_members_usage(node.syntax(), private_members)
let mut results = Vec::new();
let unused_members = traverse_members_usage(node.syntax(), private_members);

for member in unused_members {
match &member {
AnyMember::AnyJsClassMember(_) => {
results.push(UnusedMemberAction::RemoveMember(member));
}
AnyMember::TsPropertyParameter(ts_property_param) => {
// Check if the parameter is also unused in constructor body using semantic analysis
let should_rename =
check_ts_property_parameter_usage(ctx, ts_property_param);
results.push(UnusedMemberAction::RemovePrivateModifier {
member,
rename_with_underscore: should_rename,
});
}
}
}
results
}
.into_boxed_slice()
}

fn diagnostic(_: &RuleContext<Self>, state: &Self::State) -> Option<RuleDiagnostic> {
Some(RuleDiagnostic::new(
rule_category!(),
state.property_range(),
markup! {
"This private class member is defined but never used."
},
))
match state {
UnusedMemberAction::RemoveMember(_) => Some(RuleDiagnostic::new(
rule_category!(),
state.property_range(),
markup! {
"This private class member is defined but never used."
},
)),
UnusedMemberAction::RemovePrivateModifier {
rename_with_underscore,
..
} => {
if *rename_with_underscore {
Some(RuleDiagnostic::new(
rule_category!(),
state.property_range(),
markup! {
"This private class member is defined but never used."
},
))
} else {
Some(RuleDiagnostic::new(
rule_category!(),
state.property_range(),
markup! {
"This parameter is never used outside of the constructor."
},
))
}
}
}
}

fn action(ctx: &RuleContext<Self>, state: &Self::State) -> Option<JsRuleAction> {
let mut mutation = ctx.root().begin();

mutation.remove_node(state.clone());

Some(JsRuleAction::new(
ctx.metadata().action_category(ctx.category(), ctx.group()),
ctx.metadata().applicability(),
markup! { "Remove unused declaration." }.to_owned(),
mutation,
))
match state {
UnusedMemberAction::RemoveMember(member) => {
mutation.remove_node(member.clone());
Some(JsRuleAction::new(
ctx.metadata().action_category(ctx.category(), ctx.group()),
ctx.metadata().applicability(),
markup! { "Remove unused declaration." }.to_owned(),
mutation,
))
}
UnusedMemberAction::RemovePrivateModifier {
member,
rename_with_underscore,
} => {
if let AnyMember::TsPropertyParameter(ts_property_param) = member {
// Remove the private modifier
let modifiers = ts_property_param.modifiers();
for modifier in modifiers.iter() {
if let Some(accessibility_modifier) =
TsAccessibilityModifier::cast(modifier.into_syntax())
{
if accessibility_modifier.is_private() {
mutation.remove_node(accessibility_modifier);
break;
}
}
}
// If needed, rename with underscore prefix
if *rename_with_underscore {
if let Ok(AnyJsFormalParameter::JsFormalParameter(param)) =
ts_property_param.formal_parameter()
{
let binding = param.binding().ok()?;
let identifier_binding =
binding.as_any_js_binding()?.as_js_identifier_binding()?;
let name_token = identifier_binding.name_token().ok()?;
let name_trimmed = name_token.text_trimmed();
let new_name = format!("_{name_trimmed}");
if !mutation.rename_node_declaration(
ctx.model(),
identifier_binding,
&new_name,
) {
return None;
}
}
}
}
Some(JsRuleAction::new(
ctx.metadata().action_category(ctx.category(), ctx.group()),
ctx.metadata().applicability(),
markup! { "Remove private modifier" }.to_owned(),
mutation,
))
}
}
}
}

Expand Down Expand Up @@ -160,6 +273,42 @@ fn traverse_members_usage(
private_members.into_iter().collect()
}

/// Check if a TsPropertyParameter is also unused as a function parameter
fn check_ts_property_parameter_usage(
ctx: &RuleContext<NoUnusedPrivateClassMembers>,
ts_property_param: &TsPropertyParameter,
) -> bool {
if let Ok(AnyJsFormalParameter::JsFormalParameter(param)) = ts_property_param.formal_parameter()
&& let Ok(binding) = param.binding()
&& let Some(identifier_binding) = binding
.as_any_js_binding()
.and_then(|b| b.as_js_identifier_binding())
{
let name_token = match identifier_binding.name_token() {
Ok(token) => token,
Err(_) => return false,
};

let name = name_token.text_trimmed();

if name.starts_with('_') {
return false;
}

if identifier_binding
.all_references(ctx.model())
.next()
.is_some()
{
return false;
}

return true;
}

false
}

fn get_all_declared_private_members(
class_declaration: &JsClassDeclaration,
) -> FxHashSet<AnyMember> {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
---
source: crates/biome_js_analyze/tests/spec_tests.rs
assertion_line: 146
expression: invalid.ts
---
# Input
Expand Down Expand Up @@ -113,10 +114,15 @@ invalid.ts:10:22 lint/correctness/noUnusedPrivateClassMembers FIXABLE ━━
11 │
12 │ }

i Unsafe fix: Remove unused declaration.
i Unsafe fix: Remove private modifier

8 8 │
9 9 │ class TSUnusedPrivateConstructor {
10 │ - → constructor(private·nusedProperty·=·3){
10 │ + → constructor(_nusedProperty·=·3){
11 11 │
12 12 │ }

10 │ → constructor(private·nusedProperty·=·3){
│ -------------------------

```

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
class TSDoubleUnusedPrivateConstructor {
constructor(private unusedProperty = 3, private anotherUnusedProperty = 4) {
// This constructor has two unused private properties

}
}

class TSPartiallyUsedPrivateConstructor {
constructor(private param: number) {
foo(param)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
---
source: crates/biome_js_analyze/tests/spec_tests.rs
expression: invalid_issue_7101.ts
---
# Input
```ts
class TSDoubleUnusedPrivateConstructor {
constructor(private unusedProperty = 3, private anotherUnusedProperty = 4) {
// This constructor has two unused private properties

}
}

class TSPartiallyUsedPrivateConstructor {
constructor(private param: number) {
foo(param)
}
}
```

# Diagnostics
```
invalid_issue_7101.ts:2:22 lint/correctness/noUnusedPrivateClassMembers FIXABLE ━━━━━━━━━━━━━━━━━━

! This private class member is defined but never used.

1 │ class TSDoubleUnusedPrivateConstructor {
> 2 │ constructor(private unusedProperty = 3, private anotherUnusedProperty = 4) {
│ ^^^^^^^^^^^^^^^
3 │ // This constructor has two unused private properties
4 │

i Unsafe fix: Remove private modifier

1 1 │ class TSDoubleUnusedPrivateConstructor {
2 │ - → constructor(private·unusedProperty·=·3,·private·anotherUnusedProperty·=·4)·{
2 │ + → constructor(_unusedProperty·=·3,·private·anotherUnusedProperty·=·4)·{
3 3 │ // This constructor has two unused private properties
4 4 │


```

```
invalid_issue_7101.ts:2:50 lint/correctness/noUnusedPrivateClassMembers FIXABLE ━━━━━━━━━━━━━━━━━━

! This private class member is defined but never used.

1 │ class TSDoubleUnusedPrivateConstructor {
> 2 │ constructor(private unusedProperty = 3, private anotherUnusedProperty = 4) {
│ ^^^^^^^^^^^^^^^^^^^^^^
3 │ // This constructor has two unused private properties
4 │

i Unsafe fix: Remove private modifier

1 1 │ class TSDoubleUnusedPrivateConstructor {
2 │ - → constructor(private·unusedProperty·=·3,·private·anotherUnusedProperty·=·4)·{
2 │ + → constructor(private·unusedProperty·=·3,·_anotherUnusedProperty·=·4)·{
3 3 │ // This constructor has two unused private properties
4 4 │


```

```
invalid_issue_7101.ts:9:23 lint/correctness/noUnusedPrivateClassMembers FIXABLE ━━━━━━━━━━━━━━━━━━

! This parameter is never used outside of the constructor.

8 │ class TSPartiallyUsedPrivateConstructor {
> 9 │ constructor(private param: number) {
│ ^^^^^
10 │ foo(param)
11 │ }

i Unsafe fix: Remove private modifier

9 │ ··constructor(private·param:·number)·{
│ --------

```
Loading