Skip to content

Commit

Permalink
[New] forbid-component-props/forbid-dom-props: Allow a custom mes…
Browse files Browse the repository at this point in the history
…sage with forbid props

Co-authored-by: Mangesh Tamhankar <mangesh@gusto.com>
Co-authored-by: Jordan Harband <ljharb@gmail.com>
  • Loading branch information
geshwho and ljharb committed Apr 3, 2020
1 parent ab28224 commit 9861469
Show file tree
Hide file tree
Showing 6 changed files with 181 additions and 15 deletions.
7 changes: 4 additions & 3 deletions docs/rules/forbid-component-props.md
Expand Up @@ -42,16 +42,17 @@ The following patterns are **not** considered warnings:
### `forbid`

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

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


### Related rules

- [forbid-dom-props](./forbid-dom-props.md)
10 changes: 9 additions & 1 deletion docs/rules/forbid-dom-props.md
Expand Up @@ -36,14 +36,22 @@ The following patterns are **not** considered warnings:

```js
...
"react/forbid-dom-props": [<enabled>, { "forbid": [<string>] }]
"react/forbid-dom-props": [<enabled>, { "forbid": [<string>|<object>] }]
...
```

### `forbid`

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

```js
{
"propName": "someProp",
"message": "Avoid using someProp"
}
```

### Related rules

Expand Down
20 changes: 15 additions & 5 deletions lib/rules/forbid-component-props.js
Expand Up @@ -46,6 +46,9 @@ module.exports = {
items: {
type: 'string'
}
},
message: {
type: 'string'
}
}
}]
Expand All @@ -59,14 +62,18 @@ module.exports = {
const configuration = context.options[0] || {};
const forbid = new Map((configuration.forbid || DEFAULTS).map((value) => {
const propName = typeof value === 'string' ? value : value.propName;
const whitelist = typeof value === 'string' ? [] : (value.allowedFor || []);
return [propName, whitelist];
const options = {
allowList: typeof value === 'string' ? [] : (value.allowedFor || []),
message: typeof value === 'string' ? null : value.message
};
return [propName, options];
}));

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

return {
Expand All @@ -83,9 +90,12 @@ module.exports = {
return;
}

const customMessage = forbid.get(prop).message;
const errorMessage = customMessage || `Prop \`${prop}\` is forbidden on Components`;

context.report({
node,
message: `Prop \`${prop}\` is forbidden on Components`
message: errorMessage
});
}
};
Expand Down
33 changes: 27 additions & 6 deletions lib/rules/forbid-dom-props.js
Expand Up @@ -32,7 +32,19 @@ module.exports = {
forbid: {
type: 'array',
items: {
type: 'string',
onfOf: [{
type: 'string'
}, {
type: 'object',
properties: {
propName: {
type: 'string'
},
message: {
type: 'string'
}
}
}],
minLength: 1
},
uniqueItems: true
Expand All @@ -43,11 +55,17 @@ module.exports = {
},

create(context) {
function isForbidden(prop) {
const configuration = context.options[0] || {};
const configuration = context.options[0] || {};
const forbid = new Map((configuration.forbid || DEFAULTS).map((value) => {
const propName = typeof value === 'string' ? value : value.propName;
const options = {
message: typeof value === 'string' ? null : value.message
};
return [propName, options];
}));

const forbid = configuration.forbid || DEFAULTS;
return forbid.indexOf(prop) >= 0;
function isForbidden(prop) {
return forbid.has(prop);
}

return {
Expand All @@ -64,9 +82,12 @@ module.exports = {
return;
}

const customMessage = forbid.get(prop).message;
const errorMessage = customMessage || `Prop \`${prop}\` is forbidden on DOM Nodes`;

context.report({
node,
message: `Prop \`${prop}\` is forbidden on DOM Nodes`
message: errorMessage
});
}
};
Expand Down
61 changes: 61 additions & 0 deletions tests/lib/rules/forbid-component-props.js
Expand Up @@ -190,5 +190,66 @@ ruleTester.run('forbid-component-props', rule, {
column: 32,
type: 'JSXAttribute'
}]
}, {
code: 'const item = (<Foo className="foo" />);',
options: [{
forbid: [{propName: 'className', message: 'Please use ourCoolClassName instead of ClassName'}]
}],
errors: [{
message: 'Please use ourCoolClassName instead of ClassName',
line: 1,
column: 20,
type: 'JSXAttribute'
}]
}, {
code: [
'const item = () => (',
'<Foo className="foo">',
' <Bar option="high" />',
'</Foo>',
');'
].join('\n'),
options: [{
forbid: [
{propName: 'className', message: 'Please use ourCoolClassName instead of ClassName'},
{propName: 'option', message: 'Avoid using option'}
]
}],
errors: [{
message: 'Please use ourCoolClassName instead of ClassName',
line: 2,
column: 6,
type: 'JSXAttribute'
}, {
message: 'Avoid using option',
line: 3,
column: 8,
type: 'JSXAttribute'
}]
}, {
code: [
'const item = () => (',
'<Foo className="foo">',
' <Bar option="high" />',
'</Foo>',
');'
].join('\n'),
options: [{
forbid: [
{propName: 'className'},
{propName: 'option', message: 'Avoid using option'}
]
}],
errors: [{
message: 'Prop `className` is forbidden on Components',
line: 2,
column: 6,
type: 'JSXAttribute'
}, {
message: 'Avoid using option',
line: 3,
column: 8,
type: 'JSXAttribute'
}]
}]
});
65 changes: 65 additions & 0 deletions tests/lib/rules/forbid-dom-props.js
Expand Up @@ -126,5 +126,70 @@ ruleTester.run('forbid-element-props', rule, {
column: 8,
type: 'JSXAttribute'
}]
}, {
code: [
'const First = (props) => (',
' <div className="foo" />',
');'
].join('\n'),
options: [{
forbid: [{propName: 'className', message: 'Please use class instead of ClassName'}]
}],
errors: [{
message: 'Please use class instead of ClassName',
line: 2,
column: 8,
type: 'JSXAttribute'
}]
}, {
code: [
'const First = (props) => (',
' <div className="foo">',
' <div otherProp="bar" />',
' </div>',
');'
].join('\n'),
options: [{
forbid: [
{propName: 'className', message: 'Please use class instead of ClassName'},
{propName: 'otherProp', message: 'Avoid using otherProp'}
]
}],
errors: [{
message: 'Please use class instead of ClassName',
line: 2,
column: 8,
type: 'JSXAttribute'
}, {
message: 'Avoid using otherProp',
line: 3,
column: 10,
type: 'JSXAttribute'
}]
}, {
code: [
'const First = (props) => (',
' <div className="foo">',
' <div otherProp="bar" />',
' </div>',
');'
].join('\n'),
options: [{
forbid: [
{propName: 'className'},
{propName: 'otherProp', message: 'Avoid using otherProp'}
]
}],
errors: [{
message: 'Prop `className` is forbidden on DOM Nodes',
line: 2,
column: 8,
type: 'JSXAttribute'
}, {
message: 'Avoid using otherProp',
line: 3,
column: 10,
type: 'JSXAttribute'
}]
}]
});

0 comments on commit 9861469

Please sign in to comment.