Skip to content

Commit

Permalink
fix(linter/no-unused-vars): false positive for functions and classes …
Browse files Browse the repository at this point in the history
…in arrays (#6394)

Fixes a false positive when a function or class expression is an array element.
Found while linting Jest tests in an internal repo.

```js
import { stringify } from './custom-stringify';

test.each(['a', 1, function foo() {}])('When stringifying %p, matches the snapshot', input => {
    expect(stringify(input)).toMatchSnapshot();
})
```
  • Loading branch information
DonIsaac committed Oct 9, 2024
1 parent 994b60b commit e08f956
Show file tree
Hide file tree
Showing 3 changed files with 59 additions and 1 deletion.
11 changes: 10 additions & 1 deletion crates/oxc_linter/src/rules/eslint/no_unused_vars/allowed.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,13 @@ impl<'s, 'a> Symbol<'s, 'a> {

for parent in self.iter_parents() {
match parent.kind() {
AstKind::MemberExpression(_) | AstKind::ParenthesizedExpression(_) => {
AstKind::MemberExpression(_) | AstKind::ParenthesizedExpression(_)
// e.g. `const x = [function foo() {}]`
// Only considered used if the array containing the symbol is used.
| AstKind::ArrayExpressionElement(_)
| AstKind::ExpressionArrayElement(_)
| AstKind::ArrayExpression(_)
=> {
continue;
}
// Returned from another function. Definitely won't be the same
Expand All @@ -37,6 +43,9 @@ impl<'s, 'a> Symbol<'s, 'a> {
// Function declaration is passed as an argument to another function.
| AstKind::CallExpression(_) | AstKind::Argument(_)
// e.g. `const x = { foo: function foo() {} }`
// Allowed off-the-bat since objects being the only child of an
// ExpressionStatement is rare, since you would need to wrap the
// object in parentheses to avoid creating a block statement.
| AstKind::ObjectProperty(_)
// e.g. var foo = function bar() { }
// we don't want to check for violations on `bar`, just `foo`
Expand Down
31 changes: 31 additions & 0 deletions crates/oxc_linter/src/rules/eslint/no_unused_vars/tests/oxc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -689,6 +689,37 @@ fn test_imports() {
.test_and_snapshot();
}

#[test]
fn test_used_declarations() {
let pass = vec![
// function declarations passed as arguments, used in assignments, etc. are used, even if they are
// 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() {} })",
"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]())",
"const foo = function foo() {}; console.log(foo())",
"const foo = function bar() {}; console.log(foo())",
// Class expressions behave similarly
"console.log([class Foo {}])",
"export default { foo: class Foo {} }",
"export const Foo = class Foo {}",
"export const Foo = class Bar {}",
"export const Foo = @SomeDecorator() class Foo {}",
];
let fail = vec![
// array is not used, so the function is not used
";[function foo() {}]",
";[class Foo {}]",
];

Tester::new(NoUnusedVars::NAME, pass, fail)
.intentionally_allow_no_fix_tests()
.with_snapshot_suffix("oxc-used-declarations")
.test_and_snapshot();
}

#[test]
fn test_exports() {
let pass = vec![
Expand Down
18 changes: 18 additions & 0 deletions crates/oxc_linter/src/snapshots/[email protected]
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
---
source: crates/oxc_linter/src/tester.rs
---
eslint(no-unused-vars): Function 'foo' is declared but never used.
╭─[no_unused_vars.tsx:1:12]
1 │ ;[function foo() {}]
· ─┬─
· ╰── 'foo' is declared here
╰────
help: Consider removing this declaration.

eslint(no-unused-vars): Class 'Foo' is declared but never used.
╭─[no_unused_vars.tsx:1:9]
1 │ ;[class Foo {}]
· ─┬─
· ╰── 'Foo' is declared here
╰────
help: Consider removing this declaration.

0 comments on commit e08f956

Please sign in to comment.