Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Fix] no-unused-prop-types with typescript eslint parser #2661

Merged
merged 1 commit into from Jun 12, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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;
ljharb marked this conversation as resolved.
Show resolved Hide resolved
}
} else if (propTypes.typeAnnotation.type === 'TSTypeLiteral') {
foundDeclaredPropertiesList = propTypes.typeAnnotation.members;
} else {
// weird cases such as TSTypeFunction
return true;
ljharb marked this conversation as resolved.
Show resolved Hide resolved
}

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