diff --git a/README.md b/README.md index 35c9d2b..629adaf 100644 --- a/README.md +++ b/README.md @@ -72,7 +72,7 @@ or npm i --save-dev eslint-plugin-styled-components-a11y -### Configuration +### Configuration - Legacy Config (`.eslintrc`) Add styled-components-a11y to the plugins section of your .eslintrc configuration file. You can omit the eslint-plugin- prefix: @@ -100,6 +100,41 @@ Alternatively, you can configure individual rules under the rules section. } ``` +### Configuration - Flat Config + +The default export is a plugin object: + +```js +import styledA11y from 'eslint-plugin-styled-components-a11y'; + +export default [ + { + plugins: { + 'styled-components-a11y': styledA11y, + }, + rules: { + 'styled-components-a11y/rule-name': 2, + }, + }, +]; +``` + +#### Shareable Configs + +There are two shareable configs provided by the plugin: + +- `flatConfigs.recommended` +- `flatConfigs.strict` + +```js +import styledA11y from 'eslint-plugin-styled-components-a11y'; + +export default [ + styledA11y.flatConfigs.recommended, + // ... additional configs +]; +``` + ## Examples a working repo can be found [here](https://github.com/brendanmorrell/styled-components-a11y-example) at brendanmorrell/styled-components-a11y-example which has a file illustrating the linting rules in action for the above four styled component types the library is currently capable of handling diff --git a/package.json b/package.json index e0c36a2..24226d5 100644 --- a/package.json +++ b/package.json @@ -64,6 +64,6 @@ "nodemon": "^2.0.2" }, "peerDependencies": { - "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8" + "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9" } } diff --git a/src/index.js b/src/index.js index 8afb3e8..39dc946 100644 --- a/src/index.js +++ b/src/index.js @@ -1,6 +1,8 @@ +const { name, version } = require('../package.json'); const makeRule = require('./utils/makeRule'); -module.exports = { +const pluginBase = { + meta: { name, version }, rules: { 'accessible-emoji': makeRule('accessible-emoji'), 'alt-text': makeRule('alt-text'), @@ -39,205 +41,219 @@ module.exports = { scope: makeRule('scope'), 'tabindex-no-positive': makeRule('tabindex-no-positive'), }, - configs: { - recommended: { - plugins: ['styled-components-a11y'], - extends: ['plugin:jsx-a11y/recommended'], - parserOptions: { - ecmaFeatures: { - jsx: true, - }, - }, - rules: { - 'styled-components-a11y/alt-text': 'error', - 'styled-components-a11y/anchor-has-content': 'error', - 'styled-components-a11y/anchor-is-valid': 'error', - 'styled-components-a11y/aria-activedescendant-has-tabindex': 'error', - 'styled-components-a11y/aria-props': 'error', - 'styled-components-a11y/aria-proptypes': 'error', - 'styled-components-a11y/aria-role': 'error', - 'styled-components-a11y/aria-unsupported-elements': 'error', - 'styled-components-a11y/autocomplete-valid': 'error', - 'styled-components-a11y/click-events-have-key-events': 'error', - 'styled-components-a11y/control-has-associated-label': [ - 'off', - { - ignoreElements: ['audio', 'canvas', 'embed', 'input', 'textarea', 'tr', 'video'], - ignoreRoles: [ - 'grid', - 'listbox', - 'menu', - 'menubar', - 'radiogroup', - 'row', - 'tablist', - 'toolbar', - 'tree', - 'treegrid', - ], - includeRoles: ['alert', 'dialog'], - }, - ], - 'styled-components-a11y/heading-has-content': 'error', - 'styled-components-a11y/html-has-lang': 'error', - 'styled-components-a11y/iframe-has-title': 'error', - 'styled-components-a11y/img-redundant-alt': 'error', - 'styled-components-a11y/interactive-supports-focus': [ - 'error', - { - tabbable: ['button', 'checkbox', 'link', 'searchbox', 'spinbutton', 'switch', 'textbox'], - }, - ], - 'styled-components-a11y/label-has-associated-control': 'error', - 'styled-components-a11y/label-has-for': 'off', - 'styled-components-a11y/media-has-caption': 'error', - 'styled-components-a11y/mouse-events-have-key-events': 'error', - // 'styled-components-a11y/no-access-key': 'error', - 'styled-components-a11y/no-autofocus': 'error', - 'styled-components-a11y/no-distracting-elements': 'error', - 'styled-components-a11y/no-interactive-element-to-noninteractive-role': [ - 'error', - { - tr: ['none', 'presentation'], - canvas: ['img'], - }, - ], - 'styled-components-a11y/no-noninteractive-element-interactions': [ - 'error', - { - handlers: [ - 'onClick', - 'onError', - 'onLoad', - 'onMouseDown', - 'onMouseUp', - 'onKeyPress', - 'onKeyDown', - 'onKeyUp', - ], - alert: ['onKeyUp', 'onKeyDown', 'onKeyPress'], - body: ['onError', 'onLoad'], - dialog: ['onKeyUp', 'onKeyDown', 'onKeyPress'], - iframe: ['onError', 'onLoad'], - img: ['onError', 'onLoad'], - }, - ], - 'styled-components-a11y/no-noninteractive-element-to-interactive-role': [ - 'error', - { - ul: ['listbox', 'menu', 'menubar', 'radiogroup', 'tablist', 'tree', 'treegrid'], - ol: ['listbox', 'menu', 'menubar', 'radiogroup', 'tablist', 'tree', 'treegrid'], - li: ['menuitem', 'option', 'row', 'tab', 'treeitem'], - table: ['grid'], - td: ['gridcell'], - fieldset: ['radiogroup', 'presentation'], - }, - ], - 'styled-components-a11y/no-noninteractive-tabindex': [ - 'error', - { - tags: [], - roles: ['tabpanel'], - allowExpressionValues: true, - }, - ], - 'styled-components-a11y/no-redundant-roles': 'error', - 'styled-components-a11y/no-static-element-interactions': [ - 'error', - { - allowExpressionValues: true, - handlers: ['onClick', 'onMouseDown', 'onMouseUp', 'onKeyPress', 'onKeyDown', 'onKeyUp'], - }, - ], - 'styled-components-a11y/role-has-required-aria-props': 'error', - 'styled-components-a11y/role-supports-aria-props': 'error', - 'styled-components-a11y/scope': 'error', - 'styled-components-a11y/tabindex-no-positive': 'error', +}; + +const configs = { + recommended: { + plugins: ['styled-components-a11y'], + extends: ['plugin:jsx-a11y/recommended'], + parserOptions: { + ecmaFeatures: { + jsx: true, }, }, - strict: { - plugins: ['styled-components-a11y'], - extends: ['plugin:jsx-a11y/strict'], - parserOptions: { - ecmaFeatures: { - jsx: true, + rules: { + 'styled-components-a11y/alt-text': 'error', + 'styled-components-a11y/anchor-has-content': 'error', + 'styled-components-a11y/anchor-is-valid': 'error', + 'styled-components-a11y/aria-activedescendant-has-tabindex': 'error', + 'styled-components-a11y/aria-props': 'error', + 'styled-components-a11y/aria-proptypes': 'error', + 'styled-components-a11y/aria-role': 'error', + 'styled-components-a11y/aria-unsupported-elements': 'error', + 'styled-components-a11y/autocomplete-valid': 'error', + 'styled-components-a11y/click-events-have-key-events': 'error', + 'styled-components-a11y/control-has-associated-label': [ + 'off', + { + ignoreElements: ['audio', 'canvas', 'embed', 'input', 'textarea', 'tr', 'video'], + ignoreRoles: [ + 'grid', + 'listbox', + 'menu', + 'menubar', + 'radiogroup', + 'row', + 'tablist', + 'toolbar', + 'tree', + 'treegrid', + ], + includeRoles: ['alert', 'dialog'], }, + ], + 'styled-components-a11y/heading-has-content': 'error', + 'styled-components-a11y/html-has-lang': 'error', + 'styled-components-a11y/iframe-has-title': 'error', + 'styled-components-a11y/img-redundant-alt': 'error', + 'styled-components-a11y/interactive-supports-focus': [ + 'error', + { + tabbable: ['button', 'checkbox', 'link', 'searchbox', 'spinbutton', 'switch', 'textbox'], + }, + ], + 'styled-components-a11y/label-has-associated-control': 'error', + 'styled-components-a11y/label-has-for': 'off', + 'styled-components-a11y/media-has-caption': 'error', + 'styled-components-a11y/mouse-events-have-key-events': 'error', + // 'styled-components-a11y/no-access-key': 'error', + 'styled-components-a11y/no-autofocus': 'error', + 'styled-components-a11y/no-distracting-elements': 'error', + 'styled-components-a11y/no-interactive-element-to-noninteractive-role': [ + 'error', + { + tr: ['none', 'presentation'], + canvas: ['img'], + }, + ], + 'styled-components-a11y/no-noninteractive-element-interactions': [ + 'error', + { + handlers: ['onClick', 'onError', 'onLoad', 'onMouseDown', 'onMouseUp', 'onKeyPress', 'onKeyDown', 'onKeyUp'], + alert: ['onKeyUp', 'onKeyDown', 'onKeyPress'], + body: ['onError', 'onLoad'], + dialog: ['onKeyUp', 'onKeyDown', 'onKeyPress'], + iframe: ['onError', 'onLoad'], + img: ['onError', 'onLoad'], + }, + ], + 'styled-components-a11y/no-noninteractive-element-to-interactive-role': [ + 'error', + { + ul: ['listbox', 'menu', 'menubar', 'radiogroup', 'tablist', 'tree', 'treegrid'], + ol: ['listbox', 'menu', 'menubar', 'radiogroup', 'tablist', 'tree', 'treegrid'], + li: ['menuitem', 'option', 'row', 'tab', 'treeitem'], + table: ['grid'], + td: ['gridcell'], + fieldset: ['radiogroup', 'presentation'], + }, + ], + 'styled-components-a11y/no-noninteractive-tabindex': [ + 'error', + { + tags: [], + roles: ['tabpanel'], + allowExpressionValues: true, + }, + ], + 'styled-components-a11y/no-redundant-roles': 'error', + 'styled-components-a11y/no-static-element-interactions': [ + 'error', + { + allowExpressionValues: true, + handlers: ['onClick', 'onMouseDown', 'onMouseUp', 'onKeyPress', 'onKeyDown', 'onKeyUp'], + }, + ], + 'styled-components-a11y/role-has-required-aria-props': 'error', + 'styled-components-a11y/role-supports-aria-props': 'error', + 'styled-components-a11y/scope': 'error', + 'styled-components-a11y/tabindex-no-positive': 'error', + }, + }, + strict: { + plugins: ['styled-components-a11y'], + extends: ['plugin:jsx-a11y/strict'], + parserOptions: { + ecmaFeatures: { + jsx: true, }, - rules: { - 'styled-components-a11y/alt-text': 'error', - 'styled-components-a11y/anchor-has-content': 'error', - 'styled-components-a11y/anchor-is-valid': 'error', - 'styled-components-a11y/aria-activedescendant-has-tabindex': 'error', - 'styled-components-a11y/aria-props': 'error', - 'styled-components-a11y/aria-proptypes': 'error', - 'styled-components-a11y/aria-role': 'error', - 'styled-components-a11y/aria-unsupported-elements': 'error', - 'styled-components-a11y/autocomplete-valid': 'error', - 'styled-components-a11y/click-events-have-key-events': 'error', - 'styled-components-a11y/control-has-associated-label': [ - 'off', - { - ignoreElements: ['audio', 'canvas', 'embed', 'input', 'textarea', 'tr', 'video'], - ignoreRoles: [ - 'grid', - 'listbox', - 'menu', - 'menubar', - 'radiogroup', - 'row', - 'tablist', - 'toolbar', - 'tree', - 'treegrid', - ], - includeRoles: ['alert', 'dialog'], - }, - ], - 'styled-components-a11y/heading-has-content': 'error', - 'styled-components-a11y/html-has-lang': 'error', - 'styled-components-a11y/iframe-has-title': 'error', - 'styled-components-a11y/img-redundant-alt': 'error', - 'styled-components-a11y/interactive-supports-focus': [ - 'error', - { - tabbable: [ - 'button', - 'checkbox', - 'link', - 'progressbar', - 'searchbox', - 'slider', - 'spinbutton', - 'switch', - 'textbox', - ], - }, - ], - 'styled-components-a11y/label-has-for': 'off', - 'styled-components-a11y/label-has-associated-control': 'error', - 'styled-components-a11y/media-has-caption': 'error', - 'styled-components-a11y/mouse-events-have-key-events': 'error', - 'styled-components-a11y/no-access-key': 'error', - 'styled-components-a11y/no-autofocus': 'error', - 'styled-components-a11y/no-distracting-elements': 'error', - 'styled-components-a11y/no-interactive-element-to-noninteractive-role': 'error', - 'styled-components-a11y/no-noninteractive-element-interactions': [ - 'error', - { - body: ['onError', 'onLoad'], - iframe: ['onError', 'onLoad'], - img: ['onError', 'onLoad'], - }, - ], - 'styled-components-a11y/no-noninteractive-element-to-interactive-role': 'error', - 'styled-components-a11y/no-noninteractive-tabindex': 'error', - 'styled-components-a11y/no-redundant-roles': 'error', - 'styled-components-a11y/no-static-element-interactions': 'error', - 'styled-components-a11y/role-has-required-aria-props': 'error', - 'styled-components-a11y/role-supports-aria-props': 'error', - 'styled-components-a11y/scope': 'error', - 'styled-components-a11y/tabindex-no-positive': 'error', - }, + }, + rules: { + 'styled-components-a11y/alt-text': 'error', + 'styled-components-a11y/anchor-has-content': 'error', + 'styled-components-a11y/anchor-is-valid': 'error', + 'styled-components-a11y/aria-activedescendant-has-tabindex': 'error', + 'styled-components-a11y/aria-props': 'error', + 'styled-components-a11y/aria-proptypes': 'error', + 'styled-components-a11y/aria-role': 'error', + 'styled-components-a11y/aria-unsupported-elements': 'error', + 'styled-components-a11y/autocomplete-valid': 'error', + 'styled-components-a11y/click-events-have-key-events': 'error', + 'styled-components-a11y/control-has-associated-label': [ + 'off', + { + ignoreElements: ['audio', 'canvas', 'embed', 'input', 'textarea', 'tr', 'video'], + ignoreRoles: [ + 'grid', + 'listbox', + 'menu', + 'menubar', + 'radiogroup', + 'row', + 'tablist', + 'toolbar', + 'tree', + 'treegrid', + ], + includeRoles: ['alert', 'dialog'], + }, + ], + 'styled-components-a11y/heading-has-content': 'error', + 'styled-components-a11y/html-has-lang': 'error', + 'styled-components-a11y/iframe-has-title': 'error', + 'styled-components-a11y/img-redundant-alt': 'error', + 'styled-components-a11y/interactive-supports-focus': [ + 'error', + { + tabbable: [ + 'button', + 'checkbox', + 'link', + 'progressbar', + 'searchbox', + 'slider', + 'spinbutton', + 'switch', + 'textbox', + ], + }, + ], + 'styled-components-a11y/label-has-for': 'off', + 'styled-components-a11y/label-has-associated-control': 'error', + 'styled-components-a11y/media-has-caption': 'error', + 'styled-components-a11y/mouse-events-have-key-events': 'error', + 'styled-components-a11y/no-access-key': 'error', + 'styled-components-a11y/no-autofocus': 'error', + 'styled-components-a11y/no-distracting-elements': 'error', + 'styled-components-a11y/no-interactive-element-to-noninteractive-role': 'error', + 'styled-components-a11y/no-noninteractive-element-interactions': [ + 'error', + { + body: ['onError', 'onLoad'], + iframe: ['onError', 'onLoad'], + img: ['onError', 'onLoad'], + }, + ], + 'styled-components-a11y/no-noninteractive-element-to-interactive-role': 'error', + 'styled-components-a11y/no-noninteractive-tabindex': 'error', + 'styled-components-a11y/no-redundant-roles': 'error', + 'styled-components-a11y/no-static-element-interactions': 'error', + 'styled-components-a11y/role-has-required-aria-props': 'error', + 'styled-components-a11y/role-supports-aria-props': 'error', + 'styled-components-a11y/scope': 'error', + 'styled-components-a11y/tabindex-no-positive': 'error', + }, + }, +}; + +const flatConfigBase = { + languageOptions: { parserOptions: configs.parserOptions }, + plugins: { 'styled-components-a11y': pluginBase }, +}; + +module.exports = { + ...pluginBase, + configs, + flatConfigs: { + recommended: { + ...flatConfigBase, + name: 'styled-components-a11y/recommended', + rules: configs.recommended.rules, + }, + strict: { + ...flatConfigBase, + name: 'styled-components-a11y/strict', + rules: configs.strict.rules, }, }, }; diff --git a/src/utils/makeRule.js b/src/utils/makeRule.js index aa95190..98baf14 100644 --- a/src/utils/makeRule.js +++ b/src/utils/makeRule.js @@ -8,6 +8,7 @@ const collectStyledComponentData = require(process.env.NODE_ENV === 'test' const ruleNameToTypeDict = require('./ruleNameToTypeDict'); module.exports = (name) => ({ + meta: rules[name]?.meta, create(context) { const nodeParserPath = path.join(__dirname, 'nodeParsers', ruleNameToTypeDict[name]); const rule = rules[name];