Skip to content

Commit

Permalink
Merge pull request #1942 from alexzherdev/default-props-detection
Browse files Browse the repository at this point in the history
Extract defaultProps detection
  • Loading branch information
ljharb committed Sep 27, 2018
2 parents bd083bf + b81e19a commit 6075926
Show file tree
Hide file tree
Showing 5 changed files with 304 additions and 236 deletions.
136 changes: 16 additions & 120 deletions lib/rules/default-props-match-prop-types.js
Expand Up @@ -200,39 +200,6 @@ module.exports = {
});
}

/**
* Extracts a DefaultProp from an ObjectExpression node.
* @param {ASTNode} objectExpression ObjectExpression node.
* @returns {Object|string} Object representation of a defaultProp, to be consumed by
* `addDefaultPropsToComponent`, or string "unresolved", if the defaultProps
* from this ObjectExpression can't be resolved.
*/
function getDefaultPropsFromObjectExpression(objectExpression) {
const hasSpread = objectExpression.properties.find(property => property.type === 'ExperimentalSpreadProperty' || property.type === 'SpreadElement');

if (hasSpread) {
return 'unresolved';
}

return objectExpression.properties.map(defaultProp => ({
name: defaultProp.key.name,
node: defaultProp
}));
}

/**
* Marks a component's DefaultProps declaration as "unresolved". A component's DefaultProps is
* marked as "unresolved" if we cannot safely infer the values of its defaultProps declarations
* without risking false negatives.
* @param {Object} component The component to mark.
* @returns {void}
*/
function markDefaultPropsAsUnresolved(component) {
components.set(component.node, {
defaultProps: 'unresolved'
});
}

/**
* Adds propTypes to the component passed in.
* @param {ASTNode} component The component to add the propTypes to.
Expand All @@ -247,31 +214,6 @@ module.exports = {
});
}

/**
* Adds defaultProps to the component passed in.
* @param {ASTNode} component The component to add the defaultProps to.
* @param {String[]|String} defaultProps defaultProps to add to the component or the string "unresolved"
* if this component has defaultProps that can't be resolved.
* @returns {void}
*/
function addDefaultPropsToComponent(component, defaultProps) {
// Early return if this component's defaultProps is already marked as "unresolved".
if (component.defaultProps === 'unresolved') {
return;
}

if (defaultProps === 'unresolved') {
markDefaultPropsAsUnresolved(component);
return;
}

const defaults = component.defaultProps || [];

components.set(component.node, {
defaultProps: defaults.concat(defaultProps)
});
}

/**
* Tries to find a props type annotation in a stateless component.
* @param {ASTNode} node The AST node to look for a props type annotation.
Expand Down Expand Up @@ -322,8 +264,9 @@ module.exports = {
return;
}

defaultProps.forEach(defaultProp => {
const prop = propFromName(propTypes, defaultProp.name);
Object.keys(defaultProps).forEach(defaultPropName => {
const defaultProp = defaultProps[defaultPropName];
const prop = propFromName(propTypes, defaultPropName);

if (prop && (allowRequiredDefaults || !prop.isRequired)) {
return;
Expand All @@ -333,13 +276,13 @@ module.exports = {
context.report(
defaultProp.node,
'defaultProp "{{name}}" defined for isRequired propType.',
{name: defaultProp.name}
{name: defaultPropName}
);
} else {
context.report(
defaultProp.node,
'defaultProp "{{name}}" has no corresponding propTypes declaration.',
{name: defaultProp.name}
{name: defaultPropName}
);
}
});
Expand All @@ -352,9 +295,8 @@ module.exports = {
return {
MemberExpression: function(node) {
const isPropType = propsUtil.isPropTypesDeclaration(node);
const isDefaultProp = propsUtil.isDefaultPropsDeclaration(node);

if (!isPropType && !isDefaultProp) {
if (!isPropType) {
return;
}

Expand All @@ -375,21 +317,8 @@ module.exports = {
// MyComponent.propTypes = myPropTypes;
if (node.parent.type === 'AssignmentExpression') {
const expression = resolveNodeValue(node.parent.right);
if (!expression || expression.type !== 'ObjectExpression') {
// If a value can't be found, we mark the defaultProps declaration as "unresolved", because
// we should ignore this component and not report any errors for it, to avoid false-positives
// with e.g. external defaultProps declarations.
if (isDefaultProp) {
markDefaultPropsAsUnresolved(component);
}

return;
}

if (isPropType) {
if (expression && expression.type === 'ObjectExpression') {
addPropTypesToComponent(component, getPropTypesFromObjectExpression(expression));
} else {
addDefaultPropsToComponent(component, getDefaultPropsFromObjectExpression(expression));
}

return;
Expand All @@ -399,20 +328,11 @@ module.exports = {
// MyComponent.propTypes.baz = React.PropTypes.string;
if (node.parent.type === 'MemberExpression' && node.parent.parent &&
node.parent.parent.type === 'AssignmentExpression') {
if (isPropType) {
addPropTypesToComponent(component, [{
name: node.parent.property.name,
isRequired: propsUtil.isRequiredPropType(node.parent.parent.right),
node: node.parent.parent
}]);
} else {
addDefaultPropsToComponent(component, [{
name: node.parent.property.name,
node: node.parent.parent
}]);
}

return;
addPropTypesToComponent(component, [{
name: node.parent.property.name,
isRequired: propsUtil.isRequiredPropType(node.parent.parent.right),
node: node.parent.parent
}]);
}
},

Expand All @@ -438,9 +358,8 @@ module.exports = {
}

const isPropType = propsUtil.isPropTypesDeclaration(node);
const isDefaultProp = propsUtil.isDefaultPropsDeclaration(node);

if (!isPropType && !isDefaultProp) {
if (!isPropType) {
return;
}

Expand All @@ -460,11 +379,7 @@ module.exports = {
return;
}

if (isPropType) {
addPropTypesToComponent(component, getPropTypesFromObjectExpression(expression));
} else {
addDefaultPropsToComponent(component, getDefaultPropsFromObjectExpression(expression));
}
addPropTypesToComponent(component, getPropTypesFromObjectExpression(expression));
},

// e.g.:
Expand Down Expand Up @@ -495,9 +410,8 @@ module.exports = {

const propName = astUtil.getPropertyName(node);
const isPropType = propName === 'propTypes';
const isDefaultProp = propName === 'defaultProps' || propName === 'getDefaultProps';

if (!isPropType && !isDefaultProp) {
if (!isPropType) {
return;
}

Expand All @@ -512,11 +426,7 @@ module.exports = {
return;
}

if (isPropType) {
addPropTypesToComponent(component, getPropTypesFromObjectExpression(expression));
} else {
addDefaultPropsToComponent(component, getDefaultPropsFromObjectExpression(expression));
}
addPropTypesToComponent(component, getPropTypesFromObjectExpression(expression));
},

// e.g.:
Expand Down Expand Up @@ -547,25 +457,11 @@ module.exports = {
}

const isPropType = propsUtil.isPropTypesDeclaration(property);
const isDefaultProp = propsUtil.isDefaultPropsDeclaration(property);

if (!isPropType && !isDefaultProp) {
return;
}

if (isPropType && property.value.type === 'ObjectExpression') {
addPropTypesToComponent(component, getPropTypesFromObjectExpression(property.value));
return;
}

if (isDefaultProp && property.value.type === 'FunctionExpression') {
const returnStatement = utils.findReturnStatement(property);
if (!returnStatement || returnStatement.argument.type !== 'ObjectExpression') {
return;
}

addDefaultPropsToComponent(component, getDefaultPropsFromObjectExpression(returnStatement.argument));
}
});
},

Expand Down

0 comments on commit 6075926

Please sign in to comment.