Skip to content

Commit 488aaad

Browse files
committed
Add support for Flow annotations on stateless components (fixes #467)
1 parent 73e3657 commit 488aaad

File tree

2 files changed

+82
-5
lines changed

2 files changed

+82
-5
lines changed

lib/rules/prop-types.js

Lines changed: 45 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ module.exports = Components.detect(function(context, components, utils) {
6161
* @param {ASTNode} node The AST node being checked.
6262
* @returns {Boolean} True if the node is a type annotated props declaration, false if not.
6363
*/
64-
function isAnnotatedPropsDeclaration(node) {
64+
function isAnnotatedClassPropsDeclaration(node) {
6565
if (node && node.type === 'ClassProperty') {
6666
var tokens = context.getFirstTokens(node, 2);
6767
if (
@@ -76,6 +76,26 @@ module.exports = Components.detect(function(context, components, utils) {
7676
return false;
7777
}
7878

79+
/**
80+
* Checks if we are declaring a `props` argument with a flow type annotation.
81+
* @param {ASTNode} node The AST node being checked.
82+
* @returns {Boolean} True if the node is a type annotated props declaration, false if not.
83+
*/
84+
function isAnnotatedFunctionPropsDeclaration(node) {
85+
if (node && node.params && node.params.length) {
86+
var tokens = context.getFirstTokens(node.params[0], 2);
87+
if (
88+
node.params[0].typeAnnotation && (
89+
tokens[0].value === 'props' ||
90+
(tokens[1] && tokens[1].value === 'props')
91+
)
92+
) {
93+
return true;
94+
}
95+
}
96+
return false;
97+
}
98+
7999
/**
80100
* Checks if we are declaring a prop
81101
* @param {ASTNode} node The AST node being checked.
@@ -719,13 +739,33 @@ module.exports = Components.detect(function(context, components, utils) {
719739
}
720740
}
721741

742+
/**
743+
* @param {ASTNode} node We expect either an ArrowFunctionExpression,
744+
* FunctionDeclaration, or FunctionExpression
745+
*/
746+
function markAnnotatedFunctionArgumentsAsDeclared(node) {
747+
if (!node.params || !node.params.length || !isAnnotatedFunctionPropsDeclaration(node)) {
748+
return;
749+
}
750+
markPropTypesAsDeclared(node, resolveTypeAnnotation(node.params[0]));
751+
}
752+
753+
/**
754+
* @param {ASTNode} node We expect either an ArrowFunctionExpression,
755+
* FunctionDeclaration, or FunctionExpression
756+
*/
757+
function handleStatelessComponent(node) {
758+
markDestructuredFunctionArgumentsAsUsed(node);
759+
markAnnotatedFunctionArgumentsAsDeclared(node);
760+
}
761+
722762
// --------------------------------------------------------------------------
723763
// Public
724764
// --------------------------------------------------------------------------
725765

726766
return {
727767
ClassProperty: function(node) {
728-
if (isAnnotatedPropsDeclaration(node)) {
768+
if (isAnnotatedClassPropsDeclaration(node)) {
729769
markPropTypesAsDeclared(node, resolveTypeAnnotation(node));
730770
} else if (isPropTypesDeclaration(node)) {
731771
markPropTypesAsDeclared(node, node.value);
@@ -745,11 +785,11 @@ module.exports = Components.detect(function(context, components, utils) {
745785
markPropTypesAsUsed(node);
746786
},
747787

748-
FunctionDeclaration: markDestructuredFunctionArgumentsAsUsed,
788+
FunctionDeclaration: handleStatelessComponent,
749789

750-
ArrowFunctionExpression: markDestructuredFunctionArgumentsAsUsed,
790+
ArrowFunctionExpression: handleStatelessComponent,
751791

752-
FunctionExpression: markDestructuredFunctionArgumentsAsUsed,
792+
FunctionExpression: handleStatelessComponent,
753793

754794
MemberExpression: function(node) {
755795
var type;

tests/lib/rules/prop-types.js

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1108,6 +1108,43 @@ ruleTester.run('prop-types', rule, {
11081108
'}'
11091109
].join('\n'),
11101110
parserOptions: parserOptions
1111+
}, {
1112+
// Flow annotations on stateless components
1113+
code: [
1114+
'type Props = {',
1115+
' firstname: string;',
1116+
' lastname: string;',
1117+
'};',
1118+
'function Hello(props: Props): React.Element {',
1119+
' const {firstname, lastname} = props;',
1120+
' return <div>Hello {firstname} {lastname}</div>',
1121+
'}'
1122+
].join('\n'),
1123+
parser: 'babel-eslint'
1124+
}, {
1125+
code: [
1126+
'type Props = {',
1127+
' firstname: string;',
1128+
' lastname: string;',
1129+
'};',
1130+
'const Hello = function(props: Props): React.Element {',
1131+
' const {firstname, lastname} = props;',
1132+
' return <div>Hello {firstname} {lastname}</div>',
1133+
'}'
1134+
].join('\n'),
1135+
parser: 'babel-eslint'
1136+
}, {
1137+
code: [
1138+
'type Props = {',
1139+
' firstname: string;',
1140+
' lastname: string;',
1141+
'};',
1142+
'const Hello = (props: Props): React.Element => {',
1143+
' const {firstname, lastname} = props;',
1144+
' return <div>Hello {firstname} {lastname}</div>',
1145+
'}'
1146+
].join('\n'),
1147+
parser: 'babel-eslint'
11111148
}
11121149
],
11131150

0 commit comments

Comments
 (0)