Skip to content

Add further support for Flow prop types. #1138

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .eslintrc
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
{
"env": {
"node": true
"node": true,
"es6": true
},
ecmaFeatures: {
jsx: true
Expand Down
77 changes: 72 additions & 5 deletions lib/rules/prop-types.js
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,45 @@ module.exports = {
return isClassUsage || isStatelessFunctionUsage || isNextPropsUsage;
}

/**
* Checks whether the given identifier or member expression points to
* `Component`, `React.Component` or `React.PureComponent`.
* Doesn't support aliasing react to anything other than `React`.
* @param {ASTNode} node The AST node being checke.d
* @returns {Boolean} True if the node points to react, otherwise false.
*/
function isReactComponentClass(node) {
if (!node) {
return false;
}
if (node.type === 'Identifier' && node.name === 'Component') {
return true;
} else if (node.type === 'MemberExpression' && node.object.type === 'Identifier') {
return (
node.object.name === 'React' &&
node.property.type === 'Identifier' &&
(node.property.name === 'Component' || node.property.name === 'PureComponent')
);
}
return false;
}

/**
* Checks if we are declaring a class which extends `React.Component`
* with `superTypeParameters`, e.g:
* `class Foo extends React.Component<null, Object, null> {}`.
* @param {ASTNode} node The AST node being checked.
* @returns {Boolean} True if the node is a type annotated props declaration, false if not.
*/
function isAnnotatedClass(node) {
if (node && (node.type === 'ClassExpression' || node.type === 'ClassDeclaration')) {
if (isReactComponentClass(node.superClass) && node.superTypeParameters) {
return true;
}
}
return false;
}

/**
* Checks if we are declaring a `props` class property with a flow type annotation.
* @param {ASTNode} node The AST node being checked.
Expand Down Expand Up @@ -445,14 +484,25 @@ module.exports = {
* Creates the representation of the React props type annotation for the component.
* The representation is used to verify nested used properties.
* @param {ASTNode} annotation Type annotation for the props class property.
* @param {Set<ASTNode>} seen Keeps track of annotations we've already seen.
* @return {Object|Boolean} The representation of the declaration, true means
* the property is declared without the need for further analysis.
*/
function buildTypeAnnotationDeclarationTypes(annotation) {
function buildTypeAnnotationDeclarationTypes(annotation, seen) {
if (seen === void 0) {
// Keeps track of annotations we've already seen to
// prevent problems with cyclic types.
seen = new Set();
}
if (seen.has(annotation)) {
// this must be a recursive type annotation, just accept anything.
return true;
}
seen.add(annotation);
switch (annotation.type) {
case 'GenericTypeAnnotation':
if (typeScope(annotation.id.name)) {
return buildTypeAnnotationDeclarationTypes(typeScope(annotation.id.name));
return buildTypeAnnotationDeclarationTypes(typeScope(annotation.id.name), seen);
}
return true;
case 'ObjectTypeAnnotation':
Expand All @@ -461,7 +511,7 @@ module.exports = {
children: {}
};
iterateProperties(annotation.properties, function(childKey, childValue) {
shapeTypeDefinition.children[childKey] = buildTypeAnnotationDeclarationTypes(childValue);
shapeTypeDefinition.children[childKey] = buildTypeAnnotationDeclarationTypes(childValue, seen);
});
return shapeTypeDefinition;
case 'UnionTypeAnnotation':
Expand All @@ -470,7 +520,7 @@ module.exports = {
children: []
};
for (var i = 0, j = annotation.types.length; i < j; i++) {
var type = buildTypeAnnotationDeclarationTypes(annotation.types[i]);
var type = buildTypeAnnotationDeclarationTypes(annotation.types[i], seen);
// keep only complex type
if (type !== true) {
if (type.children === true) {
Expand All @@ -491,7 +541,7 @@ module.exports = {
return {
type: 'object',
children: {
__ANY_KEY__: buildTypeAnnotationDeclarationTypes(annotation.elementType)
__ANY_KEY__: buildTypeAnnotationDeclarationTypes(annotation.elementType, seen)
}
};
default:
Expand Down Expand Up @@ -818,11 +868,28 @@ module.exports = {
markAnnotatedFunctionArgumentsAsDeclared(node);
}

/**
* Handles classes possibly annotated with Flow.
* @param {ASTNode} node We expect either a ClassDeclaration or ClassExpression.
*/
function handleClass(node) {
if (isAnnotatedClass(node)) {
var typeParameters = node.superTypeParameters.params;
if (typeParameters.length > 1) {
markPropTypesAsDeclared(node, resolveTypeAnnotation(typeParameters[1]));
}
}
}

// --------------------------------------------------------------------------
// Public
// --------------------------------------------------------------------------

return {
ClassDeclaration: handleClass,

ClassExpression: handleClass,

ClassProperty: function(node) {
if (isAnnotatedClassPropsDeclaration(node)) {
markPropTypesAsDeclared(node, resolveTypeAnnotation(node));
Expand Down
Loading