diff --git a/README.md b/README.md index 02ce6578..14aedd58 100644 --- a/README.md +++ b/README.md @@ -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 | πŸ” | | | diff --git a/docs/rules/a11y-aria-label-is-well-formatted.md b/docs/rules/a11y-aria-label-is-well-formatted.md new file mode 100644 index 00000000..9c2f164a --- /dev/null +++ b/docs/rules/a11y-aria-label-is-well-formatted.md @@ -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. + + + +## 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 + +``` + +```html + +``` + +### **Correct** code for this rule πŸ‘ + +```html + +``` + +```html + +``` + +## Version diff --git a/lib/configs/react.js b/lib/configs/react.js index a49ead99..aa036960 100644 --- a/lib/configs/react.js +++ b/lib/configs/react.js @@ -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': [ diff --git a/lib/index.js b/lib/index.js index 025b23ff..c4a8b3ee 100644 --- a/lib/index.js +++ b/lib/index.js @@ -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'), diff --git a/lib/rules/a11y-aria-label-is-well-formatted.js b/lib/rules/a11y-aria-label-is-well-formatted.js new file mode 100644 index 00000000..3e9ace86 --- /dev/null +++ b/lib/rules/a11y-aria-label-is-well-formatted.js @@ -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.', + }) + } + }, + } + }, +} diff --git a/tests/a11y-aria-label-is-well-formatted.js b/tests/a11y-aria-label-is-well-formatted.js new file mode 100644 index 00000000..cf244a7e --- /dev/null +++ b/tests/a11y-aria-label-is-well-formatted.js @@ -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: "Read more;"}, + {code: "Read more;"}, + {code: ";"}, + {code: ";"}, + {code: ";"}, + {code: 'Read more'}, + ], + invalid: [ + {code: ";", errors: [{message: errorMessage}]}, + {code: ";", errors: [{message: errorMessage}]}, + {code: ";", errors: [{message: errorMessage}]}, + {code: ";", errors: [{message: errorMessage}]}, + ], +})