Skip to main content

react_auditor/rules/performance/
aria_valid.rs

1use oxc_ast::ast::Program;
2use oxc_ast_visit::Visit;
3use oxc_semantic::Semantic;
4
5use crate::rules::{Rule, RuleFinding, RuleMeta, Severity};
6
7pub struct AriaValid;
8
9const RULE_META: RuleMeta = RuleMeta {
10    id: "aria-valid",
11    default_severity: Severity::Error,
12    category: "accessibility",
13    description: "ARIA attributes must be valid",
14};
15
16const VALID_ARIA: &[&str] = &[
17    "aria-activedescendant",
18    "aria-atomic",
19    "aria-autocomplete",
20    "aria-busy",
21    "aria-checked",
22    "aria-colcount",
23    "aria-colindex",
24    "aria-colspan",
25    "aria-controls",
26    "aria-current",
27    "aria-describedby",
28    "aria-details",
29    "aria-disabled",
30    "aria-dropeffect",
31    "aria-errormessage",
32    "aria-expanded",
33    "aria-flowto",
34    "aria-grabbed",
35    "aria-haspopup",
36    "aria-hidden",
37    "aria-invalid",
38    "aria-keyshortcuts",
39    "aria-label",
40    "aria-labelledby",
41    "aria-level",
42    "aria-live",
43    "aria-modal",
44    "aria-multiline",
45    "aria-multiselectable",
46    "aria-orientation",
47    "aria-owns",
48    "aria-placeholder",
49    "aria-posinset",
50    "aria-pressed",
51    "aria-readonly",
52    "aria-relevant",
53    "aria-required",
54    "aria-roledescription",
55    "aria-rowcount",
56    "aria-rowindex",
57    "aria-rowspan",
58    "aria-selected",
59    "aria-setsize",
60    "aria-sort",
61    "aria-valuemax",
62    "aria-valuemin",
63    "aria-valuenow",
64    "aria-valuetext",
65];
66
67impl Rule for AriaValid {
68    fn meta(&self) -> &RuleMeta {
69        &RULE_META
70    }
71
72    fn run(&self, program: &Program, _semantic: &Semantic, source_text: &str) -> Vec<RuleFinding> {
73        let mut collector = AriaCollector {
74            findings: Vec::new(),
75            source: source_text,
76        };
77        collector.visit_program(program);
78        collector.findings
79    }
80}
81
82struct AriaCollector<'a> {
83    findings: Vec<RuleFinding>,
84    source: &'a str,
85}
86
87impl<'a> AriaCollector<'a> {
88    fn add_finding(&mut self, start: usize, msg: String) {
89        let line = self.source[..start].lines().count().max(1);
90        let col = start - self.source[..start].rfind('\n').map(|i| i + 1).unwrap_or(0);
91        self.findings.push(RuleFinding {
92            line,
93            column: col + 1,
94            message: msg,
95        });
96    }
97}
98
99impl<'a> Visit<'a> for AriaCollector<'a> {
100    fn visit_jsx_opening_element(&mut self, el: &oxc_ast::ast::JSXOpeningElement<'a>) {
101        for attr_item in &el.attributes {
102            if let oxc_ast::ast::JSXAttributeItem::Attribute(attr) = attr_item
103                && let oxc_ast::ast::JSXAttributeName::Identifier(id) = &attr.name
104            {
105                let name = id.name.as_str();
106                if name.starts_with("aria-") && !VALID_ARIA.contains(&name) {
107                    self.add_finding(
108                        attr.span.start as usize,
109                        format!("Unknown ARIA attribute `{name}`"),
110                    );
111                }
112            }
113        }
114    }
115}