UNPKG

@jsxtools/eslint-plugin-jsx-a11y

Version:

Static AST checker for accessibility rules on JSX elements for flat ESLint Config.

102 lines (99 loc) 3.76 kB
import { getProp, getPropValue } from '../util/module/jsx-ast-utils.js'; import { arraySchema, generateObjSchema, enumArraySchema } from '../util/schemas.js'; import getElementType from '../util/getElementType.js'; import hasAccessibleChild from '../util/hasAccessibleChild.js'; const enumValues = ["nesting", "id"]; const schema = { type: "object", properties: { components: arraySchema, required: { oneOf: [ { type: "string", enum: enumValues }, generateObjSchema({ some: enumArraySchema(enumValues) }, ["some"]), generateObjSchema({ every: enumArraySchema(enumValues) }, ["every"]) ] }, allowChildren: { type: "boolean" } } }; function validateNesting(node) { let queue = node.parent.children.slice(); let child; let opener; while (queue.length) { child = queue.shift(); opener = child.openingElement; if (child.type === "JSXElement" && opener && (opener.name.name === "input" || opener.name.name === "textarea" || opener.name.name === "select")) { return true; } if (child.children) { queue = queue.concat(child.children); } } return false; } const validateId = (node) => { const htmlForAttr = getProp(node.attributes, "htmlFor"); const htmlForValue = getPropValue(htmlForAttr); return htmlForAttr !== false && !!htmlForValue; }; const validate = (node, required, allowChildren, elementType) => { if (allowChildren === true) { return hasAccessibleChild(node.parent, elementType); } if (required === "nesting") { return validateNesting(node); } return validateId(node); }; const getValidityStatus = (node, required, allowChildren, elementType) => { if (Array.isArray(required.some)) { const isValid2 = required.some.some((rule) => validate(node, rule, allowChildren, elementType)); const message2 = !isValid2 ? `Form label must have ANY of the following types of associated control: ${required.some.join(", ")}` : null; return { isValid: isValid2, message: message2 }; } if (Array.isArray(required.every)) { const isValid2 = required.every.every((rule) => validate(node, rule, allowChildren, elementType)); const message2 = !isValid2 ? `Form label must have ALL of the following types of associated control: ${required.every.join(", ")}` : null; return { isValid: isValid2, message: message2 }; } const isValid = validate(node, required, allowChildren, elementType); const message = !isValid ? `Form label must have the following type of associated control: ${required}` : null; return { isValid, message }; }; const ruleOfLabelHasFor = { meta: { deprecated: true, replacedBy: ["label-has-associated-control"], docs: { description: "Enforce that `<label>` elements have the `htmlFor` prop.", url: "https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/tree/HEAD/docs/rules/label-has-for.md" }, schema: [schema] }, create: (context) => { const elementType = getElementType(context); return { JSXOpeningElement: (node) => { const options = context.options[0] || {}; const componentOptions = options.components || []; const typesToValidate = ["label"].concat(componentOptions); const nodeType = elementType(node); if (typesToValidate.indexOf(nodeType) === -1) { return; } const required = options.required || { every: ["nesting", "id"] }; const allowChildren = options.allowChildren || false; const { isValid, message } = getValidityStatus(node, required, allowChildren, elementType); if (!isValid) { context.report({ node, message }); } } }; } }; export { ruleOfLabelHasFor as default };