Skip to main content

react_auditor/rules/quality/
no_shadow.rs

1use oxc_ast::ast::Program;
2use oxc_semantic::Semantic;
3
4use crate::rules::{Rule, RuleFinding, RuleMeta, Severity};
5
6pub struct NoShadow;
7
8const RULE_META: RuleMeta = RuleMeta {
9    id: "no-shadow",
10    default_severity: Severity::Warning,
11    category: "quality",
12    description: "No variable shadowing in nested scopes",
13};
14
15impl Rule for NoShadow {
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 scope_id = scoping.symbol_scope_id(symbol_id);
27
28            if name.starts_with('_') || name == "arguments" {
29                continue;
30            }
31
32            let mut ancestors = scoping.scope_ancestors(scope_id);
33            ancestors.next();
34
35            for ancestor_scope_id in ancestors {
36                if scoping
37                    .get_binding(ancestor_scope_id, name.into())
38                    .is_some()
39                {
40                    let span = scoping.symbol_span(symbol_id);
41                    let start = span.start as usize;
42                    let line = source_text[..start].lines().count().max(1);
43                    let col = start - source_text[..start].rfind('\n').map(|i| i + 1).unwrap_or(0);
44
45                    findings.push(RuleFinding {
46                        line,
47                        column: col + 1,
48                        message: format!("`{name}` shadows a variable in a parent scope"),
49                    });
50                    break;
51                }
52            }
53        }
54
55        findings
56    }
57}