Skip to content

Commit

Permalink
[Fix] prop-types: add typescript-eslint-parser support
Browse files Browse the repository at this point in the history
  • Loading branch information
hank121314 committed Aug 1, 2020
1 parent 59893f9 commit 2061827
Show file tree
Hide file tree
Showing 2 changed files with 237 additions and 6 deletions.
32 changes: 26 additions & 6 deletions lib/util/propTypes.js
Expand Up @@ -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;
Expand All @@ -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 {
Expand All @@ -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);
}
}

Expand All @@ -623,7 +631,12 @@ module.exports = function propTypesInstructions(context, components, utils) {
// ReturnType<T> 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<typeof mapStateToProps>
if (astUtil.isTSTypeQuery(returnType)) {
const returnTypeFunction = flatMap(this.sourceCode.ast.body
Expand Down Expand Up @@ -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;
}
}
}
}
Expand Down
211 changes: 211 additions & 0 deletions tests/lib/rules/no-unused-prop-types.js
Expand Up @@ -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) => (
<span>
{x}
{y}
</span>
);`,
parser: parsers.TYPESCRIPT_ESLINT,
errors: [{
message: '\'z\' PropType is defined but prop is never used'
}]
},
{
// test extends
code: `
Expand Down Expand Up @@ -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) => (
<span>
{x}
</span>
);`,
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: `
Expand Down Expand Up @@ -6309,6 +6362,31 @@ ruleTester.run('no-unused-prop-types', rule, {
z: string;
}
interface Bar extends Foo {
y: string;
}
const Baz = ({ x }: Bar) => (
<span>
{x}
</span>
);`,
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;
}
Expand Down Expand Up @@ -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 <p>userId is 0</p>;
}
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 => ({
Expand All @@ -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<typeof mapStateToProps> {
}
const App = (props: InfoLibTableProps) => {
return <div></div>;
}
`,
parser: parsers.TYPESCRIPT_ESLINT,
errors: [{
message: '\'books\' PropType is defined but prop is never used'
}]
},
{
code: `
const mapStateToProps = state => ({
Expand All @@ -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<typeof mapStateToProps> {
username: string;
}
const App = (props: BooksTable) => {
return <div />;
}
`,
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<string>}> {
Expand All @@ -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<string>}> {
username: string;
}
const App = (props: BooksTable) => {
return <div></div>;
}
`,
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<string>}> & {
Expand All @@ -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<string>}> & {
username: string;
}
const App = (props: BooksTable) => {
return <div></div>;
}
`,
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<string>}>;
Expand All @@ -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<string>}>;
type Props = {
username: string;
}
type BooksTable = mapStateToProps & Props;
const App = (props: BooksTable) => {
return <div></div>;
}
`,
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'
}]
}

/* , {
Expand Down

0 comments on commit 2061827

Please sign in to comment.