diff --git a/crates/oxc_linter/src/rules/eslint/vars_on_top.rs b/crates/oxc_linter/src/rules/eslint/vars_on_top.rs index d7e91f98913bb..b9682b9f80885 100644 --- a/crates/oxc_linter/src/rules/eslint/vars_on_top.rs +++ b/crates/oxc_linter/src/rules/eslint/vars_on_top.rs @@ -99,6 +99,12 @@ impl Rule for VarsOnTop { if declaration.kind != VariableDeclarationKind::Var { return; } + + // Skip TypeScript ambient declarations (declare global, declare module, etc.) + if is_in_ambient_typescript_context(node, ctx) { + return; + } + let parent = ctx.nodes().parent_node(node.id()); match parent.kind() { @@ -219,6 +225,17 @@ fn check_var_on_top_in_function_scope( false } +fn is_in_ambient_typescript_context(node: &AstNode, ctx: &LintContext) -> bool { + ctx.nodes().ancestors(node.id()).any(|ancestor| { + if let AstKind::TSModuleDeclaration(module) = ancestor.kind() { + // Ambient if it has the `declare` keyword or is a `declare global` block + module.declare || module.kind.is_global() + } else { + false + } + }) +} + #[test] fn test() { use crate::tester::Tester; @@ -387,6 +404,15 @@ fn test() { let x; } }", // { "ecmaVersion": 2022 } + "declare global { + var __CUSTOM_FLAG__: boolean | undefined; + }", + "declare module 'foo' { + var x: string; + }", + "declare namespace MyNamespace { + var y: number; + }", ]; let fail = vec![ @@ -532,6 +558,7 @@ fn test() { var x; } }", // { "ecmaVersion": 2022 } + "namespace MyNamespace { const y: number = 123; var z: string; }", ]; Tester::new(VarsOnTop::NAME, VarsOnTop::PLUGIN, pass, fail).test_and_snapshot(); diff --git a/crates/oxc_linter/src/snapshots/eslint_vars_on_top.snap b/crates/oxc_linter/src/snapshots/eslint_vars_on_top.snap index cfa35cc44230f..780bdd553b1d7 100644 --- a/crates/oxc_linter/src/snapshots/eslint_vars_on_top.snap +++ b/crates/oxc_linter/src/snapshots/eslint_vars_on_top.snap @@ -229,3 +229,10 @@ source: crates/oxc_linter/src/tester.rs 5 │ } ╰──── help: Consider moving this to the top of the functions scope or using let or const to declare this variable. + + ⚠ eslint(vars-on-top): All 'var' declarations must be at the top of the function scope. + ╭─[vars_on_top.tsx:1:48] + 1 │ namespace MyNamespace { const y: number = 123; var z: string; } + · ────────────── + ╰──── + help: Consider moving this to the top of the functions scope or using let or const to declare this variable.