Skip to content

Commit

Permalink
[New] forbid-component-props: add disallowedFor option
Browse files Browse the repository at this point in the history
Co-authored-by: jilianfeng <jilianfeng@baidu.com>
Co-authored-by: Ell Bradshaw <ellb@tailormed.co>
  • Loading branch information
2 people authored and ljharb committed Sep 7, 2022
1 parent 477f36d commit 4a92667
Show file tree
Hide file tree
Showing 4 changed files with 232 additions and 4 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ This change log adheres to standards from [Keep a CHANGELOG](https://keepachange
* [`display-name`]: add `checkContextObjects` option ([#3529][] @JulesBlm)
* [`jsx-first-prop-new-line`]: add `multiprop` option ([#3533][] @haydncomley)
* [`no-deprecated`]: add React 18 deprecations ([#3548][] @sergei-startsev)
* [`forbid-component-props`]: add `disallowedFor` option ([#3417][] @jacketwpbb)

### Fixed
* [`no-array-index-key`]: consider flatMap ([#3530][] @k-yle)
Expand All @@ -26,6 +27,7 @@ This change log adheres to standards from [Keep a CHANGELOG](https://keepachange
[#3533]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3533
[#3530]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3530
[#3529]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3529
[#3417]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3417

## [7.32.2] - 2023.01.28

Expand Down
14 changes: 12 additions & 2 deletions docs/rules/forbid-component-props.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,18 @@ custom message, and a component allowlist:
```js
{
"propName": "someProp",
"allowedFor": [SomeComponent, AnotherComponent],
"message": "Avoid using someProp"
"allowedFor": ["SomeComponent", "AnotherComponent"],
"message": "Avoid using someProp except SomeComponent and AnotherComponent"
}
```

Use `disallowedFor` as an exclusion list to warn on props for specific components. `disallowedFor` must have at least one item.

```js
{
"propName": "someProp",
"disallowedFor": ["SomeComponent", "AnotherComponent"],
"message": "Avoid using someProp for SomeComponent and AnotherComponent"
}
```

Expand Down
29 changes: 27 additions & 2 deletions lib/rules/forbid-component-props.js
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,22 @@ module.exports = {
},
message: { type: 'string' },
},
additionalProperties: false,
},
{
type: 'object',
properties: {
propName: { type: 'string' },
disallowedFor: {
type: 'array',
uniqueItems: true,
minItems: 1,
items: { type: 'string' },
},
message: { type: 'string' },
},
required: ['disallowedFor'],
additionalProperties: false,
},
],
},
Expand All @@ -66,16 +82,25 @@ module.exports = {
const propName = typeof value === 'string' ? value : value.propName;
const options = {
allowList: typeof value === 'string' ? [] : (value.allowedFor || []),
disallowList: typeof value === 'string' ? [] : (value.disallowedFor || []),
message: typeof value === 'string' ? null : value.message,
};
return [propName, options];
}));

function isForbidden(prop, tagName) {
const options = forbid.get(prop);
const allowList = options ? options.allowList : undefined;
if (!options) {
return false;
}

// disallowList should have a least one item (schema configuration)
const isTagForbidden = options.disallowList.length > 0
? options.disallowList.indexOf(tagName) !== -1
: options.allowList.indexOf(tagName) === -1;

// if the tagName is undefined (`<this.something>`), we assume it's a forbidden element
return typeof allowList !== 'undefined' && (typeof tagName === 'undefined' || allowList.indexOf(tagName) === -1);
return typeof tagName === 'undefined' || isTagForbidden;
}

return {
Expand Down
191 changes: 191 additions & 0 deletions tests/lib/rules/forbid-component-props.js
Original file line number Diff line number Diff line change
Expand Up @@ -151,12 +151,103 @@ ruleTester.run('forbid-component-props', rule, {
},
],
},
{
code: `
const item = (<Foo className="foo" />);
`,
options: [
{
forbid: [
{
propName: 'className',
disallowedFor: ['ReactModal'],
},
],
},
],
},
{
code: `
const item = (<Foo className="foo" />);
`,
options: [
{
forbid: [
{
propName: 'className',
disallowedFor: ['ReactModal'],
},
],
},
],
},
{
code: `
<fbt:param name="Total number of files" number={true} />
`,
features: ['jsx namespace'],
},
{
code: `
const item = (
<Foo className="bar">
<ReactModal style={{color: "red"}} />
</Foo>
);
`,
options: [
{
forbid: [
{
propName: 'className',
disallowedFor: ['OtherModal', 'ReactModal'],
},
{
propName: 'style',
disallowedFor: ['Foo'],
},
],
},
],
},
{
code: `
const item = (
<Foo className="bar">
<ReactModal style={{color: "red"}} />
</Foo>
);
`,
options: [
{
forbid: [
{
propName: 'className',
disallowedFor: ['OtherModal', 'ReactModal'],
},
{
propName: 'style',
allowedFor: ['ReactModal'],
},
],
},
],
},
{
code: `
const item = (<this.ReactModal className="foo" />);
`,
options: [
{
forbid: [
{
propName: 'className',
disallowedFor: ['ReactModal'],
},
],
},
],
},
]),

invalid: parsers.all([
Expand Down Expand Up @@ -235,6 +326,34 @@ ruleTester.run('forbid-component-props', rule, {
},
],
},
{
code: `
var First = createReactClass({
propTypes: externalPropTypes,
render: function() {
return <Foo style={{color: "red"}} />;
}
});
`,
options: [
{
forbid: [
{
propName: 'style',
disallowedFor: ['Foo'],
},
],
},
],
errors: [
{
messageId: 'propIsForbidden',
data: { prop: 'style' },
line: 5,
type: 'JSXAttribute',
},
],
},
{
code: `
const item = (<Foo className="foo" />);
Expand Down Expand Up @@ -282,6 +401,78 @@ ruleTester.run('forbid-component-props', rule, {
},
],
},
{
code: `
const item = (<this.ReactModal className="foo" />);
`,
options: [
{
forbid: [
{
propName: 'className',
disallowedFor: ['this.ReactModal'],
},
],
},
],
errors: [
{
messageId: 'propIsForbidden',
data: { prop: 'className' },
line: 2,
column: 40,
type: 'JSXAttribute',
},
],
},
{
code: `
const item = (<ReactModal className="foo" />);
`,
options: [
{
forbid: [
{
propName: 'className',
disallowedFor: ['ReactModal'],
},
],
},
],
errors: [
{
messageId: 'propIsForbidden',
data: { prop: 'className' },
line: 2,
column: 35,
type: 'JSXAttribute',
},
],
},
{
code: `
const item = (<AntdLayout.Content className="antdFoo" />);
`,
options: [
{
forbid: [
{
propName: 'className',
disallowedFor: ['AntdLayout.Content'],
},
],
},
],
errors: [
{
messageId: 'propIsForbidden',
data: { prop: 'className' },
line: 2,
column: 43,
type: 'JSXAttribute',
},
],
},
{
code: `
const item = (<Foo className="foo" />);
Expand Down

0 comments on commit 4a92667

Please sign in to comment.