深入 Rome 的 useValidAriaProps 规则:为 JSX 组件校验合法 ARIA 属性

发布时间:2026/9/20 20:59:03
深入 Rome 的 useValidAriaProps 规则:为 JSX 组件校验合法 ARIA 属性 深入 Rome 的 useValidAriaProps 规则为 JSX 组件校验合法 ARIA 属性【免费下载链接】toolsUnified developer tools for JavaScript, TypeScript, and the web项目地址: https://gitcode.com/gh_mirrors/to/tools本文以 useValidAriaProps 规则文档 为骨架结合 Rome 仓库中该规则的 Rust 实现、ARIA 属性数据表与官方测试用例系统讲解该规则如何校验 JSX 中所有aria-*属性的合法性。读完本文你将掌握该规则的触发条件、完整诊断输出、常见拼写错误、源码级实现原理以及如何在rome.json中启用、禁用、调整严重级别并理解其背后的 WCAG 无障碍依据。规则概览Rome 推荐的 a11y 基础规则useValidAriaProps是 Rome本项目仓库即 Rome 工具链源码提供的一条无障碍accessibility类 lint 规则自v12.0.0起引入属于a11y规则组完整规则名为lint/a11y/useValidAriaProps。它的核心职责一句话可以概括确保所有 ARIA 属性aria-*都是合法、有效的。该规则被 Rome默认推荐启用。从源码元数据可以确认这一点——在 use_valid_aria_props.rs 中规则通过declare_rule!宏声明version: 12.0.0、name: useValidAriaProps、recommended: true三项配置明确写死在宏体内。在 a11y.rs 生成的规则组声明中a11y组共包含四条推荐规则规则名职责useValidAriaProps校验aria-*属性名是否合法本文主角useAriaPropsForRole校验元素的role所要求的 ARIA 属性是否齐全useValidLang校验lang属性是否为合法 ISO 语言noNoninteractiveElementToInteractiveRole禁止非交互元素被赋予交互角色由于recommended: true根据 Linter 文档 的说明推荐规则默认开启并以error严重级别输出诊断无需任何额外配置即可生效。触发条件检查什么样的代码从实现看该规则通过AriaAnyJsxElement查询类型匹配JSX 元素节点因此只在 JSX/TSX 语法中生效。其检测逻辑限定在HTML 元素上——源码中node.is_element()分支明确表明只有同时满足以下两个条件的属性才会被标记为非法属性名以aria-前缀开头在 Rome 内置的合法 ARIA 属性表中查不到该属性名aria_properties.get_property(...)返回None。这也就意味着拼写错误的aria-labell、随意编造的aria-lorem、空属性名aria-等都会命中规则而aria-label、aria-labelledby这类规范属性即使值是空的也不会报错该规则只校验属性名不校验属性值——属性值合法性的校验由nursery组的useAriaPropTypes规则负责。反例与完整诊断输出规则文档给出了两个典型反例以下是原文档完整继承并整理后的形式。反例一属性名拼写错误aria-labell少了一个字母input className aria-labell /Rome 输出如下诊断规则类别lint/a11y/useValidAriaPropsa11y/useValidAriaProps.js:1:1 lint/a11y/useValidAriaProps ━━━━━━━━━━━━━━━━━ ✖ The element contains invalid ARIA attribute(s) 1 │ input className aria-labell / │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ℹ aria-labell is not a valid ARIA attribute. 1 │ input className aria-labell / │ ^^^^^^^^^^^^^^^^反例二多个编造的属性名同时命中div aria-loremfoobar aria-ipsumfoobar /;a11y/useValidAriaProps.js:1:1 lint/a11y/useValidAriaProps ━━━━━━━━━━━━━━━━━ ✖ The element contains invalid ARIA attribute(s) 1 │ div aria-loremfoobar aria-ipsumfoobar /; │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ℹ aria-lorem is not a valid ARIA attribute. 1 │ div aria-loremfoobar aria-ipsumfoobar /; │ ^^^^^^^^^^^^^^^^^^^ ℹ aria-ipsum is not a valid ARIA attribute. 1 │ div aria-loremfoobar aria-ipsumfoobar /; │ ^^^^^^^^^^^^^^^^^^^^从第二个示例可以看出该规则具备逐属性定位能力主诊断消息指向整个元素随后为每个非法属性单独输出一条带精确行内区间的detail即 is not a valid ARIA attribute 说明这在多属性同时出错时非常便于定位。更多反例官方测试用例中的典型错误形态仓库中该规则的规格测试文件 invalid.jsx 收录了比文档更丰富的反例它们覆盖了真实开发中最常见的几类错误var a input className aria-labell /; // 拼写错误 var a div aria-foobar /; // 前缀后为空 var a div aria-labeledbyfoobar /; // 与 aria-labelledby 混淆 var a div aria-skldjfaria-klajsdfoobar /; // 完全臆造 var a div aria-skldjfaria-klajsdfoobar aria-skldjfaria-klajsdfoobar /; // 多个非法属性其中aria-labeledby是最具实际价值的一条正确写法是aria-labelledby双l单l写法是高频拼写错误屏幕阅读器无法识别正是这条规则要拦截的典型问题。源码实现剖析规则如何工作规则的完整实现在 use_valid_aria_props.rs整个执行分为两个阶段阶段一run收集非法属性fn run(ctx: RuleContextSelf) - Self::Signals { let node ctx.query(); let aria_properties ctx.aria_properties(); // check attributes that belong only to HTML elements if node.is_element() { let attributes: Vec_ node .attributes() .iter() .filter_map(|attribute| { let attribute attribute.as_jsx_attribute()?; let attribute_name attribute.name().ok()?.as_jsx_name()?.value_token().ok()?; if attribute_name.text_trimmed().starts_with(aria-) aria_properties .get_property(attribute_name.text_trimmed()) .is_none() { Some((attribute.range(), attribute_name.to_string())) } else { None } }) .collect(); if attributes.is_empty() { None } else { Some(attributes) } } else { None } }关键点拆解通过node.attributes().iter()遍历 JSX 元素的全部属性且只处理JsxAttribute即keyvalue或布尔属性形式JsxSpreadAttribute等被filter_map跳过用text_trimmed().starts_with(aria-)做前缀过滤确保class、id、onClick等普通属性不受影响用aria_properties.get_property(...)查合法属性表查不到即收集返回(属性区间, 属性名)元组状态类型为Vec(TextRange, String)即“位置 属性名”列表供诊断阶段逐条渲染。阶段二diagnostic生成逐属性说明let mut diagnostic RuleDiagnostic::new( rule_category!(), node.range(), markup! { The element contains invalid ARIA attribute(s) }, ); for (range, attribute_name) in attributes { diagnostic diagnostic.detail( range, markup! { Emphasis{attribute_name}/Emphasis is not a valid ARIA attribute. }, ); }主诊断指向整个元素然后循环为每个非法属性追加一条detail与文档中展示的“主消息 N 条属性级说明”的输出结构一一对应。此外该规则type Options ()即不接受任何配置选项行为固定。合法 ARIA 属性参考表规则背后的数据源规则校验所依赖的“合法属性表”并非硬编码在规则文件里而是来自独立的rome_ariacrate。入口函数定义在 lib.rspub fn is_aria_property_valid(property: str) - bool { AriaPropertiesEnum::from_str(property).is_ok() }合法属性清单由 properties.rs共 453 行通过define_property!宏逐一定义每个属性还附带PROPERTY_TYPE属性值类型与VALUEStoken 类属性的合法取值。从源码可以确认的合法属性及类型如下ARIA 属性值类型token 取值aria-activedescendantid—aria-atomicboolean—aria-autocompletetokeninlinelistbothnonearia-busyboolean—aria-checkedtristate—aria-colcount/aria-colindex/aria-colspaninteger—aria-controlsidlist—aria-currenttokenpagesteplocationdatetimetruefalsearia-describedbyidlist—aria-detailsid—aria-disabledboolean—aria-dropeffecttokenlistcopyexecutelinkmovenonepopuparia-errormessageid—aria-expandedboolean—aria-flowtoidlist—aria-grabbedboolean—aria-haspopuptokenfalsetruemenulistboxtreegriddialogaria-hiddenboolean—aria-invalidtokengrammarfalsespellingtruearia-keyshortcuts/aria-label/aria-placeholder/aria-roledescriptionstring—aria-labelledbyidlist—aria-levelinteger—aria-livetokenassertiveoffpolitearia-modal/aria-multiline/aria-multiselectableboolean—aria-orientationtokenverticalundefinedhorizontalaria-ownsidlist—aria-posinsetinteger—aria-pressedtristate—aria-readonlyboolean—aria-relevanttokenlistadditionsallremovalstextaria-requiredboolean—aria-rowcount/aria-rowindex/aria-rowspaninteger—aria-selectedboolean—aria-setsizeinteger—aria-sorttokenascendingdescendingnoneother这张表是判断“合法”的唯一权威来源任何不在表中的aria-*属性名包括拼写错误、编造属性、aria-labeledby这类与上表aria-labelledby一字之差的写法都会被useValidAriaProps标记。这也解释了为何规则的文档示例与测试用例中的aria-labell、aria-lorem、aria-ipsum、aria-全部报错。底层依赖AriaServices 与属性查询规则运行时通过ctx.aria_properties()获取属性表这一能力的载体是 aria_services.rs即 aria_services.rs中定义的AriaServicespub(crate) struct AriaServices { pub(crate) roles: ArcAriaRoles, pub(crate) properties: ArcAriaProperties, }AriaProperties从rome_ariacrate 注入规则通过get_property(name)完成 O(1) 级别的查表查询类型AriaN实现了Queryablephase()为Phases::Syntax表明该规则仅依赖语法树即可运行不需要语义模型因此执行代价极低同文件还提供extract_attributes工具方法附有单元测试用于把JsxAttributeList解析成HashMap属性名, 值列表为useAriaPropsForRole等其他 a11y 规则复用。从源码结构看Rome 将 ARIA 知识库角色表、属性表与规则实现解耦rome_aria负责“标准数据”rome_js_analyze负责“策略判断”这使得多条 a11y 规则共享同一份权威数据避免重复维护。在项目中启用、禁用与调整严重级别useValidAriaProps是推荐规则默认启用并以 error 级别输出。若需调整可以在项目根目录的 rome.json 中配置配置语法详见 Linter 文档。调整严重级别为 warn适合重构期或 CI 过渡阶段{ linter: { enabled: true, rules: { a11y: { useValidAriaProps: warn } } } }完全禁用该规则{ linter: { enabled: true, rules: { a11y: { useValidAriaProps: off } } } }在代码中按行豁免适用于确实需要保留非法属性的场景// rome-ignore lint/a11y/useValidAriaProps: 第三方组件要求保留该属性 div aria-loremfoobar /注意由于该规则type Options ()它不接受{ level: ..., options: {} }形式的选项对象配置时直接使用字符串级别即可。与 a11y 组的其他规则协同useValidAriaProps只解决“属性名是否合法”其余 ARIA 相关问题由同组或 nursery 组规则互补覆盖use_aria_props_for_role.rs检查元素声明的role所需的必需属性是否缺失use_aria_prop_types.rsnursery 组校验属性值是否符合PROPERTY_TYPE与VALUES约束no_aria_unsupported_elements.rsnursery 组检查元素上是否使用了其不支持的 ARIA 属性。三者配合基本覆盖了“属性名 → 属性值 → 元素支持性”的完整 ARIA 校验链路。无障碍依据WCAG 4.1.2规则文档明确关联了WCAG 2.1 成功标准 4.1.2Name, Role, Value名称、角色、值该标准要求所有用户界面组件必须能被辅助技术识别其名称Name、角色Role与状态值Value。aria-*属性正是传达这些信息的标准通道属性名拼写错误会直接导致屏幕阅读器等辅助技术无法解析因此useValidAriaProps通过拦截非法属性名从源头保证无障碍信息通道的可用性。如何验证测试与快照该规则拥有独立的规格测试位于 tests/specs/a11y/useValidAriaProps/invalid.jsx上述 5 条反例输入invalid.jsx.snap对应的诊断快照含精确到列的行内标记。测试由 spec_tests.rs 驱动采用快照测试模式任何对规则行为包括诊断措辞、行内标记的改动都会与快照比对从而保证文档中展示的输出与真实行为始终一致。这也是为什么规则文档中的诊断输出与源码实现能够逐字对应——文档由规则源码中的declare_rule!文档注释自动生成示例即测试测试即文档。【免费下载链接】toolsUnified developer tools for JavaScript, TypeScript, and the web项目地址: https://gitcode.com/gh_mirrors/to/tools创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考