-
-
Notifications
You must be signed in to change notification settings - Fork 27
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: add rules for prohibiting use of aria-hidden and role='presenta…
…tion' on focusable elements. (#1169) * Added rule for prohibiting use of aria-hidden and role=presentaion on focusable elements * Refactored code into seprate files and utility file * Added EOL for files with prettier errors * Removed nested ifs * Fixed EOL errors * Added more lines * Fixed prettier issues * Removed commented code
- Loading branch information
1 parent
18841d3
commit 4cf49dc
Showing
8 changed files
with
317 additions
and
0 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,79 @@ | ||
# no-aria-hidden-on-focusbable | ||
|
||
Enforce that `aria-hidden="true"` is not set on focusable elements or parent of focusable elements. | ||
|
||
`aria-hidden="true"` can be used to hide purely decorative content from screen reader users. An element with `aria-hidden="true"` that can also be reached by keyboard can lead to confusion or unexpected behavior for screen reader users. Avoid using `aria-hidden="true"` on focusable elements. | ||
|
||
See more in [WAI-ARIA Use in HTML](https://www.w3.org/TR/using-aria/#fourth). | ||
|
||
### ✔ Succeed | ||
|
||
```vue | ||
<template> | ||
<button>Press Me</button> | ||
</template> | ||
``` | ||
|
||
```vue | ||
<template> | ||
<div aria-hidden="true"><button tabindex="-1">Submit</button></div> | ||
</template> | ||
``` | ||
|
||
```vue | ||
<template> | ||
<div aria-hidden='true'><span>Some text</div></div> | ||
</template> | ||
``` | ||
|
||
```vue | ||
<template> | ||
<button tabindex="-1" aria-hidden="true">Press</button> | ||
</template> | ||
``` | ||
|
||
```vue | ||
<template> | ||
<div aria-hidden="true"><a href="#" tabindex="-1">Link</a></div> | ||
</template> | ||
``` | ||
|
||
```vue | ||
<template> | ||
<div aria-hidden="true"><span>Some text</span></div> | ||
</template> | ||
``` | ||
|
||
### ❌ Fail | ||
|
||
```vue | ||
<template> | ||
<button aria-hidden="true">press me</button> | ||
</template> | ||
``` | ||
|
||
```vue | ||
<template> | ||
<button aria-hidden="true">press me</button> | ||
</template> | ||
``` | ||
|
||
```vue | ||
<template> | ||
<a href="#" aria-hidden="true">press me</a> | ||
</template> | ||
``` | ||
|
||
```vue | ||
<template> | ||
<div aria-hidden="true"> | ||
<button>press me</button> | ||
</div> | ||
</template> | ||
``` | ||
|
||
```vue | ||
<template> | ||
<span tabindex="0" aria-hidden="true"><em>Icon</em></span> | ||
</template> | ||
``` |
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,79 @@ | ||
# no-role-presentaion-on-focusbable | ||
|
||
Enforce that `role="presentation"` is not set on focusable elements or parent of focusbale elements. | ||
|
||
`role="presentation` can be used to hide purely decorative content from screen reader users. An element with `role="presentation"` that can also be reached by keyboard can lead to confusion or unexpected behavior for screen reader users. Avoid using `role="presentation"` on focusable elements. | ||
|
||
See more in [WAI-ARIA Use in HTML](https://www.w3.org/TR/using-aria/#fourth). | ||
|
||
### ✔ Succeed | ||
|
||
```vue | ||
<template> | ||
<button>Press Me</button> | ||
</template> | ||
``` | ||
|
||
```vue | ||
<template> | ||
<div role="presentation"><button tabindex="-1">Submit</button></div> | ||
</template> | ||
``` | ||
|
||
```vue | ||
<template> | ||
<div role="presentation"><span>Some text</div></div> | ||
</template> | ||
``` | ||
|
||
```vue | ||
<template> | ||
<button tabindex="-1" role="presentation">Press</button> | ||
</template> | ||
``` | ||
|
||
```vue | ||
<template> | ||
<div role="presentation"><a href="#" tabindex="-1">Link</a></div> | ||
</template> | ||
``` | ||
|
||
```vue | ||
<template> | ||
<div role="presentation"><span>Some text</span></div> | ||
</template> | ||
``` | ||
|
||
### ❌ Fail | ||
|
||
```vue | ||
<template> | ||
<button role="presentation">press me</button> | ||
</template> | ||
``` | ||
|
||
```vue | ||
<template> | ||
<button role="presentation">press me</button> | ||
</template> | ||
``` | ||
|
||
```vue | ||
<template> | ||
<a href="#" role="presentation">press me</a> | ||
</template> | ||
``` | ||
|
||
```vue | ||
<template> | ||
<div role="presentation"> | ||
<button>press me</button> | ||
</div> | ||
</template> | ||
``` | ||
|
||
```vue | ||
<template> | ||
<span tabindex="0" role="presentation"><em>Icon</em></span> | ||
</template> | ||
``` |
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,31 @@ | ||
import rule from "../no-aria-hidden-on-focusable"; | ||
import makeRuleTester from "./makeRuleTester"; | ||
|
||
makeRuleTester("no-presentation-role-or-aria-hidden-on-focusable", rule, { | ||
valid: [ | ||
"<button>Submit</button>", | ||
"<div aria-hidden='true'><button tabindex='-1'>Some text</button></div>", | ||
"<div><button>Submit</button></div>", | ||
"<a href='#' tabindex='-1'>link</a>", | ||
"<button tabindex='-1' aria-hidden='true'>Press</button>", | ||
"<div aria-hidden='true'><a href='#' tabindex='-1'>Link</a></div>" | ||
], | ||
invalid: [ | ||
{ | ||
code: "<div aria-hidden='true'><button>Submit</button></div>", | ||
errors: [{ messageId: "default" }] | ||
}, | ||
{ | ||
code: "<button type='button' aria-hidden='true'>Submit</button>", | ||
errors: [{ messageId: "default" }] | ||
}, | ||
{ | ||
code: "<a href='#' aria-hidden='true'>Link</a>", | ||
errors: [{ messageId: "default" }] | ||
}, | ||
{ | ||
code: "<span tabindex='0' aria-hidden='true'><em>Icon</em></span>", | ||
errors: [{ messageId: "default" }] | ||
} | ||
] | ||
}); |
31 changes: 31 additions & 0 deletions
31
src/rules/__tests__/no-role-presentation-on-focusable.test.ts
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,31 @@ | ||
import rule from "../no-role-presentation-on-focusable"; | ||
import makeRuleTester from "./makeRuleTester"; | ||
|
||
makeRuleTester("no-role-presentation-role-on-focusable", rule, { | ||
valid: [ | ||
"<button>Submit</button>", | ||
"<div role='presentation'><button tabindex='-1'>Some text</button></div>", | ||
"<div><button>Submit</button></div>", | ||
"<a href='#' tabindex='-1'>link</a>", | ||
"<button tabindex='-1' role='presentation'>Press</button>", | ||
"<div role='presentation'><a href='#' tabindex='-1'>Link</a></div>" | ||
], | ||
invalid: [ | ||
{ | ||
code: "<div role='presentation'><button>Submit</button></div>", | ||
errors: [{ messageId: "default" }] | ||
}, | ||
{ | ||
code: "<button type='button' role='presentation'>Submit</button>", | ||
errors: [{ messageId: "default" }] | ||
}, | ||
{ | ||
code: "<a href='#' role='presentation'>Link</a>", | ||
errors: [{ messageId: "default" }] | ||
}, | ||
{ | ||
code: "<span tabindex='0' role='presentation'><em>Icon</em></span>", | ||
errors: [{ messageId: "default" }] | ||
} | ||
] | ||
}); |
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,37 @@ | ||
import type { Rule } from "eslint"; | ||
|
||
import { | ||
defineTemplateBodyVisitor, | ||
getElementAttributeValue, | ||
makeDocsURL | ||
} from "../utils"; | ||
import hasFocusableElements from "../utils/hasFocusableElement"; | ||
|
||
const rule: Rule.RuleModule = { | ||
meta: { | ||
type: "problem", | ||
docs: { | ||
url: makeDocsURL("no-aria-hidden-on-focusable") | ||
}, | ||
messages: { | ||
default: | ||
"Focusable/Interactive elements must not have an aria-hidden attribute." | ||
}, | ||
schema: [] | ||
}, | ||
create(context) { | ||
return defineTemplateBodyVisitor(context, { | ||
VElement(node) { | ||
const hasAriaHidden = getElementAttributeValue(node, "aria-hidden"); | ||
if (hasAriaHidden && hasFocusableElements(node)) { | ||
context.report({ | ||
node: node as any, | ||
messageId: "default" | ||
}); | ||
} | ||
} | ||
}); | ||
} | ||
}; | ||
|
||
export default rule; |
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,38 @@ | ||
import type { Rule } from "eslint"; | ||
|
||
import { | ||
defineTemplateBodyVisitor, | ||
getElementAttributeValue, | ||
makeDocsURL | ||
} from "../utils"; | ||
import hasFocusableElements from "../utils/hasFocusableElement"; | ||
|
||
const rule: Rule.RuleModule = { | ||
meta: { | ||
type: "problem", | ||
docs: { | ||
url: makeDocsURL("no-role-presentation-on-focusable") | ||
}, | ||
messages: { | ||
default: | ||
"Focusable/Interactive elements must not have a presentation role attribute." | ||
}, | ||
schema: [] | ||
}, | ||
create(context) { | ||
return defineTemplateBodyVisitor(context, { | ||
VElement(node) { | ||
const hasRolePresentation = | ||
getElementAttributeValue(node, "role") === "presentation"; | ||
if (hasRolePresentation && hasFocusableElements(node)) { | ||
context.report({ | ||
node: node as any, | ||
messageId: "default" | ||
}); | ||
} | ||
} | ||
}); | ||
} | ||
}; | ||
|
||
export default rule; |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
import type { AST } from "vue-eslint-parser"; | ||
import getElementAttributeValue from "./getElementAttributeValue"; | ||
import isInteractiveElement from "./isInteractiveElement"; | ||
|
||
function hasFocusableElements(node: AST.VElement): boolean { | ||
const tabindex = getElementAttributeValue(node, "tabindex"); | ||
|
||
if (isInteractiveElement(node)) { | ||
return tabindex !== "-1"; | ||
} | ||
|
||
if (tabindex !== null && tabindex !== "-1") { | ||
return true; | ||
} | ||
|
||
return node.children.some( | ||
(child) => child.type === "VElement" && hasFocusableElements(child) | ||
); | ||
} | ||
|
||
export default hasFocusableElements; |