Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(linter/no-unused-vars): false positive for functions and classes in arrays #6394

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
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.