label-has-for.js 4.3 KB

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