Skip to content

Commit 3730edb

Browse files
developer-bandiljharb
authored andcommitted
[New] jsx-boolean-value: add assumeUndefinedIsFalse option
1 parent 67e669d commit 3730edb

File tree

4 files changed

+84
-2
lines changed

4 files changed

+84
-2
lines changed

CHANGELOG.md

+2
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ This change log adheres to standards from [Keep a CHANGELOG](https://keepachange
1414
* [`no-unknown-property`]: support `onResize` on audio/video tags ([#3662][] @caesar1030)
1515
* [`jsx-wrap-multilines`]: add `never` option to prohibit wrapping parens on multiline JSX ([#3668][] @reedws)
1616
* [`jsx-filename-extension`]: add `ignoreFilesWithoutCode` option to allow empty files ([#3674][] @burtek)
17+
* [`jsx-boolean-value`]: add `assumeUndefinedIsFalse` option ([#3675][] @developer-bandi)
1718

1819
### Fixed
1920
* [`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
2930
* [Docs] [`jsx-key`]: fix correct example ([#3656][] @developer-bandi)
3031
* [Tests] `jsx-wrap-multilines`: passing tests ([#3545][] @burtek)
3132

33+
[#3675]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3675
3234
[#3674]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3674
3335
[#3668]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3668
3436
[#3666]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3666

docs/rules/jsx-boolean-value.md

+17-1
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,11 @@ This rule will enforce one or the other to keep consistency in your code.
1414

1515
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"`.
1616

17-
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.
17+
The second argument is optional. If provided, it must be an object. These properties are supported:
18+
19+
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.
20+
21+
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`.
1822

1923
Examples of **incorrect** code for this rule, when configured with `"never"`, or with `"always", { "never": ["personal"] }`:
2024

@@ -40,6 +44,18 @@ Examples of **correct** code for this rule, when configured with `"always"`, or
4044
var Hello = <Hello personal={true} />;
4145
```
4246

47+
Examples of **incorrect** code for this rule, when configured with `"never", { "assumeUndefinedIsFalse": true }`, or with `"always", { "never": ["personal"], "assumeUndefinedIsFalse": true }`:
48+
49+
```jsx
50+
var Hello = <Hello personal={false} />;
51+
```
52+
53+
Examples of **correct** code for this rule, when configured with `"never", { "assumeUndefinedIsFalse": true }`, or with `"always", { "never": ["personal"], "assumeUndefinedIsFalse": true }`:
54+
55+
```jsx
56+
var Hello = <Hello />;
57+
```
58+
4359
## When Not To Use It
4460

4561
If you do not want to enforce any style for boolean attributes, then you can disable this rule.

lib/rules/jsx-boolean-value.js

+30-1
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ function getErrorData(exceptions) {
3939
* @param {Set<string>} exceptions
4040
* @param {string} propName
4141
* @returns {boolean} propName
42-
*/
42+
*/
4343
function isAlways(configuration, exceptions, propName) {
4444
const isException = exceptions.has(propName);
4545
if (configuration === ALWAYS) {
@@ -66,6 +66,8 @@ const messages = {
6666
omitBoolean_noMessage: 'Value must be omitted for boolean attributes',
6767
setBoolean: 'Value must be set for boolean attributes{{exceptionsMessage}}',
6868
setBoolean_noMessage: 'Value must be set for boolean attributes',
69+
omitPropAndBoolean: 'Value and Prop must be omitted for false attributes{{exceptionsMessage}}',
70+
omitPropAndBoolean_noMessage: 'Value and Prop must be omitted for false attributes',
6971
};
7072

7173
module.exports = {
@@ -94,6 +96,9 @@ module.exports = {
9496
additionalProperties: false,
9597
properties: {
9698
[NEVER]: exceptionsSchema,
99+
assumeUndefinedIsFalse: {
100+
type: 'boolean',
101+
},
97102
},
98103
}],
99104
additionalItems: false,
@@ -106,6 +111,9 @@ module.exports = {
106111
additionalProperties: false,
107112
properties: {
108113
[ALWAYS]: exceptionsSchema,
114+
assumeUndefinedIsFalse: {
115+
type: 'boolean',
116+
},
109117
},
110118
}],
111119
additionalItems: false,
@@ -139,6 +147,7 @@ module.exports = {
139147
}
140148
if (
141149
isNever(configuration, exceptions, propName)
150+
&& !configObject.assumeUndefinedIsFalse
142151
&& value
143152
&& value.type === 'JSXExpressionContainer'
144153
&& value.expression.value === true
@@ -153,6 +162,26 @@ module.exports = {
153162
},
154163
});
155164
}
165+
if (
166+
isNever(configuration, exceptions, propName)
167+
&& configObject.assumeUndefinedIsFalse
168+
&& value
169+
&& value.type === 'JSXExpressionContainer'
170+
&& value.expression.value === false
171+
) {
172+
const data = getErrorData(exceptions);
173+
const messageId = data.exceptionsMessage
174+
? 'omitPropAndBoolean'
175+
: 'omitPropAndBoolean_noMessage';
176+
177+
report(context, messages[messageId], messageId, {
178+
node,
179+
data,
180+
fix(fixer) {
181+
return fixer.removeRange([node.name.range[0], value.range[1]]);
182+
},
183+
});
184+
}
156185
},
157186
};
158187
},

tests/lib/rules/jsx-boolean-value.js

+35
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,14 @@ ruleTester.run('jsx-boolean-value', rule, {
4848
code: '<App foo={true} bar />;',
4949
options: ['never', { always: ['foo'] }],
5050
},
51+
{
52+
code: '<App />;',
53+
options: ['never', { assumeUndefinedIsFalse: true }],
54+
},
55+
{
56+
code: '<App foo={false} />;',
57+
options: ['never', { assumeUndefinedIsFalse: true, always: ['foo'] }],
58+
},
5159
]),
5260
invalid: parsers.all([
5361
{
@@ -110,5 +118,32 @@ ruleTester.run('jsx-boolean-value', rule, {
110118
},
111119
],
112120
},
121+
{
122+
code: '<App foo={false} bak={false} />;',
123+
output: '<App />;',
124+
options: ['never', { assumeUndefinedIsFalse: true }],
125+
errors: [
126+
{ messageId: 'omitPropAndBoolean_noMessage' },
127+
{ messageId: 'omitPropAndBoolean_noMessage' },
128+
],
129+
},
130+
{
131+
code: '<App foo={true} bar={false} baz={false} bak={false} />;',
132+
output: '<App foo={true} bar={false} />;',
133+
options: [
134+
'always',
135+
{ assumeUndefinedIsFalse: true, never: ['baz', 'bak'] },
136+
],
137+
errors: [
138+
{
139+
messageId: 'omitPropAndBoolean',
140+
data: { exceptionsMessage: ' for the following props: `baz`, `bak`' },
141+
},
142+
{
143+
messageId: 'omitPropAndBoolean',
144+
data: { exceptionsMessage: ' for the following props: `baz`, `bak`' },
145+
},
146+
],
147+
},
113148
]),
114149
});

0 commit comments

Comments
 (0)