Skip to content

Commit 19772e5

Browse files
committed
fix(linter/no-unused-vars): panic when variable is redeclared as function in same scope (#11280)
* close: #11215 * close: #11234 ```js var a; function a() { a() } ``` The above code is legal; in this case, the symbol_declaration points to `var a` rather than `function a`a because the current implementation handles them in order they declared, thus we need to iterate over `symbol_redeclarations` to find the function's node id.
1 parent 4bc2650 commit 19772e5

File tree

2 files changed

+32
-3
lines changed

2 files changed

+32
-3
lines changed

crates/oxc_linter/src/rules/eslint/no_unused_vars/tests/oxc.rs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,24 @@ fn test_vars_self_use() {
203203
.test_and_snapshot();
204204
}
205205

206+
#[test]
207+
fn test_vars_self_use_js() {
208+
let pass = vec![
209+
// https://github.com/oxc-project/oxc/issues/11215
210+
"export function promisify() { var fn; function fn() {} return fn; }",
211+
];
212+
213+
let fail = vec![
214+
// https://github.com/oxc-project/oxc/issues/11215
215+
"export function promisify() { var fn; function fn() { fn() } }",
216+
];
217+
218+
Tester::new(NoUnusedVars::NAME, NoUnusedVars::PLUGIN, pass, fail)
219+
.change_rule_path_extension("js")
220+
.intentionally_allow_no_fix_tests()
221+
.test();
222+
}
223+
206224
#[test]
207225
fn test_vars_discarded_reads() {
208226
let pass = vec![

crates/oxc_linter/src/rules/eslint/no_unused_vars/usage.rs

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -745,7 +745,18 @@ impl<'a> Symbol<'_, 'a> {
745745
fn is_self_call_simple(&self, reference: &Reference) -> bool {
746746
let decl_scope_id = self.scope_id();
747747
let call_scope_id = self.get_ref_scope(reference);
748-
let Some(container_id) = self.declaration().kind().get_container_scope_id() else {
748+
let redeclarations = self.scoping().symbol_redeclarations(self.id());
749+
let container_id = if redeclarations.is_empty() {
750+
self.declaration().kind().get_container_scope_id()
751+
} else {
752+
// Syntax like `var a = 0; function a() { a() }` is legal. We need to
753+
// check the redeclarations to find the one that is a function and use
754+
// its scope id as the container id.
755+
let declaration = redeclarations.iter().find(|decl| decl.flags.is_function()).unwrap();
756+
self.nodes().get_node(declaration.declaration).kind().get_container_scope_id()
757+
};
758+
759+
let Some(container_id) = container_id else {
749760
debug_assert!(
750761
false,
751762
"Found a function call or or new expr reference on a node flagged as a function or class, but the symbol's declaration node has no scope id. It should always be a container."
@@ -860,8 +871,8 @@ impl<'a> Symbol<'_, 'a> {
860871
///
861872
/// A variable scope is the closest ancestor scope (including `scope_id`
862873
/// itself) whose kind can *outlive* the current execution slice:
863-
/// * function‑like scopes
864-
/// * class static blocks
874+
/// * function‑like scopes
875+
/// * class static blocks
865876
/// * TypeScript namespace/module blocks
866877
fn get_parent_variable_scope(&self, scope_id: ScopeId) -> ScopeId {
867878
self.scoping()

0 commit comments

Comments
 (0)