Skip to content

Commit 9861469

Browse files
geshwholjharb
andcommitted
[New] forbid-component-props/forbid-dom-props: Allow a custom message with forbid props
Co-authored-by: Mangesh Tamhankar <[email protected]> Co-authored-by: Jordan Harband <[email protected]>
1 parent ab28224 commit 9861469

File tree

6 files changed

+181
-15
lines changed

6 files changed

+181
-15
lines changed

docs/rules/forbid-component-props.md

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -42,16 +42,17 @@ The following patterns are **not** considered warnings:
4242
### `forbid`
4343

4444
An array specifying the names of props that are forbidden. The default value of this option is `['className', 'style']`.
45-
Each array element can either be a string with the property name or object specifying the property name and a component whitelist:
45+
Each array element can either be a string with the property name or object specifying the property name, an optional
46+
custom message, and a component whitelist:
4647

4748
```js
4849
{
4950
"propName": "someProp",
50-
"allowedFor": [SomeComponent, AnotherComponent]
51+
"allowedFor": [SomeComponent, AnotherComponent],
52+
"message": "Avoid using someProp"
5153
}
5254
```
5355

54-
5556
### Related rules
5657

5758
- [forbid-dom-props](./forbid-dom-props.md)

docs/rules/forbid-dom-props.md

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,14 +36,22 @@ The following patterns are **not** considered warnings:
3636

3737
```js
3838
...
39-
"react/forbid-dom-props": [<enabled>, { "forbid": [<string>] }]
39+
"react/forbid-dom-props": [<enabled>, { "forbid": [<string>|<object>] }]
4040
...
4141
```
4242

4343
### `forbid`
4444

4545
An array of strings, with the names of props that are forbidden. The default value of this option `[]`.
46+
Each array element can either be a string with the property name or object specifying the property name and an optional
47+
custom message:
4648

49+
```js
50+
{
51+
"propName": "someProp",
52+
"message": "Avoid using someProp"
53+
}
54+
```
4755

4856
### Related rules
4957

lib/rules/forbid-component-props.js

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,9 @@ module.exports = {
4646
items: {
4747
type: 'string'
4848
}
49+
},
50+
message: {
51+
type: 'string'
4952
}
5053
}
5154
}]
@@ -59,14 +62,18 @@ module.exports = {
5962
const configuration = context.options[0] || {};
6063
const forbid = new Map((configuration.forbid || DEFAULTS).map((value) => {
6164
const propName = typeof value === 'string' ? value : value.propName;
62-
const whitelist = typeof value === 'string' ? [] : (value.allowedFor || []);
63-
return [propName, whitelist];
65+
const options = {
66+
allowList: typeof value === 'string' ? [] : (value.allowedFor || []),
67+
message: typeof value === 'string' ? null : value.message
68+
};
69+
return [propName, options];
6470
}));
6571

6672
function isForbidden(prop, tagName) {
67-
const whitelist = forbid.get(prop);
73+
const options = forbid.get(prop);
74+
const allowList = options ? options.allowList : undefined;
6875
// if the tagName is undefined (`<this.something>`), we assume it's a forbidden element
69-
return typeof whitelist !== 'undefined' && (typeof tagName === 'undefined' || whitelist.indexOf(tagName) === -1);
76+
return typeof allowList !== 'undefined' && (typeof tagName === 'undefined' || allowList.indexOf(tagName) === -1);
7077
}
7178

7279
return {
@@ -83,9 +90,12 @@ module.exports = {
8390
return;
8491
}
8592

93+
const customMessage = forbid.get(prop).message;
94+
const errorMessage = customMessage || `Prop \`${prop}\` is forbidden on Components`;
95+
8696
context.report({
8797
node,
88-
message: `Prop \`${prop}\` is forbidden on Components`
98+
message: errorMessage
8999
});
90100
}
91101
};

lib/rules/forbid-dom-props.js

Lines changed: 27 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,19 @@ module.exports = {
3232
forbid: {
3333
type: 'array',
3434
items: {
35-
type: 'string',
35+
onfOf: [{
36+
type: 'string'
37+
}, {
38+
type: 'object',
39+
properties: {
40+
propName: {
41+
type: 'string'
42+
},
43+
message: {
44+
type: 'string'
45+
}
46+
}
47+
}],
3648
minLength: 1
3749
},
3850
uniqueItems: true
@@ -43,11 +55,17 @@ module.exports = {
4355
},
4456

4557
create(context) {
46-
function isForbidden(prop) {
47-
const configuration = context.options[0] || {};
58+
const configuration = context.options[0] || {};
59+
const forbid = new Map((configuration.forbid || DEFAULTS).map((value) => {
60+
const propName = typeof value === 'string' ? value : value.propName;
61+
const options = {
62+
message: typeof value === 'string' ? null : value.message
63+
};
64+
return [propName, options];
65+
}));
4866

49-
const forbid = configuration.forbid || DEFAULTS;
50-
return forbid.indexOf(prop) >= 0;
67+
function isForbidden(prop) {
68+
return forbid.has(prop);
5169
}
5270

5371
return {
@@ -64,9 +82,12 @@ module.exports = {
6482
return;
6583
}
6684

85+
const customMessage = forbid.get(prop).message;
86+
const errorMessage = customMessage || `Prop \`${prop}\` is forbidden on DOM Nodes`;
87+
6788
context.report({
6889
node,
69-
message: `Prop \`${prop}\` is forbidden on DOM Nodes`
90+
message: errorMessage
7091
});
7192
}
7293
};

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

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -190,5 +190,66 @@ ruleTester.run('forbid-component-props', rule, {
190190
column: 32,
191191
type: 'JSXAttribute'
192192
}]
193+
}, {
194+
code: 'const item = (<Foo className="foo" />);',
195+
options: [{
196+
forbid: [{propName: 'className', message: 'Please use ourCoolClassName instead of ClassName'}]
197+
}],
198+
errors: [{
199+
message: 'Please use ourCoolClassName instead of ClassName',
200+
line: 1,
201+
column: 20,
202+
type: 'JSXAttribute'
203+
}]
204+
}, {
205+
code: [
206+
'const item = () => (',
207+
'<Foo className="foo">',
208+
' <Bar option="high" />',
209+
'</Foo>',
210+
');'
211+
].join('\n'),
212+
options: [{
213+
forbid: [
214+
{propName: 'className', message: 'Please use ourCoolClassName instead of ClassName'},
215+
{propName: 'option', message: 'Avoid using option'}
216+
]
217+
}],
218+
errors: [{
219+
message: 'Please use ourCoolClassName instead of ClassName',
220+
line: 2,
221+
column: 6,
222+
type: 'JSXAttribute'
223+
}, {
224+
message: 'Avoid using option',
225+
line: 3,
226+
column: 8,
227+
type: 'JSXAttribute'
228+
}]
229+
}, {
230+
code: [
231+
'const item = () => (',
232+
'<Foo className="foo">',
233+
' <Bar option="high" />',
234+
'</Foo>',
235+
');'
236+
].join('\n'),
237+
options: [{
238+
forbid: [
239+
{propName: 'className'},
240+
{propName: 'option', message: 'Avoid using option'}
241+
]
242+
}],
243+
errors: [{
244+
message: 'Prop `className` is forbidden on Components',
245+
line: 2,
246+
column: 6,
247+
type: 'JSXAttribute'
248+
}, {
249+
message: 'Avoid using option',
250+
line: 3,
251+
column: 8,
252+
type: 'JSXAttribute'
253+
}]
193254
}]
194255
});

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

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,5 +126,70 @@ ruleTester.run('forbid-element-props', rule, {
126126
column: 8,
127127
type: 'JSXAttribute'
128128
}]
129+
}, {
130+
code: [
131+
'const First = (props) => (',
132+
' <div className="foo" />',
133+
');'
134+
].join('\n'),
135+
options: [{
136+
forbid: [{propName: 'className', message: 'Please use class instead of ClassName'}]
137+
}],
138+
errors: [{
139+
message: 'Please use class instead of ClassName',
140+
line: 2,
141+
column: 8,
142+
type: 'JSXAttribute'
143+
}]
144+
}, {
145+
code: [
146+
'const First = (props) => (',
147+
' <div className="foo">',
148+
' <div otherProp="bar" />',
149+
' </div>',
150+
');'
151+
].join('\n'),
152+
options: [{
153+
forbid: [
154+
{propName: 'className', message: 'Please use class instead of ClassName'},
155+
{propName: 'otherProp', message: 'Avoid using otherProp'}
156+
]
157+
}],
158+
errors: [{
159+
message: 'Please use class instead of ClassName',
160+
line: 2,
161+
column: 8,
162+
type: 'JSXAttribute'
163+
}, {
164+
message: 'Avoid using otherProp',
165+
line: 3,
166+
column: 10,
167+
type: 'JSXAttribute'
168+
}]
169+
}, {
170+
code: [
171+
'const First = (props) => (',
172+
' <div className="foo">',
173+
' <div otherProp="bar" />',
174+
' </div>',
175+
');'
176+
].join('\n'),
177+
options: [{
178+
forbid: [
179+
{propName: 'className'},
180+
{propName: 'otherProp', message: 'Avoid using otherProp'}
181+
]
182+
}],
183+
errors: [{
184+
message: 'Prop `className` is forbidden on DOM Nodes',
185+
line: 2,
186+
column: 8,
187+
type: 'JSXAttribute'
188+
}, {
189+
message: 'Avoid using otherProp',
190+
line: 3,
191+
column: 10,
192+
type: 'JSXAttribute'
193+
}]
129194
}]
130195
});

0 commit comments

Comments
 (0)