From 3730edb2fdff50d989383f266eae43568d266567 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E1=84=80=E1=85=B5=E1=86=B7=E1=84=89=E1=85=A1=E1=86=BC?= =?UTF-8?q?=E1=84=83=E1=85=AE?= Date: Wed, 10 Jan 2024 03:55:51 +0900 Subject: [PATCH] [New] `jsx-boolean-value`: add `assumeUndefinedIsFalse` option --- CHANGELOG.md | 2 ++ docs/rules/jsx-boolean-value.md | 18 +++++++++++++- lib/rules/jsx-boolean-value.js | 31 +++++++++++++++++++++++- tests/lib/rules/jsx-boolean-value.js | 35 ++++++++++++++++++++++++++++ 4 files changed, 84 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6f468f9264..8bd7d07507 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ This change log adheres to standards from [Keep a CHANGELOG](https://keepachange * [`no-unknown-property`]: support `onResize` on audio/video tags ([#3662][] @caesar1030) * [`jsx-wrap-multilines`]: add `never` option to prohibit wrapping parens on multiline JSX ([#3668][] @reedws) * [`jsx-filename-extension`]: add `ignoreFilesWithoutCode` option to allow empty files ([#3674][] @burtek) +* [`jsx-boolean-value`]: add `assumeUndefinedIsFalse` option ([#3675][] @developer-bandi) ### Fixed * [`jsx-no-leaked-render`]: preserve RHS parens for multiline jsx elements while fixing ([#3623][] @akulsr0) @@ -29,6 +30,7 @@ This change log adheres to standards from [Keep a CHANGELOG](https://keepachange * [Docs] [`jsx-key`]: fix correct example ([#3656][] @developer-bandi) * [Tests] `jsx-wrap-multilines`: passing tests ([#3545][] @burtek) +[#3675]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3675 [#3674]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3674 [#3668]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3668 [#3666]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3666 diff --git a/docs/rules/jsx-boolean-value.md b/docs/rules/jsx-boolean-value.md index d96285b9cf..1a0c2bde17 100644 --- a/docs/rules/jsx-boolean-value.md +++ b/docs/rules/jsx-boolean-value.md @@ -14,7 +14,11 @@ This rule will enforce one or the other to keep consistency in your code. This rule takes two arguments. If the first argument is `"always"` then it warns whenever an attribute is missing its value. If `"never"` then it warns if an attribute has a `true` value. The default value of this option is `"never"`. -The second argument is optional: if provided, it must be an object with a `"never"` property (if the first argument is `"always"`), or an `"always"` property (if the first argument is `"never"`). This property’s value must be an array of strings representing prop names. +The second argument is optional. If provided, it must be an object. These properties are supported: + +First, the `"never"` and `"always"` properties are one set. The two properties cannot be set together. `"never"` must be used when the first argument is `"always"` and `"always"` must be used when the first argument is `"never"`. This property’s value must be an array of strings representing prop names. + +When the first argument is `"never"`, a boolean `"assumeUndefinedIsFalse"` may be provided, which defaults to `false`. When `true`, an absent boolean prop will be treated as if it were explicitly set to `false`. Examples of **incorrect** code for this rule, when configured with `"never"`, or with `"always", { "never": ["personal"] }`: @@ -40,6 +44,18 @@ Examples of **correct** code for this rule, when configured with `"always"`, or var Hello = ; ``` +Examples of **incorrect** code for this rule, when configured with `"never", { "assumeUndefinedIsFalse": true }`, or with `"always", { "never": ["personal"], "assumeUndefinedIsFalse": true }`: + +```jsx +var Hello = ; +``` + +Examples of **correct** code for this rule, when configured with `"never", { "assumeUndefinedIsFalse": true }`, or with `"always", { "never": ["personal"], "assumeUndefinedIsFalse": true }`: + +```jsx +var Hello = ; +``` + ## When Not To Use It If you do not want to enforce any style for boolean attributes, then you can disable this rule. diff --git a/lib/rules/jsx-boolean-value.js b/lib/rules/jsx-boolean-value.js index 43395e2490..ae6dda0da7 100644 --- a/lib/rules/jsx-boolean-value.js +++ b/lib/rules/jsx-boolean-value.js @@ -39,7 +39,7 @@ function getErrorData(exceptions) { * @param {Set} exceptions * @param {string} propName * @returns {boolean} propName -*/ + */ function isAlways(configuration, exceptions, propName) { const isException = exceptions.has(propName); if (configuration === ALWAYS) { @@ -66,6 +66,8 @@ const messages = { 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', }; module.exports = { @@ -94,6 +96,9 @@ module.exports = { additionalProperties: false, properties: { [NEVER]: exceptionsSchema, + assumeUndefinedIsFalse: { + type: 'boolean', + }, }, }], additionalItems: false, @@ -106,6 +111,9 @@ module.exports = { additionalProperties: false, properties: { [ALWAYS]: exceptionsSchema, + assumeUndefinedIsFalse: { + type: 'boolean', + }, }, }], additionalItems: false, @@ -139,6 +147,7 @@ module.exports = { } if ( isNever(configuration, exceptions, propName) + && !configObject.assumeUndefinedIsFalse && value && value.type === 'JSXExpressionContainer' && value.expression.value === true @@ -153,6 +162,26 @@ module.exports = { }, }); } + if ( + isNever(configuration, exceptions, propName) + && configObject.assumeUndefinedIsFalse + && value + && value.type === 'JSXExpressionContainer' + && value.expression.value === false + ) { + const data = getErrorData(exceptions); + const messageId = data.exceptionsMessage + ? 'omitPropAndBoolean' + : 'omitPropAndBoolean_noMessage'; + + report(context, messages[messageId], messageId, { + node, + data, + fix(fixer) { + return fixer.removeRange([node.name.range[0], value.range[1]]); + }, + }); + } }, }; }, diff --git a/tests/lib/rules/jsx-boolean-value.js b/tests/lib/rules/jsx-boolean-value.js index de7e9f710d..678b492740 100644 --- a/tests/lib/rules/jsx-boolean-value.js +++ b/tests/lib/rules/jsx-boolean-value.js @@ -48,6 +48,14 @@ ruleTester.run('jsx-boolean-value', rule, { code: ';', options: ['never', { always: ['foo'] }], }, + { + code: ';', + options: ['never', { assumeUndefinedIsFalse: true }], + }, + { + code: ';', + options: ['never', { assumeUndefinedIsFalse: true, always: ['foo'] }], + }, ]), invalid: parsers.all([ { @@ -110,5 +118,32 @@ ruleTester.run('jsx-boolean-value', rule, { }, ], }, + { + code: ';', + output: ';', + options: ['never', { assumeUndefinedIsFalse: true }], + errors: [ + { messageId: 'omitPropAndBoolean_noMessage' }, + { messageId: 'omitPropAndBoolean_noMessage' }, + ], + }, + { + code: ';', + output: ';', + options: [ + 'always', + { assumeUndefinedIsFalse: true, never: ['baz', 'bak'] }, + ], + errors: [ + { + messageId: 'omitPropAndBoolean', + data: { exceptionsMessage: ' for the following props: `baz`, `bak`' }, + }, + { + messageId: 'omitPropAndBoolean', + data: { exceptionsMessage: ' for the following props: `baz`, `bak`' }, + }, + ], + }, ]), });