Skip to content

Commit

Permalink
Make require-default-props use defaultProps detection
Browse files Browse the repository at this point in the history
  • Loading branch information
alexzherdev committed Aug 17, 2018
1 parent 4744f92 commit b588947
Show file tree
Hide file tree
Showing 3 changed files with 26 additions and 120 deletions.
9 changes: 5 additions & 4 deletions lib/rules/default-props-match-prop-types.js
Expand Up @@ -265,8 +265,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 @@ -276,13 +277,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 Down
124 changes: 11 additions & 113 deletions lib/rules/require-default-props.js
Expand Up @@ -176,36 +176,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(property => sourceCode.getText(property.key).replace(QUOTES_REGEX, ''));
}

/**
* 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 @@ -220,35 +190,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 || {};

defaultProps.forEach(defaultProp => {
defaults[defaultProp] = true;
});

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

/**
* 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 @@ -370,9 +311,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 @@ -394,39 +334,21 @@ module.exports = {
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) {
addPropTypesToComponent(component, getPropTypesFromObjectExpression(expression));
} else {
addDefaultPropsToComponent(component, getDefaultPropsFromObjectExpression(expression));
}

addPropTypesToComponent(component, getPropTypesFromObjectExpression(expression));
return;
}

// e.g.:
// MyComponent.propTypes.baz = PropTypes.string;
if (node.parent.type === 'MemberExpression' && 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, [node.parent.property.name]);
}

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

Expand All @@ -452,9 +374,8 @@ module.exports = {
}

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

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

Expand All @@ -474,11 +395,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 @@ -508,9 +425,8 @@ module.exports = {
}

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

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

Expand All @@ -525,11 +441,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 @@ -560,25 +472,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
13 changes: 10 additions & 3 deletions lib/util/defaultProps.js
Expand Up @@ -7,7 +7,10 @@ const astUtil = require('./ast');
const propsUtil = require('./props');
const variableUtil = require('./variable');

const QUOTES_REGEX = /^["']|["']$/g;

module.exports = function defaultPropsInstructions(context, components, utils) {
const sourceCode = context.getSourceCode();
const propWrapperFunctions = new Set(context.settings.propWrapperFunctions || []);

/**
Expand Down Expand Up @@ -45,7 +48,7 @@ module.exports = function defaultPropsInstructions(context, components, utils) {
}

return objectExpression.properties.map(defaultProp => ({
name: defaultProp.key.name,
name: sourceCode.getText(defaultProp.key).replace(QUOTES_REGEX, ''),
node: defaultProp
}));
}
Expand Down Expand Up @@ -81,10 +84,14 @@ module.exports = function defaultPropsInstructions(context, components, utils) {
return;
}

const defaults = component.defaultProps || [];
const defaults = component.defaultProps || {};
const newDefaultProps = defaultProps.reduce((acc, prop) => {
acc[prop.name] = prop;
return acc;
}, Object.assign({}, defaults));

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

Expand Down

0 comments on commit b588947

Please sign in to comment.