label-has-for.js 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161
  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. }; // Breadth-first search, assuming that HTML for forms is shallow.
  38. function validateNesting(node) {
  39. var queue = node.parent.children.slice();
  40. var child;
  41. var opener;
  42. while (queue.length) {
  43. child = queue.shift();
  44. opener = child.openingElement;
  45. if (child.type === 'JSXElement' && opener && (opener.name.name === 'input' || opener.name.name === 'textarea' || opener.name.name === 'select')) {
  46. return true;
  47. }
  48. if (child.children) {
  49. queue = queue.concat(child.children);
  50. }
  51. }
  52. return false;
  53. }
  54. var validateId = function validateId(node) {
  55. var htmlForAttr = (0, _jsxAstUtils.getProp)(node.attributes, 'htmlFor');
  56. var htmlForValue = (0, _jsxAstUtils.getPropValue)(htmlForAttr);
  57. return htmlForAttr !== false && !!htmlForValue;
  58. };
  59. var validate = function validate(node, required, allowChildren, elementType) {
  60. if (allowChildren === true) {
  61. return (0, _hasAccessibleChild["default"])(node.parent, elementType);
  62. }
  63. if (required === 'nesting') {
  64. return validateNesting(node);
  65. }
  66. return validateId(node);
  67. };
  68. var getValidityStatus = function getValidityStatus(node, required, allowChildren, elementType) {
  69. if (Array.isArray(required.some)) {
  70. var _isValid = required.some.some(function (rule) {
  71. return validate(node, rule, allowChildren, elementType);
  72. });
  73. var _message = !_isValid ? "Form label must have ANY of the following types of associated control: ".concat(required.some.join(', ')) : null;
  74. return {
  75. isValid: _isValid,
  76. message: _message
  77. };
  78. }
  79. if (Array.isArray(required.every)) {
  80. var _isValid2 = required.every.every(function (rule) {
  81. return validate(node, rule, allowChildren, elementType);
  82. });
  83. var _message2 = !_isValid2 ? "Form label must have ALL of the following types of associated control: ".concat(required.every.join(', ')) : null;
  84. return {
  85. isValid: _isValid2,
  86. message: _message2
  87. };
  88. }
  89. var isValid = validate(node, required, allowChildren, elementType);
  90. var message = !isValid ? "Form label must have the following type of associated control: ".concat(required) : null;
  91. return {
  92. isValid,
  93. message
  94. };
  95. };
  96. var _default = {
  97. meta: {
  98. deprecated: true,
  99. docs: {
  100. url: 'https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/tree/HEAD/docs/rules/label-has-for.md'
  101. },
  102. schema: [schema]
  103. },
  104. create: function create(context) {
  105. var elementType = (0, _getElementType["default"])(context);
  106. return {
  107. JSXOpeningElement: function JSXOpeningElement(node) {
  108. var options = context.options[0] || {};
  109. var componentOptions = options.components || [];
  110. var typesToValidate = ['label'].concat(componentOptions);
  111. var nodeType = elementType(node); // Only check 'label' elements and custom types.
  112. if (typesToValidate.indexOf(nodeType) === -1) {
  113. return;
  114. }
  115. var required = options.required || {
  116. every: ['nesting', 'id']
  117. };
  118. var allowChildren = options.allowChildren || false;
  119. var _getValidityStatus = getValidityStatus(node, required, allowChildren, elementType),
  120. isValid = _getValidityStatus.isValid,
  121. message = _getValidityStatus.message;
  122. if (!isValid) {
  123. context.report({
  124. node,
  125. message
  126. });
  127. }
  128. }
  129. };
  130. }
  131. };
  132. exports["default"] = _default;
  133. module.exports = exports.default;