Skip to content

Commit 9d326ab

Browse files
authored
Merge pull request #703 from lencioni/forbid-component-props
Add rule to forbid className and style being passed to Components
2 parents e11d6d1 + 67919e4 commit 9d326ab

File tree

6 files changed

+265
-1
lines changed

6 files changed

+265
-1
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@ Finally, enable all of the rules that you would like to use. Use [our preset](#
8080
# List of supported rules
8181

8282
* [react/display-name](docs/rules/display-name.md): Prevent missing `displayName` in a React component definition
83+
* [react/forbid-component-props](docs/rules/forbid-component-props.md): Forbid certain props on Components
8384
* [react/forbid-prop-types](docs/rules/forbid-prop-types.md): Forbid certain propTypes
8485
* [react/no-danger](docs/rules/no-danger.md): Prevent usage of dangerous JSX properties
8586
* [react/no-deprecated](docs/rules/no-deprecated.md): Prevent usage of deprecated methods

docs/rules/forbid-component-props.md

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
# Forbid certain props on Components (forbid-component-props)
2+
3+
By default this rule prevents passing of [props that add lots of complexity](https://medium.com/brigade-engineering/don-t-pass-css-classes-between-components-e9f7ab192785) (`className`, `style`) to Components. This rule only applies to Components (e.g. `<Foo />`) and not DOM nodes (e.g. `<div />`). The list of forbidden props can be customized with the `forbid` option.
4+
5+
## Rule Details
6+
7+
This rule checks all JSX elements and verifies that no forbidden props are used
8+
on Components. This rule is off by default.
9+
10+
The following patterns are considered warnings:
11+
12+
```jsx
13+
<Hello className='foo' />
14+
```
15+
16+
```jsx
17+
<Hello style={{color: 'red'}} />
18+
```
19+
20+
The following patterns are not considered warnings:
21+
22+
```jsx
23+
<Hello name='Joe' />
24+
```
25+
26+
```jsx
27+
<div className='foo' />
28+
```
29+
30+
```jsx
31+
<div style={{color: 'red'}} />
32+
```
33+
34+
## Rule Options
35+
36+
```js
37+
...
38+
"forbid-component-props": [<enabled>, { "forbid": [<string>] }]
39+
...
40+
```
41+
42+
### `forbid`
43+
44+
An array of strings, with the names of props that are forbidden. The default value of this option is `['className', 'style']`.

docs/rules/forbid-prop-types.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ class Component extends React.Component {
5050

5151
### `forbid`
5252

53-
An array of strings, with the names of `React.PropTypes` keys that are forbidden.
53+
An array of strings, with the names of `React.PropTypes` keys that are forbidden. The default value for this option is `['any', 'array', 'object']`.
5454

5555
## When not to use
5656

index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ var rules = {
4242
'jsx-closing-bracket-location': require('./lib/rules/jsx-closing-bracket-location'),
4343
'jsx-space-before-closing': require('./lib/rules/jsx-space-before-closing'),
4444
'no-direct-mutation-state': require('./lib/rules/no-direct-mutation-state'),
45+
'forbid-component-props': require('./lib/rules/forbid-component-props'),
4546
'forbid-prop-types': require('./lib/rules/forbid-prop-types'),
4647
'prefer-es6-class': require('./lib/rules/prefer-es6-class'),
4748
'jsx-key': require('./lib/rules/jsx-key'),

lib/rules/forbid-component-props.js

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
/**
2+
* @fileoverview Forbid certain props on components
3+
* @author Joe Lencioni
4+
*/
5+
'use strict';
6+
7+
// ------------------------------------------------------------------------------
8+
// Constants
9+
// ------------------------------------------------------------------------------
10+
11+
var DEFAULTS = ['className', 'style'];
12+
13+
// ------------------------------------------------------------------------------
14+
// Rule Definition
15+
// ------------------------------------------------------------------------------
16+
17+
module.exports = {
18+
meta: {
19+
docs: {},
20+
21+
schema: [{
22+
type: 'object',
23+
properties: {
24+
forbid: {
25+
type: 'array',
26+
items: {
27+
type: 'string'
28+
}
29+
}
30+
},
31+
additionalProperties: true
32+
}]
33+
},
34+
35+
create: function(context) {
36+
37+
function isForbidden(prop) {
38+
var configuration = context.options[0] || {};
39+
40+
var forbid = configuration.forbid || DEFAULTS;
41+
return forbid.indexOf(prop) >= 0;
42+
}
43+
44+
return {
45+
JSXAttribute: function(node) {
46+
var tag = node.parent.name.name;
47+
if (tag[0] !== tag[0].toUpperCase()) {
48+
// This is a DOM node, not a Component, so exit.
49+
return;
50+
}
51+
52+
var prop = node.name.name;
53+
54+
if (!isForbidden(prop)) {
55+
return;
56+
}
57+
58+
context.report({
59+
node: node,
60+
message: 'Prop `' + prop + '` is forbidden on Components'
61+
});
62+
}
63+
};
64+
}
65+
};
Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
/**
2+
* @fileoverview Tests for forbid-component-props
3+
*/
4+
'use strict';
5+
6+
// -----------------------------------------------------------------------------
7+
// Requirements
8+
// -----------------------------------------------------------------------------
9+
10+
var rule = require('../../../lib/rules/forbid-component-props');
11+
var RuleTester = require('eslint').RuleTester;
12+
13+
var parserOptions = {
14+
ecmaVersion: 6,
15+
ecmaFeatures: {
16+
experimentalObjectRestSpread: true,
17+
jsx: true
18+
}
19+
};
20+
21+
require('babel-eslint');
22+
23+
// -----------------------------------------------------------------------------
24+
// Tests
25+
// -----------------------------------------------------------------------------
26+
27+
var CLASSNAME_ERROR_MESSAGE = 'Prop `className` is forbidden on Components';
28+
var STYLE_ERROR_MESSAGE = 'Prop `style` is forbidden on Components';
29+
30+
var ruleTester = new RuleTester();
31+
ruleTester.run('forbid-component-props', rule, {
32+
33+
valid: [{
34+
code: [
35+
'var First = React.createClass({',
36+
' render: function() {',
37+
' return <div className="foo" />;',
38+
' }',
39+
'});'
40+
].join('\n'),
41+
parserOptions: parserOptions
42+
}, {
43+
code: [
44+
'var First = React.createClass({',
45+
' render: function() {',
46+
' return <div style={{color: "red"}} />;',
47+
' }',
48+
'});'
49+
].join('\n'),
50+
options: [{forbid: ['style']}],
51+
parserOptions: parserOptions
52+
}, {
53+
code: [
54+
'var First = React.createClass({',
55+
' propTypes: externalPropTypes,',
56+
' render: function() {',
57+
' return <Foo bar="baz" />;',
58+
' }',
59+
'});'
60+
].join('\n'),
61+
parserOptions: parserOptions
62+
}, {
63+
code: [
64+
'var First = React.createClass({',
65+
' propTypes: externalPropTypes,',
66+
' render: function() {',
67+
' return <Foo className="bar" />;',
68+
' }',
69+
'});'
70+
].join('\n'),
71+
options: [{forbid: ['style']}],
72+
parserOptions: parserOptions
73+
}, {
74+
code: [
75+
'var First = React.createClass({',
76+
' propTypes: externalPropTypes,',
77+
' render: function() {',
78+
' return <Foo className="bar" />;',
79+
' }',
80+
'});'
81+
].join('\n'),
82+
options: [{forbid: ['style', 'foo']}],
83+
parserOptions: parserOptions
84+
}],
85+
86+
invalid: [{
87+
code: [
88+
'var First = React.createClass({',
89+
' propTypes: externalPropTypes,',
90+
' render: function() {',
91+
' return <Foo className="bar" />;',
92+
' }',
93+
'});'
94+
].join('\n'),
95+
parserOptions: parserOptions,
96+
errors: [{
97+
message: CLASSNAME_ERROR_MESSAGE,
98+
line: 4,
99+
column: 17,
100+
type: 'JSXAttribute'
101+
}]
102+
}, {
103+
code: [
104+
'var First = React.createClass({',
105+
' propTypes: externalPropTypes,',
106+
' render: function() {',
107+
' return <Foo style={{color: "red"}} />;',
108+
' }',
109+
'});'
110+
].join('\n'),
111+
parserOptions: parserOptions,
112+
errors: [{
113+
message: STYLE_ERROR_MESSAGE,
114+
line: 4,
115+
column: 17,
116+
type: 'JSXAttribute'
117+
}]
118+
}, {
119+
code: [
120+
'var First = React.createClass({',
121+
' propTypes: externalPropTypes,',
122+
' render: function() {',
123+
' return <Foo className="bar" />;',
124+
' }',
125+
'});'
126+
].join('\n'),
127+
parserOptions: parserOptions,
128+
options: [{forbid: ['className', 'style']}],
129+
errors: [{
130+
message: CLASSNAME_ERROR_MESSAGE,
131+
line: 4,
132+
column: 17,
133+
type: 'JSXAttribute'
134+
}]
135+
}, {
136+
code: [
137+
'var First = React.createClass({',
138+
' propTypes: externalPropTypes,',
139+
' render: function() {',
140+
' return <Foo style={{color: "red"}} />;',
141+
' }',
142+
'});'
143+
].join('\n'),
144+
parserOptions: parserOptions,
145+
options: [{forbid: ['className', 'style']}],
146+
errors: [{
147+
message: STYLE_ERROR_MESSAGE,
148+
line: 4,
149+
column: 17,
150+
type: 'JSXAttribute'
151+
}]
152+
}]
153+
});

0 commit comments

Comments
 (0)