diff --git a/lib/rules/prop-types.js b/lib/rules/prop-types.js index b514e1a414..c6f494afbf 100644 --- a/lib/rules/prop-types.js +++ b/lib/rules/prop-types.js @@ -65,6 +65,7 @@ module.exports = { // Used to track the type annotations in scope. // Necessary because babel's scopes do not track type annotations. let stack = null; + const classExpressions = []; const MISSING_MESSAGE = '\'{{name}}\' is missing in props validation'; @@ -173,7 +174,7 @@ module.exports = { * @returns {Boolean} True if the node is a class with generic prop types, false if not. */ function isSuperTypeParameterPropsDeclaration(node) { - if (node && node.type === 'ClassDeclaration') { + if (node && (node.type === 'ClassDeclaration' || node.type === 'ClassExpression')) { if (node.superTypeParameters && node.superTypeParameters.params.length > 0) { return true; } @@ -881,6 +882,7 @@ module.exports = { while (annotation && (annotation.type === 'TypeAnnotation' || annotation.type === 'NullableTypeAnnotation')) { annotation = annotation.typeAnnotation; } + if (annotation.type === 'GenericTypeAnnotation' && typeScope(annotation.id.name)) { return typeScope(annotation.id.name); } @@ -929,6 +931,13 @@ module.exports = { } }, + ClassExpression: function(node) { + // TypeParameterDeclaration need to be added to typeScope in order to handle ClassExpressions. + // This visitor is executed before TypeParameterDeclaration are scoped, therefore we postpone + // processing class expressions until when the program exists. + classExpressions.push(node); + }, + ClassProperty: function(node) { if (isAnnotatedClassPropsDeclaration(node)) { markPropTypesAsDeclared(node, resolveTypeAnnotation(node)); @@ -1020,6 +1029,14 @@ module.exports = { typeScope(node.id.name, node.right); }, + TypeParameterDeclaration: function(node) { + const identifier = node.params[0]; + + if (identifier.typeAnnotation) { + typeScope(identifier.name, identifier.typeAnnotation.typeAnnotation); + } + }, + Program: function() { stack = [{}]; }, @@ -1033,6 +1050,12 @@ module.exports = { }, 'Program:exit': function() { + classExpressions.forEach(node => { + if (isSuperTypeParameterPropsDeclaration(node)) { + markPropTypesAsDeclared(node, resolveSuperParameterPropsType(node)); + } + }); + stack = null; const list = components.list(); // Report undeclared proptypes for all classes diff --git a/tests/lib/rules/prop-types.js b/tests/lib/rules/prop-types.js index 13da06c432..1e4c3b13c4 100644 --- a/tests/lib/rules/prop-types.js +++ b/tests/lib/rules/prop-types.js @@ -1684,6 +1684,44 @@ ruleTester.run('prop-types', rule, { } `, parser: 'babel-eslint' + }, { + code: ` + type Props = { foo: string } + function higherOrderComponent() { + return class extends React.Component { + render() { + return
{this.props.foo}
+ } + } + } + `, + parser: 'babel-eslint' + }, { + code: ` + function higherOrderComponent() { + return class extends React.Component

{ + render() { + return

{this.props.foo}
+ } + } + } + `, + parser: 'babel-eslint' + }, { + code: ` + const withOverlayState = (WrappedComponent: ComponentType

): CpmponentType

=> ( + class extends React.Component

{ + constructor(props) { + super(props); + this.state = {foo: props.foo} + } + render() { + return

Hello World
+ } + } + ) + `, + parser: 'babel-eslint' }, // issue #1288 @@ -3272,6 +3310,53 @@ ruleTester.run('prop-types', rule, { type: 'Identifier' }], parser: 'babel-eslint' + }, { + code: ` + type Props = { foo: string } + function higherOrderComponent() { + return class extends React.Component { + render() { + return
{this.props.foo} - {this.props.bar}
+ } + } + } + `, + errors: [{ + message: '\'bar\' is missing in props validation' + }], + parser: 'babel-eslint' + }, { + code: ` + function higherOrderComponent() { + return class extends React.Component

{ + render() { + return

{this.props.foo} - {this.props.bar}
+ } + } + } + `, + errors: [{ + message: '\'bar\' is missing in props validation' + }], + parser: 'babel-eslint' + }, { + code: ` + const withOverlayState = (WrappedComponent: ComponentType

): CpmponentType

=> ( + class extends React.Component

{ + constructor(props) { + super(props); + this.state = {foo: props.foo, bar: props.bar} + } + render() { + return

Hello World
+ } + } + ) + `, + errors: [{ + message: '\'bar\' is missing in props validation' + }], + parser: 'babel-eslint' }, { code: ` type PropsA = {foo: string };