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 6449882038272..349cedc6ec07e 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 @@ -1292,6 +1292,19 @@ fn test_report_vars_only_used_as_types() { .test(); } +#[test] +fn test_js() { + let pass = vec![ + // https://github.com/oxc-project/oxc/issues/11215 + "export function promisify() { var fn; function fn() {}; return fn; }", + ]; + + Tester::new(NoUnusedVars::NAME, NoUnusedVars::PLUGIN, pass, vec![]) + .change_rule_path_extension("js") + .intentionally_allow_no_fix_tests() + .test(); +} + // #[test] // fn test_template() { // let pass = vec![]; diff --git a/crates/oxc_semantic/src/builder.rs b/crates/oxc_semantic/src/builder.rs index 616142138627e..3fc055f0f2195 100644 --- a/crates/oxc_semantic/src/builder.rs +++ b/crates/oxc_semantic/src/builder.rs @@ -384,6 +384,37 @@ impl<'a> SemanticBuilder<'a> { excludes: SymbolFlags, ) -> SymbolId { if let Some(symbol_id) = self.check_redeclaration(scope_id, span, name, excludes, true) { + // While cases such as: + // ```javascript + // var x; + // function x() {} + // ``` + // are illegal in TypeScript, they are legal in JavaScript. + // Since, in JavaScript, the `function` declaration is hoisted first, we need to make sure that + // if we find a `function` declaration after a `var` declaration, we need to update the + // `symbol_declarations` to point to the `function` declaration. + // ```javascript + // var x; + // function x() {} + // ``` + if !self.source_type.is_typescript() + && includes.contains(SymbolFlags::Function) + && self.scoping.symbol_flags(symbol_id) == SymbolFlags::FunctionScopedVariable + && !self.scoping.symbol_flags(symbol_id).contains(SymbolFlags::Function) + { + let old_span = self.scoping.symbol_spans[symbol_id]; + let old_flags = self.scoping.symbol_flags(symbol_id); + let old_node_id = self.scoping.symbol_declaration(symbol_id); + self.scoping.symbol_spans[symbol_id] = span; + self.scoping.symbol_flags[symbol_id] = includes; + self.scoping.symbol_scope_ids[symbol_id] = scope_id; + self.scoping.symbol_declarations[symbol_id] = self.current_node_id; + + self.scoping.add_symbol_redeclaration(symbol_id, old_flags, old_node_id, old_span); + self.scoping.union_symbol_flag(symbol_id, includes); + return symbol_id; + } + self.add_redeclare_variable(symbol_id, includes, span); self.scoping.union_symbol_flag(symbol_id, includes); return symbol_id; diff --git a/tasks/transform_conformance/snapshots/babel.snap.md b/tasks/transform_conformance/snapshots/babel.snap.md index e11bdee05df79..6413884de2443 100644 --- a/tasks/transform_conformance/snapshots/babel.snap.md +++ b/tasks/transform_conformance/snapshots/babel.snap.md @@ -1012,9 +1012,6 @@ rebuilt : ScopeId(7): [] Symbol flags mismatch for "a": after transform: SymbolId(0): SymbolFlags(BlockScopedVariable) rebuilt : SymbolId(8): SymbolFlags(Function) -Symbol span mismatch for "a": -after transform: SymbolId(0): Span { start: 27, end: 28 } -rebuilt : SymbolId(8): Span { start: 86, end: 87 } Symbol scope ID mismatch for "a": after transform: SymbolId(0): ScopeId(2) rebuilt : SymbolId(8): ScopeId(0)