-
-
Notifications
You must be signed in to change notification settings - Fork 499
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat:
noAriaHiddenOnFocusable
rule (#822)
- Loading branch information
1 parent
f32bb9e
commit cc60a55
Showing
16 changed files
with
493 additions
and
57 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
124 changes: 124 additions & 0 deletions
124
crates/biome_js_analyze/src/aria_analyzers/nursery/no_aria_hidden_on_focusable.rs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,124 @@ | ||
use crate::{aria_services::Aria, JsRuleAction}; | ||
use biome_analyze::{ | ||
context::RuleContext, declare_rule, ActionCategory, FixKind, Rule, RuleDiagnostic, | ||
}; | ||
use biome_console::markup; | ||
use biome_diagnostics::Applicability; | ||
use biome_js_syntax::jsx_ext::AnyJsxElement; | ||
use biome_rowan::{AstNode, BatchMutationExt}; | ||
|
||
declare_rule! { | ||
/// Enforce that aria-hidden="true" is not set on focusable elements. | ||
/// | ||
/// `aria-hidden="true"` can be used to hide purely decorative content from screen reader users. | ||
/// A focusable element with `aria-hidden="true"` can be reached by keyboard. | ||
/// This can lead to confusion or unexpected behavior for screen reader users. | ||
/// | ||
/// Source: https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/blob/main/docs/rules/no-aria-hidden-on-focusable.md | ||
/// | ||
/// ## Example | ||
/// | ||
/// ### Invalid | ||
/// | ||
/// ```js,expect_diagnostic | ||
/// <div aria-hidden="true" tabIndex="0" /> | ||
/// ``` | ||
/// | ||
/// ```js, expect_diagnostic | ||
/// <a href="/" aria-hidden="true" /> | ||
/// ``` | ||
/// | ||
/// ## Valid | ||
/// | ||
/// ```js | ||
/// <button aria-hidden="true" tabIndex="-1" /> | ||
/// ``` | ||
/// | ||
/// ```js | ||
/// <div aria-hidden="true"><a href="#"></a></div> | ||
/// ``` | ||
/// | ||
/// ## Resources | ||
/// | ||
/// - [aria-hidden elements do not contain focusable elements](https://dequeuniversity.com/rules/axe/html/4.4/aria-hidden-focus) | ||
/// - [Element with aria-hidden has no content in sequential focus navigation](https://www.w3.org/WAI/standards-guidelines/act/rules/6cfa84/proposed/) | ||
/// - [MDN aria-hidden](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes/aria-hidden) | ||
/// | ||
pub(crate) NoAriaHiddenOnFocusable { | ||
version: "1.3.0", | ||
name: "noAriaHiddenOnFocusable", | ||
recommended: true, | ||
fix_kind: FixKind::Unsafe, | ||
} | ||
} | ||
|
||
impl Rule for NoAriaHiddenOnFocusable { | ||
type Query = Aria<AnyJsxElement>; | ||
type State = (); | ||
type Signals = Option<Self::State>; | ||
type Options = (); | ||
|
||
fn run(ctx: &RuleContext<Self>) -> Self::Signals { | ||
let node = ctx.query(); | ||
let aria_roles = ctx.aria_roles(); | ||
let element_name = node.name().ok()?.as_jsx_name()?.value_token().ok()?; | ||
|
||
if node.is_element() { | ||
let aria_hidden_attr = node.find_attribute_by_name("aria-hidden")?; | ||
let attr_static_val = aria_hidden_attr.as_static_value()?; | ||
let attr_text = attr_static_val.text(); | ||
|
||
let attributes = ctx.extract_attributes(&node.attributes()); | ||
|
||
if attr_text == "false" { | ||
return None; | ||
} | ||
|
||
if let Some(tabindex_attr) = node.find_attribute_by_name("tabIndex") { | ||
if let Some(tabindex_static) = tabindex_attr.as_static_value() { | ||
let tabindex_text = tabindex_static.text(); | ||
let tabindex_val = tabindex_text.trim().parse::<i32>(); | ||
|
||
if let Ok(num) = tabindex_val { | ||
return (num >= 0).then_some(()); | ||
} | ||
} | ||
} | ||
|
||
if !aria_roles.is_not_interactive_element(element_name.text_trimmed(), attributes) { | ||
return Some(()); | ||
} | ||
} | ||
|
||
None | ||
} | ||
|
||
fn diagnostic(ctx: &RuleContext<Self>, _: &Self::State) -> Option<RuleDiagnostic> { | ||
let node = ctx.query(); | ||
Some( | ||
RuleDiagnostic::new( | ||
rule_category!(), | ||
node.range(), | ||
markup! { | ||
"Disallow "<Emphasis>"aria-hidden=\"true\""</Emphasis>" from being set on focusable elements." | ||
}, | ||
) | ||
.note(markup! { | ||
""<Emphasis>"aria-hidden"</Emphasis>" should not be set to "<Emphasis>"true"</Emphasis>" on focusable elements because this can lead to confusing behavior for screen reader users." | ||
}), | ||
) | ||
} | ||
|
||
fn action(ctx: &RuleContext<Self>, _: &Self::State) -> Option<JsRuleAction> { | ||
let node = ctx.query(); | ||
let mut mutation = ctx.root().begin(); | ||
let aria_hidden_attr = node.find_attribute_by_name("aria-hidden")?; | ||
mutation.remove_node(aria_hidden_attr); | ||
Some(JsRuleAction { | ||
category: ActionCategory::QuickFix, | ||
applicability: Applicability::MaybeIncorrect, | ||
message: markup! { "Remove the aria-hidden attribute from the element." }.to_owned(), | ||
mutation, | ||
}) | ||
} | ||
} |
8 changes: 8 additions & 0 deletions
8
crates/biome_js_analyze/tests/specs/nursery/noAriaHiddenOnFocusable/invalid.jsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
<> | ||
<div aria-hidden="true" tabIndex="0" /> | ||
<input aria-hidden="true" /> | ||
<a href="/" aria-hidden="true" /> | ||
<button aria-hidden="true" /> | ||
<textarea aria-hidden="true" /> | ||
<p tabIndex="0" aria-hidden="true">text</p> | ||
</> |
142 changes: 142 additions & 0 deletions
142
crates/biome_js_analyze/tests/specs/nursery/noAriaHiddenOnFocusable/invalid.jsx.snap
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,142 @@ | ||
--- | ||
source: crates/biome_js_analyze/tests/spec_tests.rs | ||
expression: invalid.jsx | ||
--- | ||
# Input | ||
```js | ||
<> | ||
<div aria-hidden="true" tabIndex="0" /> | ||
<input aria-hidden="true" /> | ||
<a href="/" aria-hidden="true" /> | ||
<button aria-hidden="true" /> | ||
<textarea aria-hidden="true" /> | ||
<p tabIndex="0" aria-hidden="true">text</p> | ||
</> | ||
``` | ||
|
||
# Diagnostics | ||
``` | ||
invalid.jsx:2:3 lint/nursery/noAriaHiddenOnFocusable FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ | ||
! Disallow aria-hidden="true" from being set on focusable elements. | ||
1 │ <> | ||
> 2 │ <div aria-hidden="true" tabIndex="0" /> | ||
│ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | ||
3 │ <input aria-hidden="true" /> | ||
4 │ <a href="/" aria-hidden="true" /> | ||
i aria-hidden should not be set to true on focusable elements because this can lead to confusing behavior for screen reader users. | ||
i Unsafe fix: Remove the aria-hidden attribute from the element. | ||
2 │ ··<div·aria-hidden="true"·tabIndex="0"·/> | ||
│ ------------------- | ||
``` | ||
|
||
``` | ||
invalid.jsx:3:3 lint/nursery/noAriaHiddenOnFocusable FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ | ||
! Disallow aria-hidden="true" from being set on focusable elements. | ||
1 │ <> | ||
2 │ <div aria-hidden="true" tabIndex="0" /> | ||
> 3 │ <input aria-hidden="true" /> | ||
│ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | ||
4 │ <a href="/" aria-hidden="true" /> | ||
5 │ <button aria-hidden="true" /> | ||
i aria-hidden should not be set to true on focusable elements because this can lead to confusing behavior for screen reader users. | ||
i Unsafe fix: Remove the aria-hidden attribute from the element. | ||
3 │ ··<input·aria-hidden="true"·/> | ||
│ ------------------- | ||
``` | ||
|
||
``` | ||
invalid.jsx:4:3 lint/nursery/noAriaHiddenOnFocusable FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ | ||
! Disallow aria-hidden="true" from being set on focusable elements. | ||
2 │ <div aria-hidden="true" tabIndex="0" /> | ||
3 │ <input aria-hidden="true" /> | ||
> 4 │ <a href="/" aria-hidden="true" /> | ||
│ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | ||
5 │ <button aria-hidden="true" /> | ||
6 │ <textarea aria-hidden="true" /> | ||
i aria-hidden should not be set to true on focusable elements because this can lead to confusing behavior for screen reader users. | ||
i Unsafe fix: Remove the aria-hidden attribute from the element. | ||
4 │ ··<a·href="/"·aria-hidden="true"·/> | ||
│ ------------------- | ||
``` | ||
|
||
``` | ||
invalid.jsx:5:3 lint/nursery/noAriaHiddenOnFocusable FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ | ||
! Disallow aria-hidden="true" from being set on focusable elements. | ||
3 │ <input aria-hidden="true" /> | ||
4 │ <a href="/" aria-hidden="true" /> | ||
> 5 │ <button aria-hidden="true" /> | ||
│ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | ||
6 │ <textarea aria-hidden="true" /> | ||
7 │ <p tabIndex="0" aria-hidden="true">text</p> | ||
i aria-hidden should not be set to true on focusable elements because this can lead to confusing behavior for screen reader users. | ||
i Unsafe fix: Remove the aria-hidden attribute from the element. | ||
5 │ ··<button·aria-hidden="true"·/> | ||
│ ------------------- | ||
``` | ||
|
||
``` | ||
invalid.jsx:6:3 lint/nursery/noAriaHiddenOnFocusable FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ | ||
! Disallow aria-hidden="true" from being set on focusable elements. | ||
4 │ <a href="/" aria-hidden="true" /> | ||
5 │ <button aria-hidden="true" /> | ||
> 6 │ <textarea aria-hidden="true" /> | ||
│ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | ||
7 │ <p tabIndex="0" aria-hidden="true">text</p> | ||
8 │ </> | ||
i aria-hidden should not be set to true on focusable elements because this can lead to confusing behavior for screen reader users. | ||
i Unsafe fix: Remove the aria-hidden attribute from the element. | ||
6 │ ··<textarea·aria-hidden="true"·/> | ||
│ ------------------- | ||
``` | ||
|
||
``` | ||
invalid.jsx:7:3 lint/nursery/noAriaHiddenOnFocusable FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ | ||
! Disallow aria-hidden="true" from being set on focusable elements. | ||
5 │ <button aria-hidden="true" /> | ||
6 │ <textarea aria-hidden="true" /> | ||
> 7 │ <p tabIndex="0" aria-hidden="true">text</p> | ||
│ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | ||
8 │ </> | ||
i aria-hidden should not be set to true on focusable elements because this can lead to confusing behavior for screen reader users. | ||
i Unsafe fix: Remove the aria-hidden attribute from the element. | ||
7 │ ··<p·tabIndex="0"·aria-hidden="true">text</p> | ||
│ ------------------ | ||
``` | ||
|
||
|
9 changes: 9 additions & 0 deletions
9
crates/biome_js_analyze/tests/specs/nursery/noAriaHiddenOnFocusable/valid.jsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
<> | ||
<div aria-hidden="true" /> | ||
<img aria-hidden="true" /> | ||
<a aria-hidden="false" href="#" /> | ||
<button aria-hidden="true" tabIndex="-1" /> | ||
<a href="/" /> | ||
<div aria-hidden="true"><a href="#"></a></div> | ||
<button /> | ||
</> |
18 changes: 18 additions & 0 deletions
18
crates/biome_js_analyze/tests/specs/nursery/noAriaHiddenOnFocusable/valid.jsx.snap
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
--- | ||
source: crates/biome_js_analyze/tests/spec_tests.rs | ||
expression: valid.jsx | ||
--- | ||
# Input | ||
```js | ||
<> | ||
<div aria-hidden="true" /> | ||
<img aria-hidden="true" /> | ||
<a aria-hidden="false" href="#" /> | ||
<button aria-hidden="true" tabIndex="-1" /> | ||
<a href="/" /> | ||
<div aria-hidden="true"><a href="#"></a></div> | ||
<button /> | ||
</> | ||
``` | ||
|
||
|
Oops, something went wrong.