Skip to content

Commit 9e090ed

Browse files
authored
Merge pull request #1412 from jseminck/no-unused-prop-types-flow-53
Add support for Flow TypedArgument in no-unsed-prop-types rule
2 parents 94fbe6f + 3f03ac6 commit 9e090ed

File tree

3 files changed

+166
-0
lines changed

3 files changed

+166
-0
lines changed

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

+49
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ const has = require('has');
1111
const Components = require('../util/Components');
1212
const variable = require('../util/variable');
1313
const annotations = require('../util/annotations');
14+
const versionUtil = require('../util/version');
1415

1516
// ------------------------------------------------------------------------------
1617
// Constants
@@ -133,6 +134,48 @@ module.exports = {
133134
return false;
134135
}
135136

137+
/**
138+
* Resolve the type annotation for a given class declaration node with superTypeParameters.
139+
*
140+
* @param {ASTNode} node The annotation or a node containing the type annotation.
141+
* @returns {ASTNode} The resolved type annotation for the node.
142+
*/
143+
function resolveSuperParameterPropsType(node) {
144+
let propsParameterPosition;
145+
try {
146+
// Flow <=0.52 had 3 required TypedParameters of which the second one is the Props.
147+
// Flow >=0.53 has 2 optional TypedParameters of which the first one is the Props.
148+
propsParameterPosition = versionUtil.testFlowVersion(context, '0.53.0') ? 0 : 1;
149+
} catch (e) {
150+
// In case there is no flow version defined, we can safely assume that when there are 3 Props we are dealing with version <= 0.52
151+
propsParameterPosition = node.superTypeParameters.params.length <= 2 ? 0 : 1;
152+
}
153+
154+
let annotation = node.superTypeParameters.params[propsParameterPosition];
155+
while (annotation && (annotation.type === 'TypeAnnotation' || annotation.type === 'NullableTypeAnnotation')) {
156+
annotation = annotation.typeAnnotation;
157+
}
158+
if (annotation.type === 'GenericTypeAnnotation' && typeScope(annotation.id.name)) {
159+
return typeScope(annotation.id.name);
160+
}
161+
return annotation;
162+
}
163+
164+
/**
165+
* Checks if we are declaring a props as a generic type in a flow-annotated class.
166+
*
167+
* @param {ASTNode} node the AST node being checked.
168+
* @returns {Boolean} True if the node is a class with generic prop types, false if not.
169+
*/
170+
function isSuperTypeParameterPropsDeclaration(node) {
171+
if (node && node.type === 'ClassDeclaration') {
172+
if (node.superTypeParameters && node.superTypeParameters.params.length > 0) {
173+
return true;
174+
}
175+
}
176+
return false;
177+
}
178+
136179
/**
137180
* Checks if we are declaring a prop
138181
* @param {ASTNode} node The AST node being checked.
@@ -866,6 +909,12 @@ module.exports = {
866909
// --------------------------------------------------------------------------
867910

868911
return {
912+
ClassDeclaration: function(node) {
913+
if (isSuperTypeParameterPropsDeclaration(node)) {
914+
markPropTypesAsDeclared(node, resolveSuperParameterPropsType(node));
915+
}
916+
},
917+
869918
ClassProperty: function(node) {
870919
if (isAnnotatedClassPropsDeclaration(node)) {
871920
markPropTypesAsDeclared(node, resolveTypeAnnotation(node));

lib/rules/prop-types.js

+1
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ const Components = require('../util/Components');
1212
const variable = require('../util/variable');
1313
const annotations = require('../util/annotations');
1414
const versionUtil = require('../util/version');
15+
1516
// ------------------------------------------------------------------------------
1617
// Constants
1718
// ------------------------------------------------------------------------------

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

+116
Original file line numberDiff line numberDiff line change
@@ -2069,6 +2069,56 @@ ruleTester.run('no-unused-prop-types', rule, {
20692069
};
20702070
`,
20712071
parser: 'babel-eslint'
2072+
}, {
2073+
code: `
2074+
type Person = {
2075+
firstname: string
2076+
}
2077+
class MyComponent extends React.Component<void, Props, void> {
2078+
render() {
2079+
return <div>Hello {this.props.firstname}</div>
2080+
}
2081+
}
2082+
`,
2083+
parser: 'babel-eslint'
2084+
}, {
2085+
code: `
2086+
type Person = {
2087+
firstname: string
2088+
}
2089+
class MyComponent extends React.Component<void, Props, void> {
2090+
render() {
2091+
return <div>Hello {this.props.firstname}</div>
2092+
}
2093+
}
2094+
`,
2095+
settings: {react: {flowVersion: '0.52'}},
2096+
parser: 'babel-eslint'
2097+
}, {
2098+
code: `
2099+
type Person = {
2100+
firstname: string
2101+
}
2102+
class MyComponent extends React.Component<Props> {
2103+
render() {
2104+
return <div>Hello {this.props.firstname}</div>
2105+
}
2106+
}
2107+
`,
2108+
parser: 'babel-eslint'
2109+
}, {
2110+
code: `
2111+
type Person = {
2112+
firstname: string
2113+
}
2114+
class MyComponent extends React.Component<Props> {
2115+
render() {
2116+
return <div>Hello {this.props.firstname}</div>
2117+
}
2118+
}
2119+
`,
2120+
settings: {react: {flowVersion: '0.53'}},
2121+
parser: 'babel-eslint'
20722122
}
20732123
],
20742124

@@ -3469,6 +3519,72 @@ ruleTester.run('no-unused-prop-types', rule, {
34693519
errors: [{
34703520
message: '\'aProp\' PropType is defined but prop is never used'
34713521
}]
3522+
}, {
3523+
code: `
3524+
type Props = {
3525+
firstname: string,
3526+
lastname: string,
3527+
}
3528+
class MyComponent extends React.Component<void, Props, void> {
3529+
render() {
3530+
return <div>Hello {this.props.firstname}</div>
3531+
}
3532+
}
3533+
`,
3534+
parser: 'babel-eslint',
3535+
errors: [{
3536+
message: '\'lastname\' PropType is defined but prop is never used'
3537+
}]
3538+
}, {
3539+
code: `
3540+
type Props = {
3541+
firstname: string,
3542+
lastname: string,
3543+
}
3544+
class MyComponent extends React.Component<void, Props, void> {
3545+
render() {
3546+
return <div>Hello {this.props.firstname}</div>
3547+
}
3548+
}
3549+
`,
3550+
settings: {react: {flowVersion: '0.52'}},
3551+
parser: 'babel-eslint',
3552+
errors: [{
3553+
message: '\'lastname\' PropType is defined but prop is never used'
3554+
}]
3555+
}, {
3556+
code: `
3557+
type Props = {
3558+
firstname: string,
3559+
lastname: string,
3560+
}
3561+
class MyComponent extends React.Component<Props> {
3562+
render() {
3563+
return <div>Hello {this.props.firstname}</div>
3564+
}
3565+
}
3566+
`,
3567+
parser: 'babel-eslint',
3568+
errors: [{
3569+
message: '\'lastname\' PropType is defined but prop is never used'
3570+
}]
3571+
}, {
3572+
code: `
3573+
type Props = {
3574+
firstname: string,
3575+
lastname: string,
3576+
}
3577+
class MyComponent extends React.Component<Props> {
3578+
render() {
3579+
return <div>Hello {this.props.firstname}</div>
3580+
}
3581+
}
3582+
`,
3583+
settings: {react: {flowVersion: '0.53'}},
3584+
parser: 'babel-eslint',
3585+
errors: [{
3586+
message: '\'lastname\' PropType is defined but prop is never used'
3587+
}]
34723588
}
34733589

34743590
/* , {

0 commit comments

Comments
 (0)