diff --git a/lib/util/propTypes.js b/lib/util/propTypes.js index 5d277cb7f4..6def79afc5 100644 --- a/lib/util/propTypes.js +++ b/lib/util/propTypes.js @@ -548,7 +548,11 @@ module.exports = function propTypesInstructions(context, components, utils) { if (astUtil.isTSTypeReference(node)) { typeName = node.typeName.name; } else if (astUtil.isTSInterfaceHeritage(node)) { - typeName = node.expression.name; + if (!node.expression && node.id) { + typeName = node.id.name; + } else { + typeName = node.expression.name; + } } if (!typeName) { this.shouldIgnorePropTypes = true; @@ -567,22 +571,22 @@ module.exports = function propTypesInstructions(context, components, utils) { this.referenceNameMap.add(typeName); /** - * From line 573 to line 578, and line 585 to line 587 are trying to handle typescript-eslint-parser + * From line 577 to line 581, and line 588 to line 590 are trying to handle typescript-eslint-parser * Need to be deprecated after remove typescript-eslint-parser support. */ const candidateTypes = this.sourceCode.ast.body.filter((item) => item.type === 'VariableDeclaration' && item.kind === 'type'); const declarations = flatMap(candidateTypes, (type) => type.declarations); // we tried to find either an interface or a type with the TypeReference name - const typeDeclaration = declarations.find((dec) => dec.id.name === typeName); + const typeDeclaration = declarations.filter((dec) => dec.id.name === typeName); const interfaceDeclarations = this.sourceCode.ast.body .filter( (item) => (astUtil.isTSInterfaceDeclaration(item) || astUtil.isTSTypeAliasDeclaration(item)) && item.id.name === typeName); - if (typeDeclaration) { - this.foundDeclaredPropertiesList = this.foundDeclaredPropertiesList.concat(typeDeclaration.init.members); + if (typeDeclaration.length !== 0) { + typeDeclaration.map((t) => t.init).forEach(this.visitTSNode, this); } else if (interfaceDeclarations.length !== 0) { interfaceDeclarations.forEach(this.traverseDeclaredInterfaceOrTypeAlias, this); } else { @@ -607,6 +611,10 @@ module.exports = function propTypesInstructions(context, components, utils) { } if (Array.isArray(node.extends)) { node.extends.forEach(this.visitTSNode, this); + // This line is trying to handle typescript-eslint-parser + // typescript-eslint-parser extension is name as heritage + } else if (Array.isArray(node.heritage)) { + node.heritage.forEach(this.visitTSNode, this); } } @@ -623,7 +631,12 @@ module.exports = function propTypesInstructions(context, components, utils) { // ReturnType should always have one parameter if (node.typeParameters) { if (node.typeParameters.params.length === 1) { - const returnType = node.typeParameters.params[0]; + let returnType = node.typeParameters.params[0]; + // This line is trying to handle typescript-eslint-parser + // typescript-eslint-parser TSTypeQuery is wrapped by TSTypeReference + if (astUtil.isTSTypeReference(returnType)) { + returnType = returnType.typeName; + } // Handle ReturnType if (astUtil.isTSTypeQuery(returnType)) { const returnTypeFunction = flatMap(this.sourceCode.ast.body @@ -671,6 +684,13 @@ module.exports = function propTypesInstructions(context, components, utils) { this.visitTSNode(returnTypeAnnotation); 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); + return; + } } } } diff --git a/tests/lib/rules/no-unused-prop-types.js b/tests/lib/rules/no-unused-prop-types.js index 8df2986122..00cbea481d 100644 --- a/tests/lib/rules/no-unused-prop-types.js +++ b/tests/lib/rules/no-unused-prop-types.js @@ -6225,6 +6225,32 @@ ruleTester.run('no-unused-prop-types', rule, { message: '\'z\' PropType is defined but prop is never used' }] }, + { + // test same name of interface should be merge + code: ` + interface Foo { + x: number; + } + + interface Foo { + z: string; + } + + interface Bar extends Foo { + y: string; + } + + const Baz = ({ x, y }: Bar) => ( + + {x} + {y} + + );`, + parser: parsers.TYPESCRIPT_ESLINT, + errors: [{ + message: '\'z\' PropType is defined but prop is never used' + }] + }, { // test extends code: ` @@ -6273,6 +6299,33 @@ ruleTester.run('no-unused-prop-types', rule, { message: '\'z\' PropType is defined but prop is never used' }] }, + { + // test extends + code: ` + interface Foo { + x: number; + } + + interface Bar { + y: string; + } + + interface Baz { + z:string; + } + + const Baz = ({ x }: Bar & Foo & Baz) => ( + + {x} + + );`, + parser: parsers.TYPESCRIPT_ESLINT, + errors: [{ + message: '\'y\' PropType is defined but prop is never used' + }, { + message: '\'z\' PropType is defined but prop is never used' + }] + }, { // test same name merge and extends code: ` @@ -6309,6 +6362,31 @@ ruleTester.run('no-unused-prop-types', rule, { z: string; } + interface Bar extends Foo { + y: string; + } + + const Baz = ({ x }: Bar) => ( + + {x} + + );`, + parser: parsers.TYPESCRIPT_ESLINT, + errors: [{ + message: '\'z\' PropType is defined but prop is never used' + }, {message: '\'y\' PropType is defined but prop is never used'}] + }, + { + // test same name merge and extends + code: ` + interface Foo { + x: number; + } + + interface Foo { + z: string; + } + interface Foo { y: string; } @@ -6362,6 +6440,45 @@ ruleTester.run('no-unused-prop-types', rule, { message: '\'birthday\' PropType is defined but prop is never used' }] }, + { + code: ` + type User = { + user: string; + } + + type UserProps = { + userId: string; + } + + type AgeProps = { + age: number; + } + + type BirthdayProps = { + birthday: string; + } + + type intersectionUserProps = AgeProps & BirthdayProps; + + type Props = User & UserProps & intersectionUserProps; + + export default (props: Props) => { + const { userId, user } = props; + + if (userId === 0) { + return

userId is 0

; + } + + return null; + }; + `, + parser: parsers.TYPESCRIPT_ESLINT, + errors: [{ + message: '\'age\' PropType is defined but prop is never used' + }, { + message: '\'birthday\' PropType is defined but prop is never used' + }] + }, { code: ` const mapStateToProps = state => ({ @@ -6380,6 +6497,24 @@ ruleTester.run('no-unused-prop-types', rule, { message: '\'books\' PropType is defined but prop is never used' }] }, + { + code: ` + const mapStateToProps = state => ({ + books: state.books + }); + + interface InfoLibTableProps extends ReturnType { + } + + const App = (props: InfoLibTableProps) => { + return
; + } + `, + parser: parsers.TYPESCRIPT_ESLINT, + errors: [{ + message: '\'books\' PropType is defined but prop is never used' + }] + }, { code: ` const mapStateToProps = state => ({ @@ -6401,6 +6536,27 @@ ruleTester.run('no-unused-prop-types', rule, { message: '\'username\' PropType is defined but prop is never used' }] }, + { + code: ` + const mapStateToProps = state => ({ + books: state.books, + }); + + interface BooksTable extends ReturnType { + username: string; + } + + const App = (props: BooksTable) => { + return
; + } + `, + parser: parsers.TYPESCRIPT_ESLINT, + errors: [{ + message: '\'books\' PropType is defined but prop is never used' + }, { + message: '\'username\' PropType is defined but prop is never used' + }] + }, { code: ` interface BooksTable extends ReturnType<() => {books:Array}> { @@ -6418,6 +6574,23 @@ ruleTester.run('no-unused-prop-types', rule, { message: '\'username\' PropType is defined but prop is never used' }] }, + { + code: ` + interface BooksTable extends ReturnType<() => {books:Array}> { + username: string; + } + + const App = (props: BooksTable) => { + return
; + } + `, + parser: parsers.TYPESCRIPT_ESLINT, + errors: [{ + message: '\'books\' PropType is defined but prop is never used' + }, { + message: '\'username\' PropType is defined but prop is never used' + }] + }, { code: ` type BooksTable = ReturnType<() => {books:Array}> & { @@ -6435,6 +6608,23 @@ ruleTester.run('no-unused-prop-types', rule, { message: '\'username\' PropType is defined but prop is never used' }] }, + { + code: ` + type BooksTable = ReturnType<() => {books:Array}> & { + username: string; + } + + const App = (props: BooksTable) => { + return
; + } + `, + parser: parsers.TYPESCRIPT_ESLINT, + errors: [{ + message: '\'books\' PropType is defined but prop is never used' + }, { + message: '\'username\' PropType is defined but prop is never used' + }] + }, { code: ` type mapStateToProps = ReturnType<() => {books:Array}>; @@ -6455,6 +6645,27 @@ ruleTester.run('no-unused-prop-types', rule, { }, { message: '\'username\' PropType is defined but prop is never used' }] + }, + { + code: ` + type mapStateToProps = ReturnType<() => {books:Array}>; + + type Props = { + username: string; + } + + type BooksTable = mapStateToProps & Props; + + const App = (props: BooksTable) => { + return
; + } + `, + parser: parsers.TYPESCRIPT_ESLINT, + errors: [{ + message: '\'books\' PropType is defined but prop is never used' + }, { + message: '\'username\' PropType is defined but prop is never used' + }] } /* , {