diff --git a/CHANGELOG.md b/CHANGELOG.md
index 7bec3e261d..93b89a71e2 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -11,6 +11,10 @@ This change log adheres to standards from [Keep a CHANGELOG](https://keepachange
* [`no-unknown-property`]: support `onBeforeToggle`, `popoverTarget`, `popoverTargetAction` attributes ([#3865][] @acusti)
* [types] fix types of flat configs ([#3874][] @ljharb)
+### Added
+* [`forbid-dom-props`]: Add `disallowedValues` option for forbidden props ([#3876][] @makxca)
+
+[#3876]: https://github.com/jsx-eslint/eslint-plugin-react/issues/3876
[#3874]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3874
[#3865]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3865
diff --git a/docs/rules/forbid-dom-props.md b/docs/rules/forbid-dom-props.md
index 0b6a323c4f..cf13beab36 100644
--- a/docs/rules/forbid-dom-props.md
+++ b/docs/rules/forbid-dom-props.md
@@ -2,7 +2,7 @@
-This rule prevents passing of props to elements. This rule only applies to DOM Nodes (e.g. `
`) and not Components (e.g. ``).
+This rule prevents passing of props to elements. This rule only applies to DOM Nodes (e.g. ``), and not Components (e.g. ``).
The list of forbidden props can be customized with the `forbid` option.
## Rule Details
@@ -44,18 +44,63 @@ Examples of **correct** code for this rule:
### `forbid`
-An array of strings, with the names of props that are forbidden. The default value of this option `[]`.
+An array of strings, with the names of props that are forbidden. The default value of this option is `[]`.
Each array element can either be a string with the property name or object specifying the property name, an optional
-custom message, and a DOM nodes disallowed list (e.g. ``):
+custom message, DOM nodes disallowed list (e.g. ``) and a list of prohibited values:
```js
{
"propName": "someProp",
"disallowedFor": ["DOMNode", "AnotherDOMNode"],
+ "disallowedValues": ["someValue"],
"message": "Avoid using someProp"
}
```
+Example of **incorrect** code for this rule, when configured with `{ forbid: [{ propName: 'someProp', disallowedFor: ['span'] }] }`.
+
+```jsx
+const First = (props) => (
+
+);
+```
+
+Example of **correct** code for this rule, when configured with `{ forbid: [{ propName: 'someProp', disallowedFor: ['span'] }] }`.
+
+```jsx
+const First = (props) => (
+
+);
+```
+
+Examples of **incorrect** code for this rule, when configured with `{ forbid: [{ propName: 'someProp', disallowedValues: ['someValue'] }] }`.
+
+```jsx
+const First = (props) => (
+
+);
+```
+
+```jsx
+const First = (props) => (
+
+);
+```
+
+Examples of **correct** code for this rule, when configured with `{ forbid: [{ propName: 'someProp', disallowedValues: ['someValue'] }] }`.
+
+```jsx
+const First = (props) => (
+
+);
+```
+
+```jsx
+const First = (props) => (
+
+);
+```
+
### Related rules
- [forbid-component-props](./forbid-component-props.md)
diff --git a/lib/rules/forbid-dom-props.js b/lib/rules/forbid-dom-props.js
index 4638a8700d..be7337a57f 100644
--- a/lib/rules/forbid-dom-props.js
+++ b/lib/rules/forbid-dom-props.js
@@ -18,23 +18,33 @@ const DEFAULTS = [];
// Rule Definition
// ------------------------------------------------------------------------------
+/** @typedef {{ disallowList: null | string[]; message: null | string; disallowedValues: string[] | null }} ForbidMapType */
/**
- * @param {Map} forbidMap // { disallowList: null | string[], message: null | string }
+ * @param {Map} forbidMap
* @param {string} prop
+ * @param {string} propValue
* @param {string} tagName
* @returns {boolean}
*/
-function isForbidden(forbidMap, prop, tagName) {
+function isForbidden(forbidMap, prop, propValue, tagName) {
const options = forbidMap.get(prop);
- return options && (
- typeof tagName === 'undefined'
- || !options.disallowList
+
+ if (!options) {
+ return false;
+ }
+
+ return (
+ !options.disallowList
|| options.disallowList.indexOf(tagName) !== -1
+ ) && (
+ !options.disallowedValues
+ || options.disallowedValues.indexOf(propValue) !== -1
);
}
const messages = {
propIsForbidden: 'Prop "{{prop}}" is forbidden on DOM Nodes',
+ propIsForbiddenWithValue: 'Prop "{{prop}}" with value "{{propValue}}" is forbidden on DOM Nodes',
};
/** @type {import('eslint').Rule.RuleModule} */
@@ -70,6 +80,13 @@ module.exports = {
type: 'string',
},
},
+ disallowedValues: {
+ type: 'array',
+ uniqueItems: true,
+ items: {
+ type: 'string',
+ },
+ },
message: {
type: 'string',
},
@@ -90,6 +107,7 @@ module.exports = {
const propName = typeof value === 'string' ? value : value.propName;
return [propName, {
disallowList: typeof value === 'string' ? null : (value.disallowedFor || null),
+ disallowedValues: typeof value === 'string' ? null : (value.disallowedValues || null),
message: typeof value === 'string' ? null : value.message,
}];
}));
@@ -103,17 +121,22 @@ module.exports = {
}
const prop = node.name.name;
+ const propValue = node.value.value;
- if (!isForbidden(forbid, prop, tag)) {
+ if (!isForbidden(forbid, prop, propValue, tag)) {
return;
}
const customMessage = forbid.get(prop).message;
+ const isValuesListSpecified = forbid.get(prop).disallowedValues !== null;
+ const message = customMessage || (isValuesListSpecified && messages.propIsForbiddenWithValue) || messages.propIsForbidden;
+ const messageId = !customMessage && ((isValuesListSpecified && 'propIsForbiddenWithValue') || 'propIsForbidden');
- report(context, customMessage || messages.propIsForbidden, !customMessage && 'propIsForbidden', {
+ report(context, message, messageId, {
node,
data: {
prop,
+ propValue,
},
});
},
diff --git a/tests/lib/rules/forbid-dom-props.js b/tests/lib/rules/forbid-dom-props.js
index a11d39fe00..67cf34da1e 100644
--- a/tests/lib/rules/forbid-dom-props.js
+++ b/tests/lib/rules/forbid-dom-props.js
@@ -112,6 +112,75 @@ ruleTester.run('forbid-dom-props', rule, {
},
],
},
+ {
+ code: `
+ const First = (props) => (
+
+ );
+ `,
+ options: [
+ {
+ forbid: [
+ {
+ propName: 'someProp',
+ disallowedValues: [],
+ },
+ ],
+ },
+ ],
+ },
+ {
+ code: `
+ const First = (props) => (
+
+ );
+ `,
+ options: [
+ {
+ forbid: [
+ {
+ propName: 'someProp',
+ disallowedValues: ['someValue'],
+ },
+ ],
+ },
+ ],
+ },
+ {
+ code: `
+ const First = (props) => (
+
+ );
+ `,
+ options: [
+ {
+ forbid: [
+ {
+ propName: 'someProp',
+ disallowedValues: ['someValue'],
+ },
+ ],
+ },
+ ],
+ },
+ {
+ code: `
+ const First = (props) => (
+
+ );
+ `,
+ options: [
+ {
+ forbid: [
+ {
+ propName: 'someProp',
+ disallowedValues: ['someValue'],
+ disallowedFor: ['span'],
+ },
+ ],
+ },
+ ],
+ },
]),
invalid: parsers.all([
@@ -191,6 +260,58 @@ ruleTester.run('forbid-dom-props', rule, {
},
],
},
+ {
+ code: `
+ const First = (props) => (
+
+ );
+ `,
+ options: [
+ {
+ forbid: [
+ {
+ propName: 'otherProp',
+ disallowedFor: ['span'],
+ },
+ ],
+ },
+ ],
+ errors: [
+ {
+ messageId: 'propIsForbidden',
+ data: { prop: 'otherProp' },
+ line: 3,
+ column: 17,
+ type: 'JSXAttribute',
+ },
+ ],
+ },
+ {
+ code: `
+ const First = (props) => (
+
+ );
+ `,
+ options: [
+ {
+ forbid: [
+ {
+ propName: 'someProp',
+ disallowedValues: ['someValue'],
+ },
+ ],
+ },
+ ],
+ errors: [
+ {
+ messageId: 'propIsForbiddenWithValue',
+ data: { prop: 'someProp', propValue: 'someValue' },
+ line: 3,
+ column: 16,
+ type: 'JSXAttribute',
+ },
+ ],
+ },
{
code: `
const First = (props) => (
@@ -324,5 +445,70 @@ ruleTester.run('forbid-dom-props', rule, {
},
],
},
+ {
+ code: `
+ const First = (props) => (
+
+ );
+ `,
+ options: [
+ {
+ forbid: [
+ {
+ propName: 'className',
+ disallowedFor: ['div', 'span'],
+ message: 'Please use class instead of ClassName',
+ },
+ { propName: 'otherProp', message: 'Avoid using otherProp' },
+ {
+ propName: 'thirdProp',
+ disallowedFor: ['p'],
+ disallowedValues: ['bar', 'baz'],
+ message: 'Do not use thirdProp with values bar and baz on p',
+ },
+ ],
+ },
+ ],
+ errors: [
+ {
+ message: 'Please use class instead of ClassName',
+ line: 3,
+ column: 16,
+ type: 'JSXAttribute',
+ },
+ {
+ message: 'Please use class instead of ClassName',
+ line: 5,
+ column: 19,
+ type: 'JSXAttribute',
+ },
+ {
+ message: 'Avoid using otherProp',
+ line: 6,
+ column: 18,
+ type: 'JSXAttribute',
+ },
+ {
+ message: 'Do not use thirdProp with values bar and baz on p',
+ line: 9,
+ column: 16,
+ type: 'JSXAttribute',
+ },
+ {
+ message: 'Do not use thirdProp with values bar and baz on p',
+ line: 10,
+ column: 16,
+ type: 'JSXAttribute',
+ },
+ ],
+ },
]),
});