diff --git a/docs/rules/jsx-boolean-value.md b/docs/rules/jsx-boolean-value.md
index 5dcfabe9e3..c888e7af70 100644
--- a/docs/rules/jsx-boolean-value.md
+++ b/docs/rules/jsx-boolean-value.md
@@ -6,27 +6,29 @@
## Rule Details
-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"`.
+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 following patterns are considered warnings when configured `"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 following patterns are considered warnings when configured `"never"`, or with `"always", { "never": ["personal"] }`:
```jsx
var Hello = ;
```
-The following patterns are not considered warnings when configured `"never"`:
+The following patterns are not considered warnings when configured `"never"`, or with `"always", { "never": ["personal"] }`:
```jsx
var Hello = ;
```
-The following patterns are considered warnings when configured `"always"`:
+The following patterns are considered warnings when configured `"always"`, or with `"never", { "always": ["personal"] }`:
```jsx
var Hello = ;
```
-The following patterns are not considered warnings when configured `"always"`:
+The following patterns are not considered warnings when configured `"always"`, or with `"never", { "always": ["personal"] }`:
```jsx
var Hello = ;
diff --git a/lib/rules/jsx-boolean-value.js b/lib/rules/jsx-boolean-value.js
index 3a824e5ab9..21bfa6cdbe 100644
--- a/lib/rules/jsx-boolean-value.js
+++ b/lib/rules/jsx-boolean-value.js
@@ -8,6 +8,41 @@
// Rule Definition
// ------------------------------------------------------------------------------
+const exceptionsSchema = {
+ type: 'array',
+ items: {type: 'string', minLength: 1},
+ uniqueItems: true
+};
+
+const ALWAYS = 'always';
+const NEVER = 'never';
+
+const errorData = new WeakMap();
+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: exceptionsMessage});
+ }
+ return errorData.get(exceptions);
+}
+
+function isAlways(configuration, exceptions, propName) {
+ const isException = exceptions.has(propName);
+ if (configuration === ALWAYS) {
+ return !isException;
+ }
+ return isException;
+}
+
+function isNever(configuration, exceptions, propName) {
+ const isException = exceptions.has(propName);
+ if (configuration === NEVER) {
+ return !isException;
+ }
+ return isException;
+}
+
module.exports = {
meta: {
docs: {
@@ -17,44 +52,73 @@ module.exports = {
},
fixable: 'code',
- schema: [{
- enum: ['always', 'never']
- }]
+ schema: {
+ anyOf: [{
+ type: 'array',
+ items: [{enum: [ALWAYS, NEVER]}],
+ additionalItems: false
+ }, {
+ type: 'array',
+ items: [{
+ enum: [ALWAYS]
+ }, {
+ type: 'object',
+ additionalProperties: false,
+ properties: {
+ [NEVER]: exceptionsSchema
+ }
+ }],
+ additionalItems: false
+ }, {
+ type: 'array',
+ items: [{
+ enum: [NEVER]
+ }, {
+ type: 'object',
+ additionalProperties: false,
+ properties: {
+ [ALWAYS]: exceptionsSchema
+ }
+ }],
+ additionalItems: false
+ }]
+ }
},
- create: function(context) {
- var configuration = context.options[0] || 'never';
+ create(context) {
+ const configuration = context.options[0] || NEVER;
+ const configObject = context.options[1] || {};
+ const exceptions = new Set((configuration === ALWAYS ? configObject[NEVER] : configObject[ALWAYS]) || []);
- var NEVER_MESSAGE = 'Value must be omitted for boolean attributes';
- var ALWAYS_MESSAGE = 'Value must be set for boolean attributes';
+ const NEVER_MESSAGE = 'Value must be omitted for boolean attributes{{exceptionsMessage}}';
+ const ALWAYS_MESSAGE = 'Value must be set for boolean attributes{{exceptionsMessage}}';
return {
- JSXAttribute: function(node) {
- switch (configuration) {
- case 'always':
- if (node.value === null) {
- context.report({
- node: node,
- message: ALWAYS_MESSAGE,
- fix: function(fixer) {
- return fixer.insertTextAfter(node, '={true}');
- }
- });
+ JSXAttribute(node) {
+ const propName = node.name && node.name.name;
+ const value = node.value;
+
+ if (isAlways(configuration, exceptions, propName) && value === null) {
+ const data = getErrorData(exceptions);
+ context.report({
+ node: node,
+ message: ALWAYS_MESSAGE,
+ data: data,
+ fix(fixer) {
+ return fixer.insertTextAfter(node, '={true}');
}
- break;
- case 'never':
- if (node.value && node.value.type === 'JSXExpressionContainer' && node.value.expression.value === true) {
- context.report({
- node: node,
- message: NEVER_MESSAGE,
- fix: function(fixer) {
- return fixer.removeRange([node.name.range[1], node.value.range[1]]);
- }
- });
+ });
+ }
+ if (isNever(configuration, exceptions, propName) && value && value.type === 'JSXExpressionContainer' && value.expression.value === true) {
+ const data = getErrorData(exceptions);
+ context.report({
+ node: node,
+ message: NEVER_MESSAGE,
+ data: data,
+ fix(fixer) {
+ return fixer.removeRange([node.name.range[1], value.range[1]]);
}
- break;
- default:
- break;
+ });
}
}
};
diff --git a/tests/lib/rules/jsx-boolean-value.js b/tests/lib/rules/jsx-boolean-value.js
index 67ab4ddaed..d737ef8d5f 100644
--- a/tests/lib/rules/jsx-boolean-value.js
+++ b/tests/lib/rules/jsx-boolean-value.js
@@ -28,20 +28,42 @@ var ruleTester = new RuleTester({parserOptions});
ruleTester.run('jsx-boolean-value', rule, {
valid: [
{code: ';', options: ['never']},
+ {code: ';', options: ['always', {never: ['foo']}]},
{code: ';'},
- {code: ';', options: ['always']}
+ {code: ';', options: ['always']},
+ {code: ';', options: ['never', {always: ['foo']}]}
],
invalid: [{
- code: ';', output: ';', options: ['never'],
+ code: ';',
+ output: ';',
+ options: ['never'],
errors: [{message: 'Value must be omitted for boolean attributes'}]
}, {
- code: ';', output: ';',
+ code: ';',
+ output: ';',
+ options: ['always', {never: ['foo', 'bar']}],
+ errors: [
+ {message: 'Value must be omitted for boolean attributes for the following props: `foo`, `bar`'},
+ {message: 'Value must be omitted for boolean attributes for the following props: `foo`, `bar`'}
+ ]
+ }, {
+ code: ';',
+ output: ';',
errors: [{message: 'Value must be omitted for boolean attributes'}]
}, {
- code: ';', output: ';',
+ code: ';',
+ output: ';',
errors: [{message: 'Value must be omitted for boolean attributes'}]
}, {
code: ';', output: ';', options: ['always'],
errors: [{message: 'Value must be set for boolean attributes'}]
+ }, {
+ code: ';',
+ output: ';',
+ options: ['never', {always: ['foo', 'bar']}],
+ errors: [
+ {message: 'Value must be set for boolean attributes for the following props: `foo`, `bar`'},
+ {message: 'Value must be set for boolean attributes for the following props: `foo`, `bar`'}
+ ]
}]
});