Skip to content

Commit

Permalink
add support for flow generics defaining stateful components PropTypes
Browse files Browse the repository at this point in the history
  • Loading branch information
pmizio committed Mar 20, 2018
1 parent a5f259f commit c2972b8
Show file tree
Hide file tree
Showing 2 changed files with 255 additions and 0 deletions.
86 changes: 86 additions & 0 deletions lib/rules/require-default-props.js
Original file line number Diff line number Diff line change
Expand Up @@ -300,6 +300,50 @@ module.exports = {
});
}

/**
* Extracts a PropType from a TypeAnnotation contained in generic node.
* @param {ASTNode} node TypeAnnotation node.
* @returns {Object[]} Array of PropType object representations, to be consumed by `addPropTypesToComponent`.
*/
function getPropTypesFromGeneric(node) {
let annotation = resolveGenericTypeAnnotation(node);

if (annotation && annotation.id) {
annotation = variableUtil.findVariableByName(context, annotation.id.name);
}

const properties = annotation ? (annotation.properties || []) : [];

const props = properties.filter(property => property.type === 'ObjectTypeProperty');

return props.map(property => {
// the `key` property is not present in ObjectTypeProperty nodes, so we need to get the key name manually.
const tokens = context.getFirstTokens(property, 1);
const name = tokens[0].value;

return {
name: name,
isRequired: !property.optional,
node: property
};
});
}

function hasPropTypesAsGeneric(node) {
return node.parent && node.parent.type === 'ClassDeclaration';
}

function handlePropTypesAsGeneric(node) {
const component = components.get(utils.getParentES6Component());
if (!component) {
return;
}

if (node.params[0]) {
addPropTypesToComponent(component, getPropTypesFromGeneric(node.params[0], context));
}
}

// --------------------------------------------------------------------------
// Public API
// --------------------------------------------------------------------------
Expand Down Expand Up @@ -519,6 +563,48 @@ module.exports = {
});
},

// e.g.:
// type HelloProps = {
// foo?: string
// };
// class Hello extends React.Component<HelloProps> {
// static defaultProps = {
// foo: 'default'
// }
// render() {
// return <div>{this.props.foo}</div>;
// }
// };
TypeParameterInstantiation: function(node) {
if (hasPropTypesAsGeneric(node)) {
handlePropTypesAsGeneric(node);
return;
}
// const getGenericPropTypes = function(nodee) {
// let properties;

// let annotation = resolveGenericTypeAnnotation(nodee);

// if (annotation && annotation.id) {
// annotation = variableUtil.findVariableByName(context, annotation.id.name);
// }

// properties = annotation ? (annotation.properties || []) : [];
// };
// console.log(node); //eslint-disable-line
// console.log('>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>'); //eslint-disable-line
// const test = hasPropsAsTypeParameterInstantiation(node); //eslint-disable-line
// console.log(test); //eslint-disable-line
// console.log('>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>'); //eslint-disable-line
// if (test) {
// handlePropsAsTypeParameterInstantiation(node);
// return;
// }
// const isPropType = propsUtil.isPropTypesDeclaration(node);
// console.log(isPropType); //eslint-disable-line
// handlePropTypeAnnotationClassProperty(node);
},

// Check for type annotations in stateless components
FunctionDeclaration: handleStatelessComponent,
ArrowFunctionExpression: handleStatelessComponent,
Expand Down
169 changes: 169 additions & 0 deletions tests/lib/rules/require-default-props.js
Original file line number Diff line number Diff line change
Expand Up @@ -749,6 +749,92 @@ ruleTester.run('require-default-props', rule, {
].join('\n'),
parser: 'babel-eslint',
options: [{forbidDefaultForRequired: true}]
},
// test support for React PropTypes as Component's class generic
{
code: [
'type HelloProps = {',
' foo: string,',
' bar?: string',
'};',

'class Hello extends React.Component<HelloProps> {',
' static defaultProps = {',
' bar: "bar"',
' }',

' render() {',
' return <div>Hello {this.props.foo}</div>;',
' }',
'}'
].join('\n'),
parser: 'babel-eslint',
options: [{forbidDefaultForRequired: true}]
},
{
code: [
'type HelloProps = {',
' foo: string,',
' bar?: string',
'};',

'class Hello extends Component<HelloProps> {',
' static defaultProps = {',
' bar: "bar"',
' }',

' render() {',
' return <div>Hello {this.props.foo}</div>;',
' }',
'}'
].join('\n'),
parser: 'babel-eslint',
options: [{forbidDefaultForRequired: true}]
},
{
code: [
'type HelloProps = {',
' foo: string,',
' bar?: string',
'};',

'type HelloState = {',
' dummyState: string',
'};',

'class Hello extends Component<HelloProps, HelloState> {',
' static defaultProps = {',
' bar: "bar"',
' }',

' render() {',
' return <div>Hello {this.props.foo}</div>;',
' }',
'}'
].join('\n'),
parser: 'babel-eslint',
options: [{forbidDefaultForRequired: true}]
},
{
code: [
'type HelloProps = {',
' foo?: string,',
' bar?: string',
'};',

'class Hello extends Component<HelloProps> {',
' static defaultProps = {',
' foo: "foo",',
' bar: "bar"',
' }',

' render() {',
' return <div>Hello {this.props.foo}</div>;',
' }',
'}'
].join('\n'),
parser: 'babel-eslint',
options: [{forbidDefaultForRequired: true}]
}
],

Expand Down Expand Up @@ -2006,6 +2092,89 @@ ruleTester.run('require-default-props', rule, {
errors: [{
message: 'propType "foo" is required and should not have a defaultProp declaration.'
}]
},
// test support for React PropTypes as Component's class generic
{
code: [
'type HelloProps = {',
' foo: string,',
' bar?: string',
'};',

'class Hello extends React.Component<HelloProps> {',

' render() {',
' return <div>Hello {this.props.foo}</div>;',
' }',
'}'
].join('\n'),
parser: 'babel-eslint',
errors: [{
message: 'propType "bar" is not required, but has no corresponding defaultProp declaration.'
}]
},
{
code: [
'type HelloProps = {',
' foo: string,',
' bar?: string',
'};',

'class Hello extends Component<HelloProps> {',

' render() {',
' return <div>Hello {this.props.foo}</div>;',
' }',
'}'
].join('\n'),
parser: 'babel-eslint',
errors: [{
message: 'propType "bar" is not required, but has no corresponding defaultProp declaration.'
}]
},
{
code: [
'type HelloProps = {',
' foo: string,',
' bar?: string',
'};',

'type HelloState = {',
' dummyState: string',
'};',

'class Hello extends Component<HelloProps, HelloState> {',

' render() {',
' return <div>Hello {this.props.foo}</div>;',
' }',
'}'
].join('\n'),
parser: 'babel-eslint',
errors: [{
message: 'propType "bar" is not required, but has no corresponding defaultProp declaration.'
}]
},
{
code: [
'type HelloProps = {',
' foo?: string,',
' bar?: string',
'};',

'class Hello extends Component<HelloProps> {',

' render() {',
' return <div>Hello {this.props.foo}</div>;',
' }',
'}'
].join('\n'),
parser: 'babel-eslint',
errors: [{
message: 'propType "foo" is not required, but has no corresponding defaultProp declaration.'
}, {
message: 'propType "bar" is not required, but has no corresponding defaultProp declaration.'
}]
}
]
});

0 comments on commit c2972b8

Please sign in to comment.