Skip to content

[New] forbid-dom-props: Add disallowedValues option for forbidden props #3877

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -7,6 +7,11 @@ This change log adheres to standards from [Keep a CHANGELOG](https://keepachange

## Unreleased

### Added
* [`forbid-dom-props`]: Add `disallowedValues` option for forbidden props ([#3876][] @makxca)

[#3876]: https://github.com/jsx-eslint/eslint-plugin-react/issues/3876

## [7.37.4] - 2025.01.12

### Fixed
49 changes: 47 additions & 2 deletions docs/rules/forbid-dom-props.md
Original file line number Diff line number Diff line change
@@ -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. `<div />`):
custom message, DOM nodes disallowed list (e.g. `<div />`), 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) => (
<span someProp="bar" />
);
```

Example of **correct** code for this rule, when configured with `{ forbid: [{ propName: 'someProp', disallowedFor: ['span'] }] }`.

```jsx
const First = (props) => (
<div someProp="bar" />
);
```

Examples of **incorrect** code for this rule, when configured with `{ forbid: [{ propName: 'someProp', disallowedValues: ['someValue'] }] }`.

```jsx
const First = (props) => (
<div someProp="someValue" />
);
```

```jsx
const First = (props) => (
<span someProp="someValue" />
);
```

Examples of **correct** code for this rule, when configured with `{ forbid: [{ propName: 'someProp', disallowedValues: ['someValue'] }] }`.

```jsx
const First = (props) => (
<Foo someProp="someValue" />
);
```

```jsx
const First = (props) => (
<div someProp="value" />
);
```

### Related rules

- [forbid-component-props](./forbid-component-props.md)
37 changes: 30 additions & 7 deletions lib/rules/forbid-dom-props.js
Original file line number Diff line number Diff line change
@@ -18,23 +18,33 @@ const DEFAULTS = [];
// Rule Definition
// ------------------------------------------------------------------------------

/** @typedef {{ disallowList: null | string[]; message: null | string; disallowedValues: string[] | null }} ForbidMapType */
/**
* @param {Map<string, object>} forbidMap // { disallowList: null | string[], message: null | string }
* @param {Map<string, ForbidMapType>} 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,
},
});
},
186 changes: 186 additions & 0 deletions tests/lib/rules/forbid-dom-props.js
Original file line number Diff line number Diff line change
@@ -112,6 +112,75 @@ ruleTester.run('forbid-dom-props', rule, {
},
],
},
{
code: `
const First = (props) => (
<div someProp="someValue" />
);
`,
options: [
{
forbid: [
{
propName: 'someProp',
disallowedValues: [],
},
],
},
],
},
{
code: `
const First = (props) => (
<Foo someProp="someValue" />
);
`,
options: [
{
forbid: [
{
propName: 'someProp',
disallowedValues: ['someValue'],
},
],
},
],
},
{
code: `
const First = (props) => (
<div someProp="value" />
);
`,
options: [
{
forbid: [
{
propName: 'someProp',
disallowedValues: ['someValue'],
},
],
},
],
},
{
code: `
const First = (props) => (
<div someProp="someValue" />
);
`,
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) => (
<span otherProp="bar" />
);
`,
options: [
{
forbid: [
{
propName: 'otherProp',
disallowedFor: ['span'],
},
],
},
],
errors: [
{
messageId: 'propIsForbidden',
data: { prop: 'otherProp' },
line: 3,
column: 17,
type: 'JSXAttribute',
},
],
},
{
code: `
const First = (props) => (
<div someProp="someValue" />
);
`,
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) => (
<div className="foo">
<input className="boo" />
<span className="foobar">Foobar</span>
<div otherProp="bar" />
<p thirdProp="foo" />
<div thirdProp="baz" />
<p thirdProp="bar" />
<p thirdProp="baz" />
</div>
);
`,
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',
},
],
},
]),
});