@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
JavaScript
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 };