diff --git a/lib/util/ast.js b/lib/util/ast.js
index edf4c38237..96402f5422 100644
--- a/lib/util/ast.js
+++ b/lib/util/ast.js
@@ -261,6 +261,12 @@ function isTSTypeQuery(node) {
return nodeType === 'TSTypeQuery';
}
+function isTSTypeParameterInstantiation(node) {
+ if (!node) return false;
+ const nodeType = node.type;
+ return nodeType === 'TSTypeParameterInstantiation';
+}
+
module.exports = {
findReturnStatement,
getFirstNodeInLine,
@@ -283,5 +289,6 @@ module.exports = {
isTSTypeAliasDeclaration,
isTSParenthesizedType,
isTSFunctionType,
- isTSTypeQuery
+ isTSTypeQuery,
+ isTSTypeParameterInstantiation
};
diff --git a/lib/util/propTypes.js b/lib/util/propTypes.js
index 50179dd647..1d32ea84c9 100644
--- a/lib/util/propTypes.js
+++ b/lib/util/propTypes.js
@@ -502,27 +502,20 @@ module.exports = function propTypesInstructions(context, components, utils) {
this.referenceNameMap = new Set();
this.sourceCode = context.getSourceCode();
this.shouldIgnorePropTypes = false;
- this.startWithTSTypeAnnotation();
+ this.visitTSNode(this.propTypes);
this.endAndStructDeclaredPropTypes();
}
- startWithTSTypeAnnotation() {
- if (astUtil.isTSTypeAnnotation(this.propTypes)) {
- const typeAnnotation = this.propTypes.typeAnnotation;
- this.visitTSNode(typeAnnotation);
- } else {
- // weird cases such as TSTypeFunction
- this.shouldIgnorePropTypes = true;
- }
- }
-
/**
* The node will be distribute to different function.
* @param {ASTNode} node
*/
visitTSNode(node) {
if (!node) return;
- if (astUtil.isTSTypeReference(node)) {
+ if (astUtil.isTSTypeAnnotation(node)) {
+ const typeAnnotation = node.typeAnnotation;
+ this.visitTSNode(typeAnnotation);
+ } else if (astUtil.isTSTypeReference(node)) {
this.searchDeclarationByName(node);
} else if (astUtil.isTSInterfaceHeritage(node)) {
this.searchDeclarationByName(node);
@@ -535,12 +528,10 @@ module.exports = function propTypesInstructions(context, components, utils) {
this.convertIntersectionTypeToPropTypes(node);
} else if (astUtil.isTSParenthesizedType(node)) {
const typeAnnotation = node.typeAnnotation;
- if (astUtil.isTSTypeLiteral(typeAnnotation)) {
- // Check node is an object literal
- if (Array.isArray(node.typeAnnotation.members)) {
- this.foundDeclaredPropertiesList = this.foundDeclaredPropertiesList
- .concat(node.typeAnnotation.members);
- }
+ this.visitTSNode(typeAnnotation);
+ } else if (astUtil.isTSTypeParameterInstantiation(node)) {
+ if (Array.isArray(node.params)) {
+ node.params.forEach(this.visitTSNode, this);
}
} else {
this.shouldIgnorePropTypes = true;
@@ -671,6 +662,19 @@ module.exports = function propTypesInstructions(context, components, utils) {
switch (res.type) {
case 'ObjectExpression':
iterateProperties(context, res.properties, (key, value, propNode) => {
+ if (propNode && propNode.argument && propNode.argument.type === 'CallExpression') {
+ if (propNode.argument.typeParameters) {
+ this.visitTSNode(propNode.argument.typeParameters);
+ } else {
+ // Ignore this CallExpression return value since it doesn't have any typeParameters to let us know it's types.
+ this.shouldIgnorePropTypes = true;
+ return;
+ }
+ }
+ if (!value) {
+ this.shouldIgnorePropTypes = true;
+ return;
+ }
const types = buildReactDeclarationTypes(value, key);
types.fullName = key;
types.name = key;
@@ -679,6 +683,14 @@ module.exports = function propTypesInstructions(context, components, utils) {
this.declaredPropTypes[key] = types;
});
break;
+ case 'CallExpression':
+ if (res.typeParameters) {
+ this.visitTSNode(res.typeParameters);
+ } else {
+ // Ignore this CallExpression return value since it doesn't have any typeParameters to let us know it's types.
+ this.shouldIgnorePropTypes = true;
+ }
+ break;
default:
}
}
@@ -689,15 +701,13 @@ module.exports = function propTypesInstructions(context, components, utils) {
// Handle ReturnType<()=>returnType>
if (astUtil.isTSFunctionType(returnType)) {
if (astUtil.isTSTypeAnnotation(returnType.returnType)) {
- const returnTypeAnnotation = returnType.returnType.typeAnnotation;
- this.visitTSNode(returnTypeAnnotation);
+ this.visitTSNode(returnType.returnType);
return;
}
// This line is trying to handle typescript-eslint-parser
// typescript-eslint-parser TSFunction name returnType as typeAnnotation
if (astUtil.isTSTypeAnnotation(returnType.typeAnnotation)) {
- const returnTypeAnnotation = returnType.typeAnnotation.typeAnnotation;
- this.visitTSNode(returnTypeAnnotation);
+ this.visitTSNode(returnType.typeAnnotation);
return;
}
}
diff --git a/tests/lib/rules/no-unused-prop-types.js b/tests/lib/rules/no-unused-prop-types.js
index 33f94a758b..7eebf7b33e 100644
--- a/tests/lib/rules/no-unused-prop-types.js
+++ b/tests/lib/rules/no-unused-prop-types.js
@@ -3763,6 +3763,89 @@ ruleTester.run('no-unused-prop-types', rule, {
};
`,
parser: parsers['@TYPESCRIPT_ESLINT']
+ },
+ // Issue: #2795
+ {
+ code: `
+ type ConnectedProps = DispatchProps &
+ StateProps
+
+ const Component = ({ prop1, prop2, prop3 }: ConnectedProps) => {
+ // Do stuff
+ return (
+ ...
+ )
+ }
+
+ const mapDispatchToProps = (dispatch: ThunkDispatch) => ({
+ ...bindActionCreators>(
+ { prop1: importedAction, prop2: anotherImportedAction },
+ dispatch,
+ ),
+ })
+
+ const mapStateToProps = (state: State) => ({
+ prop3: Selector.value(state),
+ })
+
+ type StateProps = ReturnType
+ type DispatchProps = ReturnType`,
+ parser: parsers['@TYPESCRIPT_ESLINT']
+ },
+ // Issue: #2795
+ {
+ code: `
+ type ConnectedProps = DispatchProps &
+ StateProps
+
+ const Component = ({ prop1, prop2, prop3 }: ConnectedProps) => {
+ // Do stuff
+ return (
+ ...
+ )
+ }
+
+ const mapDispatchToProps = (dispatch: ThunkDispatch) => ({
+ ...bindActionCreators(
+ { prop1: importedAction, prop2: anotherImportedAction },
+ dispatch,
+ ),
+ })
+
+ const mapStateToProps = (state: State) => ({
+ prop3: Selector.value(state),
+ })
+
+ type StateProps = ReturnType
+ type DispatchProps = ReturnType`,
+ parser: parsers['@TYPESCRIPT_ESLINT']
+ },
+ // Issue: #2795
+ {
+ code: `
+ type ConnectedProps = DispatchProps &
+ StateProps
+
+ const Component = ({ prop1, prop2, prop3 }: ConnectedProps) => {
+ // Do stuff
+ return (
+ ...
+ )
+ }
+
+ const mapDispatchToProps = (dispatch: ThunkDispatch) =>
+ bindActionCreators(
+ { prop1: importedAction, prop2: anotherImportedAction },
+ dispatch,
+ )
+
+ const mapStateToProps = (state: State) => ({
+ prop3: Selector.value(state),
+ })
+
+ type StateProps = ReturnType
+ type DispatchProps = ReturnType`,
+ parser: parsers['@TYPESCRIPT_ESLINT']
}
])
),
@@ -6765,7 +6848,38 @@ ruleTester.run('no-unused-prop-types', rule, {
}, {
message: '\'username\' PropType is defined but prop is never used'
}]
- }])
+ }]),
+ // Issue: #2795
+ {
+ code: `
+ type ConnectedProps = DispatchProps &
+ StateProps
+
+ const Component = ({ prop2, prop3 }: ConnectedProps) => {
+ // Do stuff
+ return (
+ ...
+ )
+ }
+
+ const mapDispatchToProps = (dispatch: ThunkDispatch) => ({
+ ...bindActionCreators<{prop1: ()=>void,prop2: ()=>string}>(
+ { prop1: importedAction, prop2: anotherImportedAction },
+ dispatch,
+ ),
+ })
+
+ const mapStateToProps = (state: State) => ({
+ prop3: Selector.value(state),
+ })
+
+ type StateProps = ReturnType
+ type DispatchProps = ReturnType`,
+ parser: parsers['@TYPESCRIPT_ESLINT'],
+ errors: [{
+ message: '\'prop1\' PropType is defined but prop is never used'
+ }]
+ }
/* , {
// Enable this when the following issue is fixed
diff --git a/tests/lib/rules/prop-types.js b/tests/lib/rules/prop-types.js
index ab40f13bd3..b68dafe534 100644
--- a/tests/lib/rules/prop-types.js
+++ b/tests/lib/rules/prop-types.js
@@ -3026,6 +3026,89 @@ ruleTester.run('prop-types', rule, {
}
`,
parser: parsers['@TYPESCRIPT_ESLINT']
+ },
+ // Issue: #2795
+ {
+ code: `
+ type ConnectedProps = DispatchProps &
+ StateProps
+
+ const Component = ({ prop1, prop2, prop3 }: ConnectedProps) => {
+ // Do stuff
+ return (
+ ...
+ )
+ }
+
+ const mapDispatchToProps = (dispatch: ThunkDispatch) => ({
+ ...bindActionCreators<{prop1: ()=>void,prop2: ()=>string}>(
+ { prop1: importedAction, prop2: anotherImportedAction },
+ dispatch,
+ ),
+ })
+
+ const mapStateToProps = (state: State) => ({
+ prop3: Selector.value(state),
+ })
+
+ type StateProps = ReturnType
+ type DispatchProps = ReturnType`,
+ parser: parsers['@TYPESCRIPT_ESLINT']
+ },
+ // Issue: #2795
+ {
+ code: `
+ type ConnectedProps = DispatchProps &
+ StateProps
+
+ const Component = ({ prop1, prop2, prop3 }: ConnectedProps) => {
+ // Do stuff
+ return (
+ ...
+ )
+ }
+
+ const mapDispatchToProps = (dispatch: ThunkDispatch) => ({
+ ...bindActionCreators>(
+ { prop1: importedAction, prop2: anotherImportedAction },
+ dispatch,
+ ),
+ })
+
+ const mapStateToProps = (state: State) => ({
+ prop3: Selector.value(state),
+ })
+
+ type StateProps = ReturnType
+ type DispatchProps = ReturnType`,
+ parser: parsers['@TYPESCRIPT_ESLINT']
+ },
+ // Issue: #2795
+ {
+ code: `
+ type ConnectedProps = DispatchProps &
+ StateProps
+
+ const Component = ({ prop1, prop2, prop3 }: ConnectedProps) => {
+ // Do stuff
+ return (
+ ...
+ )
+ }
+
+ const mapDispatchToProps = (dispatch: ThunkDispatch) =>
+ bindActionCreators<{prop1: ()=>void,prop2: ()=>string}>(
+ { prop1: importedAction, prop2: anotherImportedAction },
+ dispatch,
+ )
+
+ const mapStateToProps = (state: State) => ({
+ prop3: Selector.value(state),
+ })
+
+ type StateProps = ReturnType
+ type DispatchProps = ReturnType`,
+ parser: parsers['@TYPESCRIPT_ESLINT']
}
])
),