react_auditor/rules/quality/
no_var.rs1use oxc_ast::ast::{Program, VariableDeclarationKind};
2use oxc_ast_visit::Visit;
3use oxc_ast_visit::walk::walk_variable_declaration;
4use oxc_semantic::Semantic;
5
6use crate::rules::{Fix, Rule, RuleFinding, RuleMeta, Severity};
7
8pub struct NoVar;
9
10const RULE_META: RuleMeta = RuleMeta {
11 id: "no-var",
12 default_severity: Severity::Error,
13 category: "quality",
14 description: "Use const or let instead of var",
15};
16
17impl Rule for NoVar {
18 fn meta(&self) -> &RuleMeta {
19 &RULE_META
20 }
21
22 fn run(&self, program: &Program, _semantic: &Semantic, source_text: &str) -> Vec<RuleFinding> {
23 let mut collector = VarCollector {
24 findings: Vec::new(),
25 source: source_text,
26 };
27 collector.visit_program(program);
28 collector.findings
29 }
30
31 fn has_fix(&self) -> bool {
32 true
33 }
34
35 fn fix(&self, finding: &RuleFinding, source_text: &str) -> Option<Fix> {
36 let offset = crate::rules::line_col_to_offset(source_text, finding.line, finding.column)?;
37 let after = &source_text[offset..];
38 let var_len = after.find([' ', '\t', '\n', ';'])?;
39 if after.starts_with("var ") {
40 Some(Fix {
41 start: offset,
42 end: offset + var_len,
43 replacement: "const ".to_string(),
44 })
45 } else if after.starts_with("var\t") {
46 Some(Fix {
47 start: offset,
48 end: offset + var_len,
49 replacement: "const\t".to_string(),
50 })
51 } else {
52 None
53 }
54 }
55}
56
57struct VarCollector<'a> {
58 findings: Vec<RuleFinding>,
59 source: &'a str,
60}
61
62impl<'a> Visit<'a> for VarCollector<'a> {
63 fn visit_variable_declaration(&mut self, decl: &oxc_ast::ast::VariableDeclaration<'a>) {
64 if decl.kind == VariableDeclarationKind::Var {
65 let start = decl.span.start as usize;
66 let line = self.source[..start].lines().count().max(1);
67 let col = start - self.source[..start].rfind('\n').map(|i| i + 1).unwrap_or(0);
68
69 let names: Vec<String> = decl
70 .declarations
71 .iter()
72 .filter_map(|d| {
73 if let oxc_ast::ast::BindingPattern::BindingIdentifier(id) = &d.id {
74 Some(id.name.to_string())
75 } else {
76 None
77 }
78 })
79 .collect();
80
81 let vars = names.join(", ");
82 self.findings.push(RuleFinding {
83 line,
84 column: col + 1,
85 message: format!("Use const or let instead of var: {vars}"),
86 });
87 }
88 walk_variable_declaration(self, decl);
89 }
90}