diff --git a/CHANGELOG.md b/CHANGELOG.md index e8db587f69..dfa0a6f6b4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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) @@ -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 diff --git a/lib/rules/forbid-prop-types.js b/lib/rules/forbid-prop-types.js index 0eea6a78d2..f733b9ed0b 100644 --- a/lib/rules/forbid-prop-types.js +++ b/lib/rules/forbid-prop-types.js @@ -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; @@ -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') { @@ -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) ) { @@ -171,6 +224,7 @@ module.exports = { MemberExpression(node) { if ( !propsUtil.isPropTypesDeclaration(node) + && !isPropTypesPackage(node) && !shouldCheckContextTypes(node) && !shouldCheckChildContextTypes(node) ) { @@ -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') @@ -192,6 +254,7 @@ module.exports = { MethodDefinition(node) { if ( !propsUtil.isPropTypesDeclaration(node) + && !isPropTypesPackage(node) && !shouldCheckContextTypes(node) && !shouldCheckChildContextTypes(node) ) { @@ -213,6 +276,7 @@ module.exports = { if ( !propsUtil.isPropTypesDeclaration(property) + && !isPropTypesPackage(property) && !shouldCheckContextTypes(property) && !shouldCheckChildContextTypes(property) ) { diff --git a/tests/lib/rules/forbid-prop-types.js b/tests/lib/rules/forbid-prop-types.js index ee3a635df7..ad7ca97c66 100644 --- a/tests/lib/rules/forbid-prop-types.js +++ b/tests/lib/rules/forbid-prop-types.js @@ -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(), + } + `, } )), @@ -1724,17 +1809,17 @@ ruleTester.run('forbid-prop-types', rule, { import React from './React'; import { arrayOf, object } from 'prop-types'; - + const App = ({ foo }) => (
Hello world {foo}
); - + App.propTypes = { foo: arrayOf(object) } - + export default App; `, errors: [ @@ -1749,17 +1834,17 @@ ruleTester.run('forbid-prop-types', rule, { import React from './React'; import PropTypes, { arrayOf } from 'prop-types'; - + const App = ({ foo }) => (
Hello world {foo}
); - + App.propTypes = { foo: arrayOf(PropTypes.object) } - + export default App; `, errors: [ @@ -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' }, + }, + ], + }, ]), });