Skip to content

Commit ce09993

Browse files
authored
Merge pull request #1400 from jseminck/flow-53-hoc
Add support to ClassExpressions in the prop-types rule
2 parents 35eb3ce + cd7f2e8 commit ce09993

File tree

2 files changed

+109
-1
lines changed

2 files changed

+109
-1
lines changed

lib/rules/prop-types.js

+24-1
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ module.exports = {
6565
// Used to track the type annotations in scope.
6666
// Necessary because babel's scopes do not track type annotations.
6767
let stack = null;
68+
const classExpressions = [];
6869

6970
const MISSING_MESSAGE = '\'{{name}}\' is missing in props validation';
7071

@@ -173,7 +174,7 @@ module.exports = {
173174
* @returns {Boolean} True if the node is a class with generic prop types, false if not.
174175
*/
175176
function isSuperTypeParameterPropsDeclaration(node) {
176-
if (node && node.type === 'ClassDeclaration') {
177+
if (node && (node.type === 'ClassDeclaration' || node.type === 'ClassExpression')) {
177178
if (node.superTypeParameters && node.superTypeParameters.params.length > 0) {
178179
return true;
179180
}
@@ -881,6 +882,7 @@ module.exports = {
881882
while (annotation && (annotation.type === 'TypeAnnotation' || annotation.type === 'NullableTypeAnnotation')) {
882883
annotation = annotation.typeAnnotation;
883884
}
885+
884886
if (annotation.type === 'GenericTypeAnnotation' && typeScope(annotation.id.name)) {
885887
return typeScope(annotation.id.name);
886888
}
@@ -929,6 +931,13 @@ module.exports = {
929931
}
930932
},
931933

934+
ClassExpression: function(node) {
935+
// TypeParameterDeclaration need to be added to typeScope in order to handle ClassExpressions.
936+
// This visitor is executed before TypeParameterDeclaration are scoped, therefore we postpone
937+
// processing class expressions until when the program exists.
938+
classExpressions.push(node);
939+
},
940+
932941
ClassProperty: function(node) {
933942
if (isAnnotatedClassPropsDeclaration(node)) {
934943
markPropTypesAsDeclared(node, resolveTypeAnnotation(node));
@@ -1020,6 +1029,14 @@ module.exports = {
10201029
typeScope(node.id.name, node.right);
10211030
},
10221031

1032+
TypeParameterDeclaration: function(node) {
1033+
const identifier = node.params[0];
1034+
1035+
if (identifier.typeAnnotation) {
1036+
typeScope(identifier.name, identifier.typeAnnotation.typeAnnotation);
1037+
}
1038+
},
1039+
10231040
Program: function() {
10241041
stack = [{}];
10251042
},
@@ -1033,6 +1050,12 @@ module.exports = {
10331050
},
10341051

10351052
'Program:exit': function() {
1053+
classExpressions.forEach(node => {
1054+
if (isSuperTypeParameterPropsDeclaration(node)) {
1055+
markPropTypesAsDeclared(node, resolveSuperParameterPropsType(node));
1056+
}
1057+
});
1058+
10361059
stack = null;
10371060
const list = components.list();
10381061
// Report undeclared proptypes for all classes

tests/lib/rules/prop-types.js

+85
Original file line numberDiff line numberDiff line change
@@ -1684,6 +1684,44 @@ ruleTester.run('prop-types', rule, {
16841684
}
16851685
`,
16861686
parser: 'babel-eslint'
1687+
}, {
1688+
code: `
1689+
type Props = { foo: string }
1690+
function higherOrderComponent<Props>() {
1691+
return class extends React.Component<Props> {
1692+
render() {
1693+
return <div>{this.props.foo}</div>
1694+
}
1695+
}
1696+
}
1697+
`,
1698+
parser: 'babel-eslint'
1699+
}, {
1700+
code: `
1701+
function higherOrderComponent<P: { foo: string }>() {
1702+
return class extends React.Component<P> {
1703+
render() {
1704+
return <div>{this.props.foo}</div>
1705+
}
1706+
}
1707+
}
1708+
`,
1709+
parser: 'babel-eslint'
1710+
}, {
1711+
code: `
1712+
const withOverlayState = <P: {foo: string}>(WrappedComponent: ComponentType<P>): CpmponentType<P> => (
1713+
class extends React.Component<P> {
1714+
constructor(props) {
1715+
super(props);
1716+
this.state = {foo: props.foo}
1717+
}
1718+
render() {
1719+
return <div>Hello World</div>
1720+
}
1721+
}
1722+
)
1723+
`,
1724+
parser: 'babel-eslint'
16871725
},
16881726

16891727
// issue #1288
@@ -3272,6 +3310,53 @@ ruleTester.run('prop-types', rule, {
32723310
type: 'Identifier'
32733311
}],
32743312
parser: 'babel-eslint'
3313+
}, {
3314+
code: `
3315+
type Props = { foo: string }
3316+
function higherOrderComponent<Props>() {
3317+
return class extends React.Component<Props> {
3318+
render() {
3319+
return <div>{this.props.foo} - {this.props.bar}</div>
3320+
}
3321+
}
3322+
}
3323+
`,
3324+
errors: [{
3325+
message: '\'bar\' is missing in props validation'
3326+
}],
3327+
parser: 'babel-eslint'
3328+
}, {
3329+
code: `
3330+
function higherOrderComponent<P: { foo: string }>() {
3331+
return class extends React.Component<P> {
3332+
render() {
3333+
return <div>{this.props.foo} - {this.props.bar}</div>
3334+
}
3335+
}
3336+
}
3337+
`,
3338+
errors: [{
3339+
message: '\'bar\' is missing in props validation'
3340+
}],
3341+
parser: 'babel-eslint'
3342+
}, {
3343+
code: `
3344+
const withOverlayState = <P: {foo: string}>(WrappedComponent: ComponentType<P>): CpmponentType<P> => (
3345+
class extends React.Component<P> {
3346+
constructor(props) {
3347+
super(props);
3348+
this.state = {foo: props.foo, bar: props.bar}
3349+
}
3350+
render() {
3351+
return <div>Hello World</div>
3352+
}
3353+
}
3354+
)
3355+
`,
3356+
errors: [{
3357+
message: '\'bar\' is missing in props validation'
3358+
}],
3359+
parser: 'babel-eslint'
32753360
}, {
32763361
code: `
32773362
type PropsA = {foo: string };

0 commit comments

Comments
 (0)