jsx-boolean-value.js 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169
  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. /**
  19. * @param {string} configuration
  20. * @param {Set<string>} exceptions
  21. * @param {string} propName
  22. * @returns {boolean} propName
  23. */
  24. function isAlways(configuration, exceptions, propName) {
  25. const isException = exceptions.has(propName);
  26. if (configuration === ALWAYS) {
  27. return !isException;
  28. }
  29. return isException;
  30. }
  31. /**
  32. * @param {string} configuration
  33. * @param {Set<string>} exceptions
  34. * @param {string} propName
  35. * @returns {boolean} propName
  36. */
  37. function isNever(configuration, exceptions, propName) {
  38. const isException = exceptions.has(propName);
  39. if (configuration === NEVER) {
  40. return !isException;
  41. }
  42. return isException;
  43. }
  44. const messages = {
  45. omitBoolean: 'Value must be omitted for boolean attribute `{{propName}}`',
  46. setBoolean: 'Value must be set for boolean attribute `{{propName}}`',
  47. omitPropAndBoolean: 'Value must be omitted for `false` attribute: `{{propName}}`',
  48. };
  49. /** @type {import('eslint').Rule.RuleModule} */
  50. module.exports = {
  51. meta: {
  52. docs: {
  53. description: 'Enforce boolean attributes notation in JSX',
  54. category: 'Stylistic Issues',
  55. recommended: false,
  56. url: docsUrl('jsx-boolean-value'),
  57. },
  58. fixable: 'code',
  59. messages,
  60. schema: {
  61. anyOf: [{
  62. type: 'array',
  63. items: [{ enum: [ALWAYS, NEVER] }],
  64. additionalItems: false,
  65. }, {
  66. type: 'array',
  67. items: [{
  68. enum: [ALWAYS],
  69. }, {
  70. type: 'object',
  71. additionalProperties: false,
  72. properties: {
  73. [NEVER]: exceptionsSchema,
  74. assumeUndefinedIsFalse: {
  75. type: 'boolean',
  76. },
  77. },
  78. }],
  79. additionalItems: false,
  80. }, {
  81. type: 'array',
  82. items: [{
  83. enum: [NEVER],
  84. }, {
  85. type: 'object',
  86. additionalProperties: false,
  87. properties: {
  88. [ALWAYS]: exceptionsSchema,
  89. assumeUndefinedIsFalse: {
  90. type: 'boolean',
  91. },
  92. },
  93. }],
  94. additionalItems: false,
  95. }],
  96. },
  97. },
  98. create(context) {
  99. const configuration = context.options[0] || NEVER;
  100. const configObject = context.options[1] || {};
  101. const exceptions = new Set((configuration === ALWAYS ? configObject[NEVER] : configObject[ALWAYS]) || []);
  102. return {
  103. JSXAttribute(node) {
  104. const propName = node.name && node.name.name;
  105. const value = node.value;
  106. if (
  107. isAlways(configuration, exceptions, propName)
  108. && value === null
  109. ) {
  110. const messageId = 'setBoolean';
  111. const data = { propName };
  112. report(context, messages[messageId], messageId, {
  113. node,
  114. data,
  115. fix(fixer) {
  116. return fixer.insertTextAfter(node, '={true}');
  117. },
  118. });
  119. }
  120. if (
  121. isNever(configuration, exceptions, propName)
  122. && value
  123. && value.type === 'JSXExpressionContainer'
  124. && value.expression.value === true
  125. ) {
  126. const messageId = 'omitBoolean';
  127. const data = { propName };
  128. report(context, messages[messageId], messageId, {
  129. node,
  130. data,
  131. fix(fixer) {
  132. return fixer.removeRange([node.name.range[1], value.range[1]]);
  133. },
  134. });
  135. }
  136. if (
  137. isNever(configuration, exceptions, propName)
  138. && configObject.assumeUndefinedIsFalse
  139. && value
  140. && value.type === 'JSXExpressionContainer'
  141. && value.expression.value === false
  142. ) {
  143. const messageId = 'omitPropAndBoolean';
  144. const data = { propName };
  145. report(context, messages[messageId], messageId, {
  146. node,
  147. data,
  148. fix(fixer) {
  149. return fixer.removeRange([node.name.range[0], value.range[1]]);
  150. },
  151. });
  152. }
  153. },
  154. };
  155. },
  156. };