From ba53bc9942d470b6b2e69dedd1fe74b80562b512 Mon Sep 17 00:00:00 2001 From: DonIsaac <22823424+DonIsaac@users.noreply.github.com> Date: Wed, 9 Oct 2024 19:43:58 +0000 Subject: [PATCH] fix(linter/no-unused-vars): false positives in TS type assertions (#6397) Fixes several false positive cases for referenced variables and declarations that are inside type casts, `as` expressions, `satisfies` expressions, non-null assertions, and the like. ```js function foo(el) { return el + 1 } const arr = [1, 2, 3] const mapped = arr.map(foo as unknown as SomePredicateType) ``` --- .../src/rules/eslint/no_unused_vars/allowed.rs | 2 +- .../src/rules/eslint/no_unused_vars/symbol.rs | 17 +++++++++++++++-- .../rules/eslint/no_unused_vars/tests/oxc.rs | 1 + .../src/rules/eslint/no_unused_vars/usage.rs | 8 ++++---- 4 files changed, 21 insertions(+), 7 deletions(-) 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 bebf230e0ae1b..375c851782db0 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 @@ -24,7 +24,7 @@ impl<'s, 'a> Symbol<'s, 'a> { assert!(kind.is_function_like() || matches!(kind, AstKind::Class(_))); } - for parent in self.iter_parents() { + for parent in self.iter_relevant_parents() { match parent.kind() { AstKind::MemberExpression(_) | AstKind::ParenthesizedExpression(_) // e.g. `const x = [function foo() {}]` diff --git a/crates/oxc_linter/src/rules/eslint/no_unused_vars/symbol.rs b/crates/oxc_linter/src/rules/eslint/no_unused_vars/symbol.rs index 20f65a9a74921..87b96e45fb059 100644 --- a/crates/oxc_linter/src/rules/eslint/no_unused_vars/symbol.rs +++ b/crates/oxc_linter/src/rules/eslint/no_unused_vars/symbol.rs @@ -105,7 +105,12 @@ impl<'s, 'a> Symbol<'s, 'a> { self.nodes().iter_parents(self.declaration_id()) } - pub fn iter_relevant_parents( + #[inline] + pub fn iter_relevant_parents(&self) -> impl Iterator> + Clone + '_ { + self.iter_relevant_parents_of(self.declaration_id()) + } + + pub fn iter_relevant_parents_of( &self, node_id: NodeId, ) -> impl Iterator> + Clone + '_ { @@ -131,7 +136,15 @@ impl<'s, 'a> Symbol<'s, 'a> { #[inline] const fn is_relevant_kind(kind: AstKind<'a>) -> bool { - !matches!(kind, AstKind::ParenthesizedExpression(_)) + !matches!( + kind, + AstKind::ParenthesizedExpression(_) + | AstKind::TSAsExpression(_) + | AstKind::TSSatisfiesExpression(_) + | AstKind::TSInstantiationExpression(_) + | AstKind::TSNonNullExpression(_) + | AstKind::TSTypeAssertion(_) + ) } /// 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 844bf2e714808..e8ed79af2ca70 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 @@ -696,6 +696,7 @@ fn test_used_declarations() { // first put into an intermediate (e.g. an object or array) "arr.reduce(function reducer (acc, el) { return acc + el }, 0)", "console.log({ foo: function foo() {} })", + "console.log({ foo: function foo() {} as unknown as Function })", "test.each([ function foo() {} ])('test some function', (fn) => { expect(fn(1)).toBe(1) })", "export default { foo() {} }", "const arr = [function foo() {}, function bar() {}]; console.log(arr[0]())", 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 649bbf825c4cd..a9fbda3671d8a 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 @@ -244,7 +244,7 @@ impl<'s, 'a> Symbol<'s, 'a> { /// type Foo = Array /// ``` fn is_type_self_usage(&self, reference: &Reference) -> bool { - for parent in self.iter_relevant_parents(reference.node_id()).map(AstNode::kind) { + for parent in self.iter_relevant_parents_of(reference.node_id()).map(AstNode::kind) { match parent { AstKind::TSTypeAliasDeclaration(decl) => { return self == &decl.id; @@ -425,7 +425,7 @@ impl<'s, 'a> Symbol<'s, 'a> { /// Check if a [`AstNode`] is within a return statement or implicit return. fn is_in_return_statement(&self, node_id: NodeId) -> bool { - for parent in self.iter_relevant_parents(node_id).map(AstNode::kind) { + for parent in self.iter_relevant_parents_of(node_id).map(AstNode::kind) { match parent { AstKind::ReturnStatement(_) => return true, AstKind::ExpressionStatement(_) => continue, @@ -652,7 +652,7 @@ impl<'s, 'a> Symbol<'s, 'a> { /// 2. "relevant" nodes are non "transparent". For example, parenthesis are "transparent". #[inline] fn get_ref_relevant_node(&self, reference: &Reference) -> Option<&AstNode<'a>> { - self.iter_relevant_parents(reference.node_id()).next() + self.iter_relevant_parents_of(reference.node_id()).next() } /// Find the [`SymbolId`] for the nearest function declaration or expression @@ -662,7 +662,7 @@ impl<'s, 'a> Symbol<'s, 'a> { // name from the variable its assigned to. let mut needs_variable_identifier = false; - for parent in self.iter_relevant_parents(node_id) { + for parent in self.iter_relevant_parents_of(node_id) { match parent.kind() { AstKind::Function(f) => { return f.id.as_ref().and_then(|id| id.symbol_id.get());