anchor-is-valid.js 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128
  1. "use strict";
  2. var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
  3. Object.defineProperty(exports, "__esModule", {
  4. value: true
  5. });
  6. exports["default"] = void 0;
  7. var _jsxAstUtils = require("jsx-ast-utils");
  8. var _schemas = require("../util/schemas");
  9. var _getElementType = _interopRequireDefault(require("../util/getElementType"));
  10. /**
  11. * @fileoverview Performs validity check on anchor hrefs. Warns when anchors are used as buttons.
  12. * @author Almero Steyn
  13. *
  14. */
  15. // ----------------------------------------------------------------------------
  16. // Rule Definition
  17. // ----------------------------------------------------------------------------
  18. var allAspects = ['noHref', 'invalidHref', 'preferButton'];
  19. var 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';
  20. var 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';
  21. var 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';
  22. var schema = (0, _schemas.generateObjSchema)({
  23. components: _schemas.arraySchema,
  24. specialLink: _schemas.arraySchema,
  25. aspects: (0, _schemas.enumArraySchema)(allAspects, 1)
  26. });
  27. var _default = {
  28. meta: {
  29. docs: {
  30. url: 'https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/tree/HEAD/docs/rules/anchor-is-valid.md',
  31. description: 'Enforce all anchors are valid, navigable elements.'
  32. },
  33. schema: [schema]
  34. },
  35. create: function create(context) {
  36. var elementType = (0, _getElementType["default"])(context);
  37. return {
  38. JSXOpeningElement: function JSXOpeningElement(node) {
  39. var attributes = node.attributes;
  40. var options = context.options[0] || {};
  41. var componentOptions = options.components || [];
  42. var typeCheck = ['a'].concat(componentOptions);
  43. var nodeType = elementType(node); // Only check anchor elements and custom types.
  44. if (typeCheck.indexOf(nodeType) === -1) {
  45. return;
  46. } // Set up the rule aspects to check.
  47. var aspects = options.aspects || allAspects; // Create active aspect flag object. Failing checks will only report
  48. // if the related flag is set to true.
  49. var activeAspects = {};
  50. allAspects.forEach(function (aspect) {
  51. activeAspects[aspect] = aspects.indexOf(aspect) !== -1;
  52. });
  53. var propOptions = options.specialLink || [];
  54. var propsToValidate = ['href'].concat(propOptions);
  55. var values = propsToValidate.map(function (prop) {
  56. return (0, _jsxAstUtils.getProp)(node.attributes, prop);
  57. }).map(function (prop) {
  58. return (0, _jsxAstUtils.getPropValue)(prop);
  59. }); // Checks if any actual or custom href prop is provided.
  60. var hasAnyHref = values.filter(function (value) {
  61. return value === undefined || value === null;
  62. }).length !== values.length; // Need to check for spread operator as props can be spread onto the element
  63. // leading to an incorrect validation error.
  64. var hasSpreadOperator = attributes.filter(function (prop) {
  65. return prop.type === 'JSXSpreadAttribute';
  66. }).length > 0;
  67. var onClick = (0, _jsxAstUtils.getProp)(attributes, 'onClick'); // When there is no href at all, specific scenarios apply:
  68. if (!hasAnyHref) {
  69. // If no spread operator is found and no onClick event is present
  70. // it is a link without href.
  71. if (!hasSpreadOperator && activeAspects.noHref && (!onClick || onClick && !activeAspects.preferButton)) {
  72. context.report({
  73. node,
  74. message: noHrefErrorMessage
  75. });
  76. } // If no spread operator is found but an onClick is preset it should be a button.
  77. if (!hasSpreadOperator && onClick && activeAspects.preferButton) {
  78. context.report({
  79. node,
  80. message: preferButtonErrorMessage
  81. });
  82. }
  83. return;
  84. } // Hrefs have been found, now check for validity.
  85. var invalidHrefValues = values.filter(function (value) {
  86. return value !== undefined && value !== null;
  87. }).filter(function (value) {
  88. return typeof value === 'string' && (!value.length || value === '#' || /^\W*?javascript:/.test(value));
  89. });
  90. if (invalidHrefValues.length !== 0) {
  91. // If an onClick is found it should be a button, otherwise it is an invalid link.
  92. if (onClick && activeAspects.preferButton) {
  93. context.report({
  94. node,
  95. message: preferButtonErrorMessage
  96. });
  97. } else if (activeAspects.invalidHref) {
  98. context.report({
  99. node,
  100. message: invalidHrefErrorMessage
  101. });
  102. }
  103. }
  104. }
  105. };
  106. }
  107. };
  108. exports["default"] = _default;
  109. module.exports = exports.default;