Skip to main content

react_auditor/rules/quality/
no_var.rs

1use 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}