Repository: jsx-eslint/eslint-plugin-react
Pull request #3691 (2024-02-18 14:10:58)

[Fix] `jsx-boolean-value`: make error messages clearer
As mentioned in #3675, jsx-boolean-value error message is not clear.

in current code, after setting never or always in the second option, if the modification is made not only to the first option but also to the second option, an error message containing the properties of the second option is displayed as shown below. 

```
{
      code: '<App foo={true} bar={true} baz />;',
      output: '<App foo bar baz={true} />;',
      options: ['always', { never: ['foo', 'bar'] }],
      errors: [
        {
          messageId: 'omitBoolean',
          data: { exceptionsMessage: ' for the following props: `foo`, `bar`' },
        },
        {
          messageId: 'omitBoolean',
          data: { exceptionsMessage: ' for the following props: `foo`, `bar`' },
        },
        {
          messageId: 'setBoolean',
          data: { exceptionsMessage: ' for the following props: `foo`, `bar`' },
          // setBoolean attribute is not foo or bar but baz
        }
      ],
    },
```

i think this is not make sense, so change the error message include single prop that has error. so fixed test case is it 

```
    {
      code: '<App foo={true} bar={true} baz />;',
      output: '<App foo bar baz={true} />;',
      options: ['always', { never: ['foo', 'bar'] }],
      errors: [
        {
          messageId: 'omitBoolean',
          data: { exceptionsMessage: ' for the following props: `foo`' },
        },
        {
          messageId: 'omitBoolean',
          data: { exceptionsMessage: ' for the following props: `bar`' },
        },
        {
          messageId: 'setBoolean',
          data: { exceptionsMessage: ' for the following props: `baz`' },
        },
      ],
    },
```
other test case change is because of message format change


Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 0cc3999f..4fac357d 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -30,6 +30,7 @@ This change log adheres to standards from [Keep a CHANGELOG](https://keepachange
 * [`forbid-elements`]: prevent a crash on `createElement()` ([#3632][] @ljharb)
 
 ### Changed
+* [`jsx-boolean-value`]: make error messages clearer ([#3691][] @developer-bandi)
 * [Refactor] `propTypes`: extract type params to var ([#3634][] @HenryBrown0)
 * [Refactor] [`boolean-prop-naming`]: invert if statement ([#3634][] @HenryBrown0)
 * [Refactor] [`function-component-definition`]: exit early if no type params ([#3634][] @HenryBrown0)
@@ -40,6 +41,7 @@ This change log adheres to standards from [Keep a CHANGELOG](https://keepachange
 * [Docs] [`hook-use-state`]: fix an undefined variable ([#3626][] @chentsulin)
 
 [#3697]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3697
+[#3691]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3691
 [#3690]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3690
 [#3680]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3680
 [#3679]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3679
diff --git a/lib/rules/jsx-boolean-value.js b/lib/rules/jsx-boolean-value.js
index ae6dda0d..0090c0a1 100644
--- a/lib/rules/jsx-boolean-value.js
+++ b/lib/rules/jsx-boolean-value.js
@@ -21,19 +21,6 @@ const exceptionsSchema = {
 const ALWAYS = 'always';
 const NEVER = 'never';
 
-const errorData = new WeakMap();
-/**
- * @param {object} exceptions
- * @returns {object}
- */
-function getErrorData(exceptions) {
-  if (!errorData.has(exceptions)) {
-    const exceptionProps = Array.from(exceptions, (name) => `\`${name}\``).join(', ');
-    const exceptionsMessage = exceptions.size > 0 ? ` for the following props: ${exceptionProps}` : '';
-    errorData.set(exceptions, { exceptionsMessage });
-  }
-  return errorData.get(exceptions);
-}
 /**
  * @param {string} configuration
  * @param {Set<string>} exceptions
@@ -62,12 +49,9 @@ function isNever(configuration, exceptions, propName) {
 }
 
 const messages = {
-  omitBoolean: 'Value must be omitted for boolean attributes{{exceptionsMessage}}',
-  omitBoolean_noMessage: 'Value must be omitted for boolean attributes',
-  setBoolean: 'Value must be set for boolean attributes{{exceptionsMessage}}',
-  setBoolean_noMessage: 'Value must be set for boolean attributes',
-  omitPropAndBoolean: 'Value and Prop must be omitted for false attributes{{exceptionsMessage}}',
-  omitPropAndBoolean_noMessage: 'Value and Prop must be omitted for false attributes',
+  omitBoolean: 'Value must be omitted for boolean attribute `{{propName}}`',
+  setBoolean: 'Value must be set for boolean attribute `{{propName}}`',
+  omitPropAndBoolean: 'Value must be omitted for `false` attribute: `{{propName}}`',
 };
 
 module.exports = {
@@ -135,8 +119,8 @@ module.exports = {
           isAlways(configuration, exceptions, propName)
           && value === null
         ) {
-          const data = getErrorData(exceptions);
-          const messageId = data.exceptionsMessage ? 'setBoolean' : 'setBoolean_noMessage';
+          const messageId = 'setBoolean';
+          const data = { propName };
           report(context, messages[messageId], messageId, {
             node,
             data,
@@ -152,8 +136,8 @@ module.exports = {
           && value.type === 'JSXExpressionContainer'
           && value.expression.value === true
         ) {
-          const data = getErrorData(exceptions);
-          const messageId = data.exceptionsMessage ? 'omitBoolean' : 'omitBoolean_noMessage';
+          const messageId = 'omitBoolean';
+          const data = { propName };
           report(context, messages[messageId], messageId, {
             node,
             data,
@@ -169,11 +153,8 @@ module.exports = {
           && value.type === 'JSXExpressionContainer'
           && value.expression.value === false
         ) {
-          const data = getErrorData(exceptions);
-          const messageId = data.exceptionsMessage
-            ? 'omitPropAndBoolean'
-            : 'omitPropAndBoolean_noMessage';
-
+          const messageId = 'omitPropAndBoolean';
+          const data = { propName };
           report(context, messages[messageId], messageId, {
             node,
             data,


Test patch:
diff --git a/tests/lib/rules/jsx-boolean-value.js b/tests/lib/rules/jsx-boolean-value.js
index 678b4927..e8cbbb3e 100644
--- a/tests/lib/rules/jsx-boolean-value.js
+++ b/tests/lib/rules/jsx-boolean-value.js
@@ -63,7 +63,10 @@ ruleTester.run('jsx-boolean-value', rule, {
       output: '<App foo />;',
       options: ['never'],
       errors: [
-        { messageId: 'omitBoolean_noMessage' },
+        {
+          messageId: 'omitBoolean',
+          data: { propName: 'foo' },
+        },
       ],
     },
     {
@@ -73,11 +76,11 @@ ruleTester.run('jsx-boolean-value', rule, {
       errors: [
         {
           messageId: 'omitBoolean',
-          data: { exceptionsMessage: ' for the following props: `foo`, `bar`' },
+          data: { propName: 'foo' },
         },
         {
           messageId: 'omitBoolean',
-          data: { exceptionsMessage: ' for the following props: `foo`, `bar`' },
+          data: { propName: 'bar' },
         },
       ],
     },
@@ -85,14 +88,20 @@ ruleTester.run('jsx-boolean-value', rule, {
       code: '<App foo={true} />;',
       output: '<App foo />;',
       errors: [
-        { messageId: 'omitBoolean_noMessage' },
+        {
+          messageId: 'omitBoolean',
+          data: { propName: 'foo' },
+        },
       ],
     },
     {
       code: '<App foo = {true} />;',
       output: '<App foo />;',
       errors: [
-        { messageId: 'omitBoolean_noMessage' },
+        {
+          messageId: 'omitBoolean',
+          data: { propName: 'foo' },
+        },
       ],
     },
     {
@@ -100,7 +109,10 @@ ruleTester.run('jsx-boolean-value', rule, {
       output: '<App foo={true} />;',
       options: ['always'],
       errors: [
-        { messageId: 'setBoolean_noMessage' },
+        {
+          messageId: 'setBoolean',
+          data: { propName: 'foo' },
+        },
       ],
     },
     {
@@ -110,11 +122,11 @@ ruleTester.run('jsx-boolean-value', rule, {
       errors: [
         {
           messageId: 'setBoolean',
-          data: { exceptionsMessage: ' for the following props: `foo`, `bar`' },
+          data: { propName: 'foo' },
         },
         {
           messageId: 'setBoolean',
-          data: { exceptionsMessage: ' for the following props: `foo`, `bar`' },
+          data: { propName: 'bar' },
         },
       ],
     },
@@ -123,8 +135,14 @@ ruleTester.run('jsx-boolean-value', rule, {
       output: '<App   />;',
       options: ['never', { assumeUndefinedIsFalse: true }],
       errors: [
-        { messageId: 'omitPropAndBoolean_noMessage' },
-        { messageId: 'omitPropAndBoolean_noMessage' },
+        {
+          messageId: 'omitPropAndBoolean',
+          data: { propName: 'foo' },
+        },
+        {
+          messageId: 'omitPropAndBoolean',
+          data: { propName: 'bak' },
+        },
       ],
     },
     {
@@ -137,11 +155,30 @@ ruleTester.run('jsx-boolean-value', rule, {
       errors: [
         {
           messageId: 'omitPropAndBoolean',
-          data: { exceptionsMessage: ' for the following props: `baz`, `bak`' },
+          data: { propName: 'baz' },
         },
         {
           messageId: 'omitPropAndBoolean',
-          data: { exceptionsMessage: ' for the following props: `baz`, `bak`' },
+          data: { propName: 'bak' },
+        },
+      ],
+    },
+    {
+      code: '<App foo={true} bar={true} baz />;',
+      output: '<App foo bar baz={true} />;',
+      options: ['always', { never: ['foo', 'bar'] }],
+      errors: [
+        {
+          messageId: 'omitBoolean',
+          data: { propName: 'foo' },
+        },
+        {
+          messageId: 'omitBoolean',
+          data: { propName: 'bar' },
+        },
+        {
+          messageId: 'setBoolean',
+          data: { propName: 'baz' },
         },
       ],
     },