Skip to content

Commit

Permalink
Merge pull request #1733 from Miziak/master
Browse files Browse the repository at this point in the history
Issue #1724 support for Flow generic PropTypes declaration in require-default-props rule
  • Loading branch information
ljharb committed Mar 28, 2018
2 parents 55523f3 + 7d43363 commit dc2cd4c
Show file tree
Hide file tree
Showing 2 changed files with 232 additions and 0 deletions.
63 changes: 63 additions & 0 deletions lib/rules/require-default-props.js
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 && 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,25 @@ 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;
}
},

// 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
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 dc2cd4c

Please sign in to comment.