Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Fix] forbid-prop-types: Ignore objects that are not of type React.PropTypes #3326

Merged
merged 1 commit into from Aug 9, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Expand Up @@ -19,6 +19,7 @@ This change log adheres to standards from [Keep a CHANGELOG](https://keepachange
* [`display-name`]: fix identifying `_` as a capital letter ([#3335][] @apbarrero)
* [`require-default-props`]: avoid a crash when function has no props param ([#3350][] @noahnu)
* [`display-name`], component detection: fix HOF returning null as Components ([#3347][] @jxm-math)
* [`forbid-prop-types`]: Ignore objects that are not of type React.PropTypes ([#3326][] @TildaDares)

### Changed
* [Refactor] [`jsx-indent-props`]: improved readability of the checkNodesIndent function ([#3315][] @caroline223)
Expand All @@ -40,6 +41,7 @@ This change log adheres to standards from [Keep a CHANGELOG](https://keepachange
[#3331]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3331
[#3328]: https://github.com/jsx-eslint/eslint-plugin-react/issues/3328
[#3327]: https://github.com/jsx-eslint/eslint-plugin-react/issues/3327
[#3326]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3326
[#3321]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3321
[#3320]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3320
[#3317]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3317
Expand Down
64 changes: 64 additions & 0 deletions lib/rules/forbid-prop-types.js
Expand Up @@ -60,6 +60,27 @@ module.exports = {
const configuration = context.options[0] || {};
const checkContextTypes = configuration.checkContextTypes || false;
const checkChildContextTypes = configuration.checkChildContextTypes || false;
let propTypesPackageName = null;
let reactPackageName = null;
let isForeignPropTypesPackage = false;

function isPropTypesPackage(node) {
return (
node.type === 'Identifier'
&& (
node.name === null
|| node.name === propTypesPackageName
|| !isForeignPropTypesPackage
)
) || (
node.type === 'MemberExpression'
&& (
node.object.name === null
|| node.object.name === reactPackageName
|| !isForeignPropTypesPackage
)
);
}

function isForbidden(type) {
const forbid = configuration.forbid || DEFAULTS;
Expand Down Expand Up @@ -113,12 +134,18 @@ module.exports = {
value = value.object;
}
if (value.type === 'CallExpression') {
if (!isPropTypesPackage(value.callee)) {
return;
}
value.arguments.forEach((arg) => {
const name = arg.type === 'MemberExpression' ? arg.property.name : arg.name;
reportIfForbidden(name, declaration, name);
});
value = value.callee;
}
if (!isPropTypesPackage(value)) {
return;
}
if (value.property) {
target = value.property.name;
} else if (value.type === 'Identifier') {
Expand Down Expand Up @@ -157,9 +184,35 @@ module.exports = {
}

return {
ImportDeclaration(node) {
if (node.source && node.source.value === 'prop-types') { // import PropType from "prop-types"
if (node.specifiers.length > 0) {
propTypesPackageName = node.specifiers[0].local.name;
}
} else if (node.source && node.source.value === 'react') { // import { PropTypes } from "react"
if (node.specifiers.length > 0) {
reactPackageName = node.specifiers[0].local.name; // guard against accidental anonymous `import "react"`
}
if (node.specifiers.length >= 1) {
const propTypesSpecifier = node.specifiers.find((specifier) => (
specifier.imported && specifier.imported.name === 'PropTypes'
));
if (propTypesSpecifier) {
propTypesPackageName = propTypesSpecifier.local.name;
}
}
} else { // package is not imported from "react" or "prop-types"
// eslint-disable-next-line no-lonely-if
if (node.specifiers.some((x) => x.local.name === 'PropTypes')) { // assert: node.specifiers.length > 1
isForeignPropTypesPackage = true;
}
}
},

'ClassProperty, PropertyDefinition'(node) {
if (
!propsUtil.isPropTypesDeclaration(node)
&& !isPropTypesPackage(node)
&& !shouldCheckContextTypes(node)
&& !shouldCheckChildContextTypes(node)
) {
Expand All @@ -171,6 +224,7 @@ module.exports = {
MemberExpression(node) {
if (
!propsUtil.isPropTypesDeclaration(node)
&& !isPropTypesPackage(node)
&& !shouldCheckContextTypes(node)
&& !shouldCheckChildContextTypes(node)
) {
Expand All @@ -181,6 +235,14 @@ module.exports = {
},

CallExpression(node) {
if (
node.callee.object
&& !isPropTypesPackage(node.callee.object)
&& !propsUtil.isPropTypesDeclaration(node.callee)
) {
return;
}

if (
node.arguments.length > 0
&& (node.callee.name === 'shape' || astUtil.getPropertyName(node.callee) === 'shape')
Expand All @@ -192,6 +254,7 @@ module.exports = {
MethodDefinition(node) {
if (
!propsUtil.isPropTypesDeclaration(node)
&& !isPropTypesPackage(node)
&& !shouldCheckContextTypes(node)
&& !shouldCheckChildContextTypes(node)
) {
Expand All @@ -213,6 +276,7 @@ module.exports = {

if (
!propsUtil.isPropTypesDeclaration(property)
&& !isPropTypesPackage(property)
&& !shouldCheckContextTypes(property)
&& !shouldCheckChildContextTypes(property)
) {
Expand Down
148 changes: 142 additions & 6 deletions tests/lib/rules/forbid-prop-types.js
Expand Up @@ -627,6 +627,91 @@ ruleTester.run('forbid-prop-types', rule, {
bar: PropTypes.shape(Foo),
};
`,
},
{
code: `
import yup from "yup"
const formValidation = Yup.object().shape({
name: Yup.string(),
customer_ids: Yup.array()
});
`,
},
{
code: `
import yup from "Yup"
const validation = yup.object().shape({
address: yup.object({
city: yup.string(),
zip: yup.string(),
})
})
`,
options: [
{
forbid: ['string', 'object'],
},
],
},
{
code: `
import yup from "yup"
Yup.array(
Yup.object().shape({
value: Yup.number()
})
)
`,
options: [{ forbid: ['number'] }],
},
{
code: `
import CustomPropTypes from "prop-types";
class Component extends React.Component {};
Component.propTypes = {
a: CustomPropTypes.shape({
b: CustomPropTypes.String,
c: CustomPropTypes.number.isRequired,
})
}
`,
},
{
code: `
import CustomReact from "react"
class Component extends React.Component {};
Component.propTypes = {
b: CustomReact.PropTypes.string,
}
`,
},
{
code: `
import PropTypes from "yup"
class Component extends React.Component {};
Component.propTypes = {
b: PropTypes.array(),
}
`,
},
{
code: `
import { PropTypes, shape, any } from "yup"
class Component extends React.Component {};
Component.propTypes = {
b: PropTypes.any,
}
`,
options: [{ forbid: ['any'] }],
},
{
code: `
import { PropTypes } from "not-react"
class Component extends React.Component {};
Component.propTypes = {
b: PropTypes.array(),
}
`,
}
)),

Expand Down Expand Up @@ -1724,17 +1809,17 @@ ruleTester.run('forbid-prop-types', rule, {
import React from './React';

import { arrayOf, object } from 'prop-types';

const App = ({ foo }) => (
<div>
Hello world {foo}
</div>
);

App.propTypes = {
foo: arrayOf(object)
}

export default App;
`,
errors: [
Expand All @@ -1749,17 +1834,17 @@ ruleTester.run('forbid-prop-types', rule, {
import React from './React';

import PropTypes, { arrayOf } from 'prop-types';

const App = ({ foo }) => (
<div>
Hello world {foo}
</div>
);

App.propTypes = {
foo: arrayOf(PropTypes.object)
}

export default App;
`,
errors: [
Expand All @@ -1769,5 +1854,56 @@ ruleTester.run('forbid-prop-types', rule, {
},
],
},
{
code: `
import CustomPropTypes from "prop-types";
class Component extends React.Component {};
Component.propTypes = {
a: CustomPropTypes.shape({
b: CustomPropTypes.String,
c: CustomPropTypes.object.isRequired,
})
}
`,
errors: [
{
messageId: 'forbiddenPropType',
data: { target: 'object' },
},
],
},
{
code: `
import { PropTypes as CustomPropTypes } from "react";
class Component extends React.Component {};
Component.propTypes = {
a: CustomPropTypes.shape({
b: CustomPropTypes.String,
c: CustomPropTypes.object.isRequired,
})
}
`,
errors: [
{
messageId: 'forbiddenPropType',
data: { target: 'object' },
},
],
},
{
code: `
import CustomReact from "react"
class Component extends React.Component {};
Component.propTypes = {
b: CustomReact.PropTypes.object,
}
`,
errors: [
{
messageId: 'forbiddenPropType',
data: { target: 'object' },
},
],
},
]),
});