From 8a56b01d21e5ec9883516813993a2f98d9867969 Mon Sep 17 00:00:00 2001 From: Joachim Seminck Date: Sun, 27 Aug 2017 21:41:52 +0300 Subject: [PATCH 1/3] Add support for ClassExpression in prop-types rule --- lib/rules/prop-types.js | 8 ++++++- tests/lib/rules/prop-types.js | 39 +++++++++++++++++++++++++++++++++++ 2 files changed, 46 insertions(+), 1 deletion(-) diff --git a/lib/rules/prop-types.js b/lib/rules/prop-types.js index 13aa98e718..0ff574116d 100644 --- a/lib/rules/prop-types.js +++ b/lib/rules/prop-types.js @@ -173,7 +173,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; } @@ -920,6 +920,12 @@ module.exports = { } }, + ClassExpression: function(node) { + if (isSuperTypeParameterPropsDeclaration(node)) { + markPropTypesAsDeclared(node, resolveSuperParameterPropsType(node)); + } + }, + ClassProperty: function(node) { if (isAnnotatedClassPropsDeclaration(node)) { markPropTypesAsDeclared(node, resolveTypeAnnotation(node)); diff --git a/tests/lib/rules/prop-types.js b/tests/lib/rules/prop-types.js index f661197ffc..5527660a0d 100644 --- a/tests/lib/rules/prop-types.js +++ b/tests/lib/rules/prop-types.js @@ -1669,6 +1669,45 @@ ruleTester.run('prop-types', rule, { `, settings: {react: {flowVersion: '0.53'}}, 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 `function Foo() { From b92f2c62afcd631c98a7faf3b0f18732c4bf4ffd Mon Sep 17 00:00:00 2001 From: Joachim Seminck Date: Sun, 27 Aug 2017 22:35:01 +0300 Subject: [PATCH 2/3] Add invalid test cases, defer class expressions till program exit --- lib/rules/prop-types.js | 23 ++++++++++++++++++++--- tests/lib/rules/prop-types.js | 29 +++++++++++++++++++++++++++++ 2 files changed, 49 insertions(+), 3 deletions(-) diff --git a/lib/rules/prop-types.js b/lib/rules/prop-types.js index 0ff574116d..aa3ebed086 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'; @@ -872,6 +873,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); } @@ -921,9 +923,10 @@ module.exports = { }, ClassExpression: function(node) { - if (isSuperTypeParameterPropsDeclaration(node)) { - markPropTypesAsDeclared(node, resolveSuperParameterPropsType(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) { @@ -1017,6 +1020,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 = [{}]; }, @@ -1030,6 +1041,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 5527660a0d..682cfc9fd1 100644 --- a/tests/lib/rules/prop-types.js +++ b/tests/lib/rules/prop-types.js @@ -3295,6 +3295,35 @@ 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' } ] }); From 36c8aed4cbaa18d9dfaa23e85d072d6aef4b357b Mon Sep 17 00:00:00 2001 From: Joachim Seminck Date: Sun, 27 Aug 2017 22:51:31 +0300 Subject: [PATCH 3/3] Add another invalid test case, using the example from the issue --- tests/lib/rules/prop-types.js | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/tests/lib/rules/prop-types.js b/tests/lib/rules/prop-types.js index 682cfc9fd1..43b7f96bbd 100644 --- a/tests/lib/rules/prop-types.js +++ b/tests/lib/rules/prop-types.js @@ -1692,8 +1692,7 @@ ruleTester.run('prop-types', rule, { } `, parser: 'babel-eslint' - }, - { + }, { code: ` const withOverlayState = (WrappedComponent: ComponentType

): CpmponentType

=> ( class extends React.Component

{ @@ -3324,6 +3323,24 @@ ruleTester.run('prop-types', rule, { 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' } ] });