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 66a405bca59c8..3a9cc1cb34f1b 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 @@ -294,4 +294,19 @@ impl NoUnusedVars { _ => false, } } + + /// Returns `true` if this binding rest element should be allowed (i.e. not + /// reported). Currently, this handles the case where a rest element is part + /// of a TS function declaration. + pub(super) fn is_allowed_binding_rest_element(symbol: &Symbol) -> bool { + for parent in symbol.iter_parents() { + // If this is a binding rest element that is part of a TS function parameter, + // for example: `function foo(...messages: string[]) {}`, then we will allow it. + if let AstKind::Function(f) = parent.kind() { + return f.is_typescript_syntax(); + } + } + + false + } } 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 e372623322665..426a50bf05f77 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 @@ -297,6 +297,12 @@ impl NoUnusedVars { } ctx.diagnostic(diagnostic::param(symbol)); } + AstKind::BindingRestElement(_) => { + if NoUnusedVars::is_allowed_binding_rest_element(symbol) { + return; + } + ctx.diagnostic(diagnostic::declared(symbol)); + } AstKind::Class(_) | AstKind::Function(_) => { if self.is_allowed_class_or_function(symbol) { return; 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 b3bf68f00e264..6b79ab96c2601 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 @@ -495,12 +495,31 @@ fn test_functions() { ", "const foo = () => function bar() { }\nfoo()", "module.exports.foo = () => function bar() { }", + // https://github.com/oxc-project/oxc/issues/5406 + " + export function log(message: string, ...interpolations: unknown[]): void; + export function log(message: string, ...interpolations: unknown[]): void { + console.log(message, interpolations); + } + ", + "declare function func(strings: any, ...values: any[]): object" ]; let fail = vec![ "function foo() {}", "function foo() { foo() }", "const foo = () => { function bar() { } }\nfoo()", + " + export function log(message: string, ...interpolations: unknown[]): void; + export function log(message: string, ...interpolations: unknown[]): void { + console.log(message); + } + ", + " + export function log(...messages: unknown[]): void { + return; + } + ", ]; let fix = vec![ diff --git a/crates/oxc_linter/src/snapshots/no_unused_vars@oxc-functions.snap b/crates/oxc_linter/src/snapshots/no_unused_vars@oxc-functions.snap index 6903fab79de39..31c927622e235 100644 --- a/crates/oxc_linter/src/snapshots/no_unused_vars@oxc-functions.snap +++ b/crates/oxc_linter/src/snapshots/no_unused_vars@oxc-functions.snap @@ -25,3 +25,23 @@ source: crates/oxc_linter/src/tester.rs 2 │ foo() ╰──── help: Consider removing this declaration. + + ⚠ eslint(no-unused-vars): Variable 'interpolations' is declared but never used. + ╭─[no_unused_vars.tsx:3:46] + 2 │ export function log(message: string, ...interpolations: unknown[]): void; + 3 │ export function log(message: string, ...interpolations: unknown[]): void { + · ──────────────┬───────────── + · ╰── 'interpolations' is declared here + 4 │ console.log(message); + ╰──── + help: Consider removing this declaration. + + ⚠ eslint(no-unused-vars): Variable 'messages' is declared but never used. + ╭─[no_unused_vars.tsx:2:29] + 1 │ + 2 │ export function log(...messages: unknown[]): void { + · ───────────┬────────── + · ╰── 'messages' is declared here + 3 │ return; + ╰──── + help: Consider removing this declaration.