Skip to content
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
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,16 @@ use crate::fixer::{RuleFix, RuleFixer};
impl NoUnusedVars {
/// Delete a variable declaration or rename it to match `varsIgnorePattern`.
///
/// Variable declarations will only be deleted if they have 0 references of any kind. Renaming
/// is only attempted if this is not the case. Only a small set of `varsIgnorePattern` values
/// are supported for renaming. Feel free to add support for more as needed.
/// - Variable declarations will only be deleted if they have 0 references of any kind.
/// - Renaming is only attempted if this is not the case.
/// - Fixing is skipped for the following cases:
/// * Function expressions and arrow functions declared in the root scope
/// (`const x = function () {}`)
/// * Variables initialized with an `await` expression, since these often
/// have side effects (`const unusedRes = await api.createUser(data)`)
///
/// Only a small set of `varsIgnorePattern` values are supported for
/// renaming. Feel free to add support for more as needed.
#[allow(clippy::cast_possible_truncation)]
pub(in super::super) fn rename_or_remove_var_declaration<'a>(
&self,
Expand All @@ -23,7 +30,7 @@ impl NoUnusedVars {
decl: &VariableDeclarator<'a>,
decl_id: AstNodeId,
) -> RuleFix<'a> {
if decl.init.as_ref().is_some_and(Expression::is_function) {
if decl.init.as_ref().is_some_and(|init| is_skipped_init(symbol, init)) {
return fixer.noop();
}

Expand Down Expand Up @@ -119,3 +126,17 @@ impl NoUnusedVars {
Some(new_name.into())
}
}

fn is_skipped_init<'a>(symbol: &Symbol<'_, 'a>, init: &Expression<'a>) -> bool {
match init.get_inner_expression() {
// Do not delete function expressions or arrow functions declared in the
// root scope
Expression::FunctionExpression(_) | Expression::ArrowFunctionExpression(_) => {
symbol.is_root()
}
// Skip await expressions, since these are often effectful (e.g.
// sending a POST request to an API and then not using the response)
Expression::AwaitExpression(_) => true,
_ => false,
}
}
67 changes: 57 additions & 10 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 @@ -67,17 +67,11 @@ fn test_vars_simple() {
None,
FixKind::DangerousSuggestion,
),
// function expressions do not get changed
(r"const foo = () => {}", r"const foo = () => {}", None, FixKind::DangerousSuggestion),
(
r"const foo = function() {}",
r"const foo = function() {}",
None,
FixKind::DangerousSuggestion,
),
// vars initialized to `await` are not removed
("const x = await foo();", "const x = await foo();", None, FixKind::DangerousSuggestion),
(
r"const foo = function foo() {}",
r"const foo = function foo() {}",
"const x = (await foo()) as unknown as MyType",
"const x = (await foo()) as unknown as MyType",
None,
FixKind::DangerousSuggestion,
),
Expand Down Expand Up @@ -469,8 +463,61 @@ fn test_functions() {

let fail = vec!["function foo() {}", "function foo() { foo() }"];

let fix = vec![
// function declarations are never removed
("function foo() {}", "function foo() {}", None, FixKind::DangerousSuggestion),
(
"function foo() { function bar() {} }",
"function foo() { function bar() {} }",
None,
FixKind::DangerousSuggestion,
),
(
"function foo() { function bar() {} }\nfoo()",
"function foo() { function bar() {} }\nfoo()",
None,
FixKind::DangerousSuggestion,
),
// function expressions + arrow functions are not removed if declared in
// the root scope
(
"const foo = function foo() {}",
"const foo = function foo() {}",
None,
FixKind::DangerousSuggestion,
),
(r"const foo = () => {}", r"const foo = () => {}", None, FixKind::DangerousSuggestion),
// function expressions + arrow functions are removed if not declared in
// root scope
(
"
function foo() { const bar = function bar() {} }
foo();
",
"
function foo() { }
foo();
",
None,
FixKind::DangerousSuggestion,
),
(
"
function foo() { const bar = x => x }
foo();
",
"
function foo() { }
foo();
",
None,
FixKind::DangerousSuggestion,
),
];

Tester::new(NoUnusedVars::NAME, pass, fail)
.with_snapshot_suffix("oxc-functions")
.expect_fix(fix)
.test_and_snapshot();
}

Expand Down