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)
```
This commit is contained in:
DonIsaac 2024-10-09 19:43:58 +00:00
parent d3e59c6a6d
commit ba53bc9942
4 changed files with 21 additions and 7 deletions

View file

@ -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() {}]`

View file

@ -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<Item = &AstNode<'a>> + Clone + '_ {
self.iter_relevant_parents_of(self.declaration_id())
}
pub fn iter_relevant_parents_of(
&self,
node_id: NodeId,
) -> impl Iterator<Item = &AstNode<'a>> + 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(_)
)
}
/// <https://github.com/oxc-project/oxc/issues/4739>

View file

@ -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]())",

View file

@ -244,7 +244,7 @@ impl<'s, 'a> Symbol<'s, 'a> {
/// type Foo = Array<Bar>
/// ```
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());