Skip to content
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

Extract defaultProps detection #1942

Merged
merged 3 commits into from Sep 27, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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