From efb5bd76a149c7a3ab46a79e9f41f78e186dd152 Mon Sep 17 00:00:00 2001 From: Alex Zherdev Date: Sat, 11 Aug 2018 14:57:15 -0700 Subject: [PATCH 1/3] Extract defaultProps detection code from default-props-match-prop-types --- lib/rules/default-props-match-prop-types.js | 127 +--------- lib/util/Components.js | 13 +- lib/util/defaultProps.js | 257 ++++++++++++++++++++ 3 files changed, 278 insertions(+), 119 deletions(-) create mode 100644 lib/util/defaultProps.js diff --git a/lib/rules/default-props-match-prop-types.js b/lib/rules/default-props-match-prop-types.js index 203bd2db2d..10472dfee7 100644 --- a/lib/rules/default-props-match-prop-types.js +++ b/lib/rules/default-props-match-prop-types.js @@ -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. @@ -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. @@ -352,9 +294,8 @@ module.exports = { return { MemberExpression: function(node) { const isPropType = propsUtil.isPropTypesDeclaration(node); - const isDefaultProp = propsUtil.isDefaultPropsDeclaration(node); - if (!isPropType && !isDefaultProp) { + if (!isPropType) { return; } @@ -375,21 +316,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; @@ -399,20 +327,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 + }]); } }, @@ -438,9 +357,8 @@ module.exports = { } const isPropType = propsUtil.isPropTypesDeclaration(node); - const isDefaultProp = propsUtil.isDefaultPropsDeclaration(node); - if (!isPropType && !isDefaultProp) { + if (!isPropType) { return; } @@ -460,11 +378,7 @@ module.exports = { return; } - if (isPropType) { - addPropTypesToComponent(component, getPropTypesFromObjectExpression(expression)); - } else { - addDefaultPropsToComponent(component, getDefaultPropsFromObjectExpression(expression)); - } + addPropTypesToComponent(component, getPropTypesFromObjectExpression(expression)); }, // e.g.: @@ -495,9 +409,8 @@ module.exports = { const propName = astUtil.getPropertyName(node); const isPropType = propName === 'propTypes'; - const isDefaultProp = propName === 'defaultProps' || propName === 'getDefaultProps'; - if (!isPropType && !isDefaultProp) { + if (!isPropType) { return; } @@ -512,11 +425,7 @@ module.exports = { return; } - if (isPropType) { - addPropTypesToComponent(component, getPropTypesFromObjectExpression(expression)); - } else { - addDefaultPropsToComponent(component, getDefaultPropsFromObjectExpression(expression)); - } + addPropTypesToComponent(component, getPropTypesFromObjectExpression(expression)); }, // e.g.: @@ -547,25 +456,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)); - } }); }, diff --git a/lib/util/Components.js b/lib/util/Components.js index b7e2a99a63..1854c8e6a7 100644 --- a/lib/util/Components.js +++ b/lib/util/Components.js @@ -9,9 +9,10 @@ const doctrine = require('doctrine'); const variableUtil = require('./variable'); const pragmaUtil = require('./pragma'); const astUtil = require('./ast'); -const propTypes = require('./propTypes'); +const propTypesUtil = require('./propTypes'); const jsxUtil = require('./jsx'); const usedPropTypesUtil = require('./usedPropTypes'); +const defaultPropsUtil = require('./defaultProps'); function getId(node) { return node && node.range.join(':'); @@ -708,12 +709,15 @@ function componentRule(rule, context) { // Update the provided rule instructions to add the component detection const ruleInstructions = rule(context, components, utils); const updatedRuleInstructions = util._extend({}, ruleInstructions); - const propTypesInstructions = propTypes(context, components, utils); + const propTypesInstructions = propTypesUtil(context, components, utils); const usedPropTypesInstructions = usedPropTypesUtil(context, components, utils); + const defaultPropsInstructions = defaultPropsUtil(context, components, utils); const allKeys = new Set(Object.keys(detectionInstructions).concat( Object.keys(propTypesInstructions), - Object.keys(usedPropTypesInstructions) + Object.keys(usedPropTypesInstructions), + Object.keys(defaultPropsInstructions) )); + allKeys.forEach(instruction => { updatedRuleInstructions[instruction] = function(node) { if (instruction in detectionInstructions) { @@ -725,6 +729,9 @@ function componentRule(rule, context) { if (instruction in usedPropTypesInstructions) { usedPropTypesInstructions[instruction](node); } + if (instruction in defaultPropsInstructions) { + defaultPropsInstructions[instruction](node); + } return ruleInstructions[instruction] ? ruleInstructions[instruction](node) : void 0; }; }); diff --git a/lib/util/defaultProps.js b/lib/util/defaultProps.js new file mode 100644 index 0000000000..00bef47f4c --- /dev/null +++ b/lib/util/defaultProps.js @@ -0,0 +1,257 @@ +/** + * @fileoverview Common defaultProps detection functionality. + */ +'use strict'; + +const astUtil = require('./ast'); +const propsUtil = require('./props'); +const variableUtil = require('./variable'); + +module.exports = function defaultPropsInstructions(context, components, utils) { + const propWrapperFunctions = new Set(context.settings.propWrapperFunctions || []); + + /** + * Try to resolve the node passed in to a variable in the current scope. If the node passed in is not + * an Identifier, then the node is simply returned. + * @param {ASTNode} node The node to resolve. + * @returns {ASTNode|null} Return null if the value could not be resolved, ASTNode otherwise. + */ + function resolveNodeValue(node) { + if (node.type === 'Identifier') { + return variableUtil.findVariableByName(context, node.name); + } + if ( + node.type === 'CallExpression' && + propWrapperFunctions.has(node.callee.name) && + node.arguments && node.arguments[0] + ) { + return resolveNodeValue(node.arguments[0]); + } + return node; + } + + /** + * 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 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) + }); + } + + return { + MemberExpression: function(node) { + const isDefaultProp = propsUtil.isDefaultPropsDeclaration(node); + + if (!isDefaultProp) { + return; + } + + // find component this defaultProps belongs to + const component = utils.getRelatedComponent(node); + if (!component) { + return; + } + + // e.g.: + // MyComponent.propTypes = { + // foo: React.PropTypes.string.isRequired, + // bar: React.PropTypes.string + // }; + // + // or: + // + // 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; + } + + addDefaultPropsToComponent(component, getDefaultPropsFromObjectExpression(expression)); + + return; + } + + // e.g.: + // MyComponent.propTypes.baz = React.PropTypes.string; + if (node.parent.type === 'MemberExpression' && node.parent.parent && + node.parent.parent.type === 'AssignmentExpression') { + addDefaultPropsToComponent(component, [{ + name: node.parent.property.name, + node: node.parent.parent + }]); + } + }, + + // e.g.: + // class Hello extends React.Component { + // static get defaultProps() { + // return { + // name: 'Dean' + // }; + // } + // render() { + // return
Hello {this.props.name}
; + // } + // } + MethodDefinition: function(node) { + if (!node.static || node.kind !== 'get') { + return; + } + + if (!propsUtil.isDefaultPropsDeclaration(node)) { + return; + } + + // find component this propTypes/defaultProps belongs to + const component = components.get(utils.getParentES6Component()); + if (!component) { + return; + } + + const returnStatement = utils.findReturnStatement(node); + if (!returnStatement) { + return; + } + + const expression = resolveNodeValue(returnStatement.argument); + if (!expression || expression.type !== 'ObjectExpression') { + return; + } + + addDefaultPropsToComponent(component, getDefaultPropsFromObjectExpression(expression)); + }, + + // e.g.: + // class Greeting extends React.Component { + // render() { + // return ( + //

Hello, {this.props.foo} {this.props.bar}

+ // ); + // } + // static defaultProps = { + // foo: 'bar', + // bar: 'baz' + // }; + // } + ClassProperty: function(node) { + if (!(node.static && node.value)) { + return; + } + + const propName = astUtil.getPropertyName(node); + const isDefaultProp = propName === 'defaultProps' || propName === 'getDefaultProps'; + + if (!isDefaultProp) { + return; + } + + // find component this propTypes/defaultProps belongs to + const component = components.get(utils.getParentES6Component()); + if (!component) { + return; + } + + const expression = resolveNodeValue(node.value); + if (!expression || expression.type !== 'ObjectExpression') { + return; + } + + addDefaultPropsToComponent(component, getDefaultPropsFromObjectExpression(expression)); + }, + + // e.g.: + // React.createClass({ + // render: function() { + // return
{this.props.foo}
; + // }, + // getDefaultProps: function() { + // return { + // foo: 'default' + // }; + // } + // }); + ObjectExpression: function(node) { + // find component this propTypes/defaultProps belongs to + const component = utils.isES5Component(node) && components.get(node); + if (!component) { + return; + } + + // Search for the proptypes declaration + node.properties.forEach(property => { + if (property.type === 'ExperimentalSpreadProperty' || property.type === 'SpreadElement') { + return; + } + + const isDefaultProp = propsUtil.isDefaultPropsDeclaration(property); + + if (isDefaultProp && property.value.type === 'FunctionExpression') { + const returnStatement = utils.findReturnStatement(property); + if (!returnStatement || returnStatement.argument.type !== 'ObjectExpression') { + return; + } + + addDefaultPropsToComponent(component, getDefaultPropsFromObjectExpression(returnStatement.argument)); + } + }); + } + }; +}; From ce9ba3420625bd93ae2dfe88d3c9732ce63b7dc0 Mon Sep 17 00:00:00 2001 From: Alex Zherdev Date: Sat, 11 Aug 2018 15:16:28 -0700 Subject: [PATCH 2/3] Make require-default-props use defaultProps detection --- lib/rules/default-props-match-prop-types.js | 9 +- lib/rules/require-default-props.js | 124 ++------------------ lib/util/defaultProps.js | 13 +- 3 files changed, 26 insertions(+), 120 deletions(-) diff --git a/lib/rules/default-props-match-prop-types.js b/lib/rules/default-props-match-prop-types.js index 10472dfee7..fb9c082a26 100644 --- a/lib/rules/default-props-match-prop-types.js +++ b/lib/rules/default-props-match-prop-types.js @@ -264,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; @@ -275,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} ); } }); diff --git a/lib/rules/require-default-props.js b/lib/rules/require-default-props.js index ea8b4b153b..6b418cf86a 100644 --- a/lib/rules/require-default-props.js +++ b/lib/rules/require-default-props.js @@ -175,36 +175,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. @@ -219,35 +189,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. @@ -369,9 +310,8 @@ module.exports = { return { MemberExpression: function(node) { const isPropType = propsUtil.isPropTypesDeclaration(node); - const isDefaultProp = propsUtil.isDefaultPropsDeclaration(node); - if (!isPropType && !isDefaultProp) { + if (!isPropType) { return; } @@ -393,39 +333,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 + }]); } }, @@ -451,9 +373,8 @@ module.exports = { } const isPropType = propsUtil.isPropTypesDeclaration(node); - const isDefaultProp = propsUtil.isDefaultPropsDeclaration(node); - if (!isPropType && !isDefaultProp) { + if (!isPropType) { return; } @@ -473,11 +394,7 @@ module.exports = { return; } - if (isPropType) { - addPropTypesToComponent(component, getPropTypesFromObjectExpression(expression)); - } else { - addDefaultPropsToComponent(component, getDefaultPropsFromObjectExpression(expression)); - } + addPropTypesToComponent(component, getPropTypesFromObjectExpression(expression)); }, // e.g.: @@ -507,9 +424,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; } @@ -524,11 +440,7 @@ module.exports = { return; } - if (isPropType) { - addPropTypesToComponent(component, getPropTypesFromObjectExpression(expression)); - } else { - addDefaultPropsToComponent(component, getDefaultPropsFromObjectExpression(expression)); - } + addPropTypesToComponent(component, getPropTypesFromObjectExpression(expression)); }, // e.g.: @@ -559,25 +471,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)); - } }); }, diff --git a/lib/util/defaultProps.js b/lib/util/defaultProps.js index 00bef47f4c..8546c5e654 100644 --- a/lib/util/defaultProps.js +++ b/lib/util/defaultProps.js @@ -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 || []); /** @@ -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 })); } @@ -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 }); } From b81e19a10ff8b076b8e13b4bd8217fd017e57ea8 Mon Sep 17 00:00:00 2001 From: Alex Zherdev Date: Mon, 20 Aug 2018 14:12:30 -0700 Subject: [PATCH 3/3] Use object.fromentries --- lib/util/defaultProps.js | 10 ++++++---- package.json | 1 + 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/lib/util/defaultProps.js b/lib/util/defaultProps.js index 8546c5e654..32b0aec051 100644 --- a/lib/util/defaultProps.js +++ b/lib/util/defaultProps.js @@ -3,6 +3,7 @@ */ 'use strict'; +const fromEntries = require('object.fromentries'); const astUtil = require('./ast'); const propsUtil = require('./props'); const variableUtil = require('./variable'); @@ -85,10 +86,11 @@ module.exports = function defaultPropsInstructions(context, components, utils) { } const defaults = component.defaultProps || {}; - const newDefaultProps = defaultProps.reduce((acc, prop) => { - acc[prop.name] = prop; - return acc; - }, Object.assign({}, defaults)); + const newDefaultProps = Object.assign( + {}, + defaults, + fromEntries(defaultProps.map(prop => [prop.name, prop])) + ); components.set(component.node, { defaultProps: newDefaultProps diff --git a/package.json b/package.json index 8fb035558b..0337c577e4 100644 --- a/package.json +++ b/package.json @@ -28,6 +28,7 @@ "doctrine": "^2.1.0", "has": "^1.0.3", "jsx-ast-utils": "^2.0.1", + "object.fromentries": "^2.0.0", "prop-types": "^15.6.2" }, "devDependencies": {