Update checked-in dependencies

This commit is contained in:
github-actions[bot]
2023-07-17 20:17:37 +00:00
parent 99c9f6a498
commit e266801e21
242 changed files with 2638 additions and 9296 deletions

View 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>`.',
})
}
}
},
}
},
}

View 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.',
})
}
},
}
},
}

View 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 elements 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}.`,
})
}
}
},
}
},
}

View 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"`.',
})
}
},
}
},
}

View File

@@ -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 elements role in the `elementRolesMap`
// - Get the elements name
const key = {name: getElementType(context, node)}
// - Get the elements disambiguating attributes
for (const prop of ['aria-expanded', 'type', 'size', 'role', 'href', 'multiple', 'scope']) {
// - Only provide `aria-expanded` when its 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 elements 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}.`,
})
}
}
},
}
},
}