Skip to content

Commit 59398c2

Browse files
committed
[New] jsx-boolean-value: add inverse option for always/never
Fixes #1249
1 parent 32bcb48 commit 59398c2

File tree

3 files changed

+89
-36
lines changed

3 files changed

+89
-36
lines changed

docs/rules/jsx-boolean-value.md

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,27 +6,29 @@
66

77
## Rule Details
88

9-
This rule takes one argument. If it 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"`.
9+
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"`.
1010

11-
The following patterns are considered warnings when configured `"never"`:
11+
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.
12+
13+
The following patterns are considered warnings when configured `"never"`, or with `"always", { "never": ["personal"] }`:
1214

1315
```jsx
1416
var Hello = <Hello personal={true} />;
1517
```
1618

17-
The following patterns are not considered warnings when configured `"never"`:
19+
The following patterns are not considered warnings when configured `"never"`, or with `"always", { "never": ["personal"] }`:
1820

1921
```jsx
2022
var Hello = <Hello personal />;
2123
```
2224

23-
The following patterns are considered warnings when configured `"always"`:
25+
The following patterns are considered warnings when configured `"always"`, or with `"never", { "always": ["personal"] }`:
2426

2527
```jsx
2628
var Hello = <Hello personal />;
2729
```
2830

29-
The following patterns are not considered warnings when configured `"always"`:
31+
The following patterns are not considered warnings when configured `"always"`, or with `"never", { "always": ["personal"] }`:
3032

3133
```jsx
3234
var Hello = <Hello personal={true} />;

lib/rules/jsx-boolean-value.js

Lines changed: 73 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,15 @@
88
// Rule Definition
99
// ------------------------------------------------------------------------------
1010

11+
const exceptionsSchema = {
12+
type: 'array',
13+
items: {type: 'string', minLength: 1},
14+
uniqueItems: true
15+
};
16+
17+
const ALWAYS = 'always';
18+
const NEVER = 'never';
19+
1120
module.exports = {
1221
meta: {
1322
docs: {
@@ -17,45 +26,79 @@ module.exports = {
1726
},
1827
fixable: 'code',
1928

20-
schema: [{
21-
enum: ['always', 'never']
22-
}]
29+
schema: {
30+
anyOf: [{
31+
type: 'array',
32+
items: [{enum: [ALWAYS, NEVER]}],
33+
additionalItems: false
34+
}, {
35+
type: 'array',
36+
items: [{
37+
enum: [ALWAYS]
38+
}, {
39+
type: 'object',
40+
additionalProperties: false,
41+
properties: {
42+
[NEVER]: exceptionsSchema
43+
}
44+
}],
45+
additionalItems: false
46+
}, {
47+
type: 'array',
48+
items: [{
49+
enum: [NEVER]
50+
}, {
51+
type: 'object',
52+
additionalProperties: false,
53+
properties: {
54+
[ALWAYS]: exceptionsSchema
55+
}
56+
}],
57+
additionalItems: false
58+
}]
59+
}
2360
},
2461

2562
create: function(context) {
2663

27-
var configuration = context.options[0] || 'never';
64+
const configuration = context.options[0] || NEVER;
65+
const configObject = context.options[1] || {};
66+
const exceptions = new Set((configuration === ALWAYS ? configObject[NEVER] : configObject[ALWAYS]) || []);
67+
const exceptionProps = Array.from(exceptions, (name) => `\`${name}\``).join(', ');
68+
const exceptionsMessage = exceptions.size > 0 ? ` for the following props: ${exceptionProps}` : '';
69+
const data = {exceptionsMessage: exceptionsMessage};
2870

29-
var NEVER_MESSAGE = 'Value must be omitted for boolean attributes';
30-
var ALWAYS_MESSAGE = 'Value must be set for boolean attributes';
71+
const NEVER_MESSAGE = 'Value must be omitted for boolean attributes{{exceptionsMessage}}';
72+
const ALWAYS_MESSAGE = 'Value must be set for boolean attributes{{exceptionsMessage}}';
3173

3274
return {
33-
JSXAttribute: function(node) {
34-
switch (configuration) {
35-
case 'always':
36-
if (node.value === null) {
37-
context.report({
38-
node: node,
39-
message: ALWAYS_MESSAGE,
40-
fix: function(fixer) {
41-
return fixer.insertTextAfter(node, '={true}');
42-
}
43-
});
75+
JSXAttribute(node) {
76+
const propName = node.name && node.name.name;
77+
const value = node.value;
78+
const isException = exceptions.has(propName);
79+
80+
const isAlways = configuration === ALWAYS ? !isException : isException;
81+
const isNever = configuration === NEVER ? !isException : isException;
82+
83+
if (isAlways && value === null) {
84+
context.report({
85+
node: node,
86+
message: ALWAYS_MESSAGE,
87+
data: data,
88+
fix(fixer) {
89+
return fixer.insertTextAfter(node, '={true}');
4490
}
45-
break;
46-
case 'never':
47-
if (node.value && node.value.type === 'JSXExpressionContainer' && node.value.expression.value === true) {
48-
context.report({
49-
node: node,
50-
message: NEVER_MESSAGE,
51-
fix: function(fixer) {
52-
return fixer.removeRange([node.name.range[1], node.value.range[1]]);
53-
}
54-
});
91+
});
92+
}
93+
if (isNever && value && value.type === 'JSXExpressionContainer' && value.expression.value === true) {
94+
context.report({
95+
node: node,
96+
message: NEVER_MESSAGE,
97+
data: data,
98+
fix(fixer) {
99+
return fixer.removeRange([node.name.range[1], value.range[1]]);
55100
}
56-
break;
57-
default:
58-
break;
101+
});
59102
}
60103
}
61104
};

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

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,12 +28,17 @@ var ruleTester = new RuleTester({parserOptions});
2828
ruleTester.run('jsx-boolean-value', rule, {
2929
valid: [
3030
{code: '<App foo />;', options: ['never']},
31+
{code: '<App foo />;', options: ['always', {never: ['foo']}]},
3132
{code: '<App foo />;'},
32-
{code: '<App foo={true} />;', options: ['always']}
33+
{code: '<App foo={true} />;', options: ['always']},
34+
{code: '<App foo={true} />;', options: ['never', {always: ['foo']}]}
3335
],
3436
invalid: [{
3537
code: '<App foo={true} />;', output: '<App foo />;', options: ['never'],
3638
errors: [{message: 'Value must be omitted for boolean attributes'}]
39+
}, {
40+
code: '<App foo={true} />;', output: '<App foo />;', options: ['always', {never: ['foo', 'bar']}],
41+
errors: [{message: 'Value must be omitted for boolean attributes for the following props: `foo`, `bar`'}]
3742
}, {
3843
code: '<App foo={true} />;', output: '<App foo />;',
3944
errors: [{message: 'Value must be omitted for boolean attributes'}]
@@ -43,5 +48,8 @@ ruleTester.run('jsx-boolean-value', rule, {
4348
}, {
4449
code: '<App foo />;', output: '<App foo={true} />;', options: ['always'],
4550
errors: [{message: 'Value must be set for boolean attributes'}]
51+
}, {
52+
code: '<App foo />;', output: '<App foo={true} />;', options: ['never', {always: ['foo', 'bar']}],
53+
errors: [{message: 'Value must be set for boolean attributes for the following props: `foo`, `bar`'}]
4654
}]
4755
});

0 commit comments

Comments
 (0)