label-has-associated-control.js 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112
  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. var _mayContainChildComponent = _interopRequireDefault(require("../util/mayContainChildComponent"));
  11. var _mayHaveAccessibleLabel = _interopRequireDefault(require("../util/mayHaveAccessibleLabel"));
  12. /**
  13. * @fileoverview Enforce label tags have an associated control.
  14. * @author Jesse Beach
  15. *
  16. *
  17. */
  18. // ----------------------------------------------------------------------------
  19. // Rule Definition
  20. // ----------------------------------------------------------------------------
  21. var errorMessage = 'A form label must be associated with a control.';
  22. var schema = (0, _schemas.generateObjSchema)({
  23. labelComponents: _schemas.arraySchema,
  24. labelAttributes: _schemas.arraySchema,
  25. controlComponents: _schemas.arraySchema,
  26. assert: {
  27. description: 'Assert that the label has htmlFor, a nested label, both or either',
  28. type: 'string',
  29. "enum": ['htmlFor', 'nesting', 'both', 'either']
  30. },
  31. depth: {
  32. description: 'JSX tree depth limit to check for accessible label',
  33. type: 'integer',
  34. minimum: 0
  35. }
  36. });
  37. var validateId = function validateId(node) {
  38. var htmlForAttr = (0, _jsxAstUtils.getProp)(node.attributes, 'htmlFor');
  39. var htmlForValue = (0, _jsxAstUtils.getPropValue)(htmlForAttr);
  40. return htmlForAttr !== false && !!htmlForValue;
  41. };
  42. var _default = exports["default"] = {
  43. meta: {
  44. docs: {
  45. description: 'Enforce that a `label` tag has a text label and an associated control.',
  46. url: 'https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/blob/main/docs/rules/label-has-associated-control.md'
  47. },
  48. schema: [schema]
  49. },
  50. create: function create(context) {
  51. var options = context.options[0] || {};
  52. var labelComponents = options.labelComponents || [];
  53. var assertType = options.assert || 'either';
  54. var componentNames = ['label'].concat(labelComponents);
  55. var elementType = (0, _getElementType["default"])(context);
  56. var rule = function rule(node) {
  57. if (componentNames.indexOf(elementType(node.openingElement)) === -1) {
  58. return;
  59. }
  60. var controlComponents = ['input', 'meter', 'output', 'progress', 'select', 'textarea'].concat(options.controlComponents || []);
  61. // Prevent crazy recursion.
  62. var recursionDepth = Math.min(options.depth === undefined ? 2 : options.depth, 25);
  63. var hasLabelId = validateId(node.openingElement);
  64. // Check for multiple control components.
  65. var hasNestedControl = controlComponents.some(function (name) {
  66. return (0, _mayContainChildComponent["default"])(node, name, recursionDepth, elementType);
  67. });
  68. var hasAccessibleLabel = (0, _mayHaveAccessibleLabel["default"])(node, recursionDepth, options.labelAttributes);
  69. if (hasAccessibleLabel) {
  70. switch (assertType) {
  71. case 'htmlFor':
  72. if (hasLabelId) {
  73. return;
  74. }
  75. break;
  76. case 'nesting':
  77. if (hasNestedControl) {
  78. return;
  79. }
  80. break;
  81. case 'both':
  82. if (hasLabelId && hasNestedControl) {
  83. return;
  84. }
  85. break;
  86. case 'either':
  87. if (hasLabelId || hasNestedControl) {
  88. return;
  89. }
  90. break;
  91. default:
  92. break;
  93. }
  94. }
  95. // htmlFor case
  96. context.report({
  97. node: node.openingElement,
  98. message: errorMessage
  99. });
  100. };
  101. // Create visitor selectors.
  102. return {
  103. JSXElement: rule
  104. };
  105. }
  106. };
  107. module.exports = exports.default;