UNPKG

@jsxtools/eslint-plugin-jsx-a11y

Version:

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

82 lines (79 loc) 4.02 kB
import safeRegexTest from 'safe-regex-test'; import { getPropValue, getProp } from '../util/module/jsx-ast-utils.js'; import { generateObjSchema, arraySchema, enumArraySchema } from '../util/schemas.js'; import getElementType from '../util/getElementType.js'; const allAspects = ["noHref", "invalidHref", "preferButton"]; const preferButtonErrorMessage = "Anchor used as a button. Anchors are primarily expected to navigate. Use the button element instead. Learn more: https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/blob/HEAD/docs/rules/anchor-is-valid.md"; const noHrefErrorMessage = "The href attribute is required for an anchor to be keyboard accessible. Provide a valid, navigable address as the href value. If you cannot provide an href, but still need the element to resemble a link, use a button and change it with appropriate styles. Learn more: https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/blob/HEAD/docs/rules/anchor-is-valid.md"; const invalidHrefErrorMessage = "The href attribute requires a valid value to be accessible. Provide a valid, navigable address as the href value. If you cannot provide a valid href, but still need the element to resemble a link, use a button and change it with appropriate styles. Learn more: https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/blob/HEAD/docs/rules/anchor-is-valid.md"; const schema = generateObjSchema({ components: arraySchema, specialLink: arraySchema, aspects: enumArraySchema(allAspects, 1) }); const ruleOfAnchorIsValid = { meta: { docs: { url: "https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/tree/HEAD/docs/rules/anchor-is-valid.md", description: "Enforce all anchors are valid, navigable elements." }, schema: [schema] }, create: (context) => { const elementType = getElementType(context); const testJShref = safeRegexTest(/^\W*?javascript:/); return { JSXOpeningElement: (node) => { const { attributes } = node; const options = context.options[0] || {}; const componentOptions = options.components || []; const typeCheck = ["a"].concat(componentOptions); const nodeType = elementType(node); if (typeCheck.indexOf(nodeType) === -1) { return; } const aspects = options.aspects || allAspects; const activeAspects = {}; allAspects.forEach((aspect) => { activeAspects[aspect] = aspects.indexOf(aspect) !== -1; }); const propOptions = options.specialLink || []; const propsToValidate = ["href"].concat(propOptions); const values = propsToValidate.map((prop) => getPropValue(getProp(node.attributes, prop))); const hasAnyHref = values.some((value) => value != null); const hasSpreadOperator = attributes.some((prop) => prop.type === "JSXSpreadAttribute"); const onClick = getProp(attributes, "onClick"); if (!hasAnyHref) { if (!hasSpreadOperator && activeAspects.noHref && (!onClick || onClick && !activeAspects.preferButton)) { context.report({ node, message: noHrefErrorMessage }); } if (!hasSpreadOperator && onClick && activeAspects.preferButton) { context.report({ node, message: preferButtonErrorMessage }); } return; } const invalidHrefValues = values.filter((value) => value != null && (typeof value === "string" && (!value.length || value === "#" || testJShref(value)))); if (invalidHrefValues.length !== 0) { if (onClick && activeAspects.preferButton) { context.report({ node, message: preferButtonErrorMessage }); } else if (activeAspects.invalidHref) { context.report({ node, message: invalidHrefErrorMessage }); } } } }; } }; export { ruleOfAnchorIsValid as default };