diff --git a/crates/oxc_linter/src/rules/eslint/no_unused_vars/allowed.rs b/crates/oxc_linter/src/rules/eslint/no_unused_vars/allowed.rs index 4a3616d8c4b582..1a51b5a7ca9e35 100644 --- a/crates/oxc_linter/src/rules/eslint/no_unused_vars/allowed.rs +++ b/crates/oxc_linter/src/rules/eslint/no_unused_vars/allowed.rs @@ -7,7 +7,72 @@ use oxc_semantic::Semantic; use super::binding_pattern::CheckBinding; use super::{options::ArgsOption, NoUnusedVars, Symbol}; +impl<'s, 'a> Symbol<'s, 'a> { + /// Returns `true` if this function is a callback passed to another function + pub fn is_function_callback(&self) -> bool { + debug_assert!(self.declaration().kind().is_function_like()); + + for parent in self.iter_parents() { + match parent.kind() { + AstKind::MemberExpression(_) | AstKind::ParenthesizedExpression(_) => { + continue; + } + AstKind::CallExpression(_) => { + return true; + } + _ => { + return false; + } + } + } + + false + } + + /// ```ts + /// var foo = function foo() {}; + /// // ^^^ does not have a read reference, needs manual check + /// foo() + /// ``` + pub fn is_function_assigned_to_same_name_variable(&self) -> bool { + debug_assert!(self.declaration().kind().is_function_like()); + + for parent in self.iter_parents() { + match parent.kind() { + AstKind::MemberExpression(_) | AstKind::ParenthesizedExpression(_) => { + continue; + } + AstKind::VariableDeclarator(decl) => { + return decl + .id + .get_binding_identifier() + .is_some_and(|id| id.name == self.name()) + } + _ => { + return false; + } + } + } + + false + } +} impl NoUnusedVars { + /// Returns `true` if this unused variable declaration should be allowed + /// (i.e. not reported) + pub(super) fn is_allowed_variable_declaration<'a>( + &self, + symbol: &Symbol<'_, 'a>, + decl: &VariableDeclarator<'a>, + ) -> bool { + if decl.kind.is_var() && self.vars.is_local() && symbol.is_root() { + return true; + } + + // check if res is an array/object unpacking pattern that should be ignored + matches!(decl.id.check_unused_binding_pattern(&*self, symbol), Some(res) if res.is_ignore()) + } + pub(super) fn is_allowed_argument<'a>( &self, semantic: &Semantic<'a>, diff --git a/crates/oxc_linter/src/rules/eslint/no_unused_vars/binding_pattern.rs b/crates/oxc_linter/src/rules/eslint/no_unused_vars/binding_pattern.rs index 4ffdd24be7260a..c10b7199b618c2 100644 --- a/crates/oxc_linter/src/rules/eslint/no_unused_vars/binding_pattern.rs +++ b/crates/oxc_linter/src/rules/eslint/no_unused_vars/binding_pattern.rs @@ -142,7 +142,17 @@ impl<'a> CheckBinding<'a> for ArrayPattern<'a> { let Some(el) = el.as_ref() else { continue; }; - let res = el.check_unused_binding_pattern(options, symbol); + // const [a, _b, c] = arr; console.log(a, b) + // here, res will contain data for _b, and we want to check if it + // can be ignored (if it matches destructuredArrayIgnorePattern) + let res = el.check_unused_binding_pattern(options, symbol).map(|res| { + let is_ignorable = options + .destructured_array_ignore_pattern + .as_ref() + .is_some_and(|pattern| pattern.is_match(symbol.name())); + return res | is_ignorable; + }); + if res.is_some() { return res; } diff --git a/crates/oxc_linter/src/rules/eslint/no_unused_vars/mod.rs b/crates/oxc_linter/src/rules/eslint/no_unused_vars/mod.rs index 543e18b02ff4c7..00610ccdefe92b 100644 --- a/crates/oxc_linter/src/rules/eslint/no_unused_vars/mod.rs +++ b/crates/oxc_linter/src/rules/eslint/no_unused_vars/mod.rs @@ -178,9 +178,9 @@ impl Rule for NoUnusedVars { } } AstKind::VariableDeclarator(decl) => { - if decl.kind.is_var() && self.vars.is_local() && symbol.is_root() { + if self.is_allowed_variable_declaration(&symbol, decl) { return; - } + }; let report = if let Some(last_write) = symbol.references().rev().find(|r| r.is_write()) { diagnostic::assign(&symbol, last_write.span()) @@ -195,14 +195,22 @@ impl Rule for NoUnusedVars { } ctx.diagnostic(diagnostic::param(&symbol)); } - AstKind::CatchParameter(_) => ctx.diagnostic(diagnostic::declared(&symbol)), - unknown => { - debug_assert!( - false, - "NoUnusedVars::run_on_symbol - unhandled AstKind: {:?}", - unknown.debug_name() - ) + AstKind::Function(_) => { + if symbol.is_function_callback() + || symbol.is_function_assigned_to_same_name_variable() + { + return; + } + ctx.diagnostic(diagnostic::declared(&symbol)); } + _ => ctx.diagnostic(diagnostic::declared(&symbol)), + // unknown => { + // debug_assert!( + // false, + // "NoUnusedVars::run_on_symbol - unhandled AstKind: {:?}", + // unknown.debug_name() + // ) + // } }; // match (is_ignored, is_used) { // (true, true) => {