Skip to content

Commit

Permalink
Merge pull request #1236 from gpeal/gpeal--prop-types
Browse files Browse the repository at this point in the history
Register prop-types when set through Flow SuperTypeParameters
  • Loading branch information
ljharb committed Jun 8, 2017
2 parents d77a205 + ee66c3b commit 52f54be
Show file tree
Hide file tree
Showing 2 changed files with 228 additions and 0 deletions.
38 changes: 38 additions & 0 deletions lib/rules/prop-types.js
Expand Up @@ -151,6 +151,21 @@ module.exports = {
return false;
}

/**
* Checks if we are declaring a props as a generic type in a flow-annotated class.
*
* @param {ASTNode} node the AST node being checked.
* @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.superTypeParameters && node.superTypeParameters.params.length >= 2) {
return true;
}
}
return false;
}

/**
* Checks if we are declaring a prop
* @param {ASTNode} node The AST node being checked.
Expand Down Expand Up @@ -803,6 +818,23 @@ module.exports = {
return annotation;
}

/**
* Resolve the type annotation for a given class declaration node with superTypeParameters.
*
* @param {ASTNode} node The annotation or a node containing the type annotation.
* @returns {ASTNode} The resolved type annotation for the node.
*/
function resolveSuperParameterPropsType(node) {
var annotation = node.superTypeParameters.params[1];
while (annotation && (annotation.type === 'TypeAnnotation' || annotation.type === 'NullableTypeAnnotation')) {
annotation = annotation.typeAnnotation;
}
if (annotation.type === 'GenericTypeAnnotation' && typeScope(annotation.id.name)) {
return typeScope(annotation.id.name);
}
return annotation;
}

/**
* @param {ASTNode} node We expect either an ArrowFunctionExpression,
* FunctionDeclaration, or FunctionExpression
Expand Down Expand Up @@ -839,6 +871,12 @@ module.exports = {
// --------------------------------------------------------------------------

return {
ClassDeclaration: function(node) {
if (isSuperTypeParameterPropsDeclaration(node)) {
markPropTypesAsDeclared(node, resolveSuperParameterPropsType(node));
}
},

ClassProperty: function(node) {
if (isAnnotatedClassPropsDeclaration(node)) {
markPropTypesAsDeclared(node, resolveTypeAnnotation(node));
Expand Down
190 changes: 190 additions & 0 deletions tests/lib/rules/prop-types.js
Expand Up @@ -1414,6 +1414,88 @@ ruleTester.run('prop-types', rule, {
'}'
].join('\n'),
parser: 'babel-eslint'
}, {
code: [
'type Person = {',
' firstname: string',
'};',
'class Hello extends React.Component<void, Person, void> {',
' render () {',
' return <div>Hello {this.props.firstname}</div>;',
' }',
'}'
].join('\n'),
parser: 'babel-eslint'
}, {
code: [
'type Person = {',
' firstname: string',
'};',
'class Hello extends React.Component<void, Person, void> {',
' render () {',
' const { firstname } = this.props;',
' return <div>Hello {firstname}</div>;',
' }',
'}'
].join('\n'),
parser: 'babel-eslint'
}, {
code: [
'type Person = {',
' firstname: string',
'};',
'class Hello extends React.Component<void, Person, void> {',
' render () {',
' const {',
' firstname,',
' } = this.props;',
' return <div>Hello {firstname}</div>;',
' }',
'}'
].join('\n'),
parser: 'babel-eslint'
}, {
code: [
'type Props = {name: {firstname: string;};};',
'class Hello extends React.Component<void, Props, void> {',
' render () {',
' return <div>Hello {this.props.name.firstname}</div>;',
' }',
'}'
].join('\n'),
parser: 'babel-eslint'
}, {
code: [
'import type Props from "fake";',
'class Hello extends React.Component<void, Props, void> {',
' render () {',
' return <div>Hello {this.props.firstname}</div>;',
' }',
'}'
].join('\n'),
parser: 'babel-eslint'
}, {
code: [
'type Person = {',
' firstname: string',
'};',
'class Hello extends React.Component<void, { person: Person }, void> {',
' render () {',
' return <div>Hello {this.props.person.firstname}</div>;',
' }',
'}'
].join('\n'),
parser: 'babel-eslint'
}, {
code: [
'type Props = {result?: {ok?: ?string | boolean;}|{ok?: ?number | Array}};',
'class Hello extends React.Component<void, Props, void> {',
' render () {',
' return <div>Hello {this.props.result.ok}</div>;',
' }',
'}'
].join('\n'),
parser: 'babel-eslint'
}
],

Expand Down Expand Up @@ -2603,6 +2685,114 @@ ruleTester.run('prop-types', rule, {
errors: [
{message: '\'name\' is missing in props validation'}
]
}, {
code: [
'type Person = {',
' firstname: string',
'};',
'class Hello extends React.Component<void, Person, void> {',
' render () {',
' return <div>Hello {this.props.lastname}</div>;',
' }',
'}'
].join('\n'),
errors: [{
message: '\'lastname\' is missing in props validation',
line: 6,
column: 35,
type: 'Identifier'
}],
parser: 'babel-eslint'
}, {
code: [
'type Person = {',
' firstname: string',
'};',
'class Hello extends React.Component<void, Person, void> {',
' render () {',
' const { lastname } = this.props;',
' return <div>Hello {lastname}</div>;',
' }',
'}'
].join('\n'),
errors: [{
message: '\'lastname\' is missing in props validation',
line: 6,
column: 13,
type: 'Property'
}],
parser: 'babel-eslint'
}, {
code: [
'type Person = {',
' firstname: string',
'};',
'class Hello extends React.Component<void, Person, void> {',
' render () {',
' const {',
' lastname,',
' } = this.props;',
' return <div>Hello {lastname}</div>;',
' }',
'}'
].join('\n'),
errors: [{
message: '\'lastname\' is missing in props validation',
line: 7,
column: 7,
type: 'Property'
}],
parser: 'babel-eslint'
}, {
code: [
'type Props = {name: {firstname: string;};};',
'class Hello extends React.Component<void, Props, void> {',
' render () {',
' return <div>Hello {this.props.name.lastname}</div>;',
' }',
'}'
].join('\n'),
errors: [{
message: '\'name.lastname\' is missing in props validation',
line: 4,
column: 40,
type: 'Identifier'
}],
parser: 'babel-eslint'
}, {
code: [
'type Props = {result?: {ok: string | boolean;}|{ok: number | Array}};',
'class Hello extends React.Component<void, Props, void> {',
' render () {',
' return <div>Hello {this.props.result.notok}</div>;',
' }',
'}'
].join('\n'),
errors: [{
message: '\'result.notok\' is missing in props validation',
line: 4,
column: 42,
type: 'Identifier'
}],
parser: 'babel-eslint'
}, {
code: [
'type Person = {',
' firstname: string',
'};',
'class Hello extends React.Component<void, { person: Person }, void> {',
' render () {',
' return <div>Hello {this.props.person.lastname}</div>;',
' }',
'}'
].join('\n'),
errors: [{
message: '\'person.lastname\' is missing in props validation',
line: 6,
column: 42,
type: 'Identifier'
}],
parser: 'babel-eslint'
}
]
});

0 comments on commit 52f54be

Please sign in to comment.