label-has-associated-control.js 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131
  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 = {
  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 || []); // Prevent crazy recursion.
  61. var recursionDepth = Math.min(options.depth === undefined ? 2 : options.depth, 25);
  62. var hasLabelId = validateId(node.openingElement); // Check for multiple control components.
  63. var hasNestedControl = controlComponents.some(function (name) {
  64. return (0, _mayContainChildComponent["default"])(node, name, recursionDepth, elementType);
  65. });
  66. var hasAccessibleLabel = (0, _mayHaveAccessibleLabel["default"])(node, recursionDepth, options.labelAttributes);
  67. if (hasAccessibleLabel) {
  68. switch (assertType) {
  69. case 'htmlFor':
  70. if (hasLabelId) {
  71. return;
  72. }
  73. break;
  74. case 'nesting':
  75. if (hasNestedControl) {
  76. return;
  77. }
  78. break;
  79. case 'both':
  80. if (hasLabelId && hasNestedControl) {
  81. return;
  82. }
  83. break;
  84. case 'either':
  85. if (hasLabelId || hasNestedControl) {
  86. return;
  87. }
  88. break;
  89. default:
  90. break;
  91. }
  92. } // htmlFor case
  93. context.report({
  94. node: node.openingElement,
  95. message: errorMessage
  96. });
  97. }; // Create visitor selectors.
  98. return {
  99. JSXElement: rule
  100. };
  101. }
  102. };
  103. exports["default"] = _default;
  104. module.exports = exports.default;