Skip to content

Commit 4a92667

Browse files
jilianfengEll Bradshaw
authored andcommitted
[New] forbid-component-props: add disallowedFor option
Co-authored-by: jilianfeng <[email protected]> Co-authored-by: Ell Bradshaw <[email protected]>
1 parent 477f36d commit 4a92667

File tree

4 files changed

+232
-4
lines changed

4 files changed

+232
-4
lines changed

CHANGELOG.md

+2
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ This change log adheres to standards from [Keep a CHANGELOG](https://keepachange
99
* [`display-name`]: add `checkContextObjects` option ([#3529][] @JulesBlm)
1010
* [`jsx-first-prop-new-line`]: add `multiprop` option ([#3533][] @haydncomley)
1111
* [`no-deprecated`]: add React 18 deprecations ([#3548][] @sergei-startsev)
12+
* [`forbid-component-props`]: add `disallowedFor` option ([#3417][] @jacketwpbb)
1213

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

3032
## [7.32.2] - 2023.01.28
3133

docs/rules/forbid-component-props.md

+12-2
Original file line numberDiff line numberDiff line change
@@ -50,8 +50,18 @@ custom message, and a component allowlist:
5050
```js
5151
{
5252
"propName": "someProp",
53-
"allowedFor": [SomeComponent, AnotherComponent],
54-
"message": "Avoid using someProp"
53+
"allowedFor": ["SomeComponent", "AnotherComponent"],
54+
"message": "Avoid using someProp except SomeComponent and AnotherComponent"
55+
}
56+
```
57+
58+
Use `disallowedFor` as an exclusion list to warn on props for specific components. `disallowedFor` must have at least one item.
59+
60+
```js
61+
{
62+
"propName": "someProp",
63+
"disallowedFor": ["SomeComponent", "AnotherComponent"],
64+
"message": "Avoid using someProp for SomeComponent and AnotherComponent"
5565
}
5666
```
5767

lib/rules/forbid-component-props.js

+27-2
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,22 @@ module.exports = {
5252
},
5353
message: { type: 'string' },
5454
},
55+
additionalProperties: false,
56+
},
57+
{
58+
type: 'object',
59+
properties: {
60+
propName: { type: 'string' },
61+
disallowedFor: {
62+
type: 'array',
63+
uniqueItems: true,
64+
minItems: 1,
65+
items: { type: 'string' },
66+
},
67+
message: { type: 'string' },
68+
},
69+
required: ['disallowedFor'],
70+
additionalProperties: false,
5571
},
5672
],
5773
},
@@ -66,16 +82,25 @@ module.exports = {
6682
const propName = typeof value === 'string' ? value : value.propName;
6783
const options = {
6884
allowList: typeof value === 'string' ? [] : (value.allowedFor || []),
85+
disallowList: typeof value === 'string' ? [] : (value.disallowedFor || []),
6986
message: typeof value === 'string' ? null : value.message,
7087
};
7188
return [propName, options];
7289
}));
7390

7491
function isForbidden(prop, tagName) {
7592
const options = forbid.get(prop);
76-
const allowList = options ? options.allowList : undefined;
93+
if (!options) {
94+
return false;
95+
}
96+
97+
// disallowList should have a least one item (schema configuration)
98+
const isTagForbidden = options.disallowList.length > 0
99+
? options.disallowList.indexOf(tagName) !== -1
100+
: options.allowList.indexOf(tagName) === -1;
101+
77102
// if the tagName is undefined (`<this.something>`), we assume it's a forbidden element
78-
return typeof allowList !== 'undefined' && (typeof tagName === 'undefined' || allowList.indexOf(tagName) === -1);
103+
return typeof tagName === 'undefined' || isTagForbidden;
79104
}
80105

81106
return {

tests/lib/rules/forbid-component-props.js

+191
Original file line numberDiff line numberDiff line change
@@ -151,12 +151,103 @@ ruleTester.run('forbid-component-props', rule, {
151151
},
152152
],
153153
},
154+
{
155+
code: `
156+
const item = (<Foo className="foo" />);
157+
`,
158+
options: [
159+
{
160+
forbid: [
161+
{
162+
propName: 'className',
163+
disallowedFor: ['ReactModal'],
164+
},
165+
],
166+
},
167+
],
168+
},
169+
{
170+
code: `
171+
const item = (<Foo className="foo" />);
172+
`,
173+
options: [
174+
{
175+
forbid: [
176+
{
177+
propName: 'className',
178+
disallowedFor: ['ReactModal'],
179+
},
180+
],
181+
},
182+
],
183+
},
154184
{
155185
code: `
156186
<fbt:param name="Total number of files" number={true} />
157187
`,
158188
features: ['jsx namespace'],
159189
},
190+
{
191+
code: `
192+
const item = (
193+
<Foo className="bar">
194+
<ReactModal style={{color: "red"}} />
195+
</Foo>
196+
);
197+
`,
198+
options: [
199+
{
200+
forbid: [
201+
{
202+
propName: 'className',
203+
disallowedFor: ['OtherModal', 'ReactModal'],
204+
},
205+
{
206+
propName: 'style',
207+
disallowedFor: ['Foo'],
208+
},
209+
],
210+
},
211+
],
212+
},
213+
{
214+
code: `
215+
const item = (
216+
<Foo className="bar">
217+
<ReactModal style={{color: "red"}} />
218+
</Foo>
219+
);
220+
`,
221+
options: [
222+
{
223+
forbid: [
224+
{
225+
propName: 'className',
226+
disallowedFor: ['OtherModal', 'ReactModal'],
227+
},
228+
{
229+
propName: 'style',
230+
allowedFor: ['ReactModal'],
231+
},
232+
],
233+
},
234+
],
235+
},
236+
{
237+
code: `
238+
const item = (<this.ReactModal className="foo" />);
239+
`,
240+
options: [
241+
{
242+
forbid: [
243+
{
244+
propName: 'className',
245+
disallowedFor: ['ReactModal'],
246+
},
247+
],
248+
},
249+
],
250+
},
160251
]),
161252

162253
invalid: parsers.all([
@@ -235,6 +326,34 @@ ruleTester.run('forbid-component-props', rule, {
235326
},
236327
],
237328
},
329+
{
330+
code: `
331+
var First = createReactClass({
332+
propTypes: externalPropTypes,
333+
render: function() {
334+
return <Foo style={{color: "red"}} />;
335+
}
336+
});
337+
`,
338+
options: [
339+
{
340+
forbid: [
341+
{
342+
propName: 'style',
343+
disallowedFor: ['Foo'],
344+
},
345+
],
346+
},
347+
],
348+
errors: [
349+
{
350+
messageId: 'propIsForbidden',
351+
data: { prop: 'style' },
352+
line: 5,
353+
type: 'JSXAttribute',
354+
},
355+
],
356+
},
238357
{
239358
code: `
240359
const item = (<Foo className="foo" />);
@@ -282,6 +401,78 @@ ruleTester.run('forbid-component-props', rule, {
282401
},
283402
],
284403
},
404+
{
405+
code: `
406+
const item = (<this.ReactModal className="foo" />);
407+
`,
408+
options: [
409+
{
410+
forbid: [
411+
{
412+
propName: 'className',
413+
disallowedFor: ['this.ReactModal'],
414+
},
415+
],
416+
},
417+
],
418+
errors: [
419+
{
420+
messageId: 'propIsForbidden',
421+
data: { prop: 'className' },
422+
line: 2,
423+
column: 40,
424+
type: 'JSXAttribute',
425+
},
426+
],
427+
},
428+
{
429+
code: `
430+
const item = (<ReactModal className="foo" />);
431+
`,
432+
options: [
433+
{
434+
forbid: [
435+
{
436+
propName: 'className',
437+
disallowedFor: ['ReactModal'],
438+
},
439+
],
440+
},
441+
],
442+
errors: [
443+
{
444+
messageId: 'propIsForbidden',
445+
data: { prop: 'className' },
446+
line: 2,
447+
column: 35,
448+
type: 'JSXAttribute',
449+
},
450+
],
451+
},
452+
{
453+
code: `
454+
const item = (<AntdLayout.Content className="antdFoo" />);
455+
`,
456+
options: [
457+
{
458+
forbid: [
459+
{
460+
propName: 'className',
461+
disallowedFor: ['AntdLayout.Content'],
462+
},
463+
],
464+
},
465+
],
466+
errors: [
467+
{
468+
messageId: 'propIsForbidden',
469+
data: { prop: 'className' },
470+
line: 2,
471+
column: 43,
472+
type: 'JSXAttribute',
473+
},
474+
],
475+
},
285476
{
286477
code: `
287478
const item = (<Foo className="foo" />);

0 commit comments

Comments
 (0)