Skip to content

Commit aa3a5ff

Browse files
feat: new rule mapDispatchToProps-prefer-shorthand (#19)
* feat: new rule mapDispatchToProps-prefer-shorthand
1 parent 6b447b1 commit aa3a5ff

File tree

5 files changed

+205
-1
lines changed

5 files changed

+205
-1
lines changed
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
# Enforces that mapDispatchToProps uses a shorthand method to wrap actions in dispatch calls whenever possible. (react-redux/mapDispatchToProps-prefer-shorthand)
2+
3+
>[mapDispatchToProps(dispatch, [ownProps]): dispatchProps] (Object or Function): If an object is passed, each function inside it is assumed to be a Redux action creator. An object with the same function names, but with every action creator wrapped into a dispatch call so they may be invoked directly, will be merged into the component’s props.
4+
5+
[source](https://github.com/reactjs/react-redux/blob/master/docs/api.md#connectmapstatetoprops-mapdispatchtoprops-mergeprops-options)
6+
7+
## Rule details
8+
9+
The following pattern is considered incorrect:
10+
11+
```js
12+
const mapDispatchToProps = (dispatch) => ({
13+
action: () => dispatch(action())
14+
})
15+
16+
// it should use equivalent shorthand wrapping instead:
17+
// const mapDispatchToProps = {action}
18+
```
19+
20+
```js
21+
const mapDispatchToProps = (dispatch) => ({
22+
action: () => dispatch(action()),
23+
action1: (arg1, arg2) => dispatch(action(arg1, arg2))
24+
})
25+
```
26+
27+
The following patterns are considered correct:
28+
29+
30+
```js
31+
const mapDispatchToProps = {action}
32+
```
33+
34+
```js
35+
const mapDispatchToProps = (dispatch) => ({
36+
action: () => dispatch(actionHelper(true))
37+
})
38+
```
39+
40+
```js
41+
const mapDispatchToProps = (dispatch) => ({
42+
action: () => dispatch(action()),
43+
action1: (arg1, arg2) => dispatch(action(arg1 + arg2))
44+
})
45+
```

index.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
const rules = {
22
'connect-prefer-minimum-two-arguments': require('./lib/rules/connect-prefer-minimum-two-arguments'),
33
'connect-prefer-named-arguments': require('./lib/rules/connect-prefer-named-arguments'),
4+
'mapDispatchToProps-prefer-shorthand': require('./lib/rules/mapDispatchToProps-prefer-shorthand'),
45
'mapDispatchToProps-returns-object': require('./lib/rules/mapDispatchToProps-returns-object'),
56
'mapDispatchToProps-prefer-parameters-names': require('./lib/rules/mapDispatchToProps-prefer-parameters-names'),
67
'mapStateToProps-no-store': require('./lib/rules/mapStateToProps-no-store'),
@@ -27,6 +28,7 @@ module.exports = {
2728
'react-redux/connect-prefer-minimum-two-arguments': 0,
2829
'react-redux/connect-prefer-named-arguments': 2,
2930
'react-redux/mapDispatchToProps-prefer-parameters-names': 2,
31+
'react-redux/mapDispatchToProps-prefer-shorthand': 2,
3032
'react-redux/mapDispatchToProps-returns-object': 2,
3133
'react-redux/mapStateToProps-no-store': 2,
3234
'react-redux/mapStateToProps-prefer-parameters-names': 2,
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
const isReactReduxConnect = require('../isReactReduxConnect');
2+
const utils = require('../utils');
3+
4+
const report = function (context, node) {
5+
context.report({
6+
message: 'mapDispatchToProps should use a shorthand dispatch wrapping instead',
7+
node,
8+
});
9+
};
10+
11+
const getParamsString = (params, context) =>
12+
params.map(param => context.getSource(param)).join(',');
13+
14+
15+
const propertyCanUseShortHandButDoesnt = (context, prop, dispatchName) => {
16+
const propName = prop.key && prop.key.name;
17+
const sourceCode = context.getSource(prop.value).replace(/(\r\n|\n|\r|\t| |;)/gm, '');
18+
if (prop.value && (prop.value.type === 'ArrowFunctionExpression' ||
19+
prop.value.type === 'FunctionExpression')) {
20+
const fncDef = prop.value;
21+
const paramString = getParamsString(fncDef.params, context);
22+
if ((sourceCode === `(${paramString})=>${dispatchName}(${propName}(${paramString}))`)
23+
|| (sourceCode === `function(${paramString}){return${dispatchName}(${propName}(${paramString}))}`
24+
)) {
25+
return true;
26+
}
27+
}
28+
return false;
29+
};
30+
31+
const checkReturnNode = function (context, returnNode, dispatchName) {
32+
if (returnNode.properties.every(prop =>
33+
propertyCanUseShortHandButDoesnt(context, prop, dispatchName))
34+
) {
35+
report(context, returnNode);
36+
}
37+
};
38+
39+
40+
module.exports = function (context) {
41+
return {
42+
VariableDeclaration(node) {
43+
node.declarations.forEach((decl) => {
44+
if (decl.id && decl.id.name === 'mapDispatchToProps') {
45+
if (decl.init && (
46+
decl.init.type === 'ArrowFunctionExpression' ||
47+
decl.init.type === 'FunctionExpression'
48+
)) {
49+
const returnNode = utils.getReturnNode(decl.init);
50+
if (returnNode && returnNode.type === 'ObjectExpression') {
51+
checkReturnNode(context, returnNode, 'dispatch');
52+
}
53+
}
54+
}
55+
});
56+
},
57+
FunctionDeclaration(node) {
58+
if (node.id && node.id.name === 'mapDispatchToProps') {
59+
const returnNode = utils.getReturnNode(node.body);
60+
if (returnNode && returnNode.type === 'ObjectExpression') {
61+
checkReturnNode(context, returnNode, 'dispatch');
62+
}
63+
}
64+
},
65+
CallExpression(node) {
66+
if (isReactReduxConnect(node)) {
67+
const mapDispatchToProps = node.arguments && node.arguments[1];
68+
if (mapDispatchToProps && (
69+
mapDispatchToProps.type === 'ArrowFunctionExpression' ||
70+
mapDispatchToProps.type === 'FunctionExpression')
71+
) {
72+
const returnNode = utils.getReturnNode(mapDispatchToProps);
73+
if (returnNode && returnNode.type === 'ObjectExpression') {
74+
checkReturnNode(context, returnNode, 'dispatch');
75+
}
76+
}
77+
}
78+
},
79+
};
80+
};

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@
3535
"eslint-plugin-standard": "^3.0.1",
3636
"husky": "^0.14.3",
3737
"mocha": "^4.0.1",
38-
"semantic-release": "^11.0.2"
38+
"semantic-release": "^12.4.1"
3939
},
4040
"engines": {
4141
"node": ">=6.10.0"
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
require('babel-eslint');
2+
3+
const rule = require('../../../lib/rules/mapDispatchToProps-prefer-shorthand');
4+
const RuleTester = require('eslint').RuleTester;
5+
6+
const parserOptions = {
7+
ecmaVersion: 6,
8+
sourceType: 'module',
9+
ecmaFeatures: {
10+
experimentalObjectRestSpread: true,
11+
},
12+
};
13+
14+
const ruleTester = new RuleTester({ parserOptions });
15+
16+
ruleTester.run('mapDispatchToProps-prefer-shorthand', rule, {
17+
valid: [
18+
'function mapDispatchToProps () {return {action}}',
19+
`const mapDispatchToProps = dispatch => ({
20+
onDoSomething: function() {return dispatch(toDo())},
21+
action2: (arg1, arg2) => dispatch(action2(arg1, arg2)),
22+
});`,
23+
`const mapDispatchToProps = dispatch => ({
24+
onDoSomething: () => dispatch(onDoSomething()),
25+
action2: (arg1, arg2) => dispatch(action2(arg1 + arg2)),
26+
});`,
27+
'const mapDispatchToProps = {}',
28+
'const mapDispatchToProps = null',
29+
'const mapDispatchToProps = actionsMap',
30+
'const mapDispatchToProps = {...actions}',
31+
'const mapDispatchToProps = {anAction: anAction}',
32+
`export default connect(
33+
state => ({
34+
productsList: state.Products.productsList,
35+
}),
36+
{ fetchProducts }
37+
)(Products);
38+
`,
39+
`const mapDispatchToProps = dispatch => ({
40+
onDoSomething: () => dispatch(toSomethingElse())
41+
});`,
42+
'connect(null, null)(App)',
43+
'function mapDispatchToProps () {return aThing}',
44+
],
45+
invalid: [{
46+
code: `const mapDispatchToProps = dispatch => ({
47+
onDoSomething: () => dispatch(onDoSomething()),
48+
action1: () => dispatch(action1()),
49+
action2: (arg1, arg2) => dispatch(action2(arg1, arg2)),
50+
});`,
51+
errors: [
52+
{
53+
message: 'mapDispatchToProps should use a shorthand dispatch wrapping instead',
54+
},
55+
],
56+
}, {
57+
code: `const mapDispatchToProps = dispatch => ({
58+
onDoSomething: function() {return dispatch(onDoSomething())}
59+
});`,
60+
errors: [
61+
{
62+
message: 'mapDispatchToProps should use a shorthand dispatch wrapping instead',
63+
},
64+
],
65+
}, {
66+
code: `const mapDispatchToProps = function(dispatch) {
67+
return { requestFilteredItems: (client, keyword) =>
68+
dispatch(requestFilteredItems(client, keyword))
69+
};
70+
}`,
71+
errors: [
72+
{
73+
message: 'mapDispatchToProps should use a shorthand dispatch wrapping instead',
74+
},
75+
],
76+
}],
77+
});

0 commit comments

Comments
 (0)