@jsxtools/eslint-plugin-jsx-a11y
Version:
Static AST checker for accessibility rules on JSX elements for flat ESLint Config.
85 lines (82 loc) • 3.05 kB
JavaScript
const jsxAstUtils = require('../util/module/jsx-ast-utils.cjs');
const schemas = require('../util/schemas.cjs');
const getElementType = require('../util/getElementType.cjs');
const isDOMElement = require('../util/isDOMElement.cjs');
const isHiddenFromScreenReader = require('../util/isHiddenFromScreenReader.cjs');
const isInteractiveElement = require('../util/isInteractiveElement.cjs');
const isInteractiveRole = require('../util/isInteractiveRole.cjs');
const mayHaveAccessibleLabel = require('../util/mayHaveAccessibleLabel.cjs');
const errorMessage = "A control must be associated with a text label.";
const ignoreList = ["link"];
const schema = schemas.generateObjSchema({
labelAttributes: schemas.arraySchema,
controlComponents: schemas.arraySchema,
ignoreElements: schemas.arraySchema,
ignoreRoles: schemas.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 = jsxAstUtils.getLiteralPropValue(jsxAstUtils.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
};
}
};
module.exports = ruleOfControlHasAssociatedLabel;