react_auditor/rules/quality/
no_unused_vars_rule.rs1use 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}