label-has-for.js 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140
  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 _hasAccessibleChild = _interopRequireDefault(require("../util/hasAccessibleChild"));
  11. /**
  12. * @fileoverview Enforce label tags have htmlFor attribute.
  13. * @author Ethan Cohen
  14. */
  15. // ----------------------------------------------------------------------------
  16. // Rule Definition
  17. // ----------------------------------------------------------------------------
  18. var enumValues = ['nesting', 'id'];
  19. var schema = {
  20. type: 'object',
  21. properties: {
  22. components: _schemas.arraySchema,
  23. required: {
  24. oneOf: [{
  25. type: 'string',
  26. "enum": enumValues
  27. }, (0, _schemas.generateObjSchema)({
  28. some: (0, _schemas.enumArraySchema)(enumValues)
  29. }, ['some']), (0, _schemas.generateObjSchema)({
  30. every: (0, _schemas.enumArraySchema)(enumValues)
  31. }, ['every'])]
  32. },
  33. allowChildren: {
  34. type: 'boolean'
  35. }
  36. }
  37. };
  38. // Breadth-first search, assuming that HTML for forms is shallow.
  39. function validateNesting(node) {
  40. var queue = node.parent.children.slice();
  41. var child;
  42. var opener;
  43. while (queue.length) {
  44. child = queue.shift();
  45. opener = child.openingElement;
  46. if (child.type === 'JSXElement' && opener && (opener.name.name === 'input' || opener.name.name === 'textarea' || opener.name.name === 'select')) {
  47. return true;
  48. }
  49. if (child.children) {
  50. queue = queue.concat(child.children);
  51. }
  52. }
  53. return false;
  54. }
  55. var validateId = function validateId(node) {
  56. var htmlForAttr = (0, _jsxAstUtils.getProp)(node.attributes, 'htmlFor');
  57. var htmlForValue = (0, _jsxAstUtils.getPropValue)(htmlForAttr);
  58. return htmlForAttr !== false && !!htmlForValue;
  59. };
  60. var validate = function validate(node, required, allowChildren, elementType) {
  61. if (allowChildren === true) {
  62. return (0, _hasAccessibleChild["default"])(node.parent, elementType);
  63. }
  64. if (required === 'nesting') {
  65. return validateNesting(node);
  66. }
  67. return validateId(node);
  68. };
  69. var getValidityStatus = function getValidityStatus(node, required, allowChildren, elementType) {
  70. if (Array.isArray(required.some)) {
  71. var _isValid = required.some.some(function (rule) {
  72. return validate(node, rule, allowChildren, elementType);
  73. });
  74. var _message = !_isValid ? "Form label must have ANY of the following types of associated control: ".concat(required.some.join(', ')) : null;
  75. return {
  76. isValid: _isValid,
  77. message: _message
  78. };
  79. }
  80. if (Array.isArray(required.every)) {
  81. var _isValid2 = required.every.every(function (rule) {
  82. return validate(node, rule, allowChildren, elementType);
  83. });
  84. var _message2 = !_isValid2 ? "Form label must have ALL of the following types of associated control: ".concat(required.every.join(', ')) : null;
  85. return {
  86. isValid: _isValid2,
  87. message: _message2
  88. };
  89. }
  90. var isValid = validate(node, required, allowChildren, elementType);
  91. var message = !isValid ? "Form label must have the following type of associated control: ".concat(required) : null;
  92. return {
  93. isValid,
  94. message
  95. };
  96. };
  97. var _default = exports["default"] = {
  98. meta: {
  99. deprecated: true,
  100. replacedBy: ['label-has-associated-control'],
  101. docs: {
  102. description: 'Enforce that `<label>` elements have the `htmlFor` prop.',
  103. url: 'https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/tree/HEAD/docs/rules/label-has-for.md'
  104. },
  105. schema: [schema]
  106. },
  107. create: function create(context) {
  108. var elementType = (0, _getElementType["default"])(context);
  109. return {
  110. JSXOpeningElement: function JSXOpeningElement(node) {
  111. var options = context.options[0] || {};
  112. var componentOptions = options.components || [];
  113. var typesToValidate = ['label'].concat(componentOptions);
  114. var nodeType = elementType(node);
  115. // Only check 'label' elements and custom types.
  116. if (typesToValidate.indexOf(nodeType) === -1) {
  117. return;
  118. }
  119. var required = options.required || {
  120. every: ['nesting', 'id']
  121. };
  122. var allowChildren = options.allowChildren || false;
  123. var _getValidityStatus = getValidityStatus(node, required, allowChildren, elementType),
  124. isValid = _getValidityStatus.isValid,
  125. message = _getValidityStatus.message;
  126. if (!isValid) {
  127. context.report({
  128. node,
  129. message
  130. });
  131. }
  132. }
  133. };
  134. }
  135. };
  136. module.exports = exports.default;