Skip to main content

react_auditor/rules/quality/
no_unused_vars_rule.rs

1use oxc_ast::ast::Program;
2use oxc_semantic::Semantic;
3
4use crate::rules::{Rule, RuleFinding, RuleMeta, Severity};
5
6pub struct NoUnusedVars;
7
8const RULE_META: RuleMeta = RuleMeta {
9    id: "no-unused-vars",
10    default_severity: Severity::Error,
11    category: "quality",
12    description: "No unused variables, imports, or parameters",
13};
14
15impl Rule for NoUnusedVars {
16    fn meta(&self) -> &RuleMeta {
17        &RULE_META
18    }
19
20    fn run(&self, _program: &Program, semantic: &Semantic, source_text: &str) -> Vec<RuleFinding> {
21        let mut findings = Vec::new();
22        let scoping = semantic.scoping();
23
24        for symbol_id in scoping.symbol_ids() {
25            let name = scoping.symbol_name(symbol_id);
26            let flags = scoping.symbol_flags(symbol_id);
27
28            if name.starts_with('_') {
29                continue;
30            }
31
32            let is_function = flags.contains(oxc_syntax::symbol::SymbolFlags::Function);
33
34            let mut refs = semantic.symbol_references(symbol_id);
35            let has_reads = refs.any(|r| r.flags().is_read());
36
37            if !has_reads && !is_function {
38                let span = scoping.symbol_span(symbol_id);
39                let start = span.start as usize;
40                let line = source_text[..start].lines().count().max(1);
41                let col = start - source_text[..start].rfind('\n').map(|i| i + 1).unwrap_or(0);
42
43                findings.push(RuleFinding {
44                    line,
45                    column: col + 1,
46                    message: format!("`{name}` is declared but never used"),
47                });
48            }
49        }
50
51        findings
52    }
53}