diff --git a/src/index.js b/src/index.js index 7b931fe..eaea267 100644 --- a/src/index.js +++ b/src/index.js @@ -1,296 +1,344 @@ /* eslint-disable global-require */ +// @ts-check +import accessibleEmoji from './rules/accessible-emoji'; +import altText from './rules/alt-text'; +import anchorAmbiguousText from './rules/anchor-ambiguous-text'; +import anchorHasContent from './rules/anchor-has-content'; +import anchorIsValid from './rules/anchor-is-valid'; +import ariaActivedescendantHasTabindex from './rules/aria-activedescendant-has-tabindex'; +import ariaProps from './rules/aria-props'; +import ariaProptypes from './rules/aria-proptypes'; +import ariaRole from './rules/aria-role'; +import ariaUnsupportedElements from './rules/aria-unsupported-elements'; +import autocompleteValid from './rules/autocomplete-valid'; +import clickEventsHaveKeyEvents from './rules/click-events-have-key-events'; +import controlHasAssociatedLabel from './rules/control-has-associated-label'; +import headingHasContent from './rules/heading-has-content'; +import htmlHasLang from './rules/html-has-lang'; +import iframeHasTitle from './rules/iframe-has-title'; +import imgRedundantAlt from './rules/img-redundant-alt'; +import interactiveSupportsFocus from './rules/interactive-supports-focus'; +import labelHasAssociatedControl from './rules/label-has-associated-control'; +import labelHasFor from './rules/label-has-for'; +import lang from './rules/lang'; +import mediaHasCaption from './rules/media-has-caption'; +import mouseEventsHaveKeyEvents from './rules/mouse-events-have-key-events'; +import noAriaHiddenOnFocusable from './rules/no-aria-hidden-on-focusable'; +import noAccessKey from './rules/no-access-key'; +import noAutofocus from './rules/no-autofocus'; +import noDistractingElements from './rules/no-distracting-elements'; +import noInteractiveElementToNoninteractiveRole from './rules/no-interactive-element-to-noninteractive-role'; +import noNoninteractiveElementInteractions from './rules/no-noninteractive-element-interactions'; +import noNoninteractiveElementToInteractiveRole from './rules/no-noninteractive-element-to-interactive-role'; +import noNoninteractiveTabindex from './rules/no-noninteractive-tabindex'; +import noOnChange from './rules/no-onchange'; +import noRedundantRoles from './rules/no-redundant-roles'; +import noStaticElementInteractions from './rules/no-static-element-interactions'; +import preferTagOverRole from './rules/prefer-tag-over-role'; +import roleHasRequiredAriaProps from './rules/role-has-required-aria-props'; +import roleSupportsAriaProps from './rules/role-supports-aria-props'; +import scope from './rules/scope'; +import tabindexNoPositive from './rules/tabindex-no-positive'; -module.exports = { - rules: { - 'accessible-emoji': require('./rules/accessible-emoji'), - 'alt-text': require('./rules/alt-text'), - 'anchor-ambiguous-text': require('./rules/anchor-ambiguous-text'), - 'anchor-has-content': require('./rules/anchor-has-content'), - 'anchor-is-valid': require('./rules/anchor-is-valid'), - 'aria-activedescendant-has-tabindex': require('./rules/aria-activedescendant-has-tabindex'), - 'aria-props': require('./rules/aria-props'), - 'aria-proptypes': require('./rules/aria-proptypes'), - 'aria-role': require('./rules/aria-role'), - 'aria-unsupported-elements': require('./rules/aria-unsupported-elements'), - 'autocomplete-valid': require('./rules/autocomplete-valid'), - 'click-events-have-key-events': require('./rules/click-events-have-key-events'), - 'control-has-associated-label': require('./rules/control-has-associated-label'), - 'heading-has-content': require('./rules/heading-has-content'), - 'html-has-lang': require('./rules/html-has-lang'), - 'iframe-has-title': require('./rules/iframe-has-title'), - 'img-redundant-alt': require('./rules/img-redundant-alt'), - 'interactive-supports-focus': require('./rules/interactive-supports-focus'), - 'label-has-associated-control': require('./rules/label-has-associated-control'), - 'label-has-for': require('./rules/label-has-for'), - lang: require('./rules/lang'), - 'media-has-caption': require('./rules/media-has-caption'), - 'mouse-events-have-key-events': require('./rules/mouse-events-have-key-events'), - 'no-access-key': require('./rules/no-access-key'), - 'no-aria-hidden-on-focusable': require('./rules/no-aria-hidden-on-focusable'), - 'no-autofocus': require('./rules/no-autofocus'), - 'no-distracting-elements': require('./rules/no-distracting-elements'), - 'no-interactive-element-to-noninteractive-role': require('./rules/no-interactive-element-to-noninteractive-role'), - 'no-noninteractive-element-interactions': require('./rules/no-noninteractive-element-interactions'), - 'no-noninteractive-element-to-interactive-role': require('./rules/no-noninteractive-element-to-interactive-role'), - 'no-noninteractive-tabindex': require('./rules/no-noninteractive-tabindex'), - 'no-onchange': require('./rules/no-onchange'), - 'no-redundant-roles': require('./rules/no-redundant-roles'), - 'no-static-element-interactions': require('./rules/no-static-element-interactions'), - 'prefer-tag-over-role': require('./rules/prefer-tag-over-role'), - 'role-has-required-aria-props': require('./rules/role-has-required-aria-props'), - 'role-supports-aria-props': require('./rules/role-supports-aria-props'), - scope: require('./rules/scope'), - 'tabindex-no-positive': require('./rules/tabindex-no-positive'), - }, - configs: { - recommended: { - plugins: [ - 'jsx-a11y', +export const rules = kebabCase({ + accessibleEmoji, + altText, + anchorAmbiguousText, + anchorHasContent, + anchorIsValid, + ariaActivedescendantHasTabindex, + ariaProps, + ariaProptypes, + ariaRole, + ariaUnsupportedElements, + autocompleteValid, + clickEventsHaveKeyEvents, + controlHasAssociatedLabel, + headingHasContent, + htmlHasLang, + iframeHasTitle, + imgRedundantAlt, + interactiveSupportsFocus, + labelHasAssociatedControl, + labelHasFor, + lang, + mediaHasCaption, + mouseEventsHaveKeyEvents, + noAccessKey, + noAriaHiddenOnFocusable, + noAutofocus, + noDistractingElements, + noInteractiveElementToNoninteractiveRole, + noNoninteractiveElementInteractions, + noNoninteractiveElementToInteractiveRole, + noNoninteractiveTabindex, + noOnChange, + noRedundantRoles, + noStaticElementInteractions, + preferTagOverRole, + roleHasRequiredAriaProps, + roleSupportsAriaProps, + scope, + tabindexNoPositive, +}); +export const configs = { + recommended: { + plugins: [ + 'jsx-a11y', + ], + parserOptions: { + ecmaFeatures: { + jsx: true, + }, + }, + rules: { + 'jsx-a11y/alt-text': 'error', + 'jsx-a11y/anchor-ambiguous-text': 'off', // TODO: error + 'jsx-a11y/anchor-has-content': 'error', + 'jsx-a11y/anchor-is-valid': 'error', + 'jsx-a11y/aria-activedescendant-has-tabindex': 'error', + 'jsx-a11y/aria-props': 'error', + 'jsx-a11y/aria-proptypes': 'error', + 'jsx-a11y/aria-role': 'error', + 'jsx-a11y/aria-unsupported-elements': 'error', + 'jsx-a11y/autocomplete-valid': 'error', + 'jsx-a11y/click-events-have-key-events': 'error', + 'jsx-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', + ], + }], + 'jsx-a11y/heading-has-content': 'error', + 'jsx-a11y/html-has-lang': 'error', + 'jsx-a11y/iframe-has-title': 'error', + 'jsx-a11y/img-redundant-alt': 'error', + 'jsx-a11y/interactive-supports-focus': [ + 'error', + { + tabbable: [ + 'button', + 'checkbox', + 'link', + 'searchbox', + 'spinbutton', + 'switch', + 'textbox', + ], + }, ], - parserOptions: { - ecmaFeatures: { - jsx: true, + 'jsx-a11y/label-has-associated-control': 'error', + 'jsx-a11y/label-has-for': 'off', + 'jsx-a11y/media-has-caption': 'error', + 'jsx-a11y/mouse-events-have-key-events': 'error', + 'jsx-a11y/no-access-key': 'error', + 'jsx-a11y/no-autofocus': 'error', + 'jsx-a11y/no-distracting-elements': 'error', + 'jsx-a11y/no-interactive-element-to-noninteractive-role': [ + 'error', + { + tr: ['none', 'presentation'], + canvas: ['img'], }, - }, - rules: { - 'jsx-a11y/alt-text': 'error', - 'jsx-a11y/anchor-ambiguous-text': 'off', // TODO: error - 'jsx-a11y/anchor-has-content': 'error', - 'jsx-a11y/anchor-is-valid': 'error', - 'jsx-a11y/aria-activedescendant-has-tabindex': 'error', - 'jsx-a11y/aria-props': 'error', - 'jsx-a11y/aria-proptypes': 'error', - 'jsx-a11y/aria-role': 'error', - 'jsx-a11y/aria-unsupported-elements': 'error', - 'jsx-a11y/autocomplete-valid': 'error', - 'jsx-a11y/click-events-have-key-events': 'error', - 'jsx-a11y/control-has-associated-label': ['off', { - ignoreElements: [ - 'audio', - 'canvas', - 'embed', - 'input', - 'textarea', - 'tr', - 'video', + ], + 'jsx-a11y/no-noninteractive-element-interactions': [ + 'error', + { + handlers: [ + 'onClick', + 'onError', + 'onLoad', + 'onMouseDown', + 'onMouseUp', + 'onKeyPress', + 'onKeyDown', + 'onKeyUp', ], - ignoreRoles: [ - 'grid', + alert: ['onKeyUp', 'onKeyDown', 'onKeyPress'], + body: ['onError', 'onLoad'], + dialog: ['onKeyUp', 'onKeyDown', 'onKeyPress'], + iframe: ['onError', 'onLoad'], + img: ['onError', 'onLoad'], + }, + ], + 'jsx-a11y/no-noninteractive-element-to-interactive-role': [ + 'error', + { + ul: [ 'listbox', 'menu', 'menubar', 'radiogroup', - 'row', 'tablist', - 'toolbar', 'tree', 'treegrid', ], - includeRoles: [ - 'alert', - 'dialog', - ], - }], - 'jsx-a11y/heading-has-content': 'error', - 'jsx-a11y/html-has-lang': 'error', - 'jsx-a11y/iframe-has-title': 'error', - 'jsx-a11y/img-redundant-alt': 'error', - 'jsx-a11y/interactive-supports-focus': [ - 'error', - { - tabbable: [ - 'button', - 'checkbox', - 'link', - 'searchbox', - 'spinbutton', - 'switch', - 'textbox', - ], - }, - ], - 'jsx-a11y/label-has-associated-control': 'error', - 'jsx-a11y/label-has-for': 'off', - 'jsx-a11y/media-has-caption': 'error', - 'jsx-a11y/mouse-events-have-key-events': 'error', - 'jsx-a11y/no-access-key': 'error', - 'jsx-a11y/no-autofocus': 'error', - 'jsx-a11y/no-distracting-elements': 'error', - 'jsx-a11y/no-interactive-element-to-noninteractive-role': [ - 'error', - { - tr: ['none', 'presentation'], - canvas: ['img'], - }, - ], - 'jsx-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'], - }, - ], - 'jsx-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'], - }, - ], - 'jsx-a11y/no-noninteractive-tabindex': [ - 'error', - { - tags: [], - roles: ['tabpanel'], - allowExpressionValues: true, - }, - ], - 'jsx-a11y/no-redundant-roles': 'error', - 'jsx-a11y/no-static-element-interactions': [ - 'error', - { - allowExpressionValues: true, - handlers: [ - 'onClick', - 'onMouseDown', - 'onMouseUp', - 'onKeyPress', - 'onKeyDown', - 'onKeyUp', - ], - }, - ], - 'jsx-a11y/role-has-required-aria-props': 'error', - 'jsx-a11y/role-supports-aria-props': 'error', - 'jsx-a11y/scope': 'error', - 'jsx-a11y/tabindex-no-positive': 'error', - }, - }, - strict: { - plugins: [ - 'jsx-a11y', - ], - parserOptions: { - ecmaFeatures: { - jsx: true, - }, - }, - rules: { - 'jsx-a11y/alt-text': 'error', - 'jsx-a11y/anchor-has-content': 'error', - 'jsx-a11y/anchor-is-valid': 'error', - 'jsx-a11y/aria-activedescendant-has-tabindex': 'error', - 'jsx-a11y/aria-props': 'error', - 'jsx-a11y/aria-proptypes': 'error', - 'jsx-a11y/aria-role': 'error', - 'jsx-a11y/aria-unsupported-elements': 'error', - 'jsx-a11y/autocomplete-valid': 'error', - 'jsx-a11y/click-events-have-key-events': 'error', - 'jsx-a11y/control-has-associated-label': ['off', { - ignoreElements: [ - 'audio', - 'canvas', - 'embed', - 'input', - 'textarea', - 'tr', - 'video', - ], - ignoreRoles: [ - 'grid', + ol: [ 'listbox', 'menu', 'menubar', 'radiogroup', - 'row', 'tablist', - 'toolbar', 'tree', 'treegrid', ], - includeRoles: [ - 'alert', - 'dialog', + li: ['menuitem', 'option', 'row', 'tab', 'treeitem'], + table: ['grid'], + td: ['gridcell'], + fieldset: ['radiogroup', 'presentation'], + }, + ], + 'jsx-a11y/no-noninteractive-tabindex': [ + 'error', + { + tags: [], + roles: ['tabpanel'], + allowExpressionValues: true, + }, + ], + 'jsx-a11y/no-redundant-roles': 'error', + 'jsx-a11y/no-static-element-interactions': [ + 'error', + { + allowExpressionValues: true, + handlers: [ + 'onClick', + 'onMouseDown', + 'onMouseUp', + 'onKeyPress', + 'onKeyDown', + 'onKeyUp', ], - }], - 'jsx-a11y/heading-has-content': 'error', - 'jsx-a11y/html-has-lang': 'error', - 'jsx-a11y/iframe-has-title': 'error', - 'jsx-a11y/img-redundant-alt': 'error', - 'jsx-a11y/interactive-supports-focus': [ - 'error', - { - tabbable: [ - 'button', - 'checkbox', - 'link', - 'progressbar', - 'searchbox', - 'slider', - 'spinbutton', - 'switch', - 'textbox', - ], - }, + }, + ], + 'jsx-a11y/role-has-required-aria-props': 'error', + 'jsx-a11y/role-supports-aria-props': 'error', + 'jsx-a11y/scope': 'error', + 'jsx-a11y/tabindex-no-positive': 'error', + }, + }, + strict: { + plugins: [ + 'jsx-a11y', + ], + parserOptions: { + ecmaFeatures: { + jsx: true, + }, + }, + rules: { + 'jsx-a11y/alt-text': 'error', + 'jsx-a11y/anchor-has-content': 'error', + 'jsx-a11y/anchor-is-valid': 'error', + 'jsx-a11y/aria-activedescendant-has-tabindex': 'error', + 'jsx-a11y/aria-props': 'error', + 'jsx-a11y/aria-proptypes': 'error', + 'jsx-a11y/aria-role': 'error', + 'jsx-a11y/aria-unsupported-elements': 'error', + 'jsx-a11y/autocomplete-valid': 'error', + 'jsx-a11y/click-events-have-key-events': 'error', + 'jsx-a11y/control-has-associated-label': ['off', { + ignoreElements: [ + 'audio', + 'canvas', + 'embed', + 'input', + 'textarea', + 'tr', + 'video', ], - 'jsx-a11y/label-has-for': 'off', - 'jsx-a11y/label-has-associated-control': 'error', - 'jsx-a11y/media-has-caption': 'error', - 'jsx-a11y/mouse-events-have-key-events': 'error', - 'jsx-a11y/no-access-key': 'error', - 'jsx-a11y/no-autofocus': 'error', - 'jsx-a11y/no-distracting-elements': 'error', - 'jsx-a11y/no-interactive-element-to-noninteractive-role': 'error', - 'jsx-a11y/no-noninteractive-element-interactions': [ - 'error', - { - body: ['onError', 'onLoad'], - iframe: ['onError', 'onLoad'], - img: ['onError', 'onLoad'], - }, + ignoreRoles: [ + 'grid', + 'listbox', + 'menu', + 'menubar', + 'radiogroup', + 'row', + 'tablist', + 'toolbar', + 'tree', + 'treegrid', ], - 'jsx-a11y/no-noninteractive-element-to-interactive-role': 'error', - 'jsx-a11y/no-noninteractive-tabindex': 'error', - 'jsx-a11y/no-redundant-roles': 'error', - 'jsx-a11y/no-static-element-interactions': 'error', - 'jsx-a11y/role-has-required-aria-props': 'error', - 'jsx-a11y/role-supports-aria-props': 'error', - 'jsx-a11y/scope': 'error', - 'jsx-a11y/tabindex-no-positive': 'error', - }, + includeRoles: [ + 'alert', + 'dialog', + ], + }], + 'jsx-a11y/heading-has-content': 'error', + 'jsx-a11y/html-has-lang': 'error', + 'jsx-a11y/iframe-has-title': 'error', + 'jsx-a11y/img-redundant-alt': 'error', + 'jsx-a11y/interactive-supports-focus': [ + 'error', + { + tabbable: [ + 'button', + 'checkbox', + 'link', + 'progressbar', + 'searchbox', + 'slider', + 'spinbutton', + 'switch', + 'textbox', + ], + }, + ], + 'jsx-a11y/label-has-for': 'off', + 'jsx-a11y/label-has-associated-control': 'error', + 'jsx-a11y/media-has-caption': 'error', + 'jsx-a11y/mouse-events-have-key-events': 'error', + 'jsx-a11y/no-access-key': 'error', + 'jsx-a11y/no-autofocus': 'error', + 'jsx-a11y/no-distracting-elements': 'error', + 'jsx-a11y/no-interactive-element-to-noninteractive-role': 'error', + 'jsx-a11y/no-noninteractive-element-interactions': [ + 'error', + { + body: ['onError', 'onLoad'], + iframe: ['onError', 'onLoad'], + img: ['onError', 'onLoad'], + }, + ], + 'jsx-a11y/no-noninteractive-element-to-interactive-role': 'error', + 'jsx-a11y/no-noninteractive-tabindex': 'error', + 'jsx-a11y/no-redundant-roles': 'error', + 'jsx-a11y/no-static-element-interactions': 'error', + 'jsx-a11y/role-has-required-aria-props': 'error', + 'jsx-a11y/role-supports-aria-props': 'error', + 'jsx-a11y/scope': 'error', + 'jsx-a11y/tabindex-no-positive': 'error', }, }, }; + +/** @param {object} obj */ +function kebabCase(obj) { + return Object.fromEntries( + Object.entries(obj).map(([key, value]) => [ + key.replace(/([A-Z])/g, '-$1').toLowerCase(), + value, + ]), + ); +} diff --git a/src/util/mayContainChildComponent.js b/src/util/mayContainChildComponent.js index 43a03ef..5e1035e 100644 --- a/src/util/mayContainChildComponent.js +++ b/src/util/mayContainChildComponent.js @@ -9,7 +9,7 @@ import type { JSXOpeningElement, Node } from 'ast-types-flow'; import { elementType as rawElementType } from 'jsx-ast-utils'; -import minimatch from 'minimatch'; +import { minimatch } from 'minimatch'; export default function mayContainChildComponent( root: Node,