Skip to main content

react_auditor/rules/react/
no_index_key.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 NoIndexKey;
8
9const RULE_META: RuleMeta = RuleMeta {
10    id: "no-index-key",
11    default_severity: Severity::Warning,
12    category: "react",
13    description: "Prefer stable IDs over array index as key",
14};
15
16impl Rule for NoIndexKey {
17    fn meta(&self) -> &RuleMeta {
18        &RULE_META
19    }
20
21    fn run(&self, program: &Program, _semantic: &Semantic, source_text: &str) -> Vec<RuleFinding> {
22        let mut collector = IndexKeyCollector {
23            findings: Vec::new(),
24            source: source_text,
25        };
26        collector.visit_program(program);
27        collector.findings
28    }
29}
30
31struct IndexKeyCollector<'a> {
32    findings: Vec<RuleFinding>,
33    source: &'a str,
34}
35
36impl<'a> Visit<'a> for IndexKeyCollector<'a> {
37    fn visit_jsx_opening_element(&mut self, el: &oxc_ast::ast::JSXOpeningElement<'a>) {
38        for attr_item in &el.attributes {
39            if let oxc_ast::ast::JSXAttributeItem::Attribute(attr) = attr_item {
40                let is_key = matches!(&attr.name, oxc_ast::ast::JSXAttributeName::Identifier(id) if id.name.as_str() == "key");
41                if !is_key {
42                    continue;
43                }
44                if let Some(val) = &attr.value
45                    && let oxc_ast::ast::JSXAttributeValue::ExpressionContainer(container) = val
46                    && let oxc_ast::ast::JSXExpression::Identifier(ident) = &container.expression
47                    && ident.name.as_str() == "index"
48                {
49                    let start = attr.span.start as usize;
50                    let line = self.source[..start].lines().count().max(1);
51                    let col = start - self.source[..start].rfind('\n').map(|i| i + 1).unwrap_or(0);
52                    self.findings.push(RuleFinding {
53                        line,
54                        column: col + 1,
55                        message: "Avoid using array index as `key` — prefer a stable ID"
56                            .to_string(),
57                    });
58                }
59            }
60        }
61    }
62}