anchor-is-valid.js 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120
  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 = exports["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);
  44. // Only check anchor elements and custom types.
  45. if (typeCheck.indexOf(nodeType) === -1) {
  46. return;
  47. }
  48. // Set up the rule aspects to check.
  49. var aspects = options.aspects || allAspects;
  50. // Create active aspect flag object. Failing checks will only report
  51. // if the related flag is set to true.
  52. var activeAspects = {};
  53. allAspects.forEach(function (aspect) {
  54. activeAspects[aspect] = aspects.indexOf(aspect) !== -1;
  55. });
  56. var propOptions = options.specialLink || [];
  57. var propsToValidate = ['href'].concat(propOptions);
  58. var values = propsToValidate.map(function (prop) {
  59. return (0, _jsxAstUtils.getPropValue)((0, _jsxAstUtils.getProp)(node.attributes, prop));
  60. });
  61. // Checks if any actual or custom href prop is provided.
  62. var hasAnyHref = values.some(function (value) {
  63. return value != null;
  64. });
  65. // Need to check for spread operator as props can be spread onto the element
  66. // leading to an incorrect validation error.
  67. var hasSpreadOperator = attributes.some(function (prop) {
  68. return prop.type === 'JSXSpreadAttribute';
  69. });
  70. var onClick = (0, _jsxAstUtils.getProp)(attributes, 'onClick');
  71. // When there is no href at all, specific scenarios apply:
  72. if (!hasAnyHref) {
  73. // If no spread operator is found and no onClick event is present
  74. // it is a link without href.
  75. if (!hasSpreadOperator && activeAspects.noHref && (!onClick || onClick && !activeAspects.preferButton)) {
  76. context.report({
  77. node,
  78. message: noHrefErrorMessage
  79. });
  80. }
  81. // If no spread operator is found but an onClick is preset it should be a button.
  82. if (!hasSpreadOperator && onClick && activeAspects.preferButton) {
  83. context.report({
  84. node,
  85. message: preferButtonErrorMessage
  86. });
  87. }
  88. return;
  89. }
  90. // Hrefs have been found, now check for validity.
  91. var invalidHrefValues = values.filter(function (value) {
  92. return value != null && typeof value === 'string' && (!value.length || value === '#' || /^\W*?javascript:/.test(value));
  93. });
  94. if (invalidHrefValues.length !== 0) {
  95. // If an onClick is found it should be a button, otherwise it is an invalid link.
  96. if (onClick && activeAspects.preferButton) {
  97. context.report({
  98. node,
  99. message: preferButtonErrorMessage
  100. });
  101. } else if (activeAspects.invalidHref) {
  102. context.report({
  103. node,
  104. message: invalidHrefErrorMessage
  105. });
  106. }
  107. }
  108. }
  109. };
  110. }
  111. };
  112. module.exports = exports.default;