From 0ef6255b15d79c4be69aa3ae4ec0b9394dd30751 Mon Sep 17 00:00:00 2001 From: Denis Tuzhik Date: Wed, 2 Dec 2020 13:06:25 +0200 Subject: [PATCH] feat: Add an option to allow retun value of bindActionCreators call Fixes #30 --- .editorconfig | 16 ++++ .../mapDispatchToProps-returns-object.md | 24 +++++ .../mapDispatchToProps-returns-object.js | 12 ++- lib/utils.js | 13 ++- .../mapDispatchToProps-returns-object.js | 92 +++++++++++++++++++ 5 files changed, 153 insertions(+), 4 deletions(-) create mode 100644 .editorconfig diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..41302d1 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,16 @@ +# EditorConfig helps developers define and maintain consistent +# coding styles between different editors and IDEs +# editorconfig.org + +root = true + +[*] +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true +indent_style = space +indent_size = 2 + +[*.{diff,md}] +trim_trailing_whitespace = false diff --git a/docs/rules/mapDispatchToProps-returns-object.md b/docs/rules/mapDispatchToProps-returns-object.md index d669ddd..bdd6243 100644 --- a/docs/rules/mapDispatchToProps-returns-object.md +++ b/docs/rules/mapDispatchToProps-returns-object.md @@ -39,6 +39,30 @@ const mapDispatchToProps = {anAction: anAction} const mapDispatchToProps = (dispatch) => ({anAction: dispatch(anAction())}) ``` +## Options + +### allowReturnBindFn +Rule example +```js +{ + 'react-redux/mapDispatchToProps-returns-object': ['error', { allowReturnBindFn: true }], +} +``` + +If this option is set to true, return the result of `bindActionCreators` considered to be valid: + +```js +const mapDispatchToProps = (dispatch) => { + return bindActionCreators( + { + requestFilteredItems, + showAlert: showAlertAction, + }, + dispatch + ); +} +``` + ## Not supported use cases. #### mapDispatchToProps is a function but actions are not bound to dispatch diff --git a/lib/rules/mapDispatchToProps-returns-object.js b/lib/rules/mapDispatchToProps-returns-object.js index 3d9cd8b..389f8d8 100644 --- a/lib/rules/mapDispatchToProps-returns-object.js +++ b/lib/rules/mapDispatchToProps-returns-object.js @@ -10,6 +10,11 @@ const report = function (context, node) { module.exports = function (context) { + const config = context.options[0] || {}; + + const shouldAllowBindActionCreators = returnNode => + config.allowReturnBindFn && utils.isReturnNodeBindActionCreatorsCall(returnNode); + return { VariableDeclaration(node) { node.declarations.forEach((decl) => { @@ -19,7 +24,7 @@ module.exports = function (context) { decl.init.type === 'FunctionExpression' )) { const returnNode = utils.getReturnNode(decl.init); - if (!utils.isObject(returnNode)) { + if (!utils.isObject(returnNode) && !shouldAllowBindActionCreators(returnNode)) { report(context, node); } } @@ -29,7 +34,8 @@ module.exports = function (context) { FunctionDeclaration(node) { if (node.id && node.id.name === 'mapDispatchToProps') { const returnNode = utils.getReturnNode(node.body); - if (!utils.isObject(returnNode)) { + + if (!utils.isObject(returnNode) && !shouldAllowBindActionCreators(returnNode)) { report(context, node); } } @@ -42,7 +48,7 @@ module.exports = function (context) { mapDispatchToProps.type === 'FunctionExpression') ) { const returnNode = utils.getReturnNode(mapDispatchToProps); - if (!utils.isObject(returnNode)) { + if (!utils.isObject(returnNode) && !shouldAllowBindActionCreators(returnNode)) { report(context, node); } } diff --git a/lib/utils.js b/lib/utils.js index 0a57144..c2052cc 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -1,14 +1,24 @@ 'use strict'; +const BIND_ACTION_CREATORS_FN_NAME = 'bindActionCreators'; + const isObject = node => node && ( node.type === 'ObjectExpression' || node.type === 'Identifier' ); +const isReturnNodeBindActionCreatorsCall = (node) => { + if (node.type === 'CallExpression') { + return node.callee.type === 'Identifier' && node.callee.name === BIND_ACTION_CREATORS_FN_NAME; + } + + return false; +}; + const getReturnNode = (node) => { const body = node && node.body; if (!body) { return node; - } else if (isObject(body)) { + } else if (isObject(body) || body.type === 'CallExpression') { return body; } else if (body.type === 'BlockStatement') { return getReturnNode(body); @@ -28,4 +38,5 @@ const getReturnNode = (node) => { module.exports = { getReturnNode, isObject, + isReturnNodeBindActionCreatorsCall, }; diff --git a/tests/lib/rules/mapDispatchToProps-returns-object.js b/tests/lib/rules/mapDispatchToProps-returns-object.js index d505a82..ea66be8 100644 --- a/tests/lib/rules/mapDispatchToProps-returns-object.js +++ b/tests/lib/rules/mapDispatchToProps-returns-object.js @@ -93,3 +93,95 @@ ruleTester.run('mapDispatchToProps-returns-object', rule, { ], }], }); + +ruleTester.run('mapDispatchToProps-returns-object-allowReturnBindFn', rule, { + valid: [ + { + options: [{ allowReturnBindFn: true }], + code: `function mapDispatchToProps(dispatch) { + return bindActionCreators( + { + requestFilteredItems, + showAlert: showAlertAction, + }, + dispatch + ); + }`, + }, + { + options: [{ allowReturnBindFn: true }], + code: `const mapDispatchToProps = (dispatch) => { + return bindActionCreators( + { + requestFilteredItems, + showAlert: showAlertAction, + }, + dispatch + ); + }`, + }, + { + options: [{ allowReturnBindFn: true }], + code: `const mapDispatchToProps = (dispatch) => + bindActionCreators( + { + requestFilteredItems, + showAlert: showAlertAction, + }, + dispatch + ); + `, + }, + { + options: [{ allowReturnBindFn: true }], + code: `export default connect( + state => ({ + productsList: state.Products.productsList, + }), + function (dispatch) { + return bindActionCreators( + { + requestFilteredItems, + showAlert: showAlertAction, + }, + dispatch + ) + } + )(Products);`, + }, + { + options: [{ allowReturnBindFn: true }], + code: `export default connect( + state => ({ + productsList: state.Products.productsList, + }), + (dispatch) => { + return bindActionCreators( + { + requestFilteredItems, + showAlert: showAlertAction, + }, + dispatch + ) + } + )(Products);`, + }, + { + options: [{ allowReturnBindFn: true }], + code: `export default connect( + state => ({ + productsList: state.Products.productsList, + }), + (dispatch) => + bindActionCreators( + { + requestFilteredItems, + showAlert: showAlertAction, + }, + dispatch + ) + )(Products);`, + }, + ], + invalid: [], +});