Skip to content

Commit c2972b8

Browse files
committed
add support for flow generics defaining stateful components PropTypes
1 parent a5f259f commit c2972b8

File tree

2 files changed

+255
-0
lines changed

2 files changed

+255
-0
lines changed

lib/rules/require-default-props.js

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -300,6 +300,50 @@ module.exports = {
300300
});
301301
}
302302

303+
/**
304+
* Extracts a PropType from a TypeAnnotation contained in generic node.
305+
* @param {ASTNode} node TypeAnnotation node.
306+
* @returns {Object[]} Array of PropType object representations, to be consumed by `addPropTypesToComponent`.
307+
*/
308+
function getPropTypesFromGeneric(node) {
309+
let annotation = resolveGenericTypeAnnotation(node);
310+
311+
if (annotation && annotation.id) {
312+
annotation = variableUtil.findVariableByName(context, annotation.id.name);
313+
}
314+
315+
const properties = annotation ? (annotation.properties || []) : [];
316+
317+
const props = properties.filter(property => property.type === 'ObjectTypeProperty');
318+
319+
return props.map(property => {
320+
// the `key` property is not present in ObjectTypeProperty nodes, so we need to get the key name manually.
321+
const tokens = context.getFirstTokens(property, 1);
322+
const name = tokens[0].value;
323+
324+
return {
325+
name: name,
326+
isRequired: !property.optional,
327+
node: property
328+
};
329+
});
330+
}
331+
332+
function hasPropTypesAsGeneric(node) {
333+
return node.parent && node.parent.type === 'ClassDeclaration';
334+
}
335+
336+
function handlePropTypesAsGeneric(node) {
337+
const component = components.get(utils.getParentES6Component());
338+
if (!component) {
339+
return;
340+
}
341+
342+
if (node.params[0]) {
343+
addPropTypesToComponent(component, getPropTypesFromGeneric(node.params[0], context));
344+
}
345+
}
346+
303347
// --------------------------------------------------------------------------
304348
// Public API
305349
// --------------------------------------------------------------------------
@@ -519,6 +563,48 @@ module.exports = {
519563
});
520564
},
521565

566+
// e.g.:
567+
// type HelloProps = {
568+
// foo?: string
569+
// };
570+
// class Hello extends React.Component<HelloProps> {
571+
// static defaultProps = {
572+
// foo: 'default'
573+
// }
574+
// render() {
575+
// return <div>{this.props.foo}</div>;
576+
// }
577+
// };
578+
TypeParameterInstantiation: function(node) {
579+
if (hasPropTypesAsGeneric(node)) {
580+
handlePropTypesAsGeneric(node);
581+
return;
582+
}
583+
// const getGenericPropTypes = function(nodee) {
584+
// let properties;
585+
586+
// let annotation = resolveGenericTypeAnnotation(nodee);
587+
588+
// if (annotation && annotation.id) {
589+
// annotation = variableUtil.findVariableByName(context, annotation.id.name);
590+
// }
591+
592+
// properties = annotation ? (annotation.properties || []) : [];
593+
// };
594+
// console.log(node); //eslint-disable-line
595+
// console.log('>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>'); //eslint-disable-line
596+
// const test = hasPropsAsTypeParameterInstantiation(node); //eslint-disable-line
597+
// console.log(test); //eslint-disable-line
598+
// console.log('>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>'); //eslint-disable-line
599+
// if (test) {
600+
// handlePropsAsTypeParameterInstantiation(node);
601+
// return;
602+
// }
603+
// const isPropType = propsUtil.isPropTypesDeclaration(node);
604+
// console.log(isPropType); //eslint-disable-line
605+
// handlePropTypeAnnotationClassProperty(node);
606+
},
607+
522608
// Check for type annotations in stateless components
523609
FunctionDeclaration: handleStatelessComponent,
524610
ArrowFunctionExpression: handleStatelessComponent,

tests/lib/rules/require-default-props.js

Lines changed: 169 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -749,6 +749,92 @@ ruleTester.run('require-default-props', rule, {
749749
].join('\n'),
750750
parser: 'babel-eslint',
751751
options: [{forbidDefaultForRequired: true}]
752+
},
753+
// test support for React PropTypes as Component's class generic
754+
{
755+
code: [
756+
'type HelloProps = {',
757+
' foo: string,',
758+
' bar?: string',
759+
'};',
760+
761+
'class Hello extends React.Component<HelloProps> {',
762+
' static defaultProps = {',
763+
' bar: "bar"',
764+
' }',
765+
766+
' render() {',
767+
' return <div>Hello {this.props.foo}</div>;',
768+
' }',
769+
'}'
770+
].join('\n'),
771+
parser: 'babel-eslint',
772+
options: [{forbidDefaultForRequired: true}]
773+
},
774+
{
775+
code: [
776+
'type HelloProps = {',
777+
' foo: string,',
778+
' bar?: string',
779+
'};',
780+
781+
'class Hello extends Component<HelloProps> {',
782+
' static defaultProps = {',
783+
' bar: "bar"',
784+
' }',
785+
786+
' render() {',
787+
' return <div>Hello {this.props.foo}</div>;',
788+
' }',
789+
'}'
790+
].join('\n'),
791+
parser: 'babel-eslint',
792+
options: [{forbidDefaultForRequired: true}]
793+
},
794+
{
795+
code: [
796+
'type HelloProps = {',
797+
' foo: string,',
798+
' bar?: string',
799+
'};',
800+
801+
'type HelloState = {',
802+
' dummyState: string',
803+
'};',
804+
805+
'class Hello extends Component<HelloProps, HelloState> {',
806+
' static defaultProps = {',
807+
' bar: "bar"',
808+
' }',
809+
810+
' render() {',
811+
' return <div>Hello {this.props.foo}</div>;',
812+
' }',
813+
'}'
814+
].join('\n'),
815+
parser: 'babel-eslint',
816+
options: [{forbidDefaultForRequired: true}]
817+
},
818+
{
819+
code: [
820+
'type HelloProps = {',
821+
' foo?: string,',
822+
' bar?: string',
823+
'};',
824+
825+
'class Hello extends Component<HelloProps> {',
826+
' static defaultProps = {',
827+
' foo: "foo",',
828+
' bar: "bar"',
829+
' }',
830+
831+
' render() {',
832+
' return <div>Hello {this.props.foo}</div>;',
833+
' }',
834+
'}'
835+
].join('\n'),
836+
parser: 'babel-eslint',
837+
options: [{forbidDefaultForRequired: true}]
752838
}
753839
],
754840

@@ -2006,6 +2092,89 @@ ruleTester.run('require-default-props', rule, {
20062092
errors: [{
20072093
message: 'propType "foo" is required and should not have a defaultProp declaration.'
20082094
}]
2095+
},
2096+
// test support for React PropTypes as Component's class generic
2097+
{
2098+
code: [
2099+
'type HelloProps = {',
2100+
' foo: string,',
2101+
' bar?: string',
2102+
'};',
2103+
2104+
'class Hello extends React.Component<HelloProps> {',
2105+
2106+
' render() {',
2107+
' return <div>Hello {this.props.foo}</div>;',
2108+
' }',
2109+
'}'
2110+
].join('\n'),
2111+
parser: 'babel-eslint',
2112+
errors: [{
2113+
message: 'propType "bar" is not required, but has no corresponding defaultProp declaration.'
2114+
}]
2115+
},
2116+
{
2117+
code: [
2118+
'type HelloProps = {',
2119+
' foo: string,',
2120+
' bar?: string',
2121+
'};',
2122+
2123+
'class Hello extends Component<HelloProps> {',
2124+
2125+
' render() {',
2126+
' return <div>Hello {this.props.foo}</div>;',
2127+
' }',
2128+
'}'
2129+
].join('\n'),
2130+
parser: 'babel-eslint',
2131+
errors: [{
2132+
message: 'propType "bar" is not required, but has no corresponding defaultProp declaration.'
2133+
}]
2134+
},
2135+
{
2136+
code: [
2137+
'type HelloProps = {',
2138+
' foo: string,',
2139+
' bar?: string',
2140+
'};',
2141+
2142+
'type HelloState = {',
2143+
' dummyState: string',
2144+
'};',
2145+
2146+
'class Hello extends Component<HelloProps, HelloState> {',
2147+
2148+
' render() {',
2149+
' return <div>Hello {this.props.foo}</div>;',
2150+
' }',
2151+
'}'
2152+
].join('\n'),
2153+
parser: 'babel-eslint',
2154+
errors: [{
2155+
message: 'propType "bar" is not required, but has no corresponding defaultProp declaration.'
2156+
}]
2157+
},
2158+
{
2159+
code: [
2160+
'type HelloProps = {',
2161+
' foo?: string,',
2162+
' bar?: string',
2163+
'};',
2164+
2165+
'class Hello extends Component<HelloProps> {',
2166+
2167+
' render() {',
2168+
' return <div>Hello {this.props.foo}</div>;',
2169+
' }',
2170+
'}'
2171+
].join('\n'),
2172+
parser: 'babel-eslint',
2173+
errors: [{
2174+
message: 'propType "foo" is not required, but has no corresponding defaultProp declaration.'
2175+
}, {
2176+
message: 'propType "bar" is not required, but has no corresponding defaultProp declaration.'
2177+
}]
20092178
}
20102179
]
20112180
});

0 commit comments

Comments
 (0)