Skip to content

Commit dc2cd4c

Browse files
authored
Merge pull request #1733 from Miziak/master
Issue #1724 support for Flow generic PropTypes declaration in require-default-props rule
2 parents 55523f3 + 7d43363 commit dc2cd4c

File tree

2 files changed

+232
-0
lines changed

2 files changed

+232
-0
lines changed

lib/rules/require-default-props.js

Lines changed: 63 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 && 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,25 @@ 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+
},
584+
522585
// Check for type annotations in stateless components
523586
FunctionDeclaration: handleStatelessComponent,
524587
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)