jsx-boolean-value.js 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159
  1. /**
  2. * @fileoverview Enforce boolean attributes notation in JSX
  3. * @author Yannick Croissant
  4. */
  5. 'use strict';
  6. const docsUrl = require('../util/docsUrl');
  7. const report = require('../util/report');
  8. // ------------------------------------------------------------------------------
  9. // Rule Definition
  10. // ------------------------------------------------------------------------------
  11. const exceptionsSchema = {
  12. type: 'array',
  13. items: { type: 'string', minLength: 1 },
  14. uniqueItems: true,
  15. };
  16. const ALWAYS = 'always';
  17. const NEVER = 'never';
  18. const errorData = new WeakMap();
  19. /**
  20. * @param {object} exceptions
  21. * @returns {object}
  22. */
  23. function getErrorData(exceptions) {
  24. if (!errorData.has(exceptions)) {
  25. const exceptionProps = Array.from(exceptions, (name) => `\`${name}\``).join(', ');
  26. const exceptionsMessage = exceptions.size > 0 ? ` for the following props: ${exceptionProps}` : '';
  27. errorData.set(exceptions, { exceptionsMessage });
  28. }
  29. return errorData.get(exceptions);
  30. }
  31. /**
  32. * @param {string} configuration
  33. * @param {Set<string>} exceptions
  34. * @param {string} propName
  35. * @returns {boolean} propName
  36. */
  37. function isAlways(configuration, exceptions, propName) {
  38. const isException = exceptions.has(propName);
  39. if (configuration === ALWAYS) {
  40. return !isException;
  41. }
  42. return isException;
  43. }
  44. /**
  45. * @param {string} configuration
  46. * @param {Set<string>} exceptions
  47. * @param {string} propName
  48. * @returns {boolean} propName
  49. */
  50. function isNever(configuration, exceptions, propName) {
  51. const isException = exceptions.has(propName);
  52. if (configuration === NEVER) {
  53. return !isException;
  54. }
  55. return isException;
  56. }
  57. const messages = {
  58. omitBoolean: 'Value must be omitted for boolean attributes{{exceptionsMessage}}',
  59. omitBoolean_noMessage: 'Value must be omitted for boolean attributes',
  60. setBoolean: 'Value must be set for boolean attributes{{exceptionsMessage}}',
  61. setBoolean_noMessage: 'Value must be set for boolean attributes',
  62. };
  63. module.exports = {
  64. meta: {
  65. docs: {
  66. description: 'Enforce boolean attributes notation in JSX',
  67. category: 'Stylistic Issues',
  68. recommended: false,
  69. url: docsUrl('jsx-boolean-value'),
  70. },
  71. fixable: 'code',
  72. messages,
  73. schema: {
  74. anyOf: [{
  75. type: 'array',
  76. items: [{ enum: [ALWAYS, NEVER] }],
  77. additionalItems: false,
  78. }, {
  79. type: 'array',
  80. items: [{
  81. enum: [ALWAYS],
  82. }, {
  83. type: 'object',
  84. additionalProperties: false,
  85. properties: {
  86. [NEVER]: exceptionsSchema,
  87. },
  88. }],
  89. additionalItems: false,
  90. }, {
  91. type: 'array',
  92. items: [{
  93. enum: [NEVER],
  94. }, {
  95. type: 'object',
  96. additionalProperties: false,
  97. properties: {
  98. [ALWAYS]: exceptionsSchema,
  99. },
  100. }],
  101. additionalItems: false,
  102. }],
  103. },
  104. },
  105. create(context) {
  106. const configuration = context.options[0] || NEVER;
  107. const configObject = context.options[1] || {};
  108. const exceptions = new Set((configuration === ALWAYS ? configObject[NEVER] : configObject[ALWAYS]) || []);
  109. return {
  110. JSXAttribute(node) {
  111. const propName = node.name && node.name.name;
  112. const value = node.value;
  113. if (
  114. isAlways(configuration, exceptions, propName)
  115. && value === null
  116. ) {
  117. const data = getErrorData(exceptions);
  118. const messageId = data.exceptionsMessage ? 'setBoolean' : 'setBoolean_noMessage';
  119. report(context, messages[messageId], messageId, {
  120. node,
  121. data,
  122. fix(fixer) {
  123. return fixer.insertTextAfter(node, '={true}');
  124. },
  125. });
  126. }
  127. if (
  128. isNever(configuration, exceptions, propName)
  129. && value
  130. && value.type === 'JSXExpressionContainer'
  131. && value.expression.value === true
  132. ) {
  133. const data = getErrorData(exceptions);
  134. const messageId = data.exceptionsMessage ? 'omitBoolean' : 'omitBoolean_noMessage';
  135. report(context, messages[messageId], messageId, {
  136. node,
  137. data,
  138. fix(fixer) {
  139. return fixer.removeRange([node.name.range[1], value.range[1]]);
  140. },
  141. });
  142. }
  143. },
  144. };
  145. },
  146. };