diff --git a/crates/oxc_linter/src/rules/eslint/no_var.rs b/crates/oxc_linter/src/rules/eslint/no_var.rs index 04a618c42ef48..7f7bee97322e7 100644 --- a/crates/oxc_linter/src/rules/eslint/no_var.rs +++ b/crates/oxc_linter/src/rules/eslint/no_var.rs @@ -1,4 +1,7 @@ -use oxc_ast::{ast::VariableDeclarationKind, AstKind}; +use oxc_ast::{ + ast::{BindingPattern, BindingPatternKind, VariableDeclarationKind}, + AstKind, +}; use oxc_diagnostics::OxcDiagnostic; use oxc_macros::declare_oxc_lint; use oxc_span::Span; @@ -41,19 +44,57 @@ declare_oxc_lint!( /// ``` NoVar, restriction, - pending // TODO: add suggestion that replaces `var` with `let` or `const` depending on if its written to + fix ); impl Rule for NoVar { fn run<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) { if let AstKind::VariableDeclaration(dec) = node.kind() { if dec.kind == VariableDeclarationKind::Var { - ctx.diagnostic(no_var_diagnostic(Span::new(dec.span.start, dec.span.start + 3))); + let is_written_to = dec.declarations.iter().any(|v| is_written_to(&v.id, ctx)); + + ctx.diagnostic_with_fix( + no_var_diagnostic(Span::new(dec.span.start, dec.span.start + 3)), + |fixer| { + fixer.replace( + Span::new(dec.span.start, dec.span.start + 3), + if is_written_to { "let" } else { "const" }, + ) + }, + ); } } } } +fn is_written_to(binding_pat: &BindingPattern, ctx: &LintContext) -> bool { + match &binding_pat.kind { + BindingPatternKind::BindingIdentifier(binding_ident) => ctx + .semantic() + .symbol_references(binding_ident.symbol_id.get().expect("symbol id should be set")) + .any(oxc_semantic::Reference::is_write), + BindingPatternKind::ObjectPattern(object_pat) => { + if object_pat.properties.iter().any(|prop| is_written_to(&prop.value, ctx)) { + return true; + } + + if let Some(rest) = &object_pat.rest { + is_written_to(&rest.argument, ctx) + } else { + false + } + } + BindingPatternKind::AssignmentPattern(_) => true, + BindingPatternKind::ArrayPattern(array_pat) => array_pat.elements.iter().any(|elem| { + if let Some(elem) = elem { + is_written_to(elem, ctx) + } else { + false + } + }), + } +} + #[test] fn test() { use crate::tester::Tester; @@ -109,5 +150,13 @@ fn test() { ("var bar = function () { foo(); }; var foo = function() {};", None), ]; - Tester::new(NoVar::NAME, pass, fail).test_and_snapshot(); + let fix = vec![ + ("var foo", "const foo"), + ("var foo; foo += 1", "let foo; foo += 1"), + ("var foo,bar; bar = 'que'", "let foo,bar; bar = 'que'"), + ("var { a } = {}; a = fn()", "let { a } = {}; a = fn()"), + ("var { a } = {}; let b = a", "const { a } = {}; let b = a"), + ]; + + Tester::new(NoVar::NAME, pass, fail).expect_fix(fix).test_and_snapshot(); }