Skip to content

Commit

Permalink
[Fix] no-unused-prop-types: fix with typescript eslint parser
Browse files Browse the repository at this point in the history
Fixes #2569.

Co-authored-by: Antoine <antoine.sauvage@melix.net>
Co-authored-by: Jordan Harband <ljharb@gmail.com>
  • Loading branch information
eltonio450 and ljharb committed Jun 7, 2020
1 parent fbd799c commit 13a863b
Show file tree
Hide file tree
Showing 3 changed files with 174 additions and 0 deletions.
46 changes: 46 additions & 0 deletions lib/util/propTypes.js
Expand Up @@ -4,6 +4,8 @@

'use strict';

const flatMap = require('array.prototype.flatmap');

const annotations = require('./annotations');
const propsUtil = require('./props');
const variableUtil = require('./variable');
Expand Down Expand Up @@ -281,6 +283,47 @@ module.exports = function propTypesInstructions(context, components, utils) {
return ignorePropsValidation;
}

function declarePropTypesForTSTypeAnnotation(propTypes, declaredPropTypes) {
let foundDeclaredPropertiesList = [];
if (propTypes.typeAnnotation.type === 'TSTypeReference') {
const typeName = propTypes.typeAnnotation.typeName.name;
if (!typeName) {
return true;
}
// the game here is to find the type declaration in the code
const candidateTypes = context.getSourceCode().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 interfaceDeclaration = context.getSourceCode().ast.body.find((item) => item.type === 'TSInterfaceDeclaration' && item.id.name === typeName);

if (typeDeclaration) {
foundDeclaredPropertiesList = typeDeclaration.init.members;
} else if (interfaceDeclaration) {
foundDeclaredPropertiesList = interfaceDeclaration.body.body;
} else {
// type not found, for example can be an exported type, etc. Can issue a warning in the future.
return true;
}
} else if (propTypes.typeAnnotation.type === 'TSTypeLiteral') {
foundDeclaredPropertiesList = propTypes.typeAnnotation.members;
} else {
// weird cases such as TSTypeFunction
return true;
}

foundDeclaredPropertiesList.forEach((tsPropertySignature) => {
declaredPropTypes[tsPropertySignature.key.name] = {
fullName: tsPropertySignature.key.name,
name: tsPropertySignature.key.name,
node: tsPropertySignature,
isRequired: !tsPropertySignature.optional
};
});
return false;
}

/**
* Marks all props found inside IntersectionTypeAnnotation as declared.
* Since InterSectionTypeAnnotations can be nested, this handles recursively.
Expand Down Expand Up @@ -547,6 +590,9 @@ module.exports = function propTypesInstructions(context, components, utils) {
ignorePropsValidation = true;
}
break;
case 'TSTypeAnnotation':
ignorePropsValidation = declarePropTypesForTSTypeAnnotation(propTypes, declaredPropTypes);
break;
case null:
break;
default:
Expand Down
1 change: 1 addition & 0 deletions package.json
Expand Up @@ -29,6 +29,7 @@
"bugs": "https://github.com/yannickcr/eslint-plugin-react/issues",
"dependencies": {
"array-includes": "^3.1.1",
"array.prototype.flatmap": "^1.2.3",
"doctrine": "^2.1.0",
"has": "^1.0.3",
"jsx-ast-utils": "^2.4.1",
Expand Down
127 changes: 127 additions & 0 deletions tests/lib/rules/no-unused-prop-types.js
Expand Up @@ -3211,6 +3211,42 @@ ruleTester.run('no-unused-prop-types', rule, {
'export default Thing;'
].join('\n'),
parser: parsers.TYPESCRIPT_ESLINT
},
// this test checks that there is no crash if no declaration is found (TSTypeLiteral).
{
code: [
'const Hello = (props: {firstname: string, lastname: string}) => {',
' return <div {...props}></div>;',
'}'
].join('\n'),
parser: parsers.TYPESCRIPT_ESLINT
},
// this test checks that there is no crash if no declaration is found (TSTypeReference).
{
code: [
'const Hello = (props: UnfoundProps) => {',
' return <div {...props}></div>;',
'}'
].join('\n'),
parser: parsers.TYPESCRIPT_ESLINT
},
{
// Omit, etc, cannot be handled, but must not trigger an error
code: [
'const Hello = (props: Omit<{a: string, b: string, c: string}, "a">) => {',
' return <div>{props.b}</div>;',
'}'
].join('\n'),
parser: parsers.TYPESCRIPT_ESLINT
},
{
// neither TSTypeReference or TSTypeLiteral, we do nothing. Weird case
code: [
'const Hello = (props: () => any) => {',
' return <div>{props.firstname}</div>;',
'}'
].join('\n'),
parser: parsers.TYPESCRIPT_ESLINT
}
],

Expand Down Expand Up @@ -5312,6 +5348,97 @@ ruleTester.run('no-unused-prop-types', rule, {
}, {
message: '\'thisPropsPropUnused\' PropType is defined but prop is never used'
}]
}, {
code: [
'type Person = {',
' lastname: string',
'};',
'const Hello = (props: Person) => {',
' return <div>Hello {props.firstname}</div>;',
'}'
].join('\n'),
parser: parsers.BABEL_ESLINT,
errors: [{
message: '\'lastname\' PropType is defined but prop is never used'
}]
}, {
code: [
'type Person = {',
' lastname: string',
'};',
'const Hello = (props: Person) => {',
' return <div>Hello {props.firstname}</div>;',
'}'
].join('\n'),
parser: parsers.TYPESCRIPT_ESLINT,
errors: [
{
message: '\'lastname\' PropType is defined but prop is never used'
}
]
},
{
code: [
'type Person = {',
' lastname?: string',
'};',
'const Hello = (props: Person) => {',
' return <div>Hello {props.firstname}</div>;',
'}'
].join('\n'),
parser: parsers.TYPESCRIPT_ESLINT,
errors: [
{
message: '\'lastname\' PropType is defined but prop is never used'
}
]
},
{
code: [
'type Person = {',
' firstname: string',
' lastname: string',
'};',
'const Hello = ({firstname}: Person) => {',
' return <div>Hello {firstname}</div>;',
'}'
].join('\n'),
parser: parsers.TYPESCRIPT_ESLINT,
errors: [
{
message: '\'lastname\' PropType is defined but prop is never used'
}
]
},
{
code: [
'interface Person {',
' firstname: string',
' lastname: string',
'};',
'const Hello = ({firstname}: Person) => {',
' return <div>Hello {firstname}</div>;',
'}'
].join('\n'),
parser: parsers.TYPESCRIPT_ESLINT,
errors: [
{
message: '\'lastname\' PropType is defined but prop is never used'
}
]
},
{
code: [
'const Hello = ({firstname}: {firstname: string, lastname: string}) => {',
' return <div>Hello {firstname}</div>;',
'}'
].join('\n'),
parser: parsers.TYPESCRIPT_ESLINT,
errors: [
{
message: '\'lastname\' PropType is defined but prop is never used'
}
]
}

/* , {
Expand Down

0 comments on commit 13a863b

Please sign in to comment.