Skip to content

Commit

Permalink
New rule to flag invalid aria-label format (#418)
Browse files Browse the repository at this point in the history
  • Loading branch information
khiga8 authored Mar 23, 2023
1 parent aadb4ce commit 3126329
Show file tree
Hide file tree
Showing 6 changed files with 126 additions and 22 deletions.
45 changes: 23 additions & 22 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,27 +82,28 @@ This config will be interpreted in the following way:
🔧 Automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/user-guide/command-line-interface#--fix).\
❌ Deprecated.

| Name                      | Description | 💼 | 🔧 ||
| :------------------------------------------------------------------- | :----------------------------------------------------------------------------------------------------------------------- | :- | :- | :- |
| [a11y-no-generic-link-text](docs/rules/a11y-no-generic-link-text.md) | disallow generic link text | | ||
| [array-foreach](docs/rules/array-foreach.md) | enforce `for..of` loops over `Array.forEach` || | |
| [async-currenttarget](docs/rules/async-currenttarget.md) | disallow `event.currentTarget` calls inside of async functions | 🔍 | | |
| [async-preventdefault](docs/rules/async-preventdefault.md) | disallow `event.preventDefault` calls inside of async functions | 🔍 | | |
| [authenticity-token](docs/rules/authenticity-token.md) | disallow usage of CSRF tokens in JavaScript | 🔐 | | |
| [get-attribute](docs/rules/get-attribute.md) | disallow wrong usage of attribute names | 🔍 | 🔧 | |
| [js-class-name](docs/rules/js-class-name.md) | enforce a naming convention for js- prefixed classes | 🔐 | | |
| [no-blur](docs/rules/no-blur.md) | disallow usage of `Element.prototype.blur()` | 🔍 | | |
| [no-d-none](docs/rules/no-d-none.md) | disallow usage the `d-none` CSS class | 🔐 | | |
| [no-dataset](docs/rules/no-dataset.md) | enforce usage of `Element.prototype.getAttribute` instead of `Element.prototype.datalist` | 🔍 | | |
| [no-dynamic-script-tag](docs/rules/no-dynamic-script-tag.md) | disallow creating dynamic script tags || | |
| [no-implicit-buggy-globals](docs/rules/no-implicit-buggy-globals.md) | disallow implicit global variables || | |
| [no-inner-html](docs/rules/no-inner-html.md) | disallow `Element.prototype.innerHTML` in favor of `Element.prototype.textContent` | 🔍 | | |
| [no-innerText](docs/rules/no-innerText.md) | disallow `Element.prototype.innerText` in favor of `Element.prototype.textContent` | 🔍 | 🔧 | |
| [no-then](docs/rules/no-then.md) | enforce using `async/await` syntax over Promises || | |
| [no-useless-passive](docs/rules/no-useless-passive.md) | disallow marking a event handler as passive when it has no effect | 🔍 | 🔧 | |
| [prefer-observers](docs/rules/prefer-observers.md) | disallow poorly performing event listeners | 🔍 | | |
| [require-passive-events](docs/rules/require-passive-events.md) | enforce marking high frequency event handlers as passive | 🔍 | | |
| [role-supports-aria-props](docs/rules/role-supports-aria-props.md) | Enforce that elements with explicit or implicit roles defined contain only `aria-*` properties supported by that `role`. | ⚛️ | | |
| [unescaped-html-literal](docs/rules/unescaped-html-literal.md) | disallow unescaped HTML literals | 🔍 | | |
| Name                              | Description | 💼 | 🔧 ||
| :----------------------------------------------------------------------------------- | :----------------------------------------------------------------------------------------------------------------------- | :- | :- | :- |
| [a11y-aria-label-is-well-formatted](docs/rules/a11y-aria-label-is-well-formatted.md) | [aria-label] text should be formatted as you would visual text. | ⚛️ | | |
| [a11y-no-generic-link-text](docs/rules/a11y-no-generic-link-text.md) | disallow generic link text | | ||
| [array-foreach](docs/rules/array-foreach.md) | enforce `for..of` loops over `Array.forEach` || | |
| [async-currenttarget](docs/rules/async-currenttarget.md) | disallow `event.currentTarget` calls inside of async functions | 🔍 | | |
| [async-preventdefault](docs/rules/async-preventdefault.md) | disallow `event.preventDefault` calls inside of async functions | 🔍 | | |
| [authenticity-token](docs/rules/authenticity-token.md) | disallow usage of CSRF tokens in JavaScript | 🔐 | | |
| [get-attribute](docs/rules/get-attribute.md) | disallow wrong usage of attribute names | 🔍 | 🔧 | |
| [js-class-name](docs/rules/js-class-name.md) | enforce a naming convention for js- prefixed classes | 🔐 | | |
| [no-blur](docs/rules/no-blur.md) | disallow usage of `Element.prototype.blur()` | 🔍 | | |
| [no-d-none](docs/rules/no-d-none.md) | disallow usage the `d-none` CSS class | 🔐 | | |
| [no-dataset](docs/rules/no-dataset.md) | enforce usage of `Element.prototype.getAttribute` instead of `Element.prototype.datalist` | 🔍 | | |
| [no-dynamic-script-tag](docs/rules/no-dynamic-script-tag.md) | disallow creating dynamic script tags || | |
| [no-implicit-buggy-globals](docs/rules/no-implicit-buggy-globals.md) | disallow implicit global variables || | |
| [no-inner-html](docs/rules/no-inner-html.md) | disallow `Element.prototype.innerHTML` in favor of `Element.prototype.textContent` | 🔍 | | |
| [no-innerText](docs/rules/no-innerText.md) | disallow `Element.prototype.innerText` in favor of `Element.prototype.textContent` | 🔍 | 🔧 | |
| [no-then](docs/rules/no-then.md) | enforce using `async/await` syntax over Promises || | |
| [no-useless-passive](docs/rules/no-useless-passive.md) | disallow marking a event handler as passive when it has no effect | 🔍 | 🔧 | |
| [prefer-observers](docs/rules/prefer-observers.md) | disallow poorly performing event listeners | 🔍 | | |
| [require-passive-events](docs/rules/require-passive-events.md) | enforce marking high frequency event handlers as passive | 🔍 | | |
| [role-supports-aria-props](docs/rules/role-supports-aria-props.md) | Enforce that elements with explicit or implicit roles defined contain only `aria-*` properties supported by that `role`. | ⚛️ | | |
| [unescaped-html-literal](docs/rules/unescaped-html-literal.md) | disallow unescaped HTML literals | 🔍 | | |

<!-- end auto-generated rules list -->
39 changes: 39 additions & 0 deletions docs/rules/a11y-aria-label-is-well-formatted.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# [aria-label] text should be formatted as you would visual text (`github/a11y-aria-label-is-well-formatted`)

💼 This rule is enabled in the ⚛️ `react` config.

<!-- end auto-generated rule header -->

## Rule Details

`[aria-label]` content should be formatted in the same way you would visual text. Please use sentence case.

Do not connect the words like you would an ID. An `aria-label` is not an ID, and should be formatted as human-friendly text.

## Resources

- [Using aria-label](https://www.w3.org/WAI/tutorials/forms/labels/#using-aria-label)

## Examples

### **Incorrect** code for this rule 👎

```html
<a href="..." aria-label="learn more"></a>
```

```html
<a href="..." aria-label="go-to-link"></a>
```

### **Correct** code for this rule 👍

```html
<a href="..." aria-label="Learn more"></a>
```

```html
<a href="..." aria-label="Homepage"></a>
```

## Version
1 change: 1 addition & 0 deletions lib/configs/react.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ module.exports = {
extends: ['plugin:jsx-a11y/recommended'],
rules: {
'jsx-a11y/role-supports-aria-props': 'off', // Override with github/role-supports-aria-props until https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/issues/910 is resolved
'github/a11y-aria-label-is-well-formatted': 'error',
'github/role-supports-aria-props': 'error',
'jsx-a11y/no-aria-hidden-on-focusable': 'error',
'jsx-a11y/anchor-ambiguous-text': [
Expand Down
1 change: 1 addition & 0 deletions lib/index.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
module.exports = {
rules: {
'a11y-no-generic-link-text': require('./rules/a11y-no-generic-link-text'),
'a11y-aria-label-is-well-formatted': require('./rules/a11y-aria-label-is-well-formatted'),
'array-foreach': require('./rules/array-foreach'),
'async-currenttarget': require('./rules/async-currenttarget'),
'async-preventdefault': require('./rules/async-preventdefault'),
Expand Down
31 changes: 31 additions & 0 deletions lib/rules/a11y-aria-label-is-well-formatted.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
const {getProp} = require('jsx-ast-utils')

module.exports = {
meta: {
docs: {
description: '[aria-label] text should be formatted as you would visual text.',
url: require('../url')(module),
},
schema: [],
},

create(context) {
return {
JSXOpeningElement: node => {
const prop = getProp(node.attributes, 'aria-label')
if (!prop) return

const propValue = prop.value
if (propValue.type !== 'Literal') return

const ariaLabel = propValue.value
if (ariaLabel.match(/^[a-z]+.*$/)) {
context.report({
node,
message: '[aria-label] text should be formatted the same as you would visual text. Use sentence case.',
})
}
},
}
},
}
31 changes: 31 additions & 0 deletions tests/a11y-aria-label-is-well-formatted.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
const rule = require('../lib/rules/a11y-aria-label-is-well-formatted')
const RuleTester = require('eslint').RuleTester

const ruleTester = new RuleTester({
parserOptions: {
ecmaVersion: 'latest',
sourceType: 'module',
ecmaFeatures: {
jsx: true,
},
},
})

const errorMessage = '[aria-label] text should be formatted the same as you would visual text. Use sentence case.'

ruleTester.run('a11y-aria-label-is-well-formatted', rule, {
valid: [
{code: "<a aria-labelledby='someId' href='#'>Read more</a>;"},
{code: "<a aria-label={someName} href='#'>Read more</a>;"},
{code: "<a aria-label='This is a label'></a>;"},
{code: "<a aria-label='Valid'></a>;"},
{code: "<a aria-label='VALID'></a>;"},
{code: '<Link aria-label="Valid" href="#">Read more</Link>'},
],
invalid: [
{code: "<a aria-label='close modal'></a>;", errors: [{message: errorMessage}]},
{code: "<a aria-label='submit'></a>;", errors: [{message: errorMessage}]},
{code: "<a aria-label='submit.yml'></a>;", errors: [{message: errorMessage}]},
{code: "<a aria-label='this-is-not-an-id'></a>;", errors: [{message: errorMessage}]},
],
})

0 comments on commit 3126329

Please sign in to comment.