Skip to content

Commit 6b330b8

Browse files
TildaDaresljharb
authored andcommitted
[Fix] forbid-prop-types: Ignore objects that are not of type React.PropTypes
1 parent c28ae9e commit 6b330b8

File tree

3 files changed

+208
-6
lines changed

3 files changed

+208
-6
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ This change log adheres to standards from [Keep a CHANGELOG](https://keepachange
1919
* [`display-name`]: fix identifying `_` as a capital letter ([#3335][] @apbarrero)
2020
* [`require-default-props`]: avoid a crash when function has no props param ([#3350][] @noahnu)
2121
* [`display-name`], component detection: fix HOF returning null as Components ([#3347][] @jxm-math)
22+
* [`forbid-prop-types`]: Ignore objects that are not of type React.PropTypes ([#3326][] @TildaDares)
2223

2324
### Changed
2425
* [Refactor] [`jsx-indent-props`]: improved readability of the checkNodesIndent function ([#3315][] @caroline223)
@@ -40,6 +41,7 @@ This change log adheres to standards from [Keep a CHANGELOG](https://keepachange
4041
[#3331]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3331
4142
[#3328]: https://github.com/jsx-eslint/eslint-plugin-react/issues/3328
4243
[#3327]: https://github.com/jsx-eslint/eslint-plugin-react/issues/3327
44+
[#3326]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3326
4345
[#3321]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3321
4446
[#3320]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3320
4547
[#3317]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3317

lib/rules/forbid-prop-types.js

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,27 @@ module.exports = {
6060
const configuration = context.options[0] || {};
6161
const checkContextTypes = configuration.checkContextTypes || false;
6262
const checkChildContextTypes = configuration.checkChildContextTypes || false;
63+
let propTypesPackageName = null;
64+
let reactPackageName = null;
65+
let isForeignPropTypesPackage = false;
66+
67+
function isPropTypesPackage(node) {
68+
return (
69+
node.type === 'Identifier'
70+
&& (
71+
node.name === null
72+
|| node.name === propTypesPackageName
73+
|| !isForeignPropTypesPackage
74+
)
75+
) || (
76+
node.type === 'MemberExpression'
77+
&& (
78+
node.object.name === null
79+
|| node.object.name === reactPackageName
80+
|| !isForeignPropTypesPackage
81+
)
82+
);
83+
}
6384

6485
function isForbidden(type) {
6586
const forbid = configuration.forbid || DEFAULTS;
@@ -113,12 +134,18 @@ module.exports = {
113134
value = value.object;
114135
}
115136
if (value.type === 'CallExpression') {
137+
if (!isPropTypesPackage(value.callee)) {
138+
return;
139+
}
116140
value.arguments.forEach((arg) => {
117141
const name = arg.type === 'MemberExpression' ? arg.property.name : arg.name;
118142
reportIfForbidden(name, declaration, name);
119143
});
120144
value = value.callee;
121145
}
146+
if (!isPropTypesPackage(value)) {
147+
return;
148+
}
122149
if (value.property) {
123150
target = value.property.name;
124151
} else if (value.type === 'Identifier') {
@@ -157,9 +184,35 @@ module.exports = {
157184
}
158185

159186
return {
187+
ImportDeclaration(node) {
188+
if (node.source && node.source.value === 'prop-types') { // import PropType from "prop-types"
189+
if (node.specifiers.length > 0) {
190+
propTypesPackageName = node.specifiers[0].local.name;
191+
}
192+
} else if (node.source && node.source.value === 'react') { // import { PropTypes } from "react"
193+
if (node.specifiers.length > 0) {
194+
reactPackageName = node.specifiers[0].local.name; // guard against accidental anonymous `import "react"`
195+
}
196+
if (node.specifiers.length >= 1) {
197+
const propTypesSpecifier = node.specifiers.find((specifier) => (
198+
specifier.imported && specifier.imported.name === 'PropTypes'
199+
));
200+
if (propTypesSpecifier) {
201+
propTypesPackageName = propTypesSpecifier.local.name;
202+
}
203+
}
204+
} else { // package is not imported from "react" or "prop-types"
205+
// eslint-disable-next-line no-lonely-if
206+
if (node.specifiers.some((x) => x.local.name === 'PropTypes')) { // assert: node.specifiers.length > 1
207+
isForeignPropTypesPackage = true;
208+
}
209+
}
210+
},
211+
160212
'ClassProperty, PropertyDefinition'(node) {
161213
if (
162214
!propsUtil.isPropTypesDeclaration(node)
215+
&& !isPropTypesPackage(node)
163216
&& !shouldCheckContextTypes(node)
164217
&& !shouldCheckChildContextTypes(node)
165218
) {
@@ -171,6 +224,7 @@ module.exports = {
171224
MemberExpression(node) {
172225
if (
173226
!propsUtil.isPropTypesDeclaration(node)
227+
&& !isPropTypesPackage(node)
174228
&& !shouldCheckContextTypes(node)
175229
&& !shouldCheckChildContextTypes(node)
176230
) {
@@ -181,6 +235,14 @@ module.exports = {
181235
},
182236

183237
CallExpression(node) {
238+
if (
239+
node.callee.object
240+
&& !isPropTypesPackage(node.callee.object)
241+
&& !propsUtil.isPropTypesDeclaration(node.callee)
242+
) {
243+
return;
244+
}
245+
184246
if (
185247
node.arguments.length > 0
186248
&& (node.callee.name === 'shape' || astUtil.getPropertyName(node.callee) === 'shape')
@@ -192,6 +254,7 @@ module.exports = {
192254
MethodDefinition(node) {
193255
if (
194256
!propsUtil.isPropTypesDeclaration(node)
257+
&& !isPropTypesPackage(node)
195258
&& !shouldCheckContextTypes(node)
196259
&& !shouldCheckChildContextTypes(node)
197260
) {
@@ -213,6 +276,7 @@ module.exports = {
213276

214277
if (
215278
!propsUtil.isPropTypesDeclaration(property)
279+
&& !isPropTypesPackage(property)
216280
&& !shouldCheckContextTypes(property)
217281
&& !shouldCheckChildContextTypes(property)
218282
) {

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

Lines changed: 142 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -627,6 +627,91 @@ ruleTester.run('forbid-prop-types', rule, {
627627
bar: PropTypes.shape(Foo),
628628
};
629629
`,
630+
},
631+
{
632+
code: `
633+
import yup from "yup"
634+
const formValidation = Yup.object().shape({
635+
name: Yup.string(),
636+
customer_ids: Yup.array()
637+
});
638+
`,
639+
},
640+
{
641+
code: `
642+
import yup from "Yup"
643+
const validation = yup.object().shape({
644+
address: yup.object({
645+
city: yup.string(),
646+
zip: yup.string(),
647+
})
648+
})
649+
`,
650+
options: [
651+
{
652+
forbid: ['string', 'object'],
653+
},
654+
],
655+
},
656+
{
657+
code: `
658+
import yup from "yup"
659+
Yup.array(
660+
Yup.object().shape({
661+
value: Yup.number()
662+
})
663+
)
664+
`,
665+
options: [{ forbid: ['number'] }],
666+
},
667+
{
668+
code: `
669+
import CustomPropTypes from "prop-types";
670+
class Component extends React.Component {};
671+
Component.propTypes = {
672+
a: CustomPropTypes.shape({
673+
b: CustomPropTypes.String,
674+
c: CustomPropTypes.number.isRequired,
675+
})
676+
}
677+
`,
678+
},
679+
{
680+
code: `
681+
import CustomReact from "react"
682+
class Component extends React.Component {};
683+
Component.propTypes = {
684+
b: CustomReact.PropTypes.string,
685+
}
686+
`,
687+
},
688+
{
689+
code: `
690+
import PropTypes from "yup"
691+
class Component extends React.Component {};
692+
Component.propTypes = {
693+
b: PropTypes.array(),
694+
}
695+
`,
696+
},
697+
{
698+
code: `
699+
import { PropTypes, shape, any } from "yup"
700+
class Component extends React.Component {};
701+
Component.propTypes = {
702+
b: PropTypes.any,
703+
}
704+
`,
705+
options: [{ forbid: ['any'] }],
706+
},
707+
{
708+
code: `
709+
import { PropTypes } from "not-react"
710+
class Component extends React.Component {};
711+
Component.propTypes = {
712+
b: PropTypes.array(),
713+
}
714+
`,
630715
}
631716
)),
632717

@@ -1724,17 +1809,17 @@ ruleTester.run('forbid-prop-types', rule, {
17241809
import React from './React';
17251810
17261811
import { arrayOf, object } from 'prop-types';
1727-
1812+
17281813
const App = ({ foo }) => (
17291814
<div>
17301815
Hello world {foo}
17311816
</div>
17321817
);
1733-
1818+
17341819
App.propTypes = {
17351820
foo: arrayOf(object)
17361821
}
1737-
1822+
17381823
export default App;
17391824
`,
17401825
errors: [
@@ -1749,17 +1834,17 @@ ruleTester.run('forbid-prop-types', rule, {
17491834
import React from './React';
17501835
17511836
import PropTypes, { arrayOf } from 'prop-types';
1752-
1837+
17531838
const App = ({ foo }) => (
17541839
<div>
17551840
Hello world {foo}
17561841
</div>
17571842
);
1758-
1843+
17591844
App.propTypes = {
17601845
foo: arrayOf(PropTypes.object)
17611846
}
1762-
1847+
17631848
export default App;
17641849
`,
17651850
errors: [
@@ -1769,5 +1854,56 @@ ruleTester.run('forbid-prop-types', rule, {
17691854
},
17701855
],
17711856
},
1857+
{
1858+
code: `
1859+
import CustomPropTypes from "prop-types";
1860+
class Component extends React.Component {};
1861+
Component.propTypes = {
1862+
a: CustomPropTypes.shape({
1863+
b: CustomPropTypes.String,
1864+
c: CustomPropTypes.object.isRequired,
1865+
})
1866+
}
1867+
`,
1868+
errors: [
1869+
{
1870+
messageId: 'forbiddenPropType',
1871+
data: { target: 'object' },
1872+
},
1873+
],
1874+
},
1875+
{
1876+
code: `
1877+
import { PropTypes as CustomPropTypes } from "react";
1878+
class Component extends React.Component {};
1879+
Component.propTypes = {
1880+
a: CustomPropTypes.shape({
1881+
b: CustomPropTypes.String,
1882+
c: CustomPropTypes.object.isRequired,
1883+
})
1884+
}
1885+
`,
1886+
errors: [
1887+
{
1888+
messageId: 'forbiddenPropType',
1889+
data: { target: 'object' },
1890+
},
1891+
],
1892+
},
1893+
{
1894+
code: `
1895+
import CustomReact from "react"
1896+
class Component extends React.Component {};
1897+
Component.propTypes = {
1898+
b: CustomReact.PropTypes.object,
1899+
}
1900+
`,
1901+
errors: [
1902+
{
1903+
messageId: 'forbiddenPropType',
1904+
data: { target: 'object' },
1905+
},
1906+
],
1907+
},
17721908
]),
17731909
});

0 commit comments

Comments
 (0)