From c5acc31a5537f867769271b55ce01eb791ee17db Mon Sep 17 00:00:00 2001 From: vedadeepta Date: Sat, 28 Aug 2021 00:59:40 +0530 Subject: [PATCH] [Fix]: detect imports for generic types --- lib/util/propTypes.js | 47 ++++++++++++++++++++---- tests/lib/rules/prop-types.js | 69 +++++++++++++++++++++++++++++++++++ 2 files changed, 108 insertions(+), 8 deletions(-) diff --git a/lib/util/propTypes.js b/lib/util/propTypes.js index adc723165f..fd2f04b5ca 100644 --- a/lib/util/propTypes.js +++ b/lib/util/propTypes.js @@ -100,7 +100,8 @@ module.exports = function propTypesInstructions(context, components, utils) { const defaults = {customValidators: []}; const configuration = Object.assign({}, defaults, context.options[0] || {}); const customValidators = configuration.customValidators; - const allowedGenericTypes = ['SFC', 'StatelessComponent', 'FunctionComponent', 'FC']; + const allowedGenericTypes = new Set(['SFC', 'StatelessComponent', 'FunctionComponent', 'FC']); + const genericReactTypesImport = new Set(); /** * Returns the full scope. @@ -548,7 +549,7 @@ module.exports = function propTypesInstructions(context, components, utils) { let typeName; if (astUtil.isTSTypeReference(node)) { typeName = node.typeName.name; - const shouldTraverseTypeParams = !typeName || allowedGenericTypes.indexOf(typeName) !== -1; + const shouldTraverseTypeParams = !typeName || genericReactTypesImport.has(typeName); if (shouldTraverseTypeParams && node.typeParameters && node.typeParameters.length !== 0) { const nextNode = node.typeParameters.params[0]; this.visitTSNode(nextNode); @@ -941,13 +942,20 @@ module.exports = function propTypesInstructions(context, components, utils) { return; } - // Accounts for FC or React.FC - const typeName = annotation.typeName.name || annotation.typeName.right.name; - - if (allowedGenericTypes.indexOf(typeName) === -1) { - return; + if (annotation.typeName.name) { // if FC + const typeName = annotation.typeName.name; + if (!genericReactTypesImport.has(typeName)) return; + } else if (annotation.typeName.right.name) { // if React.FC + const right = annotation.typeName.right.name; + const left = annotation.typeName.left.name; + + if (!( + genericReactTypesImport.has(left) + && allowedGenericTypes.has(right) + )) { + return; + } } - markPropTypesAsDeclared(node, resolveTypeAnnotation(siblingIdentifier)); } } @@ -1038,6 +1046,29 @@ module.exports = function propTypesInstructions(context, components, utils) { } }, + ImportDeclaration(node) { + // parse `import ... from 'react` + if (node.source.value === 'react') { + node.specifiers.forEach((specifier) => { + if ( + // handles import * as X from 'react' + specifier.type === 'ImportNamespaceSpecifier' + // handles import React from 'react' + || specifier.type === 'ImportDefaultSpecifier' + ) { + genericReactTypesImport.add(specifier.local.name); + } + + // handles import { FC } from 'react' or import { FC as X } from 'react' + if (specifier.type === 'ImportSpecifier') { + if (allowedGenericTypes.has(specifier.imported.name)) { + genericReactTypesImport.add(specifier.local.name); + } + } + }); + } + }, + FunctionDeclaration: markAnnotatedFunctionArgumentsAsDeclared, ArrowFunctionExpression: markAnnotatedFunctionArgumentsAsDeclared, diff --git a/tests/lib/rules/prop-types.js b/tests/lib/rules/prop-types.js index 369fee1a18..819b839c6e 100644 --- a/tests/lib/rules/prop-types.js +++ b/tests/lib/rules/prop-types.js @@ -3150,6 +3150,8 @@ ruleTester.run('prop-types', rule, { }, { code: ` + import React from 'react'; + interface PersonProps { username: string; } @@ -3161,6 +3163,8 @@ ruleTester.run('prop-types', rule, { }, { code: ` + import React from 'react'; + const Person: React.FunctionComponent = (props): React.ReactElement => (
{props.username}
); @@ -3169,6 +3173,8 @@ ruleTester.run('prop-types', rule, { }, { code: ` + import React from 'react'; + interface PersonProps { username: string; } @@ -3180,6 +3186,7 @@ ruleTester.run('prop-types', rule, { }, { code: ` + import React from 'react'; const Person: React.FunctionComponent<{ username: string }> = (props): React.ReactElement => (
{props.username}
); @@ -3188,6 +3195,7 @@ ruleTester.run('prop-types', rule, { }, { code: ` + import React from 'react'; type PersonProps = { username: string; } @@ -3199,6 +3207,8 @@ ruleTester.run('prop-types', rule, { }, { code: ` + import { FunctionComponent } from 'react'; + type PersonProps = { username: string; } @@ -3210,6 +3220,7 @@ ruleTester.run('prop-types', rule, { }, { code: ` + import { FC } from 'react'; type PersonProps = { username: string; } @@ -3219,6 +3230,30 @@ ruleTester.run('prop-types', rule, { `, parser: parsers['@TYPESCRIPT_ESLINT'] }, + { + code: ` + import { FC as X } from 'react'; + interface PersonProps { + username: string; + } + const Person: X = (props): React.ReactElement => ( +
{props.username}
+ ); + `, + parser: parsers['@TYPESCRIPT_ESLINT'] + }, + { + code: ` + import * as X from 'react'; + interface PersonProps { + username: string; + } + const Person: X.FC = (props): React.ReactElement => ( +
{props.username}
+ ); + `, + parser: parsers['@TYPESCRIPT_ESLINT'] + }, { // issue: https://github.com/yannickcr/eslint-plugin-react/issues/2786 code: ` @@ -6761,6 +6796,40 @@ ruleTester.run('prop-types', rule, { } ], parser: parsers['@TYPESCRIPT_ESLINT'] + }, + { + code: ` + interface PersonProps { + username: string; + } + const Person: X.FC = (props): React.ReactElement => ( +
{props.username}
+ ); + `, + errors: [ + { + messageId: 'missingPropType', + data: {name: 'username'} + } + ], + parser: parsers['@TYPESCRIPT_ESLINT'] + }, + { + code: ` + interface PersonProps { + username: string; + } + const Person: FC = (props): React.ReactElement => ( +
{props.username}
+ ); + `, + errors: [ + { + messageId: 'missingPropType', + data: {name: 'username'} + } + ], + parser: parsers['@TYPESCRIPT_ESLINT'] } ]) )