Skip to content

Commit 0b25b66

Browse files
committed
Add forbid-foreign-prop-types rule
People may want to use babel-plugin-transform-react-remove-prop-types to remove propTypes from their components in production builds, as an optimization. The `forbib-foreign-prop-types` rule forbids using another component's prop types unless they are explicitly imported/exported, which makes that optimization less prone to error. Fixes #696
1 parent 5827897 commit 0b25b66

File tree

5 files changed

+251
-0
lines changed

5 files changed

+251
-0
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@ Finally, enable all of the rules that you would like to use. Use [our preset](#
8383
* [react/display-name](docs/rules/display-name.md): Prevent missing `displayName` in a React component definition
8484
* [react/forbid-component-props](docs/rules/forbid-component-props.md): Forbid certain props on Components
8585
* [react/forbid-prop-types](docs/rules/forbid-prop-types.md): Forbid certain propTypes
86+
* [react/forbid-foreign-prop-types](docs/rules/forbid-foreign-prop-types.md): Forbid foreign propTypes
8687
* [react/no-array-index-key](docs/rules/no-array-index-key.md): Prevent using Array index in `key` props
8788
* [react/no-children-prop](docs/rules/no-children-prop.md): Prevent passing children as props
8889
* [react/no-danger](docs/rules/no-danger.md): Prevent usage of dangerous JSX properties
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
# Forbid foreign propTypes (forbid-foreign-prop-types)
2+
3+
This rule forbids using another component's prop types unless they are explicitly imported/exported. This allows people who want to use [babel-plugin-transform-react-remove-prop-types](https://github.com/oliviertassinari/babel-plugin-transform-react-remove-prop-types) to remove propTypes from their components in production builds, to do so safely.
4+
5+
In order to ensure that imports are explicitly exported it is recommended to use the ["named" rule in eslint-plugin-import](https://github.com/benmosher/eslint-plugin-import/blob/master/docs/rules/named.md) in conjunction with this rule.
6+
7+
## Rule Details
8+
9+
This rule checks all objects and ensures that the `propTypes` property is not used.
10+
11+
The following patterns are considered warnings:
12+
13+
```js
14+
import SomeComponent from './SomeComponent';
15+
SomeComponent.propTypes;
16+
17+
var { propTypes } = SomeComponent;
18+
19+
SomeComponent['propTypes'];
20+
```
21+
22+
The following patterns are not considered warnings:
23+
24+
```js
25+
import SomeComponent, {propTypes as someComponentPropTypes} from './SomeComponent';
26+
```
27+
28+
## When not to use
29+
30+
This rule aims to make a certain production optimization, removing prop types, less prone to error. This rule may not be relevant to you if you do not wish to make use of this optimization.

index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ var allRules = {
4343
'no-direct-mutation-state': require('./lib/rules/no-direct-mutation-state'),
4444
'forbid-component-props': require('./lib/rules/forbid-component-props'),
4545
'forbid-prop-types': require('./lib/rules/forbid-prop-types'),
46+
'forbid-foreign-prop-types': require('./lib/rules/forbid-foreign-prop-types'),
4647
'prefer-es6-class': require('./lib/rules/prefer-es6-class'),
4748
'jsx-key': require('./lib/rules/jsx-key'),
4849
'no-string-refs': require('./lib/rules/no-string-refs'),
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
/**
2+
* @fileoverview Forbid using another component's propTypes
3+
* @author Ian Christian Myers
4+
*/
5+
'use strict';
6+
7+
var find = require('array.prototype.find');
8+
9+
// ------------------------------------------------------------------------------
10+
// Constants
11+
// ------------------------------------------------------------------------------
12+
13+
14+
// ------------------------------------------------------------------------------
15+
// Rule Definition
16+
// ------------------------------------------------------------------------------
17+
18+
module.exports = {
19+
meta: {
20+
docs: {
21+
description: 'Forbid using another component\'s propTypes',
22+
category: 'Best Practices',
23+
recommended: false
24+
}
25+
},
26+
27+
create: function(context) {
28+
// --------------------------------------------------------------------------
29+
// Helpers
30+
// --------------------------------------------------------------------------
31+
32+
function isLeftSideOfAssignment(node) {
33+
return node.parent.type === 'AssignmentExpression' && node.parent.left === node;
34+
}
35+
36+
return {
37+
MemberExpression: function(node) {
38+
if (!node.computed && node.property && node.property.type === 'Identifier' &&
39+
node.property.name === 'propTypes' && !isLeftSideOfAssignment(node) ||
40+
node.property && node.property.type === 'Literal' &&
41+
node.property.value === 'propTypes' && !isLeftSideOfAssignment(node)) {
42+
context.report({
43+
node: node.property,
44+
message: 'Using another component\'s propTypes is forbidden'
45+
});
46+
}
47+
},
48+
49+
ObjectPattern: function(node) {
50+
var propTypesNode = find(node.properties, function(property) {
51+
return property.type === 'Property' && property.key.name === 'propTypes';
52+
});
53+
54+
if (propTypesNode) {
55+
context.report({
56+
node: propTypesNode,
57+
message: 'Using another component\'s propTypes is forbidden'
58+
});
59+
}
60+
}
61+
};
62+
}
63+
};
Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
/**
2+
* @fileoverview Tests for forbid-foreign-prop-types
3+
*/
4+
'use strict';
5+
6+
// -----------------------------------------------------------------------------
7+
// Requirements
8+
// -----------------------------------------------------------------------------
9+
10+
var rule = require('../../../lib/rules/forbid-foreign-prop-types');
11+
var RuleTester = require('eslint').RuleTester;
12+
13+
var parserOptions = {
14+
ecmaVersion: 6,
15+
sourceType: 'module',
16+
ecmaFeatures: {
17+
jsx: true
18+
}
19+
};
20+
21+
require('babel-eslint');
22+
23+
// -----------------------------------------------------------------------------
24+
// Tests
25+
// -----------------------------------------------------------------------------
26+
27+
var ERROR_MESSAGE = 'Using another component\'s propTypes is forbidden';
28+
29+
var ruleTester = new RuleTester();
30+
ruleTester.run('forbid-foreign-prop-types', rule, {
31+
32+
valid: [{
33+
code: 'import { propTypes } from "SomeComponent";',
34+
parserOptions: parserOptions
35+
}, {
36+
code: 'import { propTypes as someComponentPropTypes } from "SomeComponent";',
37+
parserOptions: parserOptions
38+
}, {
39+
code: 'const foo = propTypes',
40+
parserOptions: parserOptions
41+
}, {
42+
code: 'foo(propTypes)',
43+
parserOptions: parserOptions
44+
}, {
45+
code: 'foo + propTypes',
46+
parserOptions: parserOptions
47+
}, {
48+
code: 'const foo = [propTypes]',
49+
parserOptions: parserOptions
50+
}, {
51+
code: 'const foo = { propTypes }',
52+
parserOptions: parserOptions
53+
}, {
54+
code: 'Foo.propTypes = propTypes',
55+
parserOptions: parserOptions
56+
}, {
57+
code: 'Foo["propTypes"] = propTypes',
58+
parserOptions: parserOptions
59+
}, {
60+
code: 'const propTypes = "bar"; Foo[propTypes];',
61+
parserOptions: parserOptions
62+
}],
63+
64+
invalid: [{
65+
code: [
66+
'var Foo = React.createClass({',
67+
' propTypes: Bar.propTypes,',
68+
' render: function() {',
69+
' return <Foo className="bar" />;',
70+
' }',
71+
'});'
72+
].join('\n'),
73+
parserOptions: parserOptions,
74+
errors: [{
75+
message: ERROR_MESSAGE,
76+
type: 'Identifier'
77+
}]
78+
},
79+
{
80+
code: [
81+
'var Foo = React.createClass({',
82+
' propTypes: Bar["propTypes"],',
83+
' render: function() {',
84+
' return <Foo className="bar" />;',
85+
' }',
86+
'});'
87+
].join('\n'),
88+
parserOptions: parserOptions,
89+
errors: [{
90+
message: ERROR_MESSAGE,
91+
type: 'Literal'
92+
}]
93+
},
94+
{
95+
code: [
96+
'var { propTypes } = SomeComponent',
97+
'var Foo = React.createClass({',
98+
' propTypes,',
99+
' render: function() {',
100+
' return <Foo className="bar" />;',
101+
' }',
102+
'});'
103+
].join('\n'),
104+
parserOptions: parserOptions,
105+
errors: [{
106+
message: ERROR_MESSAGE,
107+
type: 'Property'
108+
}]
109+
},
110+
{
111+
code: [
112+
'var { propTypes: things, ...foo } = SomeComponent',
113+
'var Foo = React.createClass({',
114+
' propTypes,',
115+
' render: function() {',
116+
' return <Foo className="bar" />;',
117+
' }',
118+
'});'
119+
].join('\n'),
120+
parser: 'babel-eslint',
121+
errors: [{
122+
message: ERROR_MESSAGE,
123+
type: 'Property'
124+
}]
125+
},
126+
{
127+
code: [
128+
'class MyComponent extends React.Component {',
129+
' static fooBar = {',
130+
' baz: Qux.propTypes.baz',
131+
' };',
132+
'}'
133+
].join('\n'),
134+
parser: 'babel-eslint',
135+
errors: [{
136+
message: ERROR_MESSAGE,
137+
type: 'Identifier'
138+
}]
139+
},
140+
{
141+
code: [
142+
'var { propTypes: typesOfProps } = SomeComponent',
143+
'var Foo = React.createClass({',
144+
' propTypes: typesOfProps,',
145+
' render: function() {',
146+
' return <Foo className="bar" />;',
147+
' }',
148+
'});'
149+
].join('\n'),
150+
parserOptions: parserOptions,
151+
errors: [{
152+
message: ERROR_MESSAGE,
153+
type: 'Property'
154+
}]
155+
}]
156+
});

0 commit comments

Comments
 (0)