@jsxtools/eslint-plugin-jsx-a11y
Version:
Static AST checker for accessibility rules on JSX elements for flat ESLint Config.
85 lines (82 loc) • 2.99 kB
JavaScript
import { getLiteralPropValue, getProp } from '../util/module/jsx-ast-utils.js';
import { generateObjSchema, arraySchema } from '../util/schemas.js';
import getElementType from '../util/getElementType.js';
import isDOMElement from '../util/isDOMElement.js';
import isHiddenFromScreenReader from '../util/isHiddenFromScreenReader.js';
import isInteractiveElement from '../util/isInteractiveElement.js';
import isInteractiveRole from '../util/isInteractiveRole.js';
import mayHaveAccessibleLabel from '../util/mayHaveAccessibleLabel.js';
const errorMessage = "A control must be associated with a text label.";
const ignoreList = ["link"];
const schema = generateObjSchema({
labelAttributes: arraySchema,
controlComponents: arraySchema,
ignoreElements: arraySchema,
ignoreRoles: arraySchema,
depth: {
description: "JSX tree depth limit to check for accessible label",
type: "integer",
minimum: 0
}
});
const ruleOfControlHasAssociatedLabel = {
meta: {
docs: {
description: "Enforce that a control (an interactive element) has a text label.",
url: "https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/blob/main/docs/rules/control-has-associated-label.md"
},
schema: [schema]
},
create: (context) => {
const elementType = getElementType(context);
const options = context.options[0] || {};
const {
labelAttributes = [],
controlComponents = [],
ignoreElements = [],
ignoreRoles = []
} = options;
const newIgnoreElements = new Set([].concat(ignoreElements, ignoreList));
const rule = (node) => {
const tag = elementType(node.openingElement);
const role = getLiteralPropValue(getProp(node.openingElement.attributes, "role"));
if (newIgnoreElements.has(tag)) {
return;
}
if (ignoreRoles.includes(role)) {
return;
}
const props = node.openingElement.attributes;
const nodeIsDOMElement = isDOMElement(tag);
const nodeIsHiddenFromScreenReader = isHiddenFromScreenReader(tag, props);
const nodeIsInteractiveElement = isInteractiveElement(tag, props);
const nodeIsInteractiveRole = isInteractiveRole(tag, props);
const nodeIsControlComponent = controlComponents.indexOf(tag) > -1;
if (nodeIsHiddenFromScreenReader) {
return;
}
let hasAccessibleLabel = true;
if (nodeIsInteractiveElement || nodeIsDOMElement && nodeIsInteractiveRole || nodeIsControlComponent) {
const recursionDepth = Math.min(
options.depth === void 0 ? 2 : options.depth,
25
);
hasAccessibleLabel = mayHaveAccessibleLabel(
node,
recursionDepth,
labelAttributes
);
}
if (!hasAccessibleLabel) {
context.report({
node: node.openingElement,
message: errorMessage
});
}
};
return {
JSXElement: rule
};
}
};
export { ruleOfControlHasAssociatedLabel as default };