@jsxtools/eslint-plugin-jsx-a11y
Version:
Static AST checker for accessibility rules on JSX elements for flat ESLint Config.
106 lines (103 loc) • 3.22 kB
JavaScript
import { getProp, getPropValue } from '../util/module/jsx-ast-utils.js';
import { generateObjSchema, arraySchema } from '../util/schemas.js';
import getElementType from '../util/getElementType.js';
import mayContainChildComponent from '../util/mayContainChildComponent.js';
import mayHaveAccessibleLabel from '../util/mayHaveAccessibleLabel.js';
const errorMessage = "A form label must be associated with a control.";
const schema = generateObjSchema({
labelComponents: arraySchema,
labelAttributes: arraySchema,
controlComponents: arraySchema,
assert: {
description: "Assert that the label has htmlFor, a nested label, both or either",
type: "string",
enum: ["htmlFor", "nesting", "both", "either"]
},
depth: {
description: "JSX tree depth limit to check for accessible label",
type: "integer",
minimum: 0
}
});
const validateId = (node) => {
const htmlForAttr = getProp(node.attributes, "htmlFor");
const htmlForValue = getPropValue(htmlForAttr);
return htmlForAttr !== false && !!htmlForValue;
};
const ruleOfLabelHasAssociatedControl = {
meta: {
docs: {
description: "Enforce that a `label` tag has a text label and an associated control.",
url: "https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/blob/main/docs/rules/label-has-associated-control.md"
},
schema: [schema]
},
create: (context) => {
const options = context.options[0] || {};
const labelComponents = options.labelComponents || [];
const assertType = options.assert || "either";
const componentNames = ["label"].concat(labelComponents);
const elementType = getElementType(context);
const rule = (node) => {
if (componentNames.indexOf(elementType(node.openingElement)) === -1) {
return;
}
const controlComponents = [
"input",
"meter",
"output",
"progress",
"select",
"textarea"
].concat(options.controlComponents || []);
const recursionDepth = Math.min(
options.depth === void 0 ? 2 : options.depth,
25
);
const hasLabelId = validateId(node.openingElement);
const hasNestedControl = controlComponents.some((name) => mayContainChildComponent(
node,
name,
recursionDepth,
elementType
));
const hasAccessibleLabel = mayHaveAccessibleLabel(
node,
recursionDepth,
options.labelAttributes
);
if (hasAccessibleLabel) {
switch (assertType) {
case "htmlFor":
if (hasLabelId) {
return;
}
break;
case "nesting":
if (hasNestedControl) {
return;
}
break;
case "both":
if (hasLabelId && hasNestedControl) {
return;
}
break;
case "either":
if (hasLabelId || hasNestedControl) {
return;
}
break;
}
}
context.report({
node: node.openingElement,
message: errorMessage
});
};
return {
JSXElement: rule
};
}
};
export { ruleOfLabelHasAssociatedControl as default };