Skip to content

Commit 5a55e16

Browse files
authored
Merge pull request #1272 from dustinsoftware/issue-1266
Add more support for wrapped propTypes
2 parents 1c15d26 + 412f44c commit 5a55e16

14 files changed

+278
-22
lines changed

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,8 @@ You can also specify some settings that will be shared across all the plugin rul
4040
"createClass": "createReactClass", // Regex for Component Factory to use, default to "createReactClass"
4141
"pragma": "React", // Pragma to use, default to "React"
4242
"version": "15.0" // React version, default to the latest React stable release
43-
}
43+
},
44+
"propWrapperFunctions": [ "forbidExtraProps" ] // The names of any functions used to wrap the propTypes object, such as `forbidExtraProps`. If this isn't set, any propTypes wrapped in a function will be skipped.
4445
}
4546
}
4647
```

docs/rules/no-unused-prop-types.md

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,14 +47,13 @@ This rule can take one argument to ignore some specific props during validation.
4747

4848
```js
4949
...
50-
"react/no-unused-prop-types": [<enabled>, { customValidators: <customValidator>, skipShapeProps: <skipShapeProps>, propWrapperFunctions: <propWrapperFunctions> }]
50+
"react/no-unused-prop-types": [<enabled>, { customValidators: <customValidator>, skipShapeProps: <skipShapeProps> }]
5151
...
5252
```
5353

5454
* `enabled`: for enabling the rule. 0=off, 1=warn, 2=error. Defaults to 0.
5555
* `customValidators`: optional array of validators used for propTypes validation.
5656
* `skipShapeProps`: In some cases it is impossible to accurately detect whether or not a `PropTypes.shape`'s values are being used. Setting this option to `true` will skip validation of `PropTypes.shape` (`true` by default).
57-
* `propWrapperFunctions`: The names of any functions used to wrap the propTypes object, such as `forbidExtraProps`. If this isn't set, any propTypes wrapped in a function will be skipped.
5857

5958
## Caveats
6059

lib/rules/default-props-match-prop-types.js

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ module.exports = {
3636
create: Components.detect(function(context, components, utils) {
3737
const configuration = context.options[0] || {};
3838
const allowRequiredDefaults = configuration.allowRequiredDefaults || false;
39+
var propWrapperFunctions = new Set(context.settings.propWrapperFunctions || []);
3940

4041
/**
4142
* Get properties name
@@ -113,7 +114,13 @@ module.exports = {
113114
if (node.type === 'Identifier') {
114115
return findVariableByName(node.name);
115116
}
116-
117+
if (
118+
node.type === 'CallExpression' &&
119+
propWrapperFunctions.has(node.callee.name) &&
120+
node.arguments && node.arguments[0]
121+
) {
122+
return node.arguments[0];
123+
}
117124
return node;
118125
}
119126

lib/rules/forbid-prop-types.js

Lines changed: 37 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@ module.exports = {
3636
},
3737

3838
create: function(context) {
39+
var propWrapperFunctions = new Set(context.settings.propWrapperFunctions || []);
40+
3941
function isForbidden(type) {
4042
var configuration = context.options[0] || {};
4143

@@ -108,17 +110,46 @@ module.exports = {
108110

109111
return {
110112
ClassProperty: function(node) {
111-
if (isPropTypesDeclaration(node) && node.value && node.value.type === 'ObjectExpression') {
112-
checkForbidden(node.value.properties);
113+
if (!isPropTypesDeclaration(node)) {
114+
return;
115+
}
116+
switch (node.value && node.value.type) {
117+
case 'ObjectExpression':
118+
checkForbidden(node.value.properties);
119+
break;
120+
case 'CallExpression':
121+
if (
122+
propWrapperFunctions.has(node.value.callee.name) &&
123+
node.value.arguments && node.value.arguments[0]
124+
) {
125+
checkForbidden(node.value.arguments[0].properties);
126+
}
127+
break;
128+
default:
129+
break;
113130
}
114131
},
115132

116133
MemberExpression: function(node) {
117-
if (isPropTypesDeclaration(node.property)) {
118-
var right = node.parent.right;
119-
if (right && right.type === 'ObjectExpression') {
134+
if (!isPropTypesDeclaration(node.property)) {
135+
return;
136+
}
137+
138+
var right = node.parent.right;
139+
switch (right && right.type) {
140+
case 'ObjectExpression':
120141
checkForbidden(right.properties);
121-
}
142+
break;
143+
case 'CallExpression':
144+
if (
145+
propWrapperFunctions.has(right.callee.name) &&
146+
right.arguments && right.arguments[0]
147+
) {
148+
checkForbidden(right.arguments[0].properties);
149+
}
150+
break;
151+
default:
152+
break;
122153
}
123154
},
124155

lib/rules/no-unused-prop-types.js

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -44,13 +44,6 @@ module.exports = {
4444
},
4545
skipShapeProps: {
4646
type: 'boolean'
47-
},
48-
propWrapperFunctions: {
49-
type: 'array',
50-
items: {
51-
type: 'string'
52-
},
53-
uniqueItems: true
5447
}
5548
},
5649
additionalProperties: false
@@ -63,7 +56,7 @@ module.exports = {
6356
var configuration = Object.assign({}, defaults, context.options[0] || {});
6457
var skipShapeProps = configuration.skipShapeProps;
6558
var customValidators = configuration.customValidators || [];
66-
var propWrapperFunctions = new Set(configuration.propWrapperFunctions || []);
59+
var propWrapperFunctions = new Set(context.settings.propWrapperFunctions || []);
6760

6861
// Used to track the type annotations in scope.
6962
// Necessary because babel's scopes do not track type annotations.

lib/rules/prop-types.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ module.exports = {
5757
create: Components.detect(function(context, components, utils) {
5858
var sourceCode = context.getSourceCode();
5959
var configuration = context.options[0] || {};
60+
var propWrapperFunctions = new Set(context.settings.propWrapperFunctions || []);
6061
var ignored = configuration.ignore || [];
6162
var customValidators = configuration.customValidators || [];
6263
var skipUndeclared = configuration.skipUndeclared || false;
@@ -769,6 +770,15 @@ module.exports = {
769770
}
770771
ignorePropsValidation = true;
771772
break;
773+
case 'CallExpression':
774+
if (
775+
propWrapperFunctions.has(propTypes.callee.name) &&
776+
propTypes.arguments && propTypes.arguments[0]
777+
) {
778+
markPropTypesAsDeclared(node, propTypes.arguments[0]);
779+
return;
780+
}
781+
break;
772782
case null:
773783
break;
774784
default:

lib/rules/require-default-props.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ module.exports = {
2727

2828
create: Components.detect(function(context, components, utils) {
2929
var sourceCode = context.getSourceCode();
30+
var propWrapperFunctions = new Set(context.settings.propWrapperFunctions || []);
3031

3132
/**
3233
* Get properties name
@@ -105,6 +106,13 @@ module.exports = {
105106
if (node.type === 'Identifier') {
106107
return findVariableByName(node.name);
107108
}
109+
if (
110+
node.type === 'CallExpression' &&
111+
propWrapperFunctions.has(node.callee.name) &&
112+
node.arguments && node.arguments[0]
113+
) {
114+
return node.arguments[0];
115+
}
108116

109117
return node;
110118
}

lib/rules/sort-prop-types.js

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ module.exports = {
4040
var requiredFirst = configuration.requiredFirst || false;
4141
var callbacksLast = configuration.callbacksLast || false;
4242
var ignoreCase = configuration.ignoreCase || false;
43+
var propWrapperFunctions = new Set(context.settings.propWrapperFunctions || []);
4344

4445
/**
4546
* Checks if node is `propTypes` declaration
@@ -144,8 +145,23 @@ module.exports = {
144145

145146
return {
146147
ClassProperty: function(node) {
147-
if (isPropTypesDeclaration(node) && node.value && node.value.type === 'ObjectExpression') {
148-
checkSorted(node.value.properties);
148+
if (!isPropTypesDeclaration(node)) {
149+
return;
150+
}
151+
switch (node.value && node.value.type) {
152+
case 'ObjectExpression':
153+
checkSorted(node.value.properties);
154+
break;
155+
case 'CallExpression':
156+
if (
157+
propWrapperFunctions.has(node.value.callee.name) &&
158+
node.value.arguments && node.value.arguments[0]
159+
) {
160+
checkSorted(node.value.arguments[0].properties);
161+
}
162+
break;
163+
default:
164+
break;
149165
}
150166
},
151167

@@ -156,6 +172,14 @@ module.exports = {
156172
var right = node.parent.right;
157173
var declarations;
158174
switch (right && right.type) {
175+
case 'CallExpression':
176+
if (
177+
propWrapperFunctions.has(right.callee.name) &&
178+
right.arguments && right.arguments[0]
179+
) {
180+
declarations = right.arguments[0].properties;
181+
}
182+
break;
159183
case 'ObjectExpression':
160184
declarations = right.properties;
161185
break;

tests/lib/rules/default-props-match-prop-types.js

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -761,6 +761,28 @@ ruleTester.run('default-props-match-prop-types', rule, {
761761
column: 3
762762
}]
763763
},
764+
{
765+
code: [
766+
'function MyStatelessComponent({ foo, bar }) {',
767+
' return <div>{foo}{bar}</div>;',
768+
'}',
769+
'MyStatelessComponent.propTypes = forbidExtraProps({',
770+
' foo: React.PropTypes.string,',
771+
' bar: React.PropTypes.string.isRequired',
772+
'})',
773+
'MyStatelessComponent.defaultProps = {',
774+
' baz: "baz"',
775+
'};'
776+
].join('\n'),
777+
settings: {
778+
propWrapperFunctions: ['forbidExtraProps']
779+
},
780+
errors: [{
781+
message: 'defaultProp "baz" has no corresponding propTypes declaration.',
782+
line: 9,
783+
column: 3
784+
}]
785+
},
764786
{
765787
code: [
766788
'function MyStatelessComponent({ foo, bar }) {',
@@ -1219,6 +1241,25 @@ ruleTester.run('default-props-match-prop-types', rule, {
12191241
column: 5
12201242
}]
12211243
},
1244+
{
1245+
code: [
1246+
'function MyStatelessComponent({ foo, bar }) {',
1247+
' return <div>{foo}{bar}</div>;',
1248+
'}',
1249+
'MyStatelessComponent.propTypes = {',
1250+
' foo: React.PropTypes.string,',
1251+
' bar: React.PropTypes.string.isRequired',
1252+
'};',
1253+
'MyStatelessComponent.defaultProps = {',
1254+
' baz: "baz"',
1255+
'};'
1256+
].join('\n'),
1257+
errors: [{
1258+
message: 'defaultProp "baz" has no corresponding propTypes declaration.',
1259+
line: 9,
1260+
column: 3
1261+
}]
1262+
},
12221263
{
12231264
code: [
12241265
'class Greeting extends React.Component {',

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

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -383,6 +383,21 @@ ruleTester.run('forbid-prop-types', rule, {
383383
'};'
384384
].join('\n'),
385385
errors: 4
386+
}, {
387+
code: [
388+
'class First extends React.Component {',
389+
' render() {',
390+
' return <div />;',
391+
' }',
392+
'}',
393+
'First.propTypes = forbidExtraProps({',
394+
' a: PropTypes.array',
395+
'});'
396+
].join('\n'),
397+
errors: 1,
398+
settings: {
399+
propWrapperFunctions: ['forbidExtraProps']
400+
}
386401
}, {
387402
code: [
388403
'class Component extends React.Component {',
@@ -397,6 +412,23 @@ ruleTester.run('forbid-prop-types', rule, {
397412
].join('\n'),
398413
parser: 'babel-eslint',
399414
errors: 2
415+
}, {
416+
code: [
417+
'class Component extends React.Component {',
418+
' static propTypes = forbidExtraProps({',
419+
' a: PropTypes.array,',
420+
' o: PropTypes.object',
421+
' });',
422+
' render() {',
423+
' return <div />;',
424+
' }',
425+
'}'
426+
].join('\n'),
427+
parser: 'babel-eslint',
428+
errors: 2,
429+
settings: {
430+
propWrapperFunctions: ['forbidExtraProps']
431+
}
400432
}, {
401433
code: [
402434
'var Hello = createReactClass({',

tests/lib/rules/no-unused-prop-types.js

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2798,7 +2798,9 @@ ruleTester.run('no-unused-prop-types', rule, {
27982798
line: 10,
27992799
column: 8
28002800
}],
2801-
options: [{propWrapperFunctions: ['forbidExtraProps']}]
2801+
settings: {
2802+
propWrapperFunctions: ['forbidExtraProps']
2803+
}
28022804
}, {
28032805
code: [
28042806
'class Hello extends Component {',
@@ -2819,7 +2821,9 @@ ruleTester.run('no-unused-prop-types', rule, {
28192821
line: 4,
28202822
column: 10
28212823
}],
2822-
options: [{propWrapperFunctions: ['forbidExtraProps']}]
2824+
settings: {
2825+
propWrapperFunctions: ['forbidExtraProps']
2826+
}
28232827
}
28242828
/* , {
28252829
// Enable this when the following issue is fixed

0 commit comments

Comments
 (0)