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
1 change: 0 additions & 1 deletion crates/oxc_linter/src/rules/eslint/no_unused_vars/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -281,7 +281,6 @@ impl NoUnusedVars {
}
let report = match symbol.references().rev().find(|r| r.is_write()) {
Some(last_write) => {
// ahg
let span = ctx.nodes().get_node(last_write.node_id()).kind().span();
diagnostic::assign(symbol, span, &self.vars_ignore_pattern)
}
Expand Down
12 changes: 12 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 @@ -171,6 +171,10 @@ fn test_vars_self_use() {
}
foo();
",
"
let cancel = () => {}
export function close() { cancel = cancel?.() }
",
];
let fail = vec![
"
Expand All @@ -183,6 +187,14 @@ fn test_vars_self_use() {
return foo
}
",
"
let cancel = () => {};
cancel = cancel?.();
",
"
let cancel = () => {};
{ cancel = cancel?.(); }
",
];

Tester::new(NoUnusedVars::NAME, NoUnusedVars::PLUGIN, pass, fail)
Expand Down
39 changes: 38 additions & 1 deletion crates/oxc_linter/src/rules/eslint/no_unused_vars/usage.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
//! This module contains logic for checking if any [`Reference`]s to a
//! [`Symbol`] are considered a usage.

use itertools::Itertools;
use oxc_ast::{AstKind, ast::*};
use oxc_semantic::{AstNode, NodeId, Reference, ScopeId, SymbolFlags, SymbolId};
use oxc_span::{GetSpan, Span};
Expand Down Expand Up @@ -427,9 +428,31 @@ impl<'a> Symbol<'_, 'a> {
match left {
AssignmentTarget::AssignmentTargetIdentifier(id) => {
if id.name == name {
// Compare *variable scopes* (the nearest function / TS module / class‑static block).
//
// If the variable scope is the same, the the variable is still unused
// ```ts
// let cancel = () => {};
// { // plain block
// cancel = cancel?.(); // `cancel` is unused
// }
// ```
//
// If the variable scope is different, the read can be observed later, so it counts as a real usage:
// ```ts
// let cancel = () => {};
// function foo() { // new var‑scope
// cancel = cancel?.(); // `cancel` is used
// }
// ```
if self.get_parent_variable_scope(self.get_ref_scope(reference))
!= self.get_parent_variable_scope(self.scope_id())
{
return false;
}
is_used_by_others = false;
} else {
return false; // we can short-circuit
return false;
}
}
AssignmentTarget::TSAsExpression(v)
Expand Down Expand Up @@ -832,4 +855,18 @@ impl<'a> Symbol<'_, 'a> {
};
}
}

/// Return the **variable scope** for the given `scope_id`.
///
/// A variable scope is the closest ancestor scope (including `scope_id`
/// itself) whose kind can *outlive* the current execution slice:
/// * function‑like scopes
/// * class static blocks
/// * TypeScript namespace/module blocks
fn get_parent_variable_scope(&self, scope_id: ScopeId) -> ScopeId {
self.scoping()
.scope_ancestors(scope_id)
.find_or_last(|scope_id| self.scoping().scope_flags(*scope_id).is_var())
.expect("scope iterator will always contain at least one element")
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,29 @@ source: crates/oxc_linter/src/tester.rs
3 │ return foo
╰────
help: Consider removing this declaration.

⚠ eslint(no-unused-vars): Variable 'cancel' is assigned a value but never used. Unused variables should start with a '_'.
╭─[no_unused_vars.tsx:2:13]
1 │
2 │ let cancel = () => {};
· ───┬──
· ╰── 'cancel' is declared here
3 │ cancel = cancel?.();
· ───┬──
· ╰── it was last assigned here
4 │
╰────
help: Did you mean to use this variable?

⚠ eslint(no-unused-vars): Variable 'cancel' is assigned a value but never used. Unused variables should start with a '_'.
╭─[no_unused_vars.tsx:2:13]
1 │
2 │ let cancel = () => {};
· ───┬──
· ╰── 'cancel' is declared here
3 │ { cancel = cancel?.(); }
· ───┬──
· ╰── it was last assigned here
4 │
╰────
help: Did you mean to use this variable?
Loading