diff --git a/CHANGELOG.md b/CHANGELOG.md index d9eeea17f8d2..e50532459c0e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -275,6 +275,7 @@ our [guidelines for writing a good changelog entry](https://github.com/biomejs/b - Add [nursery/noUnmatchableAnbSelector](https://biomejs.dev/linter/rules/no-unmatchable-anb-selector). [#2706](https://github.com/biomejs/biome/issues/2706) Contributed by @togami2864 - Add [nursery/useGenericFontNames](https://biomejs.dev/linter/rules/use-generic-font-names). [#2573](https://github.com/biomejs/biome/pull/2573) Contributed by @togami2864 - Add [nursery/noYodaExpression](https://biomejs.dev/linter/rules/no-yoda-expression/). Contributed by @michellocana +- Add [nursery/noUnusedFunctionParameters](https://biomejs.dev/linter/rules/no-unused-function-parameters/) Contributed by @printfn #### Enhancements diff --git a/crates/biome_configuration/src/linter/rules.rs b/crates/biome_configuration/src/linter/rules.rs index 8879f1c7e19a..81f7d24aa617 100644 --- a/crates/biome_configuration/src/linter/rules.rs +++ b/crates/biome_configuration/src/linter/rules.rs @@ -3378,6 +3378,9 @@ pub struct Nursery { #[doc = "Disallow unmatchable An+B selectors."] #[serde(skip_serializing_if = "Option::is_none")] pub no_unmatchable_anb_selector: Option>, + #[doc = "Disallow unused function parameters."] + #[serde(skip_serializing_if = "Option::is_none")] + pub no_unused_function_parameters: Option>, #[doc = "Disallow unnecessary concatenation of string or template literals."] #[serde(skip_serializing_if = "Option::is_none")] pub no_useless_string_concat: Option>, @@ -3474,6 +3477,7 @@ impl Nursery { "noUnknownSelectorPseudoElement", "noUnknownUnit", "noUnmatchableAnbSelector", + "noUnusedFunctionParameters", "noUselessStringConcat", "noUselessUndefinedInitialization", "noYodaExpression", @@ -3528,9 +3532,9 @@ impl Nursery { RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[21]), RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[22]), RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[23]), - RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[31]), RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[32]), - RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[36]), + RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[33]), + RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[37]), ]; const ALL_RULES_AS_FILTERS: &'static [RuleFilter<'static>] = &[ RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[0]), @@ -3574,6 +3578,7 @@ impl Nursery { RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[38]), RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[39]), RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[40]), + RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[41]), ]; #[doc = r" Retrieves the recommended rules"] pub(crate) fn is_recommended_true(&self) -> bool { @@ -3710,91 +3715,96 @@ impl Nursery { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[23])); } } - if let Some(rule) = self.no_useless_string_concat.as_ref() { + if let Some(rule) = self.no_unused_function_parameters.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[24])); } } - if let Some(rule) = self.no_useless_undefined_initialization.as_ref() { + if let Some(rule) = self.no_useless_string_concat.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[25])); } } - if let Some(rule) = self.no_yoda_expression.as_ref() { + if let Some(rule) = self.no_useless_undefined_initialization.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[26])); } } - if let Some(rule) = self.use_array_literals.as_ref() { + if let Some(rule) = self.no_yoda_expression.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[27])); } } - if let Some(rule) = self.use_consistent_builtin_instantiation.as_ref() { + if let Some(rule) = self.use_array_literals.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[28])); } } - if let Some(rule) = self.use_default_switch_clause.as_ref() { + if let Some(rule) = self.use_consistent_builtin_instantiation.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[29])); } } - if let Some(rule) = self.use_explicit_length_check.as_ref() { + if let Some(rule) = self.use_default_switch_clause.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[30])); } } - if let Some(rule) = self.use_focusable_interactive.as_ref() { + if let Some(rule) = self.use_explicit_length_check.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[31])); } } - if let Some(rule) = self.use_generic_font_names.as_ref() { + if let Some(rule) = self.use_focusable_interactive.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[32])); } } - if let Some(rule) = self.use_import_extensions.as_ref() { + if let Some(rule) = self.use_generic_font_names.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[33])); } } - if let Some(rule) = self.use_import_restrictions.as_ref() { + if let Some(rule) = self.use_import_extensions.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[34])); } } - if let Some(rule) = self.use_number_to_fixed_digits_argument.as_ref() { + if let Some(rule) = self.use_import_restrictions.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[35])); } } - if let Some(rule) = self.use_semantic_elements.as_ref() { + if let Some(rule) = self.use_number_to_fixed_digits_argument.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[36])); } } - if let Some(rule) = self.use_sorted_classes.as_ref() { + if let Some(rule) = self.use_semantic_elements.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[37])); } } - if let Some(rule) = self.use_throw_new_error.as_ref() { + if let Some(rule) = self.use_sorted_classes.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[38])); } } - if let Some(rule) = self.use_throw_only_error.as_ref() { + if let Some(rule) = self.use_throw_new_error.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[39])); } } - if let Some(rule) = self.use_top_level_regex.as_ref() { + if let Some(rule) = self.use_throw_only_error.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[40])); } } + if let Some(rule) = self.use_top_level_regex.as_ref() { + if rule.is_enabled() { + index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[41])); + } + } index_set } pub(crate) fn get_disabled_rules(&self) -> IndexSet { @@ -3919,91 +3929,96 @@ impl Nursery { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[23])); } } - if let Some(rule) = self.no_useless_string_concat.as_ref() { + if let Some(rule) = self.no_unused_function_parameters.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[24])); } } - if let Some(rule) = self.no_useless_undefined_initialization.as_ref() { + if let Some(rule) = self.no_useless_string_concat.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[25])); } } - if let Some(rule) = self.no_yoda_expression.as_ref() { + if let Some(rule) = self.no_useless_undefined_initialization.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[26])); } } - if let Some(rule) = self.use_array_literals.as_ref() { + if let Some(rule) = self.no_yoda_expression.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[27])); } } - if let Some(rule) = self.use_consistent_builtin_instantiation.as_ref() { + if let Some(rule) = self.use_array_literals.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[28])); } } - if let Some(rule) = self.use_default_switch_clause.as_ref() { + if let Some(rule) = self.use_consistent_builtin_instantiation.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[29])); } } - if let Some(rule) = self.use_explicit_length_check.as_ref() { + if let Some(rule) = self.use_default_switch_clause.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[30])); } } - if let Some(rule) = self.use_focusable_interactive.as_ref() { + if let Some(rule) = self.use_explicit_length_check.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[31])); } } - if let Some(rule) = self.use_generic_font_names.as_ref() { + if let Some(rule) = self.use_focusable_interactive.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[32])); } } - if let Some(rule) = self.use_import_extensions.as_ref() { + if let Some(rule) = self.use_generic_font_names.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[33])); } } - if let Some(rule) = self.use_import_restrictions.as_ref() { + if let Some(rule) = self.use_import_extensions.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[34])); } } - if let Some(rule) = self.use_number_to_fixed_digits_argument.as_ref() { + if let Some(rule) = self.use_import_restrictions.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[35])); } } - if let Some(rule) = self.use_semantic_elements.as_ref() { + if let Some(rule) = self.use_number_to_fixed_digits_argument.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[36])); } } - if let Some(rule) = self.use_sorted_classes.as_ref() { + if let Some(rule) = self.use_semantic_elements.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[37])); } } - if let Some(rule) = self.use_throw_new_error.as_ref() { + if let Some(rule) = self.use_sorted_classes.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[38])); } } - if let Some(rule) = self.use_throw_only_error.as_ref() { + if let Some(rule) = self.use_throw_new_error.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[39])); } } - if let Some(rule) = self.use_top_level_regex.as_ref() { + if let Some(rule) = self.use_throw_only_error.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[40])); } } + if let Some(rule) = self.use_top_level_regex.as_ref() { + if rule.is_disabled() { + index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[41])); + } + } index_set } #[doc = r" Checks if, given a rule name, matches one of the rules contained in this category"] @@ -4136,6 +4151,10 @@ impl Nursery { .no_unmatchable_anb_selector .as_ref() .map(|conf| (conf.level(), conf.get_options())), + "noUnusedFunctionParameters" => self + .no_unused_function_parameters + .as_ref() + .map(|conf| (conf.level(), conf.get_options())), "noUselessStringConcat" => self .no_useless_string_concat .as_ref() @@ -4329,6 +4348,11 @@ impl Nursery { rule_conf.set_level(severity); } } + "noUnusedFunctionParameters" => { + if let Some(rule_conf) = &mut self.no_unused_function_parameters { + rule_conf.set_level(severity); + } + } "noUselessStringConcat" => { if let Some(rule_conf) = &mut self.no_useless_string_concat { rule_conf.set_level(severity); diff --git a/crates/biome_diagnostics_categories/src/categories.rs b/crates/biome_diagnostics_categories/src/categories.rs index 3f566787414f..51366d28b4ff 100644 --- a/crates/biome_diagnostics_categories/src/categories.rs +++ b/crates/biome_diagnostics_categories/src/categories.rs @@ -135,6 +135,7 @@ define_categories! { "lint/nursery/noUnknownSelectorPseudoElement": "https://biomejs.dev/linter/rules/no-unknown-selector-pseudo-element", "lint/nursery/noUnknownUnit": "https://biomejs.dev/linter/rules/no-unknown-unit", "lint/nursery/noUnmatchableAnbSelector": "https://biomejs.dev/linter/rules/no-unmatchable-anb-selector", + "lint/nursery/noUnusedFunctionParameters": "https://biomejs.dev/linter/rules/no-unused-function-parameters", "lint/nursery/noUselessStringConcat": "https://biomejs.dev/linter/rules/no-useless-string-concat", "lint/nursery/noUselessUndefinedInitialization": "https://biomejs.dev/linter/rules/no-useless-undefined-initialization", "lint/nursery/noYodaExpression": "https://biomejs.dev/linter/rules/no-yoda-expression", diff --git a/crates/biome_js_analyze/src/lint/correctness/no_unused_variables.rs b/crates/biome_js_analyze/src/lint/correctness/no_unused_variables.rs index ea7877f49838..ab6831cc2d66 100644 --- a/crates/biome_js_analyze/src/lint/correctness/no_unused_variables.rs +++ b/crates/biome_js_analyze/src/lint/correctness/no_unused_variables.rs @@ -6,9 +6,7 @@ use biome_analyze::{ }; use biome_console::markup; use biome_js_semantic::ReferencesExtensions; -use biome_js_syntax::binding_ext::{ - AnyJsBindingDeclaration, AnyJsIdentifierBinding, JsAnyParameterParentFunction, -}; +use biome_js_syntax::binding_ext::{AnyJsBindingDeclaration, AnyJsIdentifierBinding}; use biome_js_syntax::declaration_ext::is_in_ambient_context; use biome_js_syntax::{ AnyJsExpression, JsClassExpression, JsFileSource, JsForStatement, JsFunctionExpression, @@ -26,9 +24,11 @@ declare_rule! { /// The pattern of having an underscore as prefix of a name of variable is a very diffuse /// pattern among programmers, and Biome decided to follow it. /// - /// This rule won't report unused imports. + /// This rule won't report unused imports or unused function parameters. /// If you want to report unused imports, - /// enable [noUnusedImports](https://biomejs.dev/linter/rules/no-unused-imports/). + /// enable [noUnusedImports](https://biomejs.dev/linter/rules/no-unused-imports/); + /// to report unused function parameters, enable + /// [noUnusedFunctionParameters](https://biomejs.dev/linter/rules/no-unused-function-parameters/) /// /// ## Examples /// @@ -65,7 +65,7 @@ declare_rule! { /// export function f() {} /// ``` /// - /// # Valid + /// ### Valid /// /// ```js /// function foo(b) { @@ -86,6 +86,7 @@ declare_rule! { /// } /// used_overloaded(); /// ``` + /// pub NoUnusedVariables { version: "1.0.0", name: "noUnusedVariables", @@ -108,28 +109,6 @@ pub enum SuggestedFix { PrefixUnderscore, } -fn is_function_that_is_ok_parameter_not_be_used( - parent_function: &Option, -) -> bool { - matches!( - parent_function, - Some( - // bindings in signatures are ok to not be used - JsAnyParameterParentFunction::TsMethodSignatureClassMember(_) - | JsAnyParameterParentFunction::TsCallSignatureTypeMember(_) - | JsAnyParameterParentFunction::TsConstructSignatureTypeMember(_) - | JsAnyParameterParentFunction::TsConstructorSignatureClassMember(_) - | JsAnyParameterParentFunction::TsMethodSignatureTypeMember(_) - | JsAnyParameterParentFunction::TsSetterSignatureClassMember(_) - | JsAnyParameterParentFunction::TsSetterSignatureTypeMember(_) - // bindings in function types are ok to not be used - | JsAnyParameterParentFunction::TsFunctionType(_) - // binding in declare are ok to not be used - | JsAnyParameterParentFunction::TsDeclareFunctionDeclaration(_) - ) - ) -} - fn suggestion_for_binding(binding: &AnyJsIdentifierBinding) -> Option { if binding.is_under_object_pattern_binding()? { Some(SuggestedFix::NoSuggestion) @@ -173,21 +152,7 @@ fn suggested_fix_if_unused(binding: &AnyJsIdentifierBinding) -> Option { suggestion_for_binding(binding) } - AnyJsBindingDeclaration::TsPropertyParameter(_) => None, - AnyJsBindingDeclaration::JsFormalParameter(parameter) => { - if is_function_that_is_ok_parameter_not_be_used(¶meter.parent_function()) { - None - } else { - suggestion_for_binding(binding) - } - } - AnyJsBindingDeclaration::JsRestParameter(parameter) => { - if is_function_that_is_ok_parameter_not_be_used(¶meter.parent_function()) { - None - } else { - suggestion_for_binding(binding) - } - } + // declarations need to be check if they are under `declare` AnyJsBindingDeclaration::JsArrayBindingPatternElement(_) | AnyJsBindingDeclaration::JsArrayBindingPatternRestElement(_) @@ -258,6 +223,10 @@ fn suggested_fix_if_unused(binding: &AnyJsIdentifierBinding) -> Option { None } + // Function parameters are handled by noUnusedFunctionParameters + AnyJsBindingDeclaration::JsFormalParameter(_) + | AnyJsBindingDeclaration::JsRestParameter(_) + | AnyJsBindingDeclaration::TsPropertyParameter(_) => None, } } diff --git a/crates/biome_js_analyze/src/lint/nursery.rs b/crates/biome_js_analyze/src/lint/nursery.rs index 4fc111f894e9..2b5e369a8a15 100644 --- a/crates/biome_js_analyze/src/lint/nursery.rs +++ b/crates/biome_js_analyze/src/lint/nursery.rs @@ -13,6 +13,7 @@ pub mod no_nodejs_modules; pub mod no_react_specific_props; pub mod no_restricted_imports; pub mod no_undeclared_dependencies; +pub mod no_unused_function_parameters; pub mod no_useless_string_concat; pub mod no_useless_undefined_initialization; pub mod no_yoda_expression; @@ -45,6 +46,7 @@ declare_group! { self :: no_react_specific_props :: NoReactSpecificProps , self :: no_restricted_imports :: NoRestrictedImports , self :: no_undeclared_dependencies :: NoUndeclaredDependencies , + self :: no_unused_function_parameters :: NoUnusedFunctionParameters , self :: no_useless_string_concat :: NoUselessStringConcat , self :: no_useless_undefined_initialization :: NoUselessUndefinedInitialization , self :: no_yoda_expression :: NoYodaExpression , diff --git a/crates/biome_js_analyze/src/lint/nursery/no_unused_function_parameters.rs b/crates/biome_js_analyze/src/lint/nursery/no_unused_function_parameters.rs new file mode 100644 index 000000000000..a6f5e5e21f37 --- /dev/null +++ b/crates/biome_js_analyze/src/lint/nursery/no_unused_function_parameters.rs @@ -0,0 +1,170 @@ +use biome_analyze::{ + context::RuleContext, declare_rule, ActionCategory, FixKind, Rule, RuleDiagnostic, +}; +use biome_console::markup; +use biome_js_semantic::ReferencesExtensions; +use biome_js_syntax::{ + binding_ext::{AnyJsBindingDeclaration, JsAnyParameterParentFunction}, + JsIdentifierBinding, +}; +use biome_rowan::{AstNode, BatchMutationExt}; + +use crate::{services::semantic::Semantic, utils::rename::RenameSymbolExtensions, JsRuleAction}; + +declare_rule! { + /// Disallow unused function parameters. + /// + /// There is an exception to this rule: + /// parameters that starts with underscore, e.g. `function foo(_a, _b) {}`. + /// + /// Add a link to the corresponding ESLint rule (if any): + /// + /// ## Examples + /// + /// ### Invalid + /// + /// ```js,expect_diagnostic + /// function foo(myVar) { + /// console.log('foo'); + /// } + /// ``` + /// + /// ```js,expect_diagnostic + /// new Promise((accept, reject) => { + /// window.setTimeout(accept, 1000); + /// }); + /// ``` + /// + /// ```js,expect_diagnostic + /// const squares = [[1, 1], [2, 4], [3, 9], 4, 16]]; + /// squares.filter(([k, v]) => v > 5); + /// ``` + /// + /// ### Valid + /// + /// ```js + /// function foo(myVar) { + /// console.log(myVar); + /// } + /// ``` + /// + pub NoUnusedFunctionParameters { + version: "next", + name: "noUnusedFunctionParameters", + language: "js", + recommended: false, + fix_kind: FixKind::Unsafe, + } +} + +/// Suggestion if the binding is unused +#[derive(Debug)] +pub enum SuggestedFix { + /// No suggestion will be given + NoSuggestion, + /// Suggest to prefix the name of the binding with underscore + PrefixUnderscore, +} + +fn is_function_that_is_ok_parameter_not_be_used( + parent_function: &Option, +) -> bool { + matches!( + parent_function, + Some( + // bindings in signatures are ok to not be used + JsAnyParameterParentFunction::TsMethodSignatureClassMember(_) + | JsAnyParameterParentFunction::TsCallSignatureTypeMember(_) + | JsAnyParameterParentFunction::TsConstructSignatureTypeMember(_) + | JsAnyParameterParentFunction::TsConstructorSignatureClassMember(_) + | JsAnyParameterParentFunction::TsMethodSignatureTypeMember(_) + | JsAnyParameterParentFunction::TsSetterSignatureClassMember(_) + | JsAnyParameterParentFunction::TsSetterSignatureTypeMember(_) + // bindings in function types are ok to not be used + | JsAnyParameterParentFunction::TsFunctionType(_) + // binding in declare are ok to not be used + | JsAnyParameterParentFunction::TsDeclareFunctionDeclaration(_) + ) + ) +} + +impl Rule for NoUnusedFunctionParameters { + type Query = Semantic; + type State = SuggestedFix; + type Signals = Option; + type Options = (); + + fn run(ctx: &RuleContext) -> Self::Signals { + let binding = ctx.query(); + let declaration = binding.declaration()?; + + let name = binding.name_token().ok()?; + let name = name.text_trimmed(); + + if name.starts_with('_') { + return None; + } + + let parent_function = match &declaration { + AnyJsBindingDeclaration::JsFormalParameter(parameter) => parameter.parent_function(), + AnyJsBindingDeclaration::JsRestParameter(parameter) => parameter.parent_function(), + AnyJsBindingDeclaration::JsBogusParameter(_) => { + return Some(SuggestedFix::NoSuggestion) + } + _ => return None, + }; + if is_function_that_is_ok_parameter_not_be_used(&parent_function) { + return None; + } + let model = ctx.model(); + if binding.all_references(model).next().is_some() { + return None; + } + Some(if binding.is_under_object_pattern_binding()? { + SuggestedFix::NoSuggestion + } else { + SuggestedFix::PrefixUnderscore + }) + } + + fn diagnostic(ctx: &RuleContext, _state: &Self::State) -> Option { + let binding = ctx.query(); + Some( + RuleDiagnostic::new( + rule_category!(), + binding.range(), + markup! { + "This ""parameter"" is unused." + }, + ) + .note(markup! { + "Unused parameters might be the result of an incomplete refactoring." + }), + ) + } + + fn action(ctx: &RuleContext, suggestion: &Self::State) -> Option { + match suggestion { + SuggestedFix::NoSuggestion => None, + SuggestedFix::PrefixUnderscore => { + let binding = ctx.query(); + let mut mutation = ctx.root().begin(); + + let name = binding.name_token().ok()?; + let name_trimmed = name.text_trimmed(); + let new_name = format!("_{}", name_trimmed); + + let model = ctx.model(); + mutation.rename_node_declaration(model, binding, &new_name); + + Some(JsRuleAction::new( + ActionCategory::QuickFix, + ctx.metadata().applicability(), + markup! { "If this is intentional, prepend "{name_trimmed}" with an underscore." } + .to_owned(), + mutation, + )) + } + } + } +} diff --git a/crates/biome_js_analyze/src/options.rs b/crates/biome_js_analyze/src/options.rs index dab73b900f63..fad5f52de10d 100644 --- a/crates/biome_js_analyze/src/options.rs +++ b/crates/biome_js_analyze/src/options.rs @@ -205,6 +205,7 @@ pub type NoUnsafeFinally = pub type NoUnsafeNegation = ::Options; pub type NoUnsafeOptionalChaining = < lint :: correctness :: no_unsafe_optional_chaining :: NoUnsafeOptionalChaining as biome_analyze :: Rule > :: Options ; +pub type NoUnusedFunctionParameters = < lint :: nursery :: no_unused_function_parameters :: NoUnusedFunctionParameters as biome_analyze :: Rule > :: Options ; pub type NoUnusedImports = ::Options; pub type NoUnusedLabels = diff --git a/crates/biome_js_analyze/tests/specs/correctness/noUnusedVariables/invalidClass.ts b/crates/biome_js_analyze/tests/specs/correctness/noUnusedVariables/invalidClass.ts index d542e150e98d..118c9aefd44b 100644 --- a/crates/biome_js_analyze/tests/specs/correctness/noUnusedVariables/invalidClass.ts +++ b/crates/biome_js_analyze/tests/specs/correctness/noUnusedVariables/invalidClass.ts @@ -1,5 +1,5 @@ class D { - f(a: D): D | undefined { return; } + f(_a: D): D | undefined { return; } } export {} \ No newline at end of file diff --git a/crates/biome_js_analyze/tests/specs/correctness/noUnusedVariables/invalidClass.ts.snap b/crates/biome_js_analyze/tests/specs/correctness/noUnusedVariables/invalidClass.ts.snap index 8ec845204680..cdd2181551ce 100644 --- a/crates/biome_js_analyze/tests/specs/correctness/noUnusedVariables/invalidClass.ts.snap +++ b/crates/biome_js_analyze/tests/specs/correctness/noUnusedVariables/invalidClass.ts.snap @@ -5,7 +5,7 @@ expression: invalidClass.ts # Input ```ts class D { - f(a: D): D | undefined { return; } + f(_a: D): D | undefined { return; } } export {} @@ -19,36 +19,10 @@ invalidClass.ts:1:7 lint/correctness/noUnusedVariables ━━━━━━━━ > 1 │ class D { │ ^ - 2 │ f(a: D): D | undefined { return; } + 2 │ f(_a: D): D | undefined { return; } 3 │ } i Unused variables usually are result of incomplete refactoring, typos and other source of bugs. ``` - -``` -invalidClass.ts:2:4 lint/correctness/noUnusedVariables FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - - ! This parameter is unused. - - 1 │ class D { - > 2 │ f(a: D): D | undefined { return; } - │ ^ - 3 │ } - 4 │ - - i Unused variables usually are result of incomplete refactoring, typos and other source of bugs. - - i Unsafe fix: If this is intentional, prepend a with an underscore. - - 1 1 │ class D { - 2 │ - → f(a:·D):·D·|·undefined·{·return;·} - 2 │ + → f(_a:·D):·D·|·undefined·{·return;·} - 3 3 │ } - 4 4 │ - - -``` - - diff --git a/crates/biome_js_analyze/tests/specs/correctness/noUnusedVariables/invalidFunction.ts b/crates/biome_js_analyze/tests/specs/correctness/noUnusedVariables/invalidFunction.ts index 4f185032ca2e..7ff286d5fb80 100644 --- a/crates/biome_js_analyze/tests/specs/correctness/noUnusedVariables/invalidFunction.ts +++ b/crates/biome_js_analyze/tests/specs/correctness/noUnusedVariables/invalidFunction.ts @@ -13,19 +13,6 @@ function f3() { g(); } -// parameter a is not used -{(function (a) { })} -{(function ({a}) { })} -{(function ([a]) { })} -(function (a, b) { - console.log(b); -}) - -// parameter b is not used -(function (a, b) { - console.log(a); -}) - // f5 is not used const f5 = () => { }; diff --git a/crates/biome_js_analyze/tests/specs/correctness/noUnusedVariables/invalidFunction.ts.snap b/crates/biome_js_analyze/tests/specs/correctness/noUnusedVariables/invalidFunction.ts.snap index 244c09ddd036..41a98a2308b4 100644 --- a/crates/biome_js_analyze/tests/specs/correctness/noUnusedVariables/invalidFunction.ts.snap +++ b/crates/biome_js_analyze/tests/specs/correctness/noUnusedVariables/invalidFunction.ts.snap @@ -19,19 +19,6 @@ function f3() { g(); } -// parameter a is not used -{(function (a) { })} -{(function ({a}) { })} -{(function ([a]) { })} -(function (a, b) { - console.log(b); -}) - -// parameter b is not used -(function (a, b) { - console.log(a); -}) - // f5 is not used const f5 = () => { }; @@ -94,195 +81,74 @@ invalidFunction.ts:9:10 lint/correctness/noUnusedVariables ━━━━━━━ ``` ``` -invalidFunction.ts:17:13 lint/correctness/noUnusedVariables FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - - ! This parameter is unused. - - 16 │ // parameter a is not used - > 17 │ {(function (a) { })} - │ ^ - 18 │ {(function ({a}) { })} - 19 │ {(function ([a]) { })} - - i Unused variables usually are result of incomplete refactoring, typos and other source of bugs. - - i Unsafe fix: If this is intentional, prepend a with an underscore. - - 15 15 │ - 16 16 │ // parameter a is not used - 17 │ - {(function·(a)·{·})} - 17 │ + {(function·(_a)·{·})} - 18 18 │ {(function ({a}) { })} - 19 19 │ {(function ([a]) { })} - - -``` - -``` -invalidFunction.ts:18:14 lint/correctness/noUnusedVariables ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +invalidFunction.ts:17:7 lint/correctness/noUnusedVariables FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ! This variable is unused. - 16 │ // parameter a is not used - 17 │ {(function (a) { })} - > 18 │ {(function ({a}) { })} - │ ^ - 19 │ {(function ([a]) { })} - 20 │ (function (a, b) { - - i Unused variables usually are result of incomplete refactoring, typos and other source of bugs. - - -``` - -``` -invalidFunction.ts:19:14 lint/correctness/noUnusedVariables FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - - ! This variable is unused. - - 17 │ {(function (a) { })} - 18 │ {(function ({a}) { })} - > 19 │ {(function ([a]) { })} - │ ^ - 20 │ (function (a, b) { - 21 │ console.log(b); - - i Unused variables usually are result of incomplete refactoring, typos and other source of bugs. - - i Unsafe fix: If this is intentional, prepend a with an underscore. - - 17 17 │ {(function (a) { })} - 18 18 │ {(function ({a}) { })} - 19 │ - {(function·([a])·{·})} - 19 │ + {(function·([_a])·{·})} - 20 20 │ (function (a, b) { - 21 21 │ console.log(b); - - -``` - -``` -invalidFunction.ts:20:12 lint/correctness/noUnusedVariables FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - - ! This parameter is unused. - - 18 │ {(function ({a}) { })} - 19 │ {(function ([a]) { })} - > 20 │ (function (a, b) { - │ ^ - 21 │ console.log(b); - 22 │ }) - - i Unused variables usually are result of incomplete refactoring, typos and other source of bugs. - - i Unsafe fix: If this is intentional, prepend a with an underscore. - - 18 18 │ {(function ({a}) { })} - 19 19 │ {(function ([a]) { })} - 20 │ - (function·(a,·b)·{ - 20 │ + (function·(_a,·b)·{ - 21 21 │ console.log(b); - 22 22 │ }) - - -``` - -``` -invalidFunction.ts:25:15 lint/correctness/noUnusedVariables FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - - ! This parameter is unused. - - 24 │ // parameter b is not used - > 25 │ (function (a, b) { - │ ^ - 26 │ console.log(a); - 27 │ }) - - i Unused variables usually are result of incomplete refactoring, typos and other source of bugs. - - i Unsafe fix: If this is intentional, prepend b with an underscore. - - 23 23 │ - 24 24 │ // parameter b is not used - 25 │ - (function·(a,·b)·{ - 25 │ + (function·(a,·_b)·{ - 26 26 │ console.log(a); - 27 27 │ }) - - -``` - -``` -invalidFunction.ts:30:7 lint/correctness/noUnusedVariables FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - - ! This variable is unused. - - 29 │ // f5 is not used - > 30 │ const f5 = () => { }; + 16 │ // f5 is not used + > 17 │ const f5 = () => { }; │ ^^ - 31 │ - 32 │ // f6 is recursive, but never called + 18 │ + 19 │ // f6 is recursive, but never called i Unused variables usually are result of incomplete refactoring, typos and other source of bugs. i Unsafe fix: If this is intentional, prepend f5 with an underscore. - 28 28 │ - 29 29 │ // f5 is not used - 30 │ - const·f5·=·()·=>·{·}; - 30 │ + const·_f5·=·()·=>·{·}; - 31 31 │ - 32 32 │ // f6 is recursive, but never called + 15 15 │ + 16 16 │ // f5 is not used + 17 │ - const·f5·=·()·=>·{·}; + 17 │ + const·_f5·=·()·=>·{·}; + 18 18 │ + 19 19 │ // f6 is recursive, but never called ``` ``` -invalidFunction.ts:33:7 lint/correctness/noUnusedVariables FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +invalidFunction.ts:20:7 lint/correctness/noUnusedVariables FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ! This variable is unused. - 32 │ // f6 is recursive, but never called - > 33 │ const f6 = () => { f6() }; + 19 │ // f6 is recursive, but never called + > 20 │ const f6 = () => { f6() }; │ ^^ - 34 │ - 35 │ // e is not used + 21 │ + 22 │ // e is not used i Unused variables usually are result of incomplete refactoring, typos and other source of bugs. i Unsafe fix: If this is intentional, prepend f6 with an underscore. - 31 31 │ - 32 32 │ // f6 is recursive, but never called - 33 │ - const·f6·=·()·=>·{·f6()·}; - 33 │ + const·_f6·=·()·=>·{·_f6()·}; - 34 34 │ - 35 35 │ // e is not used + 18 18 │ + 19 19 │ // f6 is recursive, but never called + 20 │ - const·f6·=·()·=>·{·f6()·}; + 20 │ + const·_f6·=·()·=>·{·_f6()·}; + 21 21 │ + 22 22 │ // e is not used ``` ``` -invalidFunction.ts:36:16 lint/correctness/noUnusedVariables FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +invalidFunction.ts:23:16 lint/correctness/noUnusedVariables FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ! This variable is unused. - 35 │ // e is not used - > 36 │ try { } catch (e) { } + 22 │ // e is not used + > 23 │ try { } catch (e) { } │ ^ - 37 │ + 24 │ i Unused variables usually are result of incomplete refactoring, typos and other source of bugs. i Unsafe fix: If this is intentional, prepend e with an underscore. - 34 34 │ - 35 35 │ // e is not used - 36 │ - try·{·}·catch·(e)·{·} - 36 │ + try·{·}·catch·(_e)·{·} - 37 37 │ + 21 21 │ + 22 22 │ // e is not used + 23 │ - try·{·}·catch·(e)·{·} + 23 │ + try·{·}·catch·(_e)·{·} + 24 24 │ ``` - - diff --git a/crates/biome_js_analyze/tests/specs/correctness/noUnusedVariables/invalidMethodParameters.ts.snap b/crates/biome_js_analyze/tests/specs/correctness/noUnusedVariables/invalidMethodParameters.ts.snap index eb5cde206082..fe8b5805ef3a 100644 --- a/crates/biome_js_analyze/tests/specs/correctness/noUnusedVariables/invalidMethodParameters.ts.snap +++ b/crates/biome_js_analyze/tests/specs/correctness/noUnusedVariables/invalidMethodParameters.ts.snap @@ -12,82 +12,3 @@ class D { console.log(new D()); export {} ``` - -# Diagnostics -``` -invalidMethodParameters.ts:2:14 lint/correctness/noUnusedVariables FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━ - - ! This parameter is unused. - - 1 │ class D { - > 2 │ constructor(a: number) {} - │ ^ - 3 │ f(a: number) {} - 4 │ set a(a: number) {} - - i Unused variables usually are result of incomplete refactoring, typos and other source of bugs. - - i Unsafe fix: If this is intentional, prepend a with an underscore. - - 1 1 │ class D { - 2 │ - → constructor(a:·number)·{} - 2 │ + → constructor(_a:·number)·{} - 3 3 │ f(a: number) {} - 4 4 │ set a(a: number) {} - - -``` - -``` -invalidMethodParameters.ts:3:4 lint/correctness/noUnusedVariables FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━ - - ! This parameter is unused. - - 1 │ class D { - 2 │ constructor(a: number) {} - > 3 │ f(a: number) {} - │ ^ - 4 │ set a(a: number) {} - 5 │ } - - i Unused variables usually are result of incomplete refactoring, typos and other source of bugs. - - i Unsafe fix: If this is intentional, prepend a with an underscore. - - 1 1 │ class D { - 2 2 │ constructor(a: number) {} - 3 │ - → f(a:·number)·{} - 3 │ + → f(_a:·number)·{} - 4 4 │ set a(a: number) {} - 5 5 │ } - - -``` - -``` -invalidMethodParameters.ts:4:8 lint/correctness/noUnusedVariables FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━ - - ! This parameter is unused. - - 2 │ constructor(a: number) {} - 3 │ f(a: number) {} - > 4 │ set a(a: number) {} - │ ^ - 5 │ } - 6 │ console.log(new D()); - - i Unused variables usually are result of incomplete refactoring, typos and other source of bugs. - - i Unsafe fix: If this is intentional, prepend a with an underscore. - - 2 2 │ constructor(a: number) {} - 3 3 │ f(a: number) {} - 4 │ - → set·a(a:·number)·{} - 4 │ + → set·a(_a:·number)·{} - 5 5 │ } - 6 6 │ console.log(new D()); - - -``` - - diff --git a/crates/biome_js_analyze/tests/specs/correctness/noUnusedVariables/invalidUsedBindingPattern.js b/crates/biome_js_analyze/tests/specs/correctness/noUnusedVariables/invalidUsedBindingPattern.js deleted file mode 100644 index 5b2e1e5d66cb..000000000000 --- a/crates/biome_js_analyze/tests/specs/correctness/noUnusedVariables/invalidUsedBindingPattern.js +++ /dev/null @@ -1,3 +0,0 @@ -export function f({ a, b }) { - console.info(b); -} diff --git a/crates/biome_js_analyze/tests/specs/correctness/noUnusedVariables/invalidUsedBindingPattern.js.snap b/crates/biome_js_analyze/tests/specs/correctness/noUnusedVariables/invalidUsedBindingPattern.js.snap deleted file mode 100644 index c1de42bf975b..000000000000 --- a/crates/biome_js_analyze/tests/specs/correctness/noUnusedVariables/invalidUsedBindingPattern.js.snap +++ /dev/null @@ -1,29 +0,0 @@ ---- -source: crates/biome_js_analyze/tests/spec_tests.rs -expression: invalidUsedBindingPattern.js ---- -# Input -```jsx -export function f({ a, b }) { - console.info(b); -} - -``` - -# Diagnostics -``` -invalidUsedBindingPattern.js:1:21 lint/correctness/noUnusedVariables ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - - ! This variable is unused. - - > 1 │ export function f({ a, b }) { - │ ^ - 2 │ console.info(b); - 3 │ } - - i Unused variables usually are result of incomplete refactoring, typos and other source of bugs. - - -``` - - diff --git a/crates/biome_js_analyze/tests/specs/correctness/noUnusedVariables/validFunctions.ts b/crates/biome_js_analyze/tests/specs/correctness/noUnusedVariables/validFunctions.ts index 3fa452d87198..f39423246f40 100644 --- a/crates/biome_js_analyze/tests/specs/correctness/noUnusedVariables/validFunctions.ts +++ b/crates/biome_js_analyze/tests/specs/correctness/noUnusedVariables/validFunctions.ts @@ -16,3 +16,16 @@ function add(a: any, b: any): any { add(1, 1); function id(a = id(null)) { return a } + +// parameter a is not used +{(function (a) { })} +{(function ({a}) { })} +{(function ([a]) { })} +(function (a, b) { + console.log(b); +}) + +// parameter b is not used +(function (a, b) { + console.log(a); +}) diff --git a/crates/biome_js_analyze/tests/specs/correctness/noUnusedVariables/validFunctions.ts.snap b/crates/biome_js_analyze/tests/specs/correctness/noUnusedVariables/validFunctions.ts.snap index 46bf640fbe06..bf952593b082 100644 --- a/crates/biome_js_analyze/tests/specs/correctness/noUnusedVariables/validFunctions.ts.snap +++ b/crates/biome_js_analyze/tests/specs/correctness/noUnusedVariables/validFunctions.ts.snap @@ -23,6 +23,17 @@ add(1, 1); function id(a = id(null)) { return a } -``` +// parameter a is not used +{(function (a) { })} +{(function ({a}) { })} +{(function ([a]) { })} +(function (a, b) { + console.log(b); +}) +// parameter b is not used +(function (a, b) { + console.log(a); +}) +``` diff --git a/crates/biome_js_analyze/tests/specs/nursery/noUnusedFunctionParameters/invalid.js b/crates/biome_js_analyze/tests/specs/nursery/noUnusedFunctionParameters/invalid.js new file mode 100644 index 000000000000..d6ab7347c5cd --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/noUnusedFunctionParameters/invalid.js @@ -0,0 +1,25 @@ +function foo(myVar) { + console.log('foo'); +} + +const data = [[1, 1], [2, 4], [3, 9], [4, 16], [5, 25]]; +data.filter(([k, v]) => v > 10); + +[{ a: 1, b: 2, c: 3 }].map(({a, b, c}) => a + c); + +new Promise((accept, reject) => { + window.setTimeout(accept, 1000); +}); + +// parameter a is not used +{(function (a) { })} +{(function ({a}) { })} +{(function ([a]) { })} +(function (a, b) { + console.log(b); +}) + +// parameter b is not used +(function (a, b) { + console.log(a); +}) diff --git a/crates/biome_js_analyze/tests/specs/nursery/noUnusedFunctionParameters/invalid.js.snap b/crates/biome_js_analyze/tests/specs/nursery/noUnusedFunctionParameters/invalid.js.snap new file mode 100644 index 000000000000..f0c6fa3e416e --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/noUnusedFunctionParameters/invalid.js.snap @@ -0,0 +1,158 @@ +--- +source: crates/biome_js_analyze/tests/spec_tests.rs +expression: invalid.js +--- +# Input +```jsx +function foo(myVar) { + console.log('foo'); +} + +const data = [[1, 1], [2, 4], [3, 9], [4, 16], [5, 25]]; +data.filter(([k, v]) => v > 10); + +[{ a: 1, b: 2, c: 3 }].map(({a, b, c}) => a + c); + +new Promise((accept, reject) => { + window.setTimeout(accept, 1000); +}); + +// parameter a is not used +{(function (a) { })} +{(function ({a}) { })} +{(function ([a]) { })} +(function (a, b) { + console.log(b); +}) + +// parameter b is not used +(function (a, b) { + console.log(a); +}) + +``` + +# Diagnostics +``` +invalid.js:1:14 lint/nursery/noUnusedFunctionParameters FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! This parameter is unused. + + > 1 │ function foo(myVar) { + │ ^^^^^ + 2 │ console.log('foo'); + 3 │ } + + i Unused parameters might be the result of an incomplete refactoring. + + i Unsafe fix: If this is intentional, prepend myVar with an underscore. + + 1 │ - function·foo(myVar)·{ + 1 │ + function·foo(_myVar)·{ + 2 2 │ console.log('foo'); + 3 3 │ } + + +``` + +``` +invalid.js:10:22 lint/nursery/noUnusedFunctionParameters FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! This parameter is unused. + + 8 │ [{ a: 1, b: 2, c: 3 }].map(({a, b, c}) => a + c); + 9 │ + > 10 │ new Promise((accept, reject) => { + │ ^^^^^^ + 11 │ window.setTimeout(accept, 1000); + 12 │ }); + + i Unused parameters might be the result of an incomplete refactoring. + + i Unsafe fix: If this is intentional, prepend reject with an underscore. + + 8 8 │ [{ a: 1, b: 2, c: 3 }].map(({a, b, c}) => a + c); + 9 9 │ + 10 │ - new·Promise((accept,·reject)·=>·{ + 10 │ + new·Promise((accept,·_reject)·=>·{ + 11 11 │ window.setTimeout(accept, 1000); + 12 12 │ }); + + +``` + +``` +invalid.js:15:13 lint/nursery/noUnusedFunctionParameters FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! This parameter is unused. + + 14 │ // parameter a is not used + > 15 │ {(function (a) { })} + │ ^ + 16 │ {(function ({a}) { })} + 17 │ {(function ([a]) { })} + + i Unused parameters might be the result of an incomplete refactoring. + + i Unsafe fix: If this is intentional, prepend a with an underscore. + + 13 13 │ + 14 14 │ // parameter a is not used + 15 │ - {(function·(a)·{·})} + 15 │ + {(function·(_a)·{·})} + 16 16 │ {(function ({a}) { })} + 17 17 │ {(function ([a]) { })} + + +``` + +``` +invalid.js:18:12 lint/nursery/noUnusedFunctionParameters FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! This parameter is unused. + + 16 │ {(function ({a}) { })} + 17 │ {(function ([a]) { })} + > 18 │ (function (a, b) { + │ ^ + 19 │ console.log(b); + 20 │ }) + + i Unused parameters might be the result of an incomplete refactoring. + + i Unsafe fix: If this is intentional, prepend a with an underscore. + + 16 16 │ {(function ({a}) { })} + 17 17 │ {(function ([a]) { })} + 18 │ - (function·(a,·b)·{ + 18 │ + (function·(_a,·b)·{ + 19 19 │ console.log(b); + 20 20 │ }) + + +``` + +``` +invalid.js:23:15 lint/nursery/noUnusedFunctionParameters FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! This parameter is unused. + + 22 │ // parameter b is not used + > 23 │ (function (a, b) { + │ ^ + 24 │ console.log(a); + 25 │ }) + + i Unused parameters might be the result of an incomplete refactoring. + + i Unsafe fix: If this is intentional, prepend b with an underscore. + + 21 21 │ + 22 22 │ // parameter b is not used + 23 │ - (function·(a,·b)·{ + 23 │ + (function·(a,·_b)·{ + 24 24 │ console.log(a); + 25 25 │ }) + + +``` diff --git a/crates/biome_js_analyze/tests/specs/nursery/noUnusedFunctionParameters/invalid.ts b/crates/biome_js_analyze/tests/specs/nursery/noUnusedFunctionParameters/invalid.ts new file mode 100644 index 000000000000..4669ca1841a0 --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/noUnusedFunctionParameters/invalid.ts @@ -0,0 +1,3 @@ +class D { + f(a: D): D | undefined { return; } +} diff --git a/crates/biome_js_analyze/tests/specs/nursery/noUnusedFunctionParameters/invalid.ts.snap b/crates/biome_js_analyze/tests/specs/nursery/noUnusedFunctionParameters/invalid.ts.snap new file mode 100644 index 000000000000..f4771cb6804f --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/noUnusedFunctionParameters/invalid.ts.snap @@ -0,0 +1,36 @@ +--- +source: crates/biome_js_analyze/tests/spec_tests.rs +expression: invalid.ts +--- +# Input +```ts +class D { + f(a: D): D | undefined { return; } +} + +``` + +# Diagnostics +``` +invalid.ts:2:4 lint/nursery/noUnusedFunctionParameters FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! This parameter is unused. + + 1 │ class D { + > 2 │ f(a: D): D | undefined { return; } + │ ^ + 3 │ } + 4 │ + + i Unused parameters might be the result of an incomplete refactoring. + + i Unsafe fix: If this is intentional, prepend a with an underscore. + + 1 1 │ class D { + 2 │ - → f(a:·D):·D·|·undefined·{·return;·} + 2 │ + → f(_a:·D):·D·|·undefined·{·return;·} + 3 3 │ } + 4 4 │ + + +``` diff --git a/crates/biome_js_analyze/tests/specs/nursery/noUnusedFunctionParameters/valid.js b/crates/biome_js_analyze/tests/specs/nursery/noUnusedFunctionParameters/valid.js new file mode 100644 index 000000000000..950702450f0a --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/noUnusedFunctionParameters/valid.js @@ -0,0 +1,15 @@ +function foo(myVar) { + console.log(myVar); +} + +function foo(_unused) { + console.log('not using the parameter'); +} + +Object.fromEntries(Object.entries({a: 'A', b: 'B', c: 'C'}).map(([k, v]) => [v, k])); + +new Promise((accept, _reject) => { + window.setTimeout(accept, 1000); +}); + +data.filter(([_k, v]) => v > 10); diff --git a/crates/biome_js_analyze/tests/specs/nursery/noUnusedFunctionParameters/valid.js.snap b/crates/biome_js_analyze/tests/specs/nursery/noUnusedFunctionParameters/valid.js.snap new file mode 100644 index 000000000000..fa987196a3a0 --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/noUnusedFunctionParameters/valid.js.snap @@ -0,0 +1,23 @@ +--- +source: crates/biome_js_analyze/tests/spec_tests.rs +expression: valid.js +--- +# Input +```jsx +function foo(myVar) { + console.log(myVar); +} + +function foo(_unused) { + console.log('not using the parameter'); +} + +Object.fromEntries(Object.entries({a: 'A', b: 'B', c: 'C'}).map(([k, v]) => [v, k])); + +new Promise((accept, _reject) => { + window.setTimeout(accept, 1000); +}); + +data.filter(([_k, v]) => v > 10); + +``` diff --git a/packages/@biomejs/backend-jsonrpc/src/workspace.ts b/packages/@biomejs/backend-jsonrpc/src/workspace.ts index 98b245bd31b3..b10d1eb60aed 100644 --- a/packages/@biomejs/backend-jsonrpc/src/workspace.ts +++ b/packages/@biomejs/backend-jsonrpc/src/workspace.ts @@ -1049,6 +1049,10 @@ export interface Nursery { * Disallow unmatchable An+B selectors. */ noUnmatchableAnbSelector?: RuleConfiguration_for_Null; + /** + * Disallow unused function parameters. + */ + noUnusedFunctionParameters?: RuleFixConfiguration_for_Null; /** * Disallow unnecessary concatenation of string or template literals. */ @@ -2307,6 +2311,7 @@ export type Category = | "lint/nursery/noUnknownSelectorPseudoElement" | "lint/nursery/noUnknownUnit" | "lint/nursery/noUnmatchableAnbSelector" + | "lint/nursery/noUnusedFunctionParameters" | "lint/nursery/noUselessStringConcat" | "lint/nursery/noUselessUndefinedInitialization" | "lint/nursery/noYodaExpression" diff --git a/packages/@biomejs/biome/configuration_schema.json b/packages/@biomejs/biome/configuration_schema.json index dba7a0d364bb..e02d6e126895 100644 --- a/packages/@biomejs/biome/configuration_schema.json +++ b/packages/@biomejs/biome/configuration_schema.json @@ -1787,6 +1787,13 @@ { "type": "null" } ] }, + "noUnusedFunctionParameters": { + "description": "Disallow unused function parameters.", + "anyOf": [ + { "$ref": "#/definitions/RuleFixConfiguration_for_Null" }, + { "type": "null" } + ] + }, "noUselessStringConcat": { "description": "Disallow unnecessary concatenation of string or template literals.", "anyOf": [