react_auditor/rules/performance/
aria_valid.rs1use 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}