forked from jsx-eslint/eslint-plugin-jsx-a11y
-
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[New] add
anchor-ambiguous-text
rule
- Loading branch information
Showing
5 changed files
with
242 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
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,97 @@ | ||
/* eslint-env jest */ | ||
/** | ||
* @fileoverview Enforce `<a>` text to not exactly match "click here", "here", "link", or "a link". | ||
* @author Matt Wang | ||
*/ | ||
|
||
// ----------------------------------------------------------------------------- | ||
// Requirements | ||
// ----------------------------------------------------------------------------- | ||
|
||
import { RuleTester } from 'eslint'; | ||
import parserOptionsMapper from '../../__util__/parserOptionsMapper'; | ||
import rule from '../../../src/rules/anchor-ambiguous-text'; | ||
|
||
// ----------------------------------------------------------------------------- | ||
// Tests | ||
// ----------------------------------------------------------------------------- | ||
|
||
const ruleTester = new RuleTester(); | ||
|
||
const DEFAULT_AMBIGUOUS_WORDS = [ | ||
'click here', | ||
'here', | ||
'link', | ||
'a link', | ||
'learn more', | ||
]; | ||
|
||
const expectedErrorGenerator = (words) => ({ | ||
message: `Ambiguous text within anchor. Screenreader users rely on link text for context; the words "${words.join('", "')}" are ambiguous and do not provide enough context.`, | ||
type: 'JSXOpeningElement', | ||
}); | ||
|
||
const expectedError = expectedErrorGenerator(DEFAULT_AMBIGUOUS_WORDS); | ||
|
||
ruleTester.run('anchor-ambiguous-text', rule, { | ||
valid: [ | ||
{ code: '<a>documentation</a>;' }, | ||
{ code: '<a>${here}</a>;' }, | ||
{ code: '<a aria-label="tutorial on using eslint-plugin-jsx-a11y">click here</a>;' }, | ||
{ code: '<a><span aria-label="tutorial on using eslint-plugin-jsx-a11y">click here</span></a>;' }, | ||
{ | ||
code: '<a>click here</a>', | ||
options: [{ | ||
words: ['disabling the defaults'], | ||
}], | ||
}, | ||
{ | ||
code: '<Link>documentation</Link>;', | ||
settings: { 'jsx-a11y': { components: { Link: 'a' } } }, | ||
}, | ||
{ | ||
code: '<Link>${here}</Link>;', | ||
settings: { 'jsx-a11y': { components: { Link: 'a' } } }, | ||
}, | ||
{ | ||
code: '<Link aria-label="tutorial on using eslint-plugin-jsx-a11y">click here</Link>;', | ||
settings: { 'jsx-a11y': { components: { Link: 'a' } } }, | ||
}, | ||
{ | ||
code: '<Link>click here</Link>', | ||
options: [{ | ||
words: ['disabling the defaults with components'], | ||
}], | ||
settings: { 'jsx-a11y': { components: { Link: 'a' } } }, | ||
}, | ||
].map(parserOptionsMapper), | ||
invalid: [ | ||
{ code: '<a>here</a>;', errors: [expectedError] }, | ||
{ code: '<a>HERE</a>;', errors: [expectedError] }, | ||
{ code: '<a>click here</a>;', errors: [expectedError] }, | ||
{ code: '<a>learn more</a>;', errors: [expectedError] }, | ||
{ code: '<a>link</a>;', errors: [expectedError] }, | ||
{ code: '<a>a link</a>;', errors: [expectedError] }, | ||
{ code: '<a aria-label="click here">something</a>;', errors: [expectedError] }, | ||
{ code: '<a> a link </a>;', errors: [expectedError] }, | ||
{ code: '<a>a<i></i> link</a>;', errors: [expectedError] }, | ||
{ code: '<a><i></i>a link</a>;', errors: [expectedError] }, | ||
{ code: '<a><span>click</span> here</a>;', errors: [expectedError] }, | ||
{ code: '<a><span> click </span> here</a>;', errors: [expectedError] }, | ||
{ code: '<a><span aria-hidden>more text</span>learn more</a>;', errors: [expectedError] }, | ||
{ code: '<a><span aria-hidden="true">more text</span>learn more</a>;', errors: [expectedError] }, | ||
{ code: '<a><CustomElement>click</CustomElement> here</a>;', errors: [expectedError] }, | ||
{ | ||
code: '<Link>here</Link>', | ||
errors: [expectedError], | ||
settings: { 'jsx-a11y': { components: { Link: 'a' } } }, | ||
}, | ||
{ | ||
code: '<a>a disallowed word</a>', | ||
errors: [expectedErrorGenerator(['a disallowed word'])], | ||
options: [{ | ||
words: ['a disallowed word'], | ||
}], | ||
}, | ||
].map(parserOptionsMapper), | ||
}); |
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,70 @@ | ||
# anchor-ambiguous-text | ||
|
||
Enforces `<a>` values are not exact matches for the phrases "click here", "here", "link", "a link", or "learn more". Screenreaders announce tags as links/interactive, but rely on values for context. Ambiguous anchor descriptions do not provide sufficient context for users. | ||
|
||
## Rule details | ||
|
||
This rule takes one optional object argument with the parameter `words`. | ||
|
||
```json | ||
{ | ||
"rules": { | ||
"jsx-a11y/anchor-ambiguous-text": [2, { | ||
"words": ["click this"], | ||
}], | ||
} | ||
} | ||
``` | ||
|
||
The `words` option allows users to modify the strings that can be checked for in the anchor text. Useful for specifying other words in other languages. The default value is set by `DEFAULT_AMBIGUOUS_WORDS`: | ||
|
||
```js | ||
const DEFAULT_AMBIGUOUS_WORDS = ['click here', 'here', 'link', 'a link', 'learn more']; | ||
``` | ||
|
||
If an element has the `aria-label` property, its value is used instead of the inner text. Note that the rule still disallows ambiguous `aria-label`s. This rule also skips over elements with `aria-hidden="true"`. | ||
|
||
Note that this rule is case-insensitive and trims whitespace. It only looks for **exact matches**. | ||
|
||
### Succeed | ||
```jsx | ||
<a>read this tutorial</a> // passes since it is not one of the disallowed words | ||
<a>${here}</a> // this is valid since 'here' is a variable name | ||
<a aria-label="tutorial on using eslint-plugin-jsx-a11y">click here</a> // the aria-label supersedes the inner text | ||
``` | ||
|
||
### Fail | ||
```jsx | ||
<a>here</a> | ||
<a>HERE</a> | ||
<a>click here</a> | ||
<a>link</a> | ||
<a>a link</a> | ||
<a> a link </a> | ||
<a><span> click </span> here</a> // goes through element children | ||
<a>a<i></i> link</a> | ||
<a><i></i>a link</a> | ||
<a><span aria-hidden="true">more text</span>learn more</a> // skips over elements with aria-hidden=true | ||
<a aria-label="click here">something</a> // the aria-label here is inaccessible | ||
``` | ||
|
||
## Accessibility guidelines | ||
|
||
Ensure anchor tags describe the content of the link, opposed to simply describing them as a link. | ||
|
||
Compare | ||
|
||
```jsx | ||
<p><a href="#">click here</a> to read a tutorial by Foo Bar</p> | ||
``` | ||
|
||
which can be more concise and accessible with | ||
|
||
```jsx | ||
<p>read <a href="#">a tutorial by Foo Bar</a></p> | ||
``` | ||
|
||
### Resources | ||
|
||
1. [WebAIM, Hyperlinks](https://webaim.org/techniques/hypertext/) | ||
2. [Deque University, Link Checklist - 'Avoid "link" (or similar) in the link text'](https://dequeuniversity.com/checklists/web/links) |
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,71 @@ | ||
/** | ||
* @fileoverview Enforce anchor text to not exactly match 'click here', 'here', 'link', 'learn more', and user-specified words. | ||
* @author Matt Wang | ||
* @flow | ||
*/ | ||
|
||
// ---------------------------------------------------------------------------- | ||
// Rule Definition | ||
// ---------------------------------------------------------------------------- | ||
|
||
import type { ESLintConfig, ESLintContext } from '../../flow/eslint'; | ||
import { arraySchema, generateObjSchema } from '../util/schemas'; | ||
import getAccessibleChildText from '../util/getAccessibleChildText'; | ||
import getElementType from '../util/getElementType'; | ||
|
||
const DEFAULT_AMBIGUOUS_WORDS = [ | ||
'click here', | ||
'here', | ||
'link', | ||
'a link', | ||
'learn more', | ||
]; | ||
|
||
const schema = generateObjSchema({ | ||
words: arraySchema, | ||
}); | ||
|
||
export default ({ | ||
meta: { | ||
docs: { | ||
url: 'https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/tree/HEAD/docs/rules/anchor-ambiguous-text.md', | ||
description: 'Enforce `<a>` text to not exactly match "click here", "here", "link", or "a link".', | ||
}, | ||
schema: [schema], | ||
}, | ||
|
||
create: (context: ESLintContext) => { | ||
const elementType = getElementType(context); | ||
|
||
const typesToValidate = ['a']; | ||
|
||
const options = context.options[0] || {}; | ||
const { words = DEFAULT_AMBIGUOUS_WORDS } = options; | ||
const ambiguousWords = new Set(words); | ||
|
||
return { | ||
JSXOpeningElement: (node) => { | ||
const nodeType = elementType(node); | ||
|
||
// Only check anchor elements and custom types. | ||
if (typesToValidate.indexOf(nodeType) === -1) { | ||
return; | ||
} | ||
|
||
const nodeText = getAccessibleChildText(node.parent, elementType); | ||
|
||
if (!ambiguousWords.has(nodeText)) { // check the value | ||
return; | ||
} | ||
|
||
context.report({ | ||
node, | ||
message: 'Ambiguous text within anchor. Screenreader users rely on link text for context; the words "{{wordsList}}" are ambiguous and do not provide enough context.', | ||
data: { | ||
wordsList: words.join('", "'), | ||
}, | ||
}); | ||
}, | ||
}; | ||
}, | ||
}: ESLintConfig); |