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 4424d3a41e9a0..ee6e95f410d0d 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 @@ -59,7 +59,7 @@ impl<'a> HasAnyUsedBinding<'a> for ArrayPattern<'a> { self.elements.iter().flatten().any(|el| { // if the destructured element is ignored, it is considered used el.get_identifier_name() - .is_some_and(|name| ctx.options.is_ignored_array_destructured(&name)) + .is_some_and(|name| ctx.options.is_ignored_array_destructured(&name).is_some()) || el.has_any_used_binding(ctx) }) || self.rest.as_ref().is_some_and(|rest| rest.argument.has_any_used_binding(ctx)) } diff --git a/crates/oxc_linter/src/rules/eslint/no_unused_vars/ignored.rs b/crates/oxc_linter/src/rules/eslint/no_unused_vars/ignored.rs index f5cff660bf1a2..496ae594c9d7a 100644 --- a/crates/oxc_linter/src/rules/eslint/no_unused_vars/ignored.rs +++ b/crates/oxc_linter/src/rules/eslint/no_unused_vars/ignored.rs @@ -15,7 +15,7 @@ pub(super) enum FoundStatus { #[default] NotFound, /// The target identifier was found and it meets ignore criteria - Ignored, + Ignored(IgnoreReason), /// The target identifier was found and does not meet ignore criteria NotIgnored, } @@ -23,12 +23,15 @@ pub(super) enum FoundStatus { impl FoundStatus { #[inline] pub const fn is_found(self) -> bool { - matches!(self, Self::Ignored | Self::NotIgnored) + matches!(self, Self::Ignored(_) | Self::NotIgnored) } #[inline] - pub const fn is_ignored(self) -> bool { - matches!(self, Self::Ignored) + pub const fn into_ignored(self) -> Ignored { + match self { + Self::Ignored(reason) => Ignored::from_reason(reason), + _ => Ignored::not_ignored(), + } } #[inline] @@ -40,19 +43,101 @@ impl FoundStatus { /// /// `false` does not make already ignored values not-ignored. #[inline] - pub fn ignore(self, is_ignored: bool) -> Self { - match self { - Self::NotIgnored if is_ignored => Self::Ignored, + pub fn ignore(self, reason: Ignored) -> Self { + match (self, reason.0) { + (Self::NotIgnored, Some(reason)) => Self::Ignored(reason), + // NamePattern takes priority + (Self::Ignored(_), Some(IgnoreReason::NamePattern)) => { + Self::Ignored(IgnoreReason::NamePattern) + } _ => self, } } } +/// Informs why a [Symbol] was ignored. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub(super) enum IgnoreReason { + /// Symbol is ignored because it is 1) rest sibling, 2) one or more of its siblings + /// are used, and 3) user has configured [ignoreRestSiblings] to `true`. + /// + /// [ignoreRestSiblings]: super::NoUnusedVarsOptions::ignore_rest_siblings + RestSibling, + /// Symbol is ignored because it's bound name matches a `IgnorePattern` + /// setting. + NamePattern, + /// Symbol is ignored because `caughtErrors` is set to `"none"`. + CaughtErrorsNone, + /// Symbol is ignored because it is a class declaration with a `static` + /// initializer block. + ClassStaticInitBlock, + /// Symbol is ignored because it is declared inside of an ambient scope. + /// + /// Not all symbols in ambient scopes are ignored. + AmbientDeclaration, +} + +/// Describes if a [Symbol] is ignored and why. +/// `None` for not ignored, `Some(reason)` when ignored. +#[derive(Debug, Clone, Copy)] +pub(super) struct Ignored(Option); + +impl Ignored { + pub fn new(is_ignored: bool, reason: IgnoreReason) -> Self { + Self(is_ignored.then_some(reason)) + } + + #[inline] + pub const fn not_ignored() -> Self { + Self(None) + } + + #[inline] + pub const fn from_reason(reason: IgnoreReason) -> Self { + Self(Some(reason)) + } + + pub fn or(self, other: Self) -> Self { + // NamePattern takes priority + match (*self, *other) { + (_, Some(IgnoreReason::NamePattern)) | (Some(IgnoreReason::NamePattern), _) => { + Self::from_reason(IgnoreReason::NamePattern) + } + (Some(_), _) => self, + _ => other, + } + } +} + +impl std::ops::Deref for Ignored { + type Target = Option; + + #[inline] + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl From for Ignored { + fn from(status: FoundStatus) -> Self { + status.into_ignored() + } +} + +impl From for FoundStatus { + fn from(ignored: Ignored) -> Self { + match ignored.0 { + Some(reason) => FoundStatus::Ignored(reason), + None => FoundStatus::NotIgnored, + } + } +} + impl NoUnusedVars { /// Check if a symbol should be ignored based on how it's declared. /// /// Does not handle ignore checks for re-assignments to array/object destructures. - pub(super) fn is_ignored(&self, symbol: &Symbol<'_, '_>) -> bool { + pub(super) fn is_ignored(&self, symbol: &Symbol<'_, '_>) -> Ignored { let declared_binding = symbol.name(); match symbol.declaration().kind() { m if m.is_module_declaration() => self.is_ignored_var(declared_binding), @@ -73,35 +158,36 @@ impl NoUnusedVars { // (i.e., declared or in a declared module). Functions without bodies inside // non-declared namespaces should still be checked. if func.r#type.is_typescript_syntax() || func.body.is_none() { - return func.declare || symbol.is_in_declared_module(); + return Ignored::new( + func.declare || symbol.is_in_declared_module(), + IgnoreReason::AmbientDeclaration, + ); } self.is_ignored_var(declared_binding) } AstKind::Class(class) => { - if class.declare - || self.ignore_class_with_static_init_block - && class.body.body.iter().any(ClassElement::is_static_block) + if class.declare { + return Ignored::from_reason(IgnoreReason::AmbientDeclaration); + } + if self.ignore_class_with_static_init_block + && class.body.body.iter().any(ClassElement::is_static_block) { - return true; + return Ignored::from_reason(IgnoreReason::ClassStaticInitBlock); } self.is_ignored_var(declared_binding) } - AstKind::CatchParameter(catch) => { - self.is_ignored_catch_err(declared_binding) - || self.is_ignored_binding_pattern(symbol, &catch.pattern) - } - AstKind::VariableDeclarator(decl) => { - self.is_ignored_var(declared_binding) - || self.is_ignored_binding_pattern(symbol, &decl.id) - } - AstKind::FormalParameter(param) => { - self.is_ignored_arg(declared_binding) - || self.is_ignored_binding_pattern(symbol, ¶m.pattern) - } - AstKind::FormalParameterRest(param) => { - self.is_ignored_arg(declared_binding) - || self.is_ignored_binding_pattern(symbol, ¶m.rest.argument) - } + AstKind::CatchParameter(catch) => self + .is_ignored_catch_err(declared_binding) + .or(self.is_ignored_binding_pattern(symbol, &catch.pattern)), + AstKind::VariableDeclarator(decl) => self + .is_ignored_var(declared_binding) + .or(self.is_ignored_binding_pattern(symbol, &decl.id)), + AstKind::FormalParameter(param) => self + .is_ignored_arg(declared_binding) + .or(self.is_ignored_binding_pattern(symbol, ¶m.pattern)), + AstKind::FormalParameterRest(param) => self + .is_ignored_arg(declared_binding) + .or(self.is_ignored_binding_pattern(symbol, ¶m.rest.argument)), s => { // panic when running test cases so we can find unsupported node kinds debug_assert!( @@ -109,7 +195,7 @@ impl NoUnusedVars { "is_ignored_decl did not know how to handle node of kind {}", s.debug_name() ); - false + Ignored::not_ignored() } } } @@ -118,18 +204,24 @@ impl NoUnusedVars { &self, symbol: &Symbol<'_, 'a>, binding: &BindingPattern<'a>, - ) -> bool { - self.should_search_destructures() - && self.search_binding_pattern(symbol, binding).is_ignored() + ) -> Ignored { + if self.should_search_destructures() { + self.search_binding_pattern(symbol, binding).into() + } else { + Ignored::not_ignored() + } } pub(super) fn is_ignored_assignment_target<'a>( &self, symbol: &Symbol<'_, 'a>, assignment: &AssignmentTarget<'a>, - ) -> bool { - self.should_search_destructures() - && self.search_assignment_target(symbol, assignment).is_ignored() + ) -> Ignored { + if self.should_search_destructures() { + self.search_assignment_target(symbol, assignment).into() + } else { + Ignored::not_ignored() + } } /// Do we need to search binding patterns to tell if a symbol is ignored, or @@ -287,10 +379,11 @@ impl NoUnusedVars { // other destructure, mark it as ignored if it matches the // configured array destructure ignore pattern. if status.is_found() { - return status.ignore( - el.is_simple_assignment_target() - && self.is_ignored_array_destructured(target.name()), - ); + return if el.is_simple_assignment_target() { + status.ignore(self.is_ignored_array_destructured(target.name())) + } else { + status + }; } // continue with search } @@ -309,8 +402,8 @@ impl NoUnusedVars { } #[inline] - fn is_ignored_spread_neighbor(&self, has_rest: bool) -> bool { - self.ignore_rest_siblings && has_rest + fn is_ignored_spread_neighbor(&self, has_rest: bool) -> Ignored { + Ignored::new(self.ignore_rest_siblings && has_rest, IgnoreReason::RestSibling) } // ========================================================================= @@ -318,24 +411,39 @@ impl NoUnusedVars { // ========================================================================= #[inline] - pub(super) fn is_ignored_var(&self, name: &str) -> bool { - Self::is_none_or_match(self.vars_ignore_pattern.as_ref(), name) + pub(super) fn is_ignored_var(&self, name: &str) -> Ignored { + Ignored::new( + Self::is_none_or_match(self.vars_ignore_pattern.as_ref(), name), + IgnoreReason::NamePattern, + ) } #[inline] - pub(super) fn is_ignored_arg(&self, name: &str) -> bool { - Self::is_none_or_match(self.args_ignore_pattern.as_ref(), name) + pub(super) fn is_ignored_arg(&self, name: &str) -> Ignored { + Ignored::new( + Self::is_none_or_match(self.args_ignore_pattern.as_ref(), name), + IgnoreReason::NamePattern, + ) } #[inline] - pub(super) fn is_ignored_array_destructured(&self, name: &str) -> bool { - Self::is_none_or_match(self.destructured_array_ignore_pattern.as_ref(), name) + pub(super) fn is_ignored_array_destructured(&self, name: &str) -> Ignored { + Ignored::new( + Self::is_none_or_match(self.destructured_array_ignore_pattern.as_ref(), name), + IgnoreReason::NamePattern, + ) } #[inline] - pub(super) fn is_ignored_catch_err(&self, name: &str) -> bool { - *!self.caught_errors - || Self::is_none_or_match(self.caught_errors_ignore_pattern.as_ref(), name) + pub(super) fn is_ignored_catch_err(&self, name: &str) -> Ignored { + if *!self.caught_errors { + Ignored::from_reason(IgnoreReason::CaughtErrorsNone) + } else { + Ignored::new( + Self::is_none_or_match(self.caught_errors_ignore_pattern.as_ref(), name), + IgnoreReason::NamePattern, + ) + } } #[inline] @@ -353,8 +461,15 @@ mod test { use oxc_span::Atom; use super::super::NoUnusedVars; + use super::{IgnoreReason, Ignored}; use crate::rule::Rule as _; + impl std::cmp::PartialEq for super::Ignored { + fn eq(&self, other: &Ignored) -> bool { + self.0.eq(&other.0) + } + } + #[test] fn test_ignored() { let rule = NoUnusedVars::from_configuration(serde_json::json!([ @@ -368,22 +483,46 @@ mod test { ])) .unwrap(); - assert!(rule.is_ignored_var("_x")); - assert!(rule.is_ignored_var(&Atom::from("_x"))); - assert!(!rule.is_ignored_var("notIgnored")); - - assert!(rule.is_ignored_arg("ignored")); - assert!(rule.is_ignored_arg("alsoIgnored")); - assert!(rule.is_ignored_arg(&Atom::from("ignored"))); - assert!(rule.is_ignored_arg(&Atom::from("alsoIgnored"))); - - assert!(rule.is_ignored_catch_err("err")); - assert!(rule.is_ignored_catch_err("error")); - assert!(!rule.is_ignored_catch_err("e")); - - assert!(rule.is_ignored_array_destructured("_x")); - assert!(rule.is_ignored_array_destructured(&Atom::from("_x"))); - assert!(!rule.is_ignored_array_destructured("notIgnored")); + assert_eq!(rule.is_ignored_var("_x"), Ignored::from_reason(IgnoreReason::NamePattern)); + assert_eq!( + rule.is_ignored_var(&Atom::from("_x")), + Ignored::from_reason(IgnoreReason::NamePattern) + ); + assert_eq!(rule.is_ignored_var("notIgnored"), Ignored::not_ignored()); + + assert_eq!(rule.is_ignored_arg("ignored"), Ignored::from_reason(IgnoreReason::NamePattern)); + assert_eq!( + rule.is_ignored_arg("alsoIgnored"), + Ignored::from_reason(IgnoreReason::NamePattern) + ); + assert_eq!( + rule.is_ignored_arg(&Atom::from("ignored")), + Ignored::from_reason(IgnoreReason::NamePattern) + ); + assert_eq!( + rule.is_ignored_arg(&Atom::from("alsoIgnored")), + Ignored::from_reason(IgnoreReason::NamePattern) + ); + + assert_eq!( + rule.is_ignored_catch_err("err"), + Ignored::from_reason(IgnoreReason::NamePattern) + ); + assert_eq!( + rule.is_ignored_catch_err("error"), + Ignored::from_reason(IgnoreReason::NamePattern) + ); + assert_eq!(rule.is_ignored_catch_err("e"), Ignored::not_ignored()); + + assert_eq!( + rule.is_ignored_array_destructured("_x"), + Ignored::from_reason(IgnoreReason::NamePattern) + ); + assert_eq!( + rule.is_ignored_array_destructured(&Atom::from("_x")), + Ignored::from_reason(IgnoreReason::NamePattern) + ); + assert_eq!(rule.is_ignored_array_destructured("notIgnored"), Ignored::not_ignored()); } #[test] @@ -395,9 +534,12 @@ mod test { } ])) .unwrap(); - assert!(rule.is_ignored_catch_err("_")); - assert!(rule.is_ignored_catch_err("_err")); - assert!(!rule.is_ignored_catch_err("err")); + assert_eq!(rule.is_ignored_catch_err("_"), Ignored::from_reason(IgnoreReason::NamePattern)); + assert_eq!( + rule.is_ignored_catch_err("_err"), + Ignored::from_reason(IgnoreReason::NamePattern) + ); + assert_eq!(rule.is_ignored_catch_err("err"), Ignored::not_ignored()); let rule = NoUnusedVars::from_configuration(serde_json::json!([ { @@ -405,8 +547,17 @@ mod test { } ])) .unwrap(); - assert!(rule.is_ignored_catch_err("_")); - assert!(rule.is_ignored_catch_err("_err")); - assert!(rule.is_ignored_catch_err("err")); + assert_eq!( + rule.is_ignored_catch_err("_"), + Ignored::from_reason(IgnoreReason::CaughtErrorsNone) + ); + assert_eq!( + rule.is_ignored_catch_err("_err"), + Ignored::from_reason(IgnoreReason::CaughtErrorsNone) + ); + assert_eq!( + rule.is_ignored_catch_err("err"), + Ignored::from_reason(IgnoreReason::CaughtErrorsNone) + ); } } 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 4ad10afa0fd65..0992e65b33d63 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 @@ -11,6 +11,7 @@ mod usage; use std::ops::Deref; +use ignored::IgnoreReason; use options::{IgnorePattern, NoUnusedVarsFixMode, NoUnusedVarsOptions}; use oxc_ast::AstKind; use oxc_diagnostics::OxcDiagnostic; @@ -234,28 +235,26 @@ impl NoUnusedVars { fn run_on_symbol_internal<'a>(&self, symbol: &Symbol<'_, 'a>, ctx: &LintContext<'a>) { let is_ignored = self.is_ignored(symbol); - if is_ignored && !self.report_used_ignore_pattern { + if is_ignored.is_some() && !self.report_used_ignore_pattern { return; } // Order matters. We want to call cheap/high "yield" functions first. let is_used = symbol.is_exported() || symbol.has_usages(self); - match (is_used, is_ignored) { - (true, true) => { + match (is_used, *is_ignored) { + // used, ignored because variable name matches one of several + // ignore patterns. Report if used. + (true, Some(IgnoreReason::NamePattern)) => { if self.report_used_ignore_pattern { ctx.diagnostic(diagnostic::used_ignored(symbol, &self.vars_ignore_pattern)); } return; - }, - // not used but ignored, no violation - (false, true) - // used and not ignored, no violation - | (true, false) => { - return - }, + } + // used, ignored because of other ignore reason (e.g. rest siblings) + (_, Some(_)) | (true, None) => return, // needs acceptance check and/or reporting - (false, false) => {} + (false, None) => {} } let declaration = symbol.declaration(); diff --git a/crates/oxc_linter/src/rules/eslint/no_unused_vars/tests/eslint.rs b/crates/oxc_linter/src/rules/eslint/no_unused_vars/tests/eslint.rs index e4c4081d7ee6b..adde47df693e0 100644 --- a/crates/oxc_linter/src/rules/eslint/no_unused_vars/tests/eslint.rs +++ b/crates/oxc_linter/src/rules/eslint/no_unused_vars/tests/eslint.rs @@ -458,7 +458,7 @@ fn test() { let fail = vec![ ("function foox() { return foox(); }", None), - // ("(function() { function foox() { if (true) { return foox(); } } }())", None), + ("(function() { function foox() { if (true) { return foox(); } } }())", None), ("var a=10", None), ("function f() { var a = 1; return function(){ f(a *= 2); }; }", None), ("function f() { var a = 1; return function(){ f(++a); }; }", None), diff --git a/crates/oxc_linter/src/rules/eslint/no_unused_vars/tests/oxc.rs b/crates/oxc_linter/src/rules/eslint/no_unused_vars/tests/oxc.rs index bd47bbbd8a7ee..9d2bf0781ea82 100644 --- a/crates/oxc_linter/src/rules/eslint/no_unused_vars/tests/oxc.rs +++ b/crates/oxc_linter/src/rules/eslint/no_unused_vars/tests/oxc.rs @@ -493,6 +493,10 @@ fn test_vars_catch() { let pass = vec![ ("try {} catch (e) { throw e }", None), ("try {} catch (e) { }", Some(json!([{ "caughtErrors": "none" }]))), + ( + "try {} catch (err) { console.error(err) }", + Some(json!([{ "caughtErrors": "none", "reportUsedIgnorePattern": true }])), + ), ("try {} catch { }", None), ("try {} catch(_) { }", Some(json!([{ "caughtErrorsIgnorePattern": "^_" }]))), ( @@ -516,6 +520,12 @@ fn test_vars_catch() { "try {} catch(foo) { }", Some(json!([{ "caughtErrors": "all", "caughtErrorsIgnorePattern": "^ignored" }])), ), + ( + "try {} catch(_err) { console.error(_err) }", + Some( + json!([{ "caughtErrors": "all", "caughtErrorsIgnorePattern": "^_", "reportUsedIgnorePattern": true }]), + ), + ), ]; // these suggestion fixes are safe @@ -1524,6 +1534,113 @@ fn test_jsx_non_ascii() { .test(); } +#[test] +fn test_ignore() { + let pass = vec![ + ("arr.map(({ x, ...rest }) => rest)", Some(json!([{ "ignoreRestSiblings": true }]))), + ( + "arr.map(({ x, ...rest }) => rest)", + Some(json!([{ "ignoreRestSiblings": true, "reportUsedIgnorePattern": false }])), + ), + // using an unpacked property does not count as an illegal usage of ignored symbol + ( + "arr.map(({ x, ...rest }) => ({ x, ...rest }))", + Some(json!([{ "ignoreRestSiblings": true, "reportUsedIgnorePattern": true }])), + ), + ("const { x: _x, y } = obj; console.log(y)", Some(json!([{ "varsIgnorePattern": "^_" }]))), + ( + "const { a: { b: [c, _d] }, ...rest } = obj; console.log(c, rest);", + Some( + json!([{ "ignoreRestSiblings": true, "reportUsedIgnorePattern": true, "destructuredArrayIgnorePattern": "^_" }]), + ), + ), + // matches argsIgnorePattern, not varsIgnorePattern + ( + "const [a, _b] = arr; console.log(a, _b)", + Some(json!([{ "reportUsedIgnorePattern": true, "argsIgnorePattern": "^_" }])), + ), + // property name matches ignore pattern; bound name does not + ( + "const { _x: x, y } = obj; console.log(x, y)", + Some(json!([{ "reportUsedIgnorePattern": true, "varsIgnorePattern": "^_" }])), + ), + ( + "arr.map(({ x, ...rest }) => rest)", + Some( + json!([{ "ignoreRestSiblings": true, "reportUsedIgnorePattern": true, "varsIgnorePattern": "^_" }]), + ), + ), + ("const _x = 1;", Some(json!([{ "varsIgnorePattern": "^_" }]))), + ("function foo(_bar) {}; foo()", Some(json!([{ "argsIgnorePattern": "^_" }]))), + ( + "const [a, _b] = arr; console.log(a)", + Some(json!([{ "destructuredArrayIgnorePattern": "^_" }])), + ), + ( + "const { x: _x, ...rest } = obj; console.log(rest)", + Some( + json!([{ "ignoreRestSiblings": true, "reportUsedIgnorePattern": true, "varsIgnorePattern": "^_" }]), + ), + ), + // top-level var matches default _ prefix and is used — but reportUsedIgnorePattern + // only fires when varsIgnorePattern is explicitly set, not the default prefix + ("const _x = 1; console.log(_x)", Some(json!([{ "reportUsedIgnorePattern": true }]))), + ]; + + let fail = vec![ + // not a rest sibling + ("const { x, y } = obj; console.log(y)", Some(json!([{ "ignoreRestSiblings": true }]))), + ( + "function _foo() {} _foo()", + Some(json!([{ "reportUsedIgnorePattern": true, "varsIgnorePattern": "^_" }])), + ), + // getting ignored both by ignoreRestSiblings and ignore pattern counts as used ignore pattern + ( + "const { x: _x, ...rest } = obj; console.log(_x, rest)", + Some( + json!([{ "ignoreRestSiblings": true, "reportUsedIgnorePattern": true, "varsIgnorePattern": "^_" }]), + ), + ), + // rest sibling also matches argsIgnorePattern and is used + ( + "arr.map(({ x: _x, ...rest }) => ({ x: _x, ...rest }))", + Some( + json!([{ "ignoreRestSiblings": true, "reportUsedIgnorePattern": true, "argsIgnorePattern": "^_" }]), + ), + ), + // ignoreRestSiblings does not apply to unpacked arrays + ( + "const { a: { b: [c, unused], ...rest } } = obj; console.log(c, rest);", + Some(json!([{ "ignoreRestSiblings": true }])), + ), + ( + "const { a: { b: [_c], ...rest } } = obj; console.log(_c, rest);", + Some( + json!([{ "ignoreRestSiblings": true, "reportUsedIgnorePattern": true, "destructuredArrayIgnorePattern": "^_" }]), + ), + ), + ( + "const [a, _b] = arr; console.log(a, _b)", + Some( + json!([{ "reportUsedIgnorePattern": true, "destructuredArrayIgnorePattern": "^_" }]), + ), + ), + ( + "function foo(_bar) { return _bar; } foo(1)", + Some(json!([{ "reportUsedIgnorePattern": true, "argsIgnorePattern": "^_" }])), + ), + ( + "try { foo() } catch (_err) { console.error(_err) }", + Some(json!([{ "reportUsedIgnorePattern": true, "caughtErrorsIgnorePattern": "^_" }])), + ), + ]; + + Tester::new(NoUnusedVars::NAME, NoUnusedVars::PLUGIN, pass, fail) + .intentionally_allow_no_fix_tests() + .with_snapshot_suffix("oxc-ignore") + .test_and_snapshot(); +} + // #[test] // fn test_template() { // let pass = vec![]; diff --git a/crates/oxc_linter/src/rules/eslint/no_unused_vars/usage.rs b/crates/oxc_linter/src/rules/eslint/no_unused_vars/usage.rs index 43af361397dc6..611c19c6e4ff4 100644 --- a/crates/oxc_linter/src/rules/eslint/no_unused_vars/usage.rs +++ b/crates/oxc_linter/src/rules/eslint/no_unused_vars/usage.rs @@ -258,7 +258,7 @@ impl<'a> Symbol<'_, 'a> { | AstKind::AssignmentTargetPropertyIdentifier(_) | AstKind::AssignmentTargetPropertyProperty(_) => {} AstKind::AssignmentExpression(assignment) => { - return options.is_ignored_assignment_target(self, &assignment.left); + return options.is_ignored_assignment_target(self, &assignment.left).is_some(); } // Needs to be checked separately from AssignmentTarget due to // weird heritage bug for object assignment patterns. @@ -267,14 +267,14 @@ impl<'a> Symbol<'_, 'a> { // expression instead of the top-level AssignmentTarget AstKind::ObjectAssignmentTarget(obj) => { match options.search_obj_assignment_target(self, obj) { - FoundStatus::Ignored => return true, + FoundStatus::Ignored(_) => return true, FoundStatus::NotIgnored => return false, FoundStatus::NotFound => {} } } AstKind::ArrayAssignmentTarget(arr) => { match options.search_array_assignment_target(self, arr) { - FoundStatus::Ignored => return true, + FoundStatus::Ignored(_) => return true, FoundStatus::NotIgnored => return false, FoundStatus::NotFound => {} } diff --git a/crates/oxc_linter/src/snapshots/eslint_no_unused_vars@eslint.snap b/crates/oxc_linter/src/snapshots/eslint_no_unused_vars@eslint.snap index d6210248c6c50..d73037c0d225f 100644 --- a/crates/oxc_linter/src/snapshots/eslint_no_unused_vars@eslint.snap +++ b/crates/oxc_linter/src/snapshots/eslint_no_unused_vars@eslint.snap @@ -1,7 +1,6 @@ --- source: crates/oxc_linter/src/tester.rs --- - ⚠ eslint(no-unused-vars): Function 'foox' is declared but never used. ╭─[no_unused_vars.tsx:1:10] 1 │ function foox() { return foox(); } @@ -10,6 +9,14 @@ source: crates/oxc_linter/src/tester.rs ╰──── help: Consider removing this declaration. + ⚠ eslint(no-unused-vars): Function 'foox' is declared but never used. + ╭─[no_unused_vars.tsx:1:24] + 1 │ (function() { function foox() { if (true) { return foox(); } } }()) + · ──┬─ + · ╰── 'foox' is declared here + ╰──── + help: Consider removing this declaration. + ⚠ eslint(no-unused-vars): Variable 'a' is declared but never used. Unused variables should start with a '_'. ╭─[no_unused_vars.tsx:1:5] 1 │ var a=10 diff --git a/crates/oxc_linter/src/snapshots/eslint_no_unused_vars@oxc-ignore.snap b/crates/oxc_linter/src/snapshots/eslint_no_unused_vars@oxc-ignore.snap new file mode 100644 index 0000000000000..c879a89c0f2f7 --- /dev/null +++ b/crates/oxc_linter/src/snapshots/eslint_no_unused_vars@oxc-ignore.snap @@ -0,0 +1,75 @@ +--- +source: crates/oxc_linter/src/tester.rs +--- + + ⚠ eslint(no-unused-vars): Variable 'x' is declared but never used. + ╭─[no_unused_vars.tsx:1:9] + 1 │ const { x, y } = obj; console.log(y) + · ┬ + · ╰── 'x' is declared here + ╰──── + help: Consider removing this declaration. + + ⚠ eslint(no-unused-vars): Function '_foo' is marked as ignored but is used. + ╭─[no_unused_vars.tsx:1:10] + 1 │ function _foo() {} _foo() + · ──┬─ + · ╰── '_foo' is declared here + ╰──── + help: Consider renaming this function to 'foo'. + + ⚠ eslint(no-unused-vars): Variable '_x' is marked as ignored but is used. + ╭─[no_unused_vars.tsx:1:12] + 1 │ const { x: _x, ...rest } = obj; console.log(_x, rest) + · ─┬ + · ╰── '_x' is declared here + ╰──── + help: Consider renaming this variable to 'x'. + + ⚠ eslint(no-unused-vars): Variable '_x' is marked as ignored but is used. + ╭─[no_unused_vars.tsx:1:15] + 1 │ arr.map(({ x: _x, ...rest }) => ({ x: _x, ...rest })) + · ─┬ + · ╰── '_x' is declared here + ╰──── + help: Consider renaming this variable. + + ⚠ eslint(no-unused-vars): Variable 'unused' is declared but never used. + ╭─[no_unused_vars.tsx:1:21] + 1 │ const { a: { b: [c, unused], ...rest } } = obj; console.log(c, rest); + · ───┬── + · ╰── 'unused' is declared here + ╰──── + help: Consider removing this declaration. + + ⚠ eslint(no-unused-vars): Variable '_c' is marked as ignored but is used. + ╭─[no_unused_vars.tsx:1:18] + 1 │ const { a: { b: [_c], ...rest } } = obj; console.log(_c, rest); + · ─┬ + · ╰── '_c' is declared here + ╰──── + help: Consider renaming this variable. + + ⚠ eslint(no-unused-vars): Variable '_b' is marked as ignored but is used. + ╭─[no_unused_vars.tsx:1:11] + 1 │ const [a, _b] = arr; console.log(a, _b) + · ─┬ + · ╰── '_b' is declared here + ╰──── + help: Consider renaming this variable. + + ⚠ eslint(no-unused-vars): Variable '_bar' is marked as ignored but is used. + ╭─[no_unused_vars.tsx:1:14] + 1 │ function foo(_bar) { return _bar; } foo(1) + · ──┬─ + · ╰── '_bar' is declared here + ╰──── + help: Consider renaming this variable. + + ⚠ eslint(no-unused-vars): Catch parameter '_err' is marked as ignored but is used. + ╭─[no_unused_vars.tsx:1:22] + 1 │ try { foo() } catch (_err) { console.error(_err) } + · ──┬─ + · ╰── '_err' is declared here + ╰──── + help: Consider renaming this catch parameter. diff --git a/crates/oxc_linter/src/snapshots/eslint_no_unused_vars@oxc-vars-catch.snap b/crates/oxc_linter/src/snapshots/eslint_no_unused_vars@oxc-vars-catch.snap index 0681d3073d743..0f0b63138e672 100644 --- a/crates/oxc_linter/src/snapshots/eslint_no_unused_vars@oxc-vars-catch.snap +++ b/crates/oxc_linter/src/snapshots/eslint_no_unused_vars@oxc-vars-catch.snap @@ -33,3 +33,11 @@ source: crates/oxc_linter/src/tester.rs · ╰── 'foo' is declared here ╰──── help: Consider handling this error. + + ⚠ eslint(no-unused-vars): Catch parameter '_err' is marked as ignored but is used. + ╭─[no_unused_vars.tsx:1:14] + 1 │ try {} catch(_err) { console.error(_err) } + · ──┬─ + · ╰── '_err' is declared here + ╰──── + help: Consider renaming this catch parameter.