├── src └── utils │ ├── nodeParsers │ ├── noop.js │ ├── JSXOpeningElement.js │ ├── JSXElement.js │ └── JSXAttribute.js │ ├── constants.js │ ├── getAsProp.js │ ├── mapChainExpressions.js │ ├── mergeStyledAttrsWithNodeAttrs.js │ ├── keyValuePairToProps.js │ ├── makeRule.js │ └── ruleNameToTypeDict.js ├── .babelrc ├── public ├── handshake.png └── linting-styled-components.png ├── .gitignore ├── .prettierrc.js ├── __tests__ ├── jsx-a11y-rules │ ├── util │ │ ├── implicitRoles │ │ │ ├── dl.js │ │ │ ├── ol.js │ │ │ ├── ul.js │ │ │ ├── form.js │ │ │ ├── h1.js │ │ │ ├── h2.js │ │ │ ├── h3.js │ │ │ ├── h4.js │ │ │ ├── h5.js │ │ │ ├── h6.js │ │ │ ├── hr.js │ │ │ ├── li.js │ │ │ ├── body.js │ │ │ ├── nav.js │ │ │ ├── button.js │ │ │ ├── details.js │ │ │ ├── dialog.js │ │ │ ├── meter.js │ │ │ ├── option.js │ │ │ ├── output.js │ │ │ ├── select.js │ │ │ ├── tbody.js │ │ │ ├── tfoot.js │ │ │ ├── thead.js │ │ │ ├── article.js │ │ │ ├── aside.js │ │ │ ├── datalist.js │ │ │ ├── section.js │ │ │ ├── textarea.js │ │ │ ├── progress.js │ │ │ ├── a.js │ │ │ ├── area.js │ │ │ ├── link.js │ │ │ ├── img.js │ │ │ ├── menu.js │ │ │ ├── menuitem.js │ │ │ ├── input.js │ │ │ └── index.js │ │ ├── isPresentationRole.js │ │ ├── isDOMElement.js │ │ ├── getComputedRole.js │ │ ├── getImplicitRole.js │ │ ├── isAbstractRole.js │ │ ├── getExplicitRole.js │ │ ├── isHiddenFromScreenReader.js │ │ ├── getSuggestion.js │ │ ├── schemas.js │ │ ├── isDisabledElement.js │ │ ├── hasAccessibleChild.js │ │ ├── getTabIndex.js │ │ ├── isNonLiteralProperty.js │ │ ├── attributesComparator.js │ │ ├── mayContainChildComponent.js │ │ ├── isSemanticRoleElement.js │ │ ├── isInteractiveRole.js │ │ ├── isNonInteractiveRole.js │ │ └── mayHaveAccessibleLabel.js │ ├── rules │ │ ├── html-has-lang.js │ │ ├── no-access-key.js │ │ ├── iframe-has-title.js │ │ ├── tabindex-no-positive.js │ │ ├── scope.js │ │ ├── no-onchange.js │ │ ├── no-distracting-elements.js │ │ ├── anchor-has-content.js │ │ ├── aria-props.js │ │ ├── no-autofocus.js │ │ ├── heading-has-content.js │ │ ├── aria-unsupported-elements.js │ │ ├── autocomplete-valid.js │ │ ├── lang.js │ │ ├── accessible-emoji.js │ │ ├── mouse-events-have-key-events.js │ │ ├── aria-role.js │ │ ├── aria-activedescendant-has-tabindex.js │ │ ├── click-events-have-key-events.js │ │ ├── img-redundant-alt.js │ │ ├── no-redundant-roles.js │ │ ├── no-noninteractive-element-to-interactive-role.js │ │ ├── role-has-required-aria-props.js │ │ ├── no-noninteractive-tabindex.js │ │ ├── role-supports-aria-props.js │ │ ├── no-interactive-element-to-noninteractive-role.js │ │ └── media-has-caption.js │ └── tests │ │ ├── scope-test.js │ │ ├── html-has-lang-test.js │ │ ├── lang-test.js │ │ ├── anchor-has-content-test.js │ │ ├── no-distracting-elements-test.js │ │ ├── iframe-has-title-test.js │ │ ├── no-onchange-test.js │ │ ├── no-autofocus-test.js │ │ ├── no-access-key-test.js │ │ ├── tabindex-no-positive-test.js │ │ ├── index.test.js │ │ ├── no-redundant-roles-test.js │ │ ├── accessible-emoji-test.js │ │ ├── aria-props-test.js │ │ ├── aria-unsupported-elements-test.js │ │ ├── aria-activedescendant-has-tabindex-test.js │ │ ├── mouse-events-have-key-events-test.js │ │ ├── heading-has-content-test.js │ │ ├── no-noninteractive-tabindex-test.js │ │ └── autocomplete-valid-test.js ├── utils │ ├── parserOptionsMapper.js │ └── getSuggestion.js ├── rules │ ├── template.js │ ├── autocomplete-valid.test.js │ ├── no-access-key.test.js │ ├── no-distracting-elements.test.js │ ├── no-interactive-element-to-noninteractive-role.test.js │ ├── html-has-lang.test.js │ ├── aria-unsupported-elements.test.js │ ├── control-has-associated-label.test.js │ ├── scope.test.js │ ├── no-onchange.test.js │ ├── aria-role.test.js │ ├── media-has-caption.test.js │ ├── heading-has-content.test.js │ ├── role-has-required-aria-props.test.js │ ├── no-static-element-interactions.test.js │ ├── no-redundant-roles.test.js │ ├── interactive-supports-focus.test.js │ ├── label-has-associated-control.test.js │ ├── accessible-emoji.test.js │ ├── aria-proptypes.test.js │ ├── label-has-for.test.js │ ├── no-noninteractive-element-to-interactive-role.test.js │ ├── role-supports-aria-props.test.js │ ├── anchor-has-content.test.js │ ├── no-noninteractive-element-interactions.test.js │ ├── img-redundant-alt.test.js │ ├── mouse-events-have-key-events.test.js │ ├── no-noninteractive-tabindex.test.js │ └── iframe-has-title.test.js └── index.test.js ├── playground ├── .eslintrc.js └── package.json └── package.json /src/utils/nodeParsers/noop.js: -------------------------------------------------------------------------------- 1 | module.exports = () => {}; 2 | -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["@babel/preset-env"], 3 | "plugins": ["@babel/plugin-proposal-optional-chaining"] 4 | } 5 | -------------------------------------------------------------------------------- /public/handshake.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brendanmorrell/eslint-plugin-styled-components-a11y/HEAD/public/handshake.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode 2 | .idea 3 | .iml 4 | .DS_Store 5 | 6 | node_modules 7 | lib 8 | playground/**.js 9 | playground/**.jsx 10 | -------------------------------------------------------------------------------- /src/utils/constants.js: -------------------------------------------------------------------------------- 1 | const __UNKNOWN_IDENTIFER__ = '__UNKNOWN_IDENTIFER__'; 2 | 3 | module.exports = { __UNKNOWN_IDENTIFER__ }; 4 | -------------------------------------------------------------------------------- /.prettierrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | trailingComma: 'all', 3 | singleQuote: true, 4 | endOfLine: 'lf', 5 | printWidth: 120, 6 | }; 7 | -------------------------------------------------------------------------------- /public/linting-styled-components.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brendanmorrell/eslint-plugin-styled-components-a11y/HEAD/public/linting-styled-components.png -------------------------------------------------------------------------------- /__tests__/jsx-a11y-rules/util/implicitRoles/dl.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Returns the implicit role for a dl tag. 3 | */ 4 | export default function getImplicitRoleForDl() { 5 | return 'list'; 6 | } 7 | -------------------------------------------------------------------------------- /__tests__/jsx-a11y-rules/util/implicitRoles/ol.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Returns the implicit role for an ol tag. 3 | */ 4 | export default function getImplicitRoleForOl() { 5 | return 'list'; 6 | } 7 | -------------------------------------------------------------------------------- /__tests__/jsx-a11y-rules/util/implicitRoles/ul.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Returns the implicit role for a ul tag. 3 | */ 4 | export default function getImplicitRoleForUl() { 5 | return 'list'; 6 | } 7 | -------------------------------------------------------------------------------- /__tests__/jsx-a11y-rules/util/implicitRoles/form.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Returns the implicit role for a form tag. 3 | */ 4 | export default function getImplicitRoleForForm() { 5 | return 'form'; 6 | } 7 | -------------------------------------------------------------------------------- /__tests__/jsx-a11y-rules/util/implicitRoles/h1.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Returns the implicit role for an h1 tag. 3 | */ 4 | export default function getImplicitRoleForH1() { 5 | return 'heading'; 6 | } 7 | -------------------------------------------------------------------------------- /__tests__/jsx-a11y-rules/util/implicitRoles/h2.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Returns the implicit role for an h2 tag. 3 | */ 4 | export default function getImplicitRoleForH2() { 5 | return 'heading'; 6 | } 7 | -------------------------------------------------------------------------------- /__tests__/jsx-a11y-rules/util/implicitRoles/h3.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Returns the implicit role for an h3 tag. 3 | */ 4 | export default function getImplicitRoleForH3() { 5 | return 'heading'; 6 | } 7 | -------------------------------------------------------------------------------- /__tests__/jsx-a11y-rules/util/implicitRoles/h4.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Returns the implicit role for an h4 tag. 3 | */ 4 | export default function getImplicitRoleForH4() { 5 | return 'heading'; 6 | } 7 | -------------------------------------------------------------------------------- /__tests__/jsx-a11y-rules/util/implicitRoles/h5.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Returns the implicit role for an h5 tag. 3 | */ 4 | export default function getImplicitRoleForH5() { 5 | return 'heading'; 6 | } 7 | -------------------------------------------------------------------------------- /__tests__/jsx-a11y-rules/util/implicitRoles/h6.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Returns the implicit role for an h6tag. 3 | */ 4 | export default function getImplicitRoleForH6() { 5 | return 'heading'; 6 | } 7 | -------------------------------------------------------------------------------- /__tests__/jsx-a11y-rules/util/implicitRoles/hr.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Returns the implicit role for an hr tag. 3 | */ 4 | export default function getImplicitRoleForHr() { 5 | return 'separator'; 6 | } 7 | -------------------------------------------------------------------------------- /__tests__/jsx-a11y-rules/util/implicitRoles/li.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Returns the implicit role for an li tag. 3 | */ 4 | export default function getImplicitRoleForLi() { 5 | return 'listitem'; 6 | } 7 | -------------------------------------------------------------------------------- /__tests__/jsx-a11y-rules/util/implicitRoles/body.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Returns the implicit role for a body tag. 3 | */ 4 | export default function getImplicitRoleForBody() { 5 | return 'document'; 6 | } 7 | -------------------------------------------------------------------------------- /__tests__/jsx-a11y-rules/util/implicitRoles/nav.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Returns the implicit role for a nav tag. 3 | */ 4 | export default function getImplicitRoleForNav() { 5 | return 'navigation'; 6 | } 7 | -------------------------------------------------------------------------------- /__tests__/jsx-a11y-rules/util/implicitRoles/button.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Returns the implicit role for a button tag. 3 | */ 4 | export default function getImplicitRoleForButton() { 5 | return 'button'; 6 | } 7 | -------------------------------------------------------------------------------- /__tests__/jsx-a11y-rules/util/implicitRoles/details.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Returns the implicit role for a details tag. 3 | */ 4 | export default function getImplicitRoleForDetails() { 5 | return 'group'; 6 | } 7 | -------------------------------------------------------------------------------- /__tests__/jsx-a11y-rules/util/implicitRoles/dialog.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Returns the implicit role for a dialog tag. 3 | */ 4 | export default function getImplicitRoleForDialog() { 5 | return 'dialog'; 6 | } 7 | -------------------------------------------------------------------------------- /__tests__/jsx-a11y-rules/util/implicitRoles/meter.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Returns the implicit role for a meter tag. 3 | */ 4 | export default function getImplicitRoleForMeter() { 5 | return 'progressbar'; 6 | } 7 | -------------------------------------------------------------------------------- /__tests__/jsx-a11y-rules/util/implicitRoles/option.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Returns the implicit role for an option tag. 3 | */ 4 | export default function getImplicitRoleForOption() { 5 | return 'option'; 6 | } 7 | -------------------------------------------------------------------------------- /__tests__/jsx-a11y-rules/util/implicitRoles/output.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Returns the implicit role for an output tag. 3 | */ 4 | export default function getImplicitRoleForOutput() { 5 | return 'status'; 6 | } 7 | -------------------------------------------------------------------------------- /__tests__/jsx-a11y-rules/util/implicitRoles/select.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Returns the implicit role for a select tag. 3 | */ 4 | export default function getImplicitRoleForSelect() { 5 | return 'listbox'; 6 | } 7 | -------------------------------------------------------------------------------- /__tests__/jsx-a11y-rules/util/implicitRoles/tbody.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Returns the implicit role for a tbody tag. 3 | */ 4 | export default function getImplicitRoleForTbody() { 5 | return 'rowgroup'; 6 | } 7 | -------------------------------------------------------------------------------- /__tests__/jsx-a11y-rules/util/implicitRoles/tfoot.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Returns the implicit role for a tfoot tag. 3 | */ 4 | export default function getImplicitRoleForTfoot() { 5 | return 'rowgroup'; 6 | } 7 | -------------------------------------------------------------------------------- /__tests__/jsx-a11y-rules/util/implicitRoles/thead.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Returns the implicit role for a thead tag. 3 | */ 4 | export default function getImplicitRoleForThead() { 5 | return 'rowgroup'; 6 | } 7 | -------------------------------------------------------------------------------- /__tests__/jsx-a11y-rules/util/implicitRoles/article.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Returns the implicit role for an article tag. 3 | */ 4 | export default function getImplicitRoleForArticle() { 5 | return 'article'; 6 | } 7 | -------------------------------------------------------------------------------- /__tests__/jsx-a11y-rules/util/implicitRoles/aside.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Returns the implicit role for an aside tag. 3 | */ 4 | export default function getImplicitRoleForAside() { 5 | return 'complementary'; 6 | } 7 | -------------------------------------------------------------------------------- /__tests__/jsx-a11y-rules/util/implicitRoles/datalist.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Returns the implicit role for a datalist tag. 3 | */ 4 | export default function getImplicitRoleForDatalist() { 5 | return 'listbox'; 6 | } 7 | -------------------------------------------------------------------------------- /__tests__/jsx-a11y-rules/util/implicitRoles/section.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Returns the implicit role for a section tag. 3 | */ 4 | export default function getImplicitRoleForSection() { 5 | return 'region'; 6 | } 7 | -------------------------------------------------------------------------------- /__tests__/jsx-a11y-rules/util/implicitRoles/textarea.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Returns the implicit role for a textarea tag. 3 | */ 4 | export default function getImplicitRoleForTextarea() { 5 | return 'textbox'; 6 | } 7 | -------------------------------------------------------------------------------- /__tests__/jsx-a11y-rules/util/implicitRoles/progress.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Returns the implicit role for a progress tag. 3 | */ 4 | export default function getImplicitRoleForProgress() { 5 | return 'progressbar'; 6 | } 7 | -------------------------------------------------------------------------------- /src/utils/getAsProp.js: -------------------------------------------------------------------------------- 1 | module.exports = (attributes) => { 2 | const [asProp] = attributes 3 | .filter((x) => x && x.name && x.name.name === 'as') 4 | .map((x) => x && x.value && x.value.value); 5 | return asProp; 6 | }; 7 | -------------------------------------------------------------------------------- /src/utils/mapChainExpressions.js: -------------------------------------------------------------------------------- 1 | module.exports = (attributes) => 2 | attributes.map((attribute) => { 3 | if (attribute.value?.expression?.type === 'ChainExpression') { 4 | attribute.value.expression = attribute.value.expression.expression; 5 | } 6 | 7 | return attribute; 8 | }); 9 | -------------------------------------------------------------------------------- /__tests__/jsx-a11y-rules/util/implicitRoles/a.js: -------------------------------------------------------------------------------- 1 | import { getProp } from 'jsx-ast-utils'; 2 | 3 | /** 4 | * Returns the implicit role for an anchor tag. 5 | */ 6 | export default function getImplicitRoleForAnchor(attributes) { 7 | if (getProp(attributes, 'href')) { 8 | return 'link'; 9 | } 10 | 11 | return ''; 12 | } 13 | -------------------------------------------------------------------------------- /__tests__/jsx-a11y-rules/util/implicitRoles/area.js: -------------------------------------------------------------------------------- 1 | import { getProp } from 'jsx-ast-utils'; 2 | 3 | /** 4 | * Returns the implicit role for an area tag. 5 | */ 6 | export default function getImplicitRoleForArea(attributes) { 7 | if (getProp(attributes, 'href')) { 8 | return 'link'; 9 | } 10 | 11 | return ''; 12 | } 13 | -------------------------------------------------------------------------------- /__tests__/jsx-a11y-rules/util/implicitRoles/link.js: -------------------------------------------------------------------------------- 1 | import { getProp } from 'jsx-ast-utils'; 2 | 3 | /** 4 | * Returns the implicit role for a link tag. 5 | */ 6 | export default function getImplicitRoleForLink(attributes) { 7 | if (getProp(attributes, 'href')) { 8 | return 'link'; 9 | } 10 | 11 | return ''; 12 | } 13 | -------------------------------------------------------------------------------- /__tests__/jsx-a11y-rules/util/isPresentationRole.js: -------------------------------------------------------------------------------- 1 | import { getProp, getLiteralPropValue } from 'jsx-ast-utils'; 2 | 3 | const presentationRoles = new Set([ 4 | 'presentation', 5 | 'none', 6 | ]); 7 | 8 | const isPresentationRole = (tagName, attributes) => presentationRoles.has(getLiteralPropValue(getProp(attributes, 'role'))); 9 | 10 | export default isPresentationRole; 11 | -------------------------------------------------------------------------------- /__tests__/jsx-a11y-rules/util/implicitRoles/img.js: -------------------------------------------------------------------------------- 1 | import { getProp, getLiteralPropValue } from 'jsx-ast-utils'; 2 | 3 | /** 4 | * Returns the implicit role for an img tag. 5 | */ 6 | export default function getImplicitRoleForImg(attributes) { 7 | const alt = getProp(attributes, 'alt'); 8 | 9 | if (alt && getLiteralPropValue(alt) === '') { 10 | return ''; 11 | } 12 | 13 | return 'img'; 14 | } 15 | -------------------------------------------------------------------------------- /__tests__/jsx-a11y-rules/util/isDOMElement.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @flow 3 | */ 4 | import { dom } from 'aria-query'; 5 | import includes from 'array-includes'; 6 | 7 | const domElements = [...dom.keys()]; 8 | 9 | /** 10 | * Returns boolean indicating whether the given element is a DOM element. 11 | */ 12 | const isDOMElement = ( 13 | tagName: string, 14 | ): boolean => includes(domElements, tagName); 15 | 16 | export default isDOMElement; 17 | -------------------------------------------------------------------------------- /__tests__/jsx-a11y-rules/util/implicitRoles/menu.js: -------------------------------------------------------------------------------- 1 | import { getProp, getLiteralPropValue } from 'jsx-ast-utils'; 2 | 3 | /** 4 | * Returns the implicit role for a menu tag. 5 | */ 6 | export default function getImplicitRoleForMenu(attributes) { 7 | const type = getProp(attributes, 'type'); 8 | 9 | if (type) { 10 | const value = getLiteralPropValue(type); 11 | 12 | return (value && value.toUpperCase() === 'TOOLBAR') ? 'toolbar' : ''; 13 | } 14 | 15 | return ''; 16 | } 17 | -------------------------------------------------------------------------------- /src/utils/mergeStyledAttrsWithNodeAttrs.js: -------------------------------------------------------------------------------- 1 | const { parse } = require('@babel/parser'); 2 | const keyValuePairToProps = require('./keyValuePairToProps'); 3 | 4 | module.exports = (styledAttrs, nodeAttrs) => { 5 | const jsxString = `
`; 6 | const ast = parse(jsxString, { plugins: ['jsx', 'estree'] }); 7 | const astAttributes = ast.program.body[0].expression.openingElement.attributes; 8 | return nodeAttrs.concat(astAttributes); 9 | }; 10 | -------------------------------------------------------------------------------- /__tests__/utils/parserOptionsMapper.js: -------------------------------------------------------------------------------- 1 | const defaultParserOptions = { 2 | ecmaVersion: 2018, 3 | ecmaFeatures: { 4 | experimentalObjectRestSpread: true, 5 | jsx: true, 6 | }, 7 | }; 8 | 9 | module.exports = function parserOptionsMapper({ code, errors, options = [], parserOptions = {}, settings = {} }) { 10 | return { 11 | code, 12 | errors, 13 | options, 14 | parserOptions: { 15 | ...defaultParserOptions, 16 | ...parserOptions, 17 | }, 18 | settings, 19 | }; 20 | }; 21 | -------------------------------------------------------------------------------- /src/utils/keyValuePairToProps.js: -------------------------------------------------------------------------------- 1 | const { __UNKNOWN_IDENTIFER__ } = require('./constants'); 2 | 3 | module.exports = ({ key, value }) => 4 | // we set all attributes which are variable identifiers as '__UNKNOWN_IDENTIFER__' just so there can be some value for the kv pair since we have no way of knowing the real value. 5 | // thus, here we want to just plug that string in without any quotes so the linter treats it just like a variable would have been handled normally 6 | `${key}=${typeof value !== 'string' || value === __UNKNOWN_IDENTIFER__ ? `{${value}}` : `"${value}"`} `; 7 | -------------------------------------------------------------------------------- /__tests__/jsx-a11y-rules/util/getComputedRole.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import type { Node } from 'ast-types-flow'; 3 | import getExplicitRole from './getExplicitRole'; 4 | import getImplicitRole from './getImplicitRole'; 5 | /** 6 | * Returns an element's computed role, which is 7 | * 8 | * 1. The valid value of its explicit role attribute; or 9 | * 2. The implicit value of its tag. 10 | */ 11 | export default function getComputedRole( 12 | tag: string, 13 | attributes: Array, 14 | ): ?string { 15 | return getExplicitRole(tag, attributes) || getImplicitRole(tag, attributes); 16 | } 17 | -------------------------------------------------------------------------------- /playground/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: ['react', 'styled-components-a11y'], 3 | extends: ['eslint-config-airbnb', 'prettier', 'prettier/react', 'plugin:styled-components-a11y/recommended'], 4 | parserOptions: { 5 | "ecmaVersion": 2020, 6 | parser: 'babel-eslint', 7 | sourceType: 'module', 8 | }, 9 | env: { 10 | es6: true, 11 | browser: true, 12 | }, 13 | settings: { 14 | "jsx-a11y": { 15 | components: { 16 | ImgComponent: 'img', 17 | }, 18 | }, 19 | }, 20 | rules: { 21 | 'react/jsx-filename-extension': 0, 22 | }, 23 | }; 24 | -------------------------------------------------------------------------------- /__tests__/rules/template.js: -------------------------------------------------------------------------------- 1 | const { RuleTester } = require('eslint'); 2 | const makeRule = require('../../src/utils/makeRule'); 3 | const parserOptionsMapper = require('../utils/parserOptionsMapper'); 4 | const getSuggestion = require('../utils/getSuggestion.js'); 5 | 6 | const ruleTester = new RuleTester(); 7 | const makeStyledTestCases = require('../utils/makeStyledTestCases'); 8 | 9 | const ruleName = ''; 10 | const rule = makeRule(ruleName); 11 | 12 | const expectedError = {}; 13 | 14 | // ## VALID 15 | 16 | // ## INVALID 17 | 18 | ruleTester.run(ruleName, rule, { 19 | valid: [], 20 | invalid: [], 21 | }); 22 | -------------------------------------------------------------------------------- /__tests__/rules/autocomplete-valid.test.js: -------------------------------------------------------------------------------- 1 | const { RuleTester } = require('eslint'); 2 | const makeRule = require('../../src/utils/makeRule'); 3 | const parserOptionsMapper = require('../utils/parserOptionsMapper'); 4 | const getSuggestion = require('../utils/getSuggestion.js'); 5 | 6 | const ruleTester = new RuleTester(); 7 | const makeStyledTestCases = require('../utils/makeStyledTestCases'); 8 | 9 | const ruleName = 'autocomplete-valid'; 10 | const rule = makeRule(ruleName); 11 | 12 | const expectedError = {}; 13 | 14 | // ## VALID 15 | 16 | // ## INVALID 17 | 18 | ruleTester.run(ruleName, rule, { 19 | valid: [], 20 | invalid: [], 21 | }); 22 | -------------------------------------------------------------------------------- /__tests__/jsx-a11y-rules/util/getImplicitRole.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import { roles as rolesMap } from 'aria-query'; 3 | import type { Node } from 'ast-types-flow'; 4 | import implicitRoles from './implicitRoles'; 5 | 6 | /** 7 | * Returns an element's implicit role given its attributes and type. 8 | * Some elements only have an implicit role when certain props are defined. 9 | */ 10 | export default function getImplicitRole( 11 | type: string, 12 | attributes: Array, 13 | ): ?string { 14 | let implicitRole; 15 | if (implicitRoles[type]) { 16 | implicitRole = implicitRoles[type](attributes); 17 | } 18 | if (rolesMap.has(implicitRole)) { 19 | return implicitRole; 20 | } 21 | return null; 22 | } 23 | -------------------------------------------------------------------------------- /__tests__/jsx-a11y-rules/util/implicitRoles/menuitem.js: -------------------------------------------------------------------------------- 1 | import { getProp, getLiteralPropValue } from 'jsx-ast-utils'; 2 | 3 | /** 4 | * Returns the implicit role for a menuitem tag. 5 | */ 6 | export default function getImplicitRoleForMenuitem(attributes) { 7 | const type = getProp(attributes, 'type'); 8 | 9 | if (type) { 10 | const value = getLiteralPropValue(type) || ''; 11 | 12 | switch (typeof value === 'string' && value.toUpperCase()) { 13 | case 'COMMAND': 14 | return 'menuitem'; 15 | case 'CHECKBOX': 16 | return 'menuitemcheckbox'; 17 | case 'RADIO': 18 | return 'menuitemradio'; 19 | default: 20 | return ''; 21 | } 22 | } 23 | 24 | return ''; 25 | } 26 | -------------------------------------------------------------------------------- /playground/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "playground", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "dependencies": { 7 | "react": "^16.13.1", 8 | "react-dom": "^16.13.1", 9 | "styled-components": "^5.1.1" 10 | }, 11 | "scripts": { 12 | "test": "echo \"Error: no test specified\" && exit 1", 13 | "reset": "rm -rf node_modules; yarn", 14 | "postinstall": "echo \"need to figure out how to use cli to reload vscode\"" 15 | }, 16 | "author": "", 17 | "license": "ISC", 18 | "devDependencies": { 19 | "eslint": "^7.1.0", 20 | "eslint-config-airbnb": "17.1.0", 21 | "eslint-plugin-react": "^7.20.0", 22 | "eslint-plugin-styled-components-a11y": "file:../" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /__tests__/jsx-a11y-rules/util/isAbstractRole.js: -------------------------------------------------------------------------------- 1 | import { 2 | dom, 3 | roles, 4 | } from 'aria-query'; 5 | import { getProp, getLiteralPropValue } from 'jsx-ast-utils'; 6 | 7 | const abstractRoles = new Set([...roles.keys()] 8 | .filter((role) => roles.get(role).abstract)); 9 | 10 | const DOMElements = [...dom.keys()]; 11 | 12 | const isAbstractRole = (tagName, attributes) => { 13 | // Do not test higher level JSX components, as we do not know what 14 | // low-level DOM element this maps to. 15 | if (DOMElements.indexOf(tagName) === -1) { 16 | return false; 17 | } 18 | 19 | const role = getLiteralPropValue(getProp(attributes, 'role')); 20 | 21 | return abstractRoles.has(role); 22 | }; 23 | 24 | export default isAbstractRole; 25 | -------------------------------------------------------------------------------- /__tests__/jsx-a11y-rules/util/getExplicitRole.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import { roles as rolesMap } from 'aria-query'; 3 | import { 4 | getProp, 5 | getLiteralPropValue, 6 | } from 'jsx-ast-utils'; 7 | import type { Node } from 'ast-types-flow'; 8 | /** 9 | * Returns an element's computed role, which is 10 | * 11 | * 1. The valid value of its explicit role attribute; or 12 | * 2. The implicit value of its tag. 13 | */ 14 | export default function getExplicitRole( 15 | tag: string, 16 | attributes: Array, 17 | ): ?string { 18 | const explicitRole = (function toLowerCase(role) { 19 | if (typeof role === 'string') { 20 | return role.toLowerCase(); 21 | } 22 | return null; 23 | }(getLiteralPropValue(getProp(attributes, 'role')))); 24 | 25 | if (rolesMap.has(explicitRole)) { 26 | return explicitRole; 27 | } 28 | return null; 29 | } 30 | -------------------------------------------------------------------------------- /__tests__/jsx-a11y-rules/util/isHiddenFromScreenReader.js: -------------------------------------------------------------------------------- 1 | import { getProp, getPropValue, getLiteralPropValue } from 'jsx-ast-utils'; 2 | 3 | /** 4 | * Returns boolean indicating that the aria-hidden prop 5 | * is present or the value is true. Will also return true if 6 | * there is an input with type='hidden'. 7 | * 8 | *
is equivalent to the DOM as