Skip to content

Commit 2061827

Browse files
committed
[Fix] prop-types: add typescript-eslint-parser support
1 parent 59893f9 commit 2061827

File tree

2 files changed

+237
-6
lines changed

2 files changed

+237
-6
lines changed

lib/util/propTypes.js

+26-6
Original file line numberDiff line numberDiff line change
@@ -548,7 +548,11 @@ module.exports = function propTypesInstructions(context, components, utils) {
548548
if (astUtil.isTSTypeReference(node)) {
549549
typeName = node.typeName.name;
550550
} else if (astUtil.isTSInterfaceHeritage(node)) {
551-
typeName = node.expression.name;
551+
if (!node.expression && node.id) {
552+
typeName = node.id.name;
553+
} else {
554+
typeName = node.expression.name;
555+
}
552556
}
553557
if (!typeName) {
554558
this.shouldIgnorePropTypes = true;
@@ -567,22 +571,22 @@ module.exports = function propTypesInstructions(context, components, utils) {
567571
this.referenceNameMap.add(typeName);
568572

569573
/**
570-
* From line 573 to line 578, and line 585 to line 587 are trying to handle typescript-eslint-parser
574+
* From line 577 to line 581, and line 588 to line 590 are trying to handle typescript-eslint-parser
571575
* Need to be deprecated after remove typescript-eslint-parser support.
572576
*/
573577
const candidateTypes = this.sourceCode.ast.body.filter((item) => item.type === 'VariableDeclaration' && item.kind === 'type');
574578
const declarations = flatMap(candidateTypes, (type) => type.declarations);
575579

576580
// we tried to find either an interface or a type with the TypeReference name
577-
const typeDeclaration = declarations.find((dec) => dec.id.name === typeName);
581+
const typeDeclaration = declarations.filter((dec) => dec.id.name === typeName);
578582

579583
const interfaceDeclarations = this.sourceCode.ast.body
580584
.filter(
581585
(item) => (astUtil.isTSInterfaceDeclaration(item)
582586
|| astUtil.isTSTypeAliasDeclaration(item))
583587
&& item.id.name === typeName);
584-
if (typeDeclaration) {
585-
this.foundDeclaredPropertiesList = this.foundDeclaredPropertiesList.concat(typeDeclaration.init.members);
588+
if (typeDeclaration.length !== 0) {
589+
typeDeclaration.map((t) => t.init).forEach(this.visitTSNode, this);
586590
} else if (interfaceDeclarations.length !== 0) {
587591
interfaceDeclarations.forEach(this.traverseDeclaredInterfaceOrTypeAlias, this);
588592
} else {
@@ -607,6 +611,10 @@ module.exports = function propTypesInstructions(context, components, utils) {
607611
}
608612
if (Array.isArray(node.extends)) {
609613
node.extends.forEach(this.visitTSNode, this);
614+
// This line is trying to handle typescript-eslint-parser
615+
// typescript-eslint-parser extension is name as heritage
616+
} else if (Array.isArray(node.heritage)) {
617+
node.heritage.forEach(this.visitTSNode, this);
610618
}
611619
}
612620

@@ -623,7 +631,12 @@ module.exports = function propTypesInstructions(context, components, utils) {
623631
// ReturnType<T> should always have one parameter
624632
if (node.typeParameters) {
625633
if (node.typeParameters.params.length === 1) {
626-
const returnType = node.typeParameters.params[0];
634+
let returnType = node.typeParameters.params[0];
635+
// This line is trying to handle typescript-eslint-parser
636+
// typescript-eslint-parser TSTypeQuery is wrapped by TSTypeReference
637+
if (astUtil.isTSTypeReference(returnType)) {
638+
returnType = returnType.typeName;
639+
}
627640
// Handle ReturnType<typeof mapStateToProps>
628641
if (astUtil.isTSTypeQuery(returnType)) {
629642
const returnTypeFunction = flatMap(this.sourceCode.ast.body
@@ -671,6 +684,13 @@ module.exports = function propTypesInstructions(context, components, utils) {
671684
this.visitTSNode(returnTypeAnnotation);
672685
return;
673686
}
687+
// This line is trying to handle typescript-eslint-parser
688+
// typescript-eslint-parser TSFunction name returnType as typeAnnotation
689+
if (astUtil.isTSTypeAnnotation(returnType.typeAnnotation)) {
690+
const returnTypeAnnotation = returnType.typeAnnotation.typeAnnotation;
691+
this.visitTSNode(returnTypeAnnotation);
692+
return;
693+
}
674694
}
675695
}
676696
}

tests/lib/rules/no-unused-prop-types.js

+211
Original file line numberDiff line numberDiff line change
@@ -6225,6 +6225,32 @@ ruleTester.run('no-unused-prop-types', rule, {
62256225
message: '\'z\' PropType is defined but prop is never used'
62266226
}]
62276227
},
6228+
{
6229+
// test same name of interface should be merge
6230+
code: `
6231+
interface Foo {
6232+
x: number;
6233+
}
6234+
6235+
interface Foo {
6236+
z: string;
6237+
}
6238+
6239+
interface Bar extends Foo {
6240+
y: string;
6241+
}
6242+
6243+
const Baz = ({ x, y }: Bar) => (
6244+
<span>
6245+
{x}
6246+
{y}
6247+
</span>
6248+
);`,
6249+
parser: parsers.TYPESCRIPT_ESLINT,
6250+
errors: [{
6251+
message: '\'z\' PropType is defined but prop is never used'
6252+
}]
6253+
},
62286254
{
62296255
// test extends
62306256
code: `
@@ -6273,6 +6299,33 @@ ruleTester.run('no-unused-prop-types', rule, {
62736299
message: '\'z\' PropType is defined but prop is never used'
62746300
}]
62756301
},
6302+
{
6303+
// test extends
6304+
code: `
6305+
interface Foo {
6306+
x: number;
6307+
}
6308+
6309+
interface Bar {
6310+
y: string;
6311+
}
6312+
6313+
interface Baz {
6314+
z:string;
6315+
}
6316+
6317+
const Baz = ({ x }: Bar & Foo & Baz) => (
6318+
<span>
6319+
{x}
6320+
</span>
6321+
);`,
6322+
parser: parsers.TYPESCRIPT_ESLINT,
6323+
errors: [{
6324+
message: '\'y\' PropType is defined but prop is never used'
6325+
}, {
6326+
message: '\'z\' PropType is defined but prop is never used'
6327+
}]
6328+
},
62766329
{
62776330
// test same name merge and extends
62786331
code: `
@@ -6309,6 +6362,31 @@ ruleTester.run('no-unused-prop-types', rule, {
63096362
z: string;
63106363
}
63116364
6365+
interface Bar extends Foo {
6366+
y: string;
6367+
}
6368+
6369+
const Baz = ({ x }: Bar) => (
6370+
<span>
6371+
{x}
6372+
</span>
6373+
);`,
6374+
parser: parsers.TYPESCRIPT_ESLINT,
6375+
errors: [{
6376+
message: '\'z\' PropType is defined but prop is never used'
6377+
}, {message: '\'y\' PropType is defined but prop is never used'}]
6378+
},
6379+
{
6380+
// test same name merge and extends
6381+
code: `
6382+
interface Foo {
6383+
x: number;
6384+
}
6385+
6386+
interface Foo {
6387+
z: string;
6388+
}
6389+
63126390
interface Foo {
63136391
y: string;
63146392
}
@@ -6362,6 +6440,45 @@ ruleTester.run('no-unused-prop-types', rule, {
63626440
message: '\'birthday\' PropType is defined but prop is never used'
63636441
}]
63646442
},
6443+
{
6444+
code: `
6445+
type User = {
6446+
user: string;
6447+
}
6448+
6449+
type UserProps = {
6450+
userId: string;
6451+
}
6452+
6453+
type AgeProps = {
6454+
age: number;
6455+
}
6456+
6457+
type BirthdayProps = {
6458+
birthday: string;
6459+
}
6460+
6461+
type intersectionUserProps = AgeProps & BirthdayProps;
6462+
6463+
type Props = User & UserProps & intersectionUserProps;
6464+
6465+
export default (props: Props) => {
6466+
const { userId, user } = props;
6467+
6468+
if (userId === 0) {
6469+
return <p>userId is 0</p>;
6470+
}
6471+
6472+
return null;
6473+
};
6474+
`,
6475+
parser: parsers.TYPESCRIPT_ESLINT,
6476+
errors: [{
6477+
message: '\'age\' PropType is defined but prop is never used'
6478+
}, {
6479+
message: '\'birthday\' PropType is defined but prop is never used'
6480+
}]
6481+
},
63656482
{
63666483
code: `
63676484
const mapStateToProps = state => ({
@@ -6380,6 +6497,24 @@ ruleTester.run('no-unused-prop-types', rule, {
63806497
message: '\'books\' PropType is defined but prop is never used'
63816498
}]
63826499
},
6500+
{
6501+
code: `
6502+
const mapStateToProps = state => ({
6503+
books: state.books
6504+
});
6505+
6506+
interface InfoLibTableProps extends ReturnType<typeof mapStateToProps> {
6507+
}
6508+
6509+
const App = (props: InfoLibTableProps) => {
6510+
return <div></div>;
6511+
}
6512+
`,
6513+
parser: parsers.TYPESCRIPT_ESLINT,
6514+
errors: [{
6515+
message: '\'books\' PropType is defined but prop is never used'
6516+
}]
6517+
},
63836518
{
63846519
code: `
63856520
const mapStateToProps = state => ({
@@ -6401,6 +6536,27 @@ ruleTester.run('no-unused-prop-types', rule, {
64016536
message: '\'username\' PropType is defined but prop is never used'
64026537
}]
64036538
},
6539+
{
6540+
code: `
6541+
const mapStateToProps = state => ({
6542+
books: state.books,
6543+
});
6544+
6545+
interface BooksTable extends ReturnType<typeof mapStateToProps> {
6546+
username: string;
6547+
}
6548+
6549+
const App = (props: BooksTable) => {
6550+
return <div />;
6551+
}
6552+
`,
6553+
parser: parsers.TYPESCRIPT_ESLINT,
6554+
errors: [{
6555+
message: '\'books\' PropType is defined but prop is never used'
6556+
}, {
6557+
message: '\'username\' PropType is defined but prop is never used'
6558+
}]
6559+
},
64046560
{
64056561
code: `
64066562
interface BooksTable extends ReturnType<() => {books:Array<string>}> {
@@ -6418,6 +6574,23 @@ ruleTester.run('no-unused-prop-types', rule, {
64186574
message: '\'username\' PropType is defined but prop is never used'
64196575
}]
64206576
},
6577+
{
6578+
code: `
6579+
interface BooksTable extends ReturnType<() => {books:Array<string>}> {
6580+
username: string;
6581+
}
6582+
6583+
const App = (props: BooksTable) => {
6584+
return <div></div>;
6585+
}
6586+
`,
6587+
parser: parsers.TYPESCRIPT_ESLINT,
6588+
errors: [{
6589+
message: '\'books\' PropType is defined but prop is never used'
6590+
}, {
6591+
message: '\'username\' PropType is defined but prop is never used'
6592+
}]
6593+
},
64216594
{
64226595
code: `
64236596
type BooksTable = ReturnType<() => {books:Array<string>}> & {
@@ -6435,6 +6608,23 @@ ruleTester.run('no-unused-prop-types', rule, {
64356608
message: '\'username\' PropType is defined but prop is never used'
64366609
}]
64376610
},
6611+
{
6612+
code: `
6613+
type BooksTable = ReturnType<() => {books:Array<string>}> & {
6614+
username: string;
6615+
}
6616+
6617+
const App = (props: BooksTable) => {
6618+
return <div></div>;
6619+
}
6620+
`,
6621+
parser: parsers.TYPESCRIPT_ESLINT,
6622+
errors: [{
6623+
message: '\'books\' PropType is defined but prop is never used'
6624+
}, {
6625+
message: '\'username\' PropType is defined but prop is never used'
6626+
}]
6627+
},
64386628
{
64396629
code: `
64406630
type mapStateToProps = ReturnType<() => {books:Array<string>}>;
@@ -6455,6 +6645,27 @@ ruleTester.run('no-unused-prop-types', rule, {
64556645
}, {
64566646
message: '\'username\' PropType is defined but prop is never used'
64576647
}]
6648+
},
6649+
{
6650+
code: `
6651+
type mapStateToProps = ReturnType<() => {books:Array<string>}>;
6652+
6653+
type Props = {
6654+
username: string;
6655+
}
6656+
6657+
type BooksTable = mapStateToProps & Props;
6658+
6659+
const App = (props: BooksTable) => {
6660+
return <div></div>;
6661+
}
6662+
`,
6663+
parser: parsers.TYPESCRIPT_ESLINT,
6664+
errors: [{
6665+
message: '\'books\' PropType is defined but prop is never used'
6666+
}, {
6667+
message: '\'username\' PropType is defined but prop is never used'
6668+
}]
64586669
}
64596670

64606671
/* , {

0 commit comments

Comments
 (0)