Skip to content

Commit 7443a37

Browse files
iansuljharb
authored andcommitted
[New] forbid-foreign-prop-types: Add allowInPropTypes option
Fixes #1647.
1 parent f7441bb commit 7443a37

File tree

2 files changed

+98
-17
lines changed

2 files changed

+98
-17
lines changed

lib/rules/forbid-foreign-prop-types.js

Lines changed: 63 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -6,26 +6,32 @@
66

77
const docsUrl = require('../util/docsUrl');
88

9-
// ------------------------------------------------------------------------------
10-
// Constants
11-
// ------------------------------------------------------------------------------
12-
13-
14-
// ------------------------------------------------------------------------------
15-
// Rule Definition
16-
// ------------------------------------------------------------------------------
17-
189
module.exports = {
1910
meta: {
2011
docs: {
2112
description: 'Forbid using another component\'s propTypes',
2213
category: 'Best Practices',
2314
recommended: false,
2415
url: docsUrl('forbid-foreign-prop-types')
25-
}
16+
},
17+
18+
schema: [
19+
{
20+
type: 'object',
21+
properties: {
22+
allowInPropTypes: {
23+
type: 'boolean'
24+
}
25+
},
26+
additionalProperties: false
27+
}
28+
]
2629
},
2730

2831
create: function(context) {
32+
const config = context.options[0] || {};
33+
const allowInPropTypes = config.allowInPropTypes || false;
34+
2935
// --------------------------------------------------------------------------
3036
// Helpers
3137
// --------------------------------------------------------------------------
@@ -34,15 +40,56 @@ module.exports = {
3440
return node.parent.type === 'AssignmentExpression' && node.parent.left === node;
3541
}
3642

43+
function findParentAssignmentExpression(node) {
44+
let parent = node.parent;
45+
46+
while (parent && parent.type !== 'Program') {
47+
if (parent.type === 'AssignmentExpression') {
48+
return parent;
49+
}
50+
parent = parent.parent;
51+
}
52+
return null;
53+
}
54+
55+
function isAllowedAssignment(node) {
56+
if (!allowInPropTypes) {
57+
return false;
58+
}
59+
60+
const assignmentExpression = findParentAssignmentExpression(node);
61+
62+
if (
63+
assignmentExpression &&
64+
assignmentExpression.left &&
65+
assignmentExpression.left.property &&
66+
assignmentExpression.left.property.name === 'propTypes'
67+
) {
68+
return true;
69+
}
70+
return false;
71+
}
72+
3773
return {
3874
MemberExpression: function(node) {
39-
if (!node.computed && node.property && node.property.type === 'Identifier' &&
40-
node.property.name === 'propTypes' && !isLeftSideOfAssignment(node) ||
41-
node.property && node.property.type === 'Literal' &&
42-
node.property.value === 'propTypes' && !isLeftSideOfAssignment(node)) {
75+
if (
76+
node.property &&
77+
(
78+
!node.computed &&
79+
node.property.type === 'Identifier' &&
80+
node.property.name === 'propTypes' &&
81+
!isLeftSideOfAssignment(node) &&
82+
!isAllowedAssignment(node)
83+
) || (
84+
node.property.type === 'Literal' &&
85+
node.property.value === 'propTypes' &&
86+
!isLeftSideOfAssignment(node) &&
87+
!isAllowedAssignment(node)
88+
)
89+
) {
4390
context.report({
4491
node: node.property,
45-
message: 'Using another component\'s propTypes is forbidden'
92+
message: 'Using propTypes from another component is not safe because they may be removed in production builds'
4693
});
4794
}
4895
},
@@ -53,7 +100,7 @@ module.exports = {
53100
if (propTypesNode) {
54101
context.report({
55102
node: propTypesNode,
56-
message: 'Using another component\'s propTypes is forbidden'
103+
message: 'Using propTypes from another component is not safe because they may be removed in production builds'
57104
});
58105
}
59106
}

tests/lib/rules/forbid-foreign-prop-types.js

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ require('babel-eslint');
2525
// Tests
2626
// -----------------------------------------------------------------------------
2727

28-
const ERROR_MESSAGE = 'Using another component\'s propTypes is forbidden';
28+
const ERROR_MESSAGE = 'Using propTypes from another component is not safe because they may be removed in production builds';
2929

3030
const ruleTester = new RuleTester({parserOptions});
3131
ruleTester.run('forbid-foreign-prop-types', rule, {
@@ -50,6 +50,21 @@ ruleTester.run('forbid-foreign-prop-types', rule, {
5050
code: 'Foo["propTypes"] = propTypes'
5151
}, {
5252
code: 'const propTypes = "bar"; Foo[propTypes];'
53+
},
54+
{
55+
code: `
56+
const Message = (props) => (<div>{props.message}</div>);
57+
Message.propTypes = {
58+
message: PropTypes.string
59+
};
60+
const Hello = (props) => (<Message>Hello {props.name}</Message>);
61+
Hello.propTypes = {
62+
name: Message.propTypes.message
63+
};
64+
`,
65+
options: [{
66+
allowInPropTypes: true
67+
}]
5368
}],
5469

5570
invalid: [{
@@ -139,5 +154,24 @@ ruleTester.run('forbid-foreign-prop-types', rule, {
139154
message: ERROR_MESSAGE,
140155
type: 'Property'
141156
}]
157+
},
158+
{
159+
code: `
160+
const Message = (props) => (<div>{props.message}</div>);
161+
Message.propTypes = {
162+
message: PropTypes.string
163+
};
164+
const Hello = (props) => (<Message>Hello {props.name}</Message>);
165+
Hello.propTypes = {
166+
name: Message.propTypes.message
167+
};
168+
`,
169+
options: [{
170+
allowInPropTypes: false
171+
}],
172+
errors: [{
173+
message: ERROR_MESSAGE,
174+
type: 'Identifier'
175+
}]
142176
}]
143177
});

0 commit comments

Comments
 (0)