Update checked-in dependencies
This commit is contained in:
parent
99c9f6a498
commit
e266801e21
242 changed files with 2638 additions and 9296 deletions
58
node_modules/eslint-plugin-github/README.md
generated
vendored
58
node_modules/eslint-plugin-github/README.md
generated
vendored
|
|
@ -47,15 +47,16 @@ _Note: This is experimental and subject to change._
|
|||
|
||||
The `react` config includes rules which target specific HTML elements. You may provide a mapping of custom components to an HTML element in your `eslintrc` configuration to increase linter coverage.
|
||||
|
||||
For each component, you may specify a `default` and/or `props`. `default` may make sense if there's a 1:1 mapping between a component and an HTML element. However, if the HTML output of a component is dependent on a prop value, you can provide a mapping using the `props` key. To minimize conflicts and complexity, this currently only supports the mapping of a single prop type.
|
||||
By default, these eslint rules will check the "as" prop for underlying element changes. If your repo uses a different prop name for polymorphic components provide the prop name in your `eslintrc` configuration under `polymorphicPropName`.
|
||||
|
||||
```json
|
||||
{
|
||||
"settings": {
|
||||
"github": {
|
||||
"polymorphicPropName": "asChild",
|
||||
"components": {
|
||||
"Box": {"default": "p"},
|
||||
"Link": {"props": {"as": {"undefined": "a", "a": "a", "button": "button"}}}
|
||||
"Box": "p",
|
||||
"Link": "a"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -66,9 +67,7 @@ This config will be interpreted in the following way:
|
|||
|
||||
- All `<Box>` elements will be treated as a `p` element type.
|
||||
- `<Link>` without a defined `as` prop will be treated as a `a`.
|
||||
- `<Link as='a'>` will treated as an `a` element type.
|
||||
- `<Link as='button'>` will be treated as a `button` element type.
|
||||
- `<Link as='summary'>` will be treated as the raw `Link` type because there is no configuration set for `as='summary'`.
|
||||
|
||||
### Rules
|
||||
|
||||
|
|
@ -82,28 +81,31 @@ 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-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 | 🔍 | | |
|
||||
| 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 | | | ❌ |
|
||||
| [a11y-no-title-attribute](docs/rules/a11y-no-title-attribute.md) | Guards against developers using the title attribute | ⚛️ | | |
|
||||
| [a11y-no-visually-hidden-interactive-element](docs/rules/a11y-no-visually-hidden-interactive-element.md) | Ensures that interactive elements are not visually hidden | ⚛️ | | |
|
||||
| [a11y-role-supports-aria-props](docs/rules/a11y-role-supports-aria-props.md) | Enforce that elements with explicit or implicit roles defined contain only `aria-*` properties supported by that `role`. | ⚛️ | | |
|
||||
| [a11y-svg-has-accessible-name](docs/rules/a11y-svg-has-accessible-name.md) | SVGs must have an accessible name | ⚛️ | | |
|
||||
| [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 | 🔍 | | |
|
||||
| [unescaped-html-literal](docs/rules/unescaped-html-literal.md) | disallow unescaped HTML literals | 🔍 | | |
|
||||
|
||||
<!-- end auto-generated rules list -->
|
||||
|
|
|
|||
7
node_modules/eslint-plugin-github/lib/configs/react.js
generated
vendored
7
node_modules/eslint-plugin-github/lib/configs/react.js
generated
vendored
|
|
@ -8,9 +8,12 @@ module.exports = {
|
|||
plugins: ['github', 'jsx-a11y'],
|
||||
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
|
||||
'jsx-a11y/role-supports-aria-props': 'off', // Override with github/a11y-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',
|
||||
'github/a11y-no-visually-hidden-interactive-element': 'error',
|
||||
'github/a11y-no-title-attribute': 'error',
|
||||
'github/a11y-svg-has-accessible-name': 'error',
|
||||
'github/a11y-role-supports-aria-props': 'error',
|
||||
'jsx-a11y/no-aria-hidden-on-focusable': 'error',
|
||||
'jsx-a11y/no-autofocus': 'off',
|
||||
'jsx-a11y/anchor-ambiguous-text': [
|
||||
|
|
|
|||
5
node_modules/eslint-plugin-github/lib/index.js
generated
vendored
5
node_modules/eslint-plugin-github/lib/index.js
generated
vendored
|
|
@ -1,7 +1,11 @@
|
|||
module.exports = {
|
||||
rules: {
|
||||
'a11y-no-visually-hidden-interactive-element': require('./rules/a11y-no-visually-hidden-interactive-element'),
|
||||
'a11y-no-generic-link-text': require('./rules/a11y-no-generic-link-text'),
|
||||
'a11y-no-title-attribute': require('./rules/a11y-no-title-attribute'),
|
||||
'a11y-aria-label-is-well-formatted': require('./rules/a11y-aria-label-is-well-formatted'),
|
||||
'a11y-role-supports-aria-props': require('./rules/a11y-role-supports-aria-props'),
|
||||
'a11y-svg-has-accessible-name': require('./rules/a11y-svg-has-accessible-name'),
|
||||
'array-foreach': require('./rules/array-foreach'),
|
||||
'async-currenttarget': require('./rules/async-currenttarget'),
|
||||
'async-preventdefault': require('./rules/async-preventdefault'),
|
||||
|
|
@ -18,7 +22,6 @@ module.exports = {
|
|||
'no-then': require('./rules/no-then'),
|
||||
'no-useless-passive': require('./rules/no-useless-passive'),
|
||||
'prefer-observers': require('./rules/prefer-observers'),
|
||||
'role-supports-aria-props': require('./rules/role-supports-aria-props'),
|
||||
'require-passive-events': require('./rules/require-passive-events'),
|
||||
'unescaped-html-literal': require('./rules/unescaped-html-literal'),
|
||||
},
|
||||
|
|
|
|||
66
node_modules/eslint-plugin-github/lib/rules/a11y-no-title-attribute.js
generated
vendored
Normal file
66
node_modules/eslint-plugin-github/lib/rules/a11y-no-title-attribute.js
generated
vendored
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
const {getProp, getPropValue} = require('jsx-ast-utils')
|
||||
const {getElementType} = require('../utils/get-element-type')
|
||||
|
||||
const SEMANTIC_ELEMENTS = [
|
||||
'a',
|
||||
'button',
|
||||
'summary',
|
||||
'select',
|
||||
'option',
|
||||
'textarea',
|
||||
'input',
|
||||
'span',
|
||||
'div',
|
||||
'p',
|
||||
'h1',
|
||||
'h2',
|
||||
'h3',
|
||||
'h4',
|
||||
'h5',
|
||||
'h6',
|
||||
'details',
|
||||
'summary',
|
||||
'dialog',
|
||||
'tr',
|
||||
'th',
|
||||
'td',
|
||||
'label',
|
||||
]
|
||||
|
||||
const ifSemanticElement = (context, node) => {
|
||||
const elementType = getElementType(context, node.openingElement, true)
|
||||
|
||||
for (const semanticElement of SEMANTIC_ELEMENTS) {
|
||||
if (elementType === semanticElement) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
meta: {
|
||||
docs: {
|
||||
description: 'Guards against developers using the title attribute',
|
||||
url: require('../url')(module),
|
||||
},
|
||||
schema: [],
|
||||
},
|
||||
|
||||
create(context) {
|
||||
return {
|
||||
JSXElement: node => {
|
||||
const elementType = getElementType(context, node.openingElement)
|
||||
if (elementType !== `iframe` && ifSemanticElement(context, node)) {
|
||||
const titleProp = getPropValue(getProp(node.openingElement.attributes, `title`))
|
||||
if (titleProp) {
|
||||
context.report({
|
||||
node,
|
||||
message: 'The title attribute is not accessible and should never be used unless for an `<iframe>`.',
|
||||
})
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
83
node_modules/eslint-plugin-github/lib/rules/a11y-no-visually-hidden-interactive-element.js
generated
vendored
Normal file
83
node_modules/eslint-plugin-github/lib/rules/a11y-no-visually-hidden-interactive-element.js
generated
vendored
Normal file
|
|
@ -0,0 +1,83 @@
|
|||
const {getProp, getPropValue} = require('jsx-ast-utils')
|
||||
const {getElementType} = require('../utils/get-element-type')
|
||||
const {generateObjSchema} = require('eslint-plugin-jsx-a11y/lib/util/schemas')
|
||||
|
||||
const defaultClassName = 'sr-only'
|
||||
const defaultcomponentName = 'VisuallyHidden'
|
||||
|
||||
const schema = generateObjSchema({
|
||||
className: {type: 'string'},
|
||||
componentName: {type: 'string'},
|
||||
htmlPropName: {type: 'string'},
|
||||
})
|
||||
|
||||
/** Note: we are not including input elements at this time
|
||||
* because a visually hidden input field might cause a false positive.
|
||||
* (e.g. fileUpload https://github.com/primer/react/pull/3492)
|
||||
*/
|
||||
const INTERACTIVE_ELEMENTS = ['a', 'button', 'summary', 'select', 'option', 'textarea']
|
||||
|
||||
const checkIfInteractiveElement = (context, node) => {
|
||||
const elementType = getElementType(context, node.openingElement)
|
||||
|
||||
for (const interactiveElement of INTERACTIVE_ELEMENTS) {
|
||||
if (elementType === interactiveElement) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// if the node is visually hidden recursively check if it has interactive children
|
||||
const checkIfVisuallyHiddenAndInteractive = (context, options, node, isParentVisuallyHidden) => {
|
||||
const {className, componentName} = options
|
||||
if (node.type === 'JSXElement') {
|
||||
const classes = getPropValue(getProp(node.openingElement.attributes, 'className'))
|
||||
const isVisuallyHiddenElement = node.openingElement.name.name === componentName
|
||||
const hasSROnlyClass = typeof classes !== 'undefined' && classes.includes(className)
|
||||
let isHidden = false
|
||||
if (hasSROnlyClass || isVisuallyHiddenElement || !!isParentVisuallyHidden) {
|
||||
if (checkIfInteractiveElement(context, node)) {
|
||||
return true
|
||||
}
|
||||
isHidden = true
|
||||
}
|
||||
if (node.children && node.children.length > 0) {
|
||||
return (
|
||||
typeof node.children?.find(child =>
|
||||
checkIfVisuallyHiddenAndInteractive(context, options, child, !!isParentVisuallyHidden || isHidden),
|
||||
) !== 'undefined'
|
||||
)
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
meta: {
|
||||
docs: {
|
||||
description: 'Ensures that interactive elements are not visually hidden',
|
||||
url: require('../url')(module),
|
||||
},
|
||||
schema: [schema],
|
||||
},
|
||||
|
||||
create(context) {
|
||||
const {options} = context
|
||||
const config = options[0] || {}
|
||||
const className = config.className || defaultClassName
|
||||
const componentName = config.componentName || defaultcomponentName
|
||||
|
||||
return {
|
||||
JSXElement: node => {
|
||||
if (checkIfVisuallyHiddenAndInteractive(context, {className, componentName}, node, false)) {
|
||||
context.report({
|
||||
node,
|
||||
message:
|
||||
'Avoid visually hidding interactive elements. Visually hiding interactive elements can be confusing to sighted keyboard users as it appears their focus has been lost when they navigate to the hidden element.',
|
||||
})
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
61
node_modules/eslint-plugin-github/lib/rules/a11y-role-supports-aria-props.js
generated
vendored
Normal file
61
node_modules/eslint-plugin-github/lib/rules/a11y-role-supports-aria-props.js
generated
vendored
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
// @ts-check
|
||||
const {aria, roles} = require('aria-query')
|
||||
const {getPropValue, propName} = require('jsx-ast-utils')
|
||||
const {getRole} = require('../utils/get-role')
|
||||
|
||||
module.exports = {
|
||||
meta: {
|
||||
docs: {
|
||||
description:
|
||||
'Enforce that elements with explicit or implicit roles defined contain only `aria-*` properties supported by that `role`.',
|
||||
url: require('../url')(module),
|
||||
},
|
||||
schema: [],
|
||||
},
|
||||
|
||||
create(context) {
|
||||
return {
|
||||
JSXOpeningElement(node) {
|
||||
// Get the element’s explicit or implicit role
|
||||
const role = getRole(context, node)
|
||||
|
||||
// Return early if role could not be determined
|
||||
if (!role) return
|
||||
|
||||
// Get allowed ARIA attributes:
|
||||
// - From the role itself
|
||||
let allowedProps = Object.keys(roles.get(role)?.props || {})
|
||||
// - From parent roles
|
||||
for (const parentRole of roles.get(role)?.superClass.flat() ?? []) {
|
||||
allowedProps = allowedProps.concat(Object.keys(roles.get(parentRole)?.props || {}))
|
||||
}
|
||||
// Dedupe, for performance
|
||||
allowedProps = Array.from(new Set(allowedProps))
|
||||
|
||||
// Get prohibited ARIA attributes:
|
||||
// - From the role itself
|
||||
let prohibitedProps = roles.get(role)?.prohibitedProps || []
|
||||
// - From parent roles
|
||||
for (const parentRole of roles.get(role)?.superClass.flat() ?? []) {
|
||||
prohibitedProps = prohibitedProps.concat(roles.get(parentRole)?.prohibitedProps || [])
|
||||
}
|
||||
// - From comparing allowed vs all ARIA attributes
|
||||
prohibitedProps = prohibitedProps.concat(aria.keys().filter(x => !allowedProps.includes(x)))
|
||||
// Dedupe, for performance
|
||||
prohibitedProps = Array.from(new Set(prohibitedProps))
|
||||
|
||||
for (const prop of node.attributes) {
|
||||
// Return early if prohibited ARIA attribute is set to an ignorable value
|
||||
if (getPropValue(prop) == null || prop.type === 'JSXSpreadAttribute') return
|
||||
|
||||
if (prohibitedProps?.includes(propName(prop))) {
|
||||
context.report({
|
||||
node,
|
||||
message: `The attribute ${propName(prop)} is not supported by the role ${role}.`,
|
||||
})
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
41
node_modules/eslint-plugin-github/lib/rules/a11y-svg-has-accessible-name.js
generated
vendored
Normal file
41
node_modules/eslint-plugin-github/lib/rules/a11y-svg-has-accessible-name.js
generated
vendored
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
const {hasProp} = require('jsx-ast-utils')
|
||||
const {getElementType} = require('../utils/get-element-type')
|
||||
|
||||
module.exports = {
|
||||
meta: {
|
||||
docs: {
|
||||
description: 'SVGs must have an accessible name',
|
||||
url: require('../url')(module),
|
||||
},
|
||||
schema: [],
|
||||
},
|
||||
|
||||
create(context) {
|
||||
return {
|
||||
JSXOpeningElement: node => {
|
||||
const elementType = getElementType(context, node)
|
||||
if (elementType !== 'svg') return
|
||||
|
||||
// Check if there is a nested title element that is the first child of the `<svg>`
|
||||
const hasNestedTitleAsFirstChild =
|
||||
node.parent.children?.[0]?.type === 'JSXElement' &&
|
||||
node.parent.children?.[0]?.openingElement?.name?.name === 'title'
|
||||
|
||||
// Check if `aria-label` or `aria-labelledby` is set
|
||||
const hasAccessibleName = hasProp(node.attributes, 'aria-label') || hasProp(node.attributes, 'aria-labelledby')
|
||||
|
||||
// Check if SVG is decorative
|
||||
const isDecorative =
|
||||
hasProp(node.attributes, 'role', 'presentation') || hasProp(node.attributes, 'aria-hidden', 'true')
|
||||
|
||||
if (elementType === 'svg' && !hasAccessibleName && !isDecorative && !hasNestedTitleAsFirstChild) {
|
||||
context.report({
|
||||
node,
|
||||
message:
|
||||
'`<svg>` must have an accessible name. Set `aria-label` or `aria-labelledby`, or nest a `<title>` element. However, if the `<svg>` is purely decorative, hide it with `aria-hidden="true"` or `role="presentation"`.',
|
||||
})
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
101
node_modules/eslint-plugin-github/lib/rules/role-supports-aria-props.js
generated
vendored
101
node_modules/eslint-plugin-github/lib/rules/role-supports-aria-props.js
generated
vendored
|
|
@ -1,101 +0,0 @@
|
|||
// @ts-check
|
||||
const {aria, elementRoles, roles} = require('aria-query')
|
||||
const {getProp, getPropValue, propName} = require('jsx-ast-utils')
|
||||
const {getElementType} = require('../utils/get-element-type')
|
||||
const ObjectMap = require('../utils/object-map')
|
||||
|
||||
// Clean-up `elementRoles` from `aria-query`
|
||||
const elementRolesMap = new ObjectMap()
|
||||
for (const [key, value] of elementRoles.entries()) {
|
||||
// - Remove unused `constraints` key
|
||||
delete key.constraints
|
||||
key.attributes = key.attributes?.filter(attribute => !('constraints' in attribute))
|
||||
// - Remove empty `attributes` key
|
||||
if (!key.attributes || key.attributes?.length === 0) {
|
||||
delete key.attributes
|
||||
}
|
||||
elementRolesMap.set(key, value)
|
||||
}
|
||||
// - Remove insufficiently-disambiguated `menuitem` entry
|
||||
elementRolesMap.delete({name: 'menuitem'})
|
||||
// - Disambiguate `menuitem` and `menu` roles by `type`
|
||||
elementRolesMap.set({name: 'menuitem', attributes: [{name: 'type', value: 'command'}]}, ['menuitem'])
|
||||
elementRolesMap.set({name: 'menuitem', attributes: [{name: 'type', value: 'radio'}]}, ['menuitemradio'])
|
||||
elementRolesMap.set({name: 'menuitem', attributes: [{name: 'type', value: 'toolbar'}]}, ['toolbar'])
|
||||
elementRolesMap.set({name: 'menu', attributes: [{name: 'type', value: 'toolbar'}]}, ['toolbar'])
|
||||
|
||||
module.exports = {
|
||||
meta: {
|
||||
docs: {
|
||||
description:
|
||||
'Enforce that elements with explicit or implicit roles defined contain only `aria-*` properties supported by that `role`.',
|
||||
url: require('../url')(module),
|
||||
},
|
||||
schema: [],
|
||||
},
|
||||
|
||||
create(context) {
|
||||
return {
|
||||
JSXOpeningElement(node) {
|
||||
// Assemble a key for looking-up the element’s role in the `elementRolesMap`
|
||||
// - Get the element’s name
|
||||
const key = {name: getElementType(context, node)}
|
||||
// - Get the element’s disambiguating attributes
|
||||
for (const prop of ['aria-expanded', 'type', 'size', 'role', 'href', 'multiple', 'scope']) {
|
||||
// - Only provide `aria-expanded` when it’s required for disambiguation
|
||||
if (prop === 'aria-expanded' && key.name !== 'summary') continue
|
||||
const value = getPropValue(getProp(node.attributes, prop))
|
||||
if (value) {
|
||||
if (!('attributes' in key)) {
|
||||
key.attributes = []
|
||||
}
|
||||
if (prop === 'href') {
|
||||
key.attributes.push({name: prop})
|
||||
} else {
|
||||
key.attributes.push({name: prop, value})
|
||||
}
|
||||
}
|
||||
}
|
||||
// Get the element’s explicit or implicit role
|
||||
const role = getPropValue(getProp(node.attributes, 'role')) ?? elementRolesMap.get(key)?.[0]
|
||||
|
||||
// Return early if role could not be determined
|
||||
if (!role) return
|
||||
|
||||
// Get allowed ARIA attributes:
|
||||
// - From the role itself
|
||||
let allowedProps = Object.keys(roles.get(role)?.props || {})
|
||||
// - From parent roles
|
||||
for (const parentRole of roles.get(role)?.superClass.flat() ?? []) {
|
||||
allowedProps = allowedProps.concat(Object.keys(roles.get(parentRole)?.props || {}))
|
||||
}
|
||||
// Dedupe, for performance
|
||||
allowedProps = Array.from(new Set(allowedProps))
|
||||
|
||||
// Get prohibited ARIA attributes:
|
||||
// - From the role itself
|
||||
let prohibitedProps = roles.get(role)?.prohibitedProps || []
|
||||
// - From parent roles
|
||||
for (const parentRole of roles.get(role)?.superClass.flat() ?? []) {
|
||||
prohibitedProps = prohibitedProps.concat(roles.get(parentRole)?.prohibitedProps || [])
|
||||
}
|
||||
// - From comparing allowed vs all ARIA attributes
|
||||
prohibitedProps = prohibitedProps.concat(aria.keys().filter(x => !allowedProps.includes(x)))
|
||||
// Dedupe, for performance
|
||||
prohibitedProps = Array.from(new Set(prohibitedProps))
|
||||
|
||||
for (const prop of node.attributes) {
|
||||
// Return early if prohibited ARIA attribute is set to an ignorable value
|
||||
if (getPropValue(prop) == null || prop.type === 'JSXSpreadAttribute') return
|
||||
|
||||
if (prohibitedProps?.includes(propName(prop))) {
|
||||
context.report({
|
||||
node,
|
||||
message: `The attribute ${propName(prop)} is not supported by the role ${role}.`,
|
||||
})
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
30
node_modules/eslint-plugin-github/lib/utils/get-element-type.js
generated
vendored
30
node_modules/eslint-plugin-github/lib/utils/get-element-type.js
generated
vendored
|
|
@ -7,30 +7,20 @@ If a prop determines the type, it can be specified with `props`.
|
|||
|
||||
For now, we only support the mapping of one prop type to an element type, rather than combinations of props.
|
||||
*/
|
||||
function getElementType(context, node) {
|
||||
function getElementType(context, node, ignoreMap = false) {
|
||||
const {settings} = context
|
||||
const rawElement = elementType(node)
|
||||
if (!settings) return rawElement
|
||||
|
||||
const componentMap = settings.github && settings.github.components
|
||||
if (!componentMap) return rawElement
|
||||
const component = componentMap[rawElement]
|
||||
if (!component) return rawElement
|
||||
let element = component.default ? component.default : rawElement
|
||||
// check if the node contains a polymorphic prop
|
||||
const polymorphicPropName = settings?.github?.polymorphicPropName ?? 'as'
|
||||
const rawElement = getPropValue(getProp(node.attributes, polymorphicPropName)) ?? elementType(node)
|
||||
|
||||
if (component.props) {
|
||||
const props = Object.entries(component.props)
|
||||
for (const [key, value] of props) {
|
||||
const propMap = value
|
||||
const propValue = getPropValue(getProp(node.attributes, key))
|
||||
const mapValue = propMap[propValue]
|
||||
// if a component configuration does not exists, return the raw element
|
||||
if (ignoreMap || !settings?.github?.components?.[rawElement]) return rawElement
|
||||
|
||||
if (mapValue) {
|
||||
element = mapValue
|
||||
}
|
||||
}
|
||||
}
|
||||
return element
|
||||
const defaultComponent = settings.github.components[rawElement]
|
||||
|
||||
// check if the default component is also defined in the configuration
|
||||
return defaultComponent ? defaultComponent : defaultComponent
|
||||
}
|
||||
|
||||
module.exports = {getElementType}
|
||||
|
|
|
|||
108
node_modules/eslint-plugin-github/lib/utils/get-role.js
generated
vendored
Normal file
108
node_modules/eslint-plugin-github/lib/utils/get-role.js
generated
vendored
Normal file
|
|
@ -0,0 +1,108 @@
|
|||
const {getProp, getPropValue} = require('jsx-ast-utils')
|
||||
const {elementRoles} = require('aria-query')
|
||||
const {getElementType} = require('./get-element-type')
|
||||
const ObjectMap = require('./object-map')
|
||||
|
||||
const elementRolesMap = cleanElementRolesMap()
|
||||
|
||||
/*
|
||||
Returns an element roles map which uses `aria-query`'s elementRoles as the foundation.
|
||||
We additionally clean the data so we're able to fetch a role using a key we construct based on the node we're looking at.
|
||||
In a few scenarios, we stray from the roles returned by `aria-query` and hard code the mapping.
|
||||
*/
|
||||
function cleanElementRolesMap() {
|
||||
const rolesMap = new ObjectMap()
|
||||
|
||||
for (const [key, value] of elementRoles.entries()) {
|
||||
// - Remove empty `attributes` key
|
||||
if (!key.attributes || key.attributes?.length === 0) {
|
||||
delete key.attributes
|
||||
}
|
||||
rolesMap.set(key, value)
|
||||
}
|
||||
// Remove insufficiently-disambiguated `menuitem` entry
|
||||
rolesMap.delete({name: 'menuitem'})
|
||||
// Disambiguate `menuitem` and `menu` roles by `type`
|
||||
rolesMap.set({name: 'menuitem', attributes: [{name: 'type', value: 'command'}]}, ['menuitem'])
|
||||
rolesMap.set({name: 'menuitem', attributes: [{name: 'type', value: 'radio'}]}, ['menuitemradio'])
|
||||
rolesMap.set({name: 'menuitem', attributes: [{name: 'type', value: 'toolbar'}]}, ['toolbar'])
|
||||
rolesMap.set({name: 'menu', attributes: [{name: 'type', value: 'toolbar'}]}, ['toolbar'])
|
||||
|
||||
/* These have constraints defined in aria-query's `elementRoles` which depend on knowledge of ancestor roles which we cant accurately determine in a linter context.
|
||||
However, we benefit more from assuming the role, than assuming it's generic or undefined so we opt to hard code the mapping */
|
||||
rolesMap.set({name: 'aside'}, ['complementary']) // `aside` still maps to `complementary` in https://www.w3.org/TR/html-aria/#docconformance.
|
||||
rolesMap.set({name: 'li'}, ['listitem']) // `li` can be generic if it's not within a list but we would never want to render `li` outside of a list.
|
||||
|
||||
return rolesMap
|
||||
}
|
||||
|
||||
/*
|
||||
Determine role of an element, based on its name and attributes.
|
||||
We construct a key and look up the element's role in `elementRolesMap`.
|
||||
If there is no match, we return undefined.
|
||||
*/
|
||||
function getRole(context, node) {
|
||||
// Early return if role is explicitly set
|
||||
const explicitRole = getPropValue(getProp(node.attributes, 'role'))
|
||||
if (explicitRole) {
|
||||
return explicitRole
|
||||
}
|
||||
|
||||
// Assemble a key for looking-up the element’s role in the `elementRolesMap`
|
||||
// - Get the element’s name
|
||||
const key = {name: getElementType(context, node)}
|
||||
|
||||
for (const prop of [
|
||||
'aria-label',
|
||||
'aria-labelledby',
|
||||
'alt',
|
||||
'type',
|
||||
'size',
|
||||
'role',
|
||||
'href',
|
||||
'multiple',
|
||||
'scope',
|
||||
'name',
|
||||
]) {
|
||||
if ((prop === 'aria-labelledby' || prop === 'aria-label') && !['section', 'form'].includes(key.name)) continue
|
||||
if (prop === 'name' && key.name !== 'form') continue
|
||||
if (prop === 'href' && key.name !== 'a' && key.name !== 'area') continue
|
||||
if (prop === 'alt' && key.name !== 'img') continue
|
||||
|
||||
const propOnNode = getProp(node.attributes, prop)
|
||||
|
||||
if (!('attributes' in key)) {
|
||||
key.attributes = []
|
||||
}
|
||||
// Disambiguate "undefined" props
|
||||
if (propOnNode === undefined && prop === 'alt' && key.name === 'img') {
|
||||
key.attributes.push({name: prop, constraints: ['undefined']})
|
||||
continue
|
||||
}
|
||||
|
||||
const value = getPropValue(propOnNode)
|
||||
if (value || (value === '' && prop === 'alt')) {
|
||||
if (
|
||||
prop === 'href' ||
|
||||
prop === 'aria-labelledby' ||
|
||||
prop === 'aria-label' ||
|
||||
prop === 'name' ||
|
||||
(prop === 'alt' && value !== '')
|
||||
) {
|
||||
key.attributes.push({name: prop, constraints: ['set']})
|
||||
} else {
|
||||
key.attributes.push({name: prop, value})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// - Remove empty `attributes` key
|
||||
if (!key.attributes || key.attributes?.length === 0) {
|
||||
delete key.attributes
|
||||
}
|
||||
|
||||
// Get the element’s implicit role
|
||||
return elementRolesMap.get(key)?.[0]
|
||||
}
|
||||
|
||||
module.exports = {getRole}
|
||||
6
node_modules/eslint-plugin-github/package.json
generated
vendored
6
node_modules/eslint-plugin-github/package.json
generated
vendored
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "eslint-plugin-github",
|
||||
"version": "4.8.0",
|
||||
"version": "4.9.0",
|
||||
"description": "An opinionated collection of ESLint shared configs and rules used by GitHub.",
|
||||
"main": "lib/index.js",
|
||||
"entries": [
|
||||
|
|
@ -15,7 +15,7 @@
|
|||
"lint:eslint-docs": "npm run update:eslint-docs -- --check",
|
||||
"lint:js": "eslint .",
|
||||
"pretest": "mkdir -p node_modules/ && ln -fs $(pwd) node_modules/",
|
||||
"test": "npm run eslint-check && npm run lint && mocha tests/**/*.js tests/",
|
||||
"test": "mocha tests/**/*.js tests/",
|
||||
"update:eslint-docs": "eslint-doc-generator"
|
||||
},
|
||||
"repository": {
|
||||
|
|
@ -32,7 +32,7 @@
|
|||
"@github/browserslist-config": "^1.0.0",
|
||||
"@typescript-eslint/eslint-plugin": "^5.1.0",
|
||||
"@typescript-eslint/parser": "^5.1.0",
|
||||
"aria-query": "^5.1.3",
|
||||
"aria-query": "^5.3.0",
|
||||
"eslint-config-prettier": ">=8.0.0",
|
||||
"eslint-plugin-escompat": "^3.3.3",
|
||||
"eslint-plugin-eslint-comments": "^3.2.0",
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue