No puede seleccionar más de 25 temas Los temas deben comenzar con una letra o número, pueden incluir guiones ('-') y pueden tener hasta 35 caracteres de largo.
 
 
 
 

211 líneas
7.9 KiB

  1. 'use strict';
  2. var _jsxAstUtils = require('jsx-ast-utils');
  3. var _schemas = require('../util/schemas');
  4. var _hasAccessibleChild = require('../util/hasAccessibleChild');
  5. var _hasAccessibleChild2 = _interopRequireDefault(_hasAccessibleChild);
  6. var _isPresentationRole = require('../util/isPresentationRole');
  7. var _isPresentationRole2 = _interopRequireDefault(_isPresentationRole);
  8. function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
  9. function _toConsumableArray(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) { arr2[i] = arr[i]; } return arr2; } else { return Array.from(arr); } } /**
  10. * @fileoverview Enforce all elements that require alternative text have it.
  11. * @author Ethan Cohen
  12. */
  13. // ----------------------------------------------------------------------------
  14. // Rule Definition
  15. // ----------------------------------------------------------------------------
  16. var DEFAULT_ELEMENTS = ['img', 'object', 'area', 'input[type="image"]'];
  17. var schema = (0, _schemas.generateObjSchema)({
  18. elements: _schemas.arraySchema,
  19. img: _schemas.arraySchema,
  20. object: _schemas.arraySchema,
  21. area: _schemas.arraySchema,
  22. 'input[type="image"]': _schemas.arraySchema
  23. });
  24. var ruleByElement = {
  25. img: function img(context, node) {
  26. var nodeType = (0, _jsxAstUtils.elementType)(node);
  27. var altProp = (0, _jsxAstUtils.getProp)(node.attributes, 'alt');
  28. // Missing alt prop error.
  29. if (altProp === undefined) {
  30. if ((0, _isPresentationRole2.default)(nodeType, node.attributes)) {
  31. context.report({
  32. node: node,
  33. message: 'Prefer alt="" over a presentational role. First rule of aria is to not use aria if it can be achieved via native HTML.'
  34. });
  35. return;
  36. }
  37. context.report({
  38. node: node,
  39. message: nodeType + ' elements must have an alt prop, either with meaningful text, or an empty string for decorative images.'
  40. });
  41. return;
  42. }
  43. // Check if alt prop is undefined.
  44. var altValue = (0, _jsxAstUtils.getPropValue)(altProp);
  45. var isNullValued = altProp.value === null; // <img alt />
  46. if (altValue && !isNullValued || altValue === '') {
  47. return;
  48. }
  49. // Undefined alt prop error.
  50. context.report({
  51. node: node,
  52. message: 'Invalid alt value for ' + nodeType + '. Use alt="" for presentational images.'
  53. });
  54. },
  55. object: function object(context, node) {
  56. var ariaLabelProp = (0, _jsxAstUtils.getProp)(node.attributes, 'aria-label');
  57. var arialLabelledByProp = (0, _jsxAstUtils.getProp)(node.attributes, 'aria-labelledby');
  58. var hasLabel = ariaLabelProp !== undefined || arialLabelledByProp !== undefined;
  59. var titleProp = (0, _jsxAstUtils.getLiteralPropValue)((0, _jsxAstUtils.getProp)(node.attributes, 'title'));
  60. var hasTitleAttr = !!titleProp;
  61. if (hasLabel || hasTitleAttr || (0, _hasAccessibleChild2.default)(node.parent)) {
  62. return;
  63. }
  64. context.report({
  65. node: node,
  66. message: 'Embedded <object> elements must have alternative text by providing inner text, aria-label or aria-labelledby props.'
  67. });
  68. },
  69. area: function area(context, node) {
  70. var ariaLabelPropValue = (0, _jsxAstUtils.getPropValue)((0, _jsxAstUtils.getProp)(node.attributes, 'aria-label'));
  71. var arialLabelledByPropValue = (0, _jsxAstUtils.getPropValue)((0, _jsxAstUtils.getProp)(node.attributes, 'aria-labelledby'));
  72. var hasLabel = ariaLabelPropValue !== undefined || arialLabelledByPropValue !== undefined;
  73. if (hasLabel) {
  74. return;
  75. }
  76. var altProp = (0, _jsxAstUtils.getProp)(node.attributes, 'alt');
  77. if (altProp === undefined) {
  78. context.report({
  79. node: node,
  80. message: 'Each area of an image map must have a text alternative through the `alt`, `aria-label`, or `aria-labelledby` prop.'
  81. });
  82. return;
  83. }
  84. var altValue = (0, _jsxAstUtils.getPropValue)(altProp);
  85. var isNullValued = altProp.value === null; // <area alt />
  86. if (altValue && !isNullValued || altValue === '') {
  87. return;
  88. }
  89. context.report({
  90. node: node,
  91. message: 'Each area of an image map must have a text alternative through the `alt`, `aria-label`, or `aria-labelledby` prop.'
  92. });
  93. },
  94. 'input[type="image"]': function inputImage(context, node) {
  95. // Only test input[type="image"]
  96. var nodeType = (0, _jsxAstUtils.elementType)(node);
  97. if (nodeType === 'input') {
  98. var typePropValue = (0, _jsxAstUtils.getPropValue)((0, _jsxAstUtils.getProp)(node.attributes, 'type'));
  99. if (typePropValue !== 'image') {
  100. return;
  101. }
  102. }
  103. var ariaLabelPropValue = (0, _jsxAstUtils.getPropValue)((0, _jsxAstUtils.getProp)(node.attributes, 'aria-label'));
  104. var arialLabelledByPropValue = (0, _jsxAstUtils.getPropValue)((0, _jsxAstUtils.getProp)(node.attributes, 'aria-labelledby'));
  105. var hasLabel = ariaLabelPropValue !== undefined || arialLabelledByPropValue !== undefined;
  106. if (hasLabel) {
  107. return;
  108. }
  109. var altProp = (0, _jsxAstUtils.getProp)(node.attributes, 'alt');
  110. if (altProp === undefined) {
  111. context.report({
  112. node: node,
  113. message: '<input> elements with type="image" must have a text alternative through the `alt`, `aria-label`, or `aria-labelledby` prop.'
  114. });
  115. return;
  116. }
  117. var altValue = (0, _jsxAstUtils.getPropValue)(altProp);
  118. var isNullValued = altProp.value === null; // <area alt />
  119. if (altValue && !isNullValued || altValue === '') {
  120. return;
  121. }
  122. context.report({
  123. node: node,
  124. message: '<input> elements with type="image" must have a text alternative through the `alt`, `aria-label`, or `aria-labelledby` prop.'
  125. });
  126. }
  127. };
  128. module.exports = {
  129. meta: {
  130. docs: {
  131. url: 'https://github.com/evcohen/eslint-plugin-jsx-a11y/tree/master/docs/rules/alt-text.md'
  132. },
  133. schema: [schema]
  134. },
  135. create: function create(context) {
  136. var _ref;
  137. var options = context.options[0] || {};
  138. // Elements to validate for alt text.
  139. var elementOptions = options.elements || DEFAULT_ELEMENTS;
  140. // Get custom components for just the elements that will be tested.
  141. var customComponents = elementOptions.map(function (element) {
  142. return options[element];
  143. }).reduce(function (components, customComponentsForElement) {
  144. return components.concat(customComponentsForElement || []);
  145. }, []);
  146. var typesToValidate = new Set((_ref = []).concat.apply(_ref, [customComponents].concat(_toConsumableArray(elementOptions))).map(function (type) {
  147. if (type === 'input[type="image"]') {
  148. return 'input';
  149. }
  150. return type;
  151. }));
  152. return {
  153. JSXOpeningElement: function JSXOpeningElement(node) {
  154. var nodeType = (0, _jsxAstUtils.elementType)(node);
  155. if (!typesToValidate.has(nodeType)) {
  156. return;
  157. }
  158. var DOMElement = nodeType;
  159. if (DOMElement === 'input') {
  160. DOMElement = 'input[type="image"]';
  161. }
  162. // Map nodeType to the DOM element if we are running this on a custom component.
  163. if (elementOptions.indexOf(DOMElement) === -1) {
  164. DOMElement = elementOptions.find(function (element) {
  165. var customComponentsForElement = options[element] || [];
  166. return customComponentsForElement.indexOf(nodeType) > -1;
  167. });
  168. }
  169. ruleByElement[DOMElement](context, node);
  170. }
  171. };
  172. }
  173. };