Skip to content

Commit

Permalink
add new rules prefer-invoke, prefer-map, prefer-wrapper-methods
Browse files Browse the repository at this point in the history
  • Loading branch information
ganimomer committed Sep 6, 2015
1 parent 173d6de commit 7ba1279
Show file tree
Hide file tree
Showing 10 changed files with 266 additions and 1 deletion.
8 changes: 7 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,10 @@ Finally, enable all of the rules that you would like to use.
"lodash3/no-unnecessary-bind": 1,
"lodash3/unwrap": 1,
"lodash3/prefer-compact": 1,
"lodash3/no-double-unwrap": 1
"lodash3/no-double-unwrap": 1,
"lodash3/prefer-map": 1,
"lodash3/prefer-wrapper-methods": 1,

This comment has been minimized.

Copy link
@cafesanu

cafesanu Sep 6, 2015

I think you meant prefer-wrapper-method

This comment has been minimized.

Copy link
@ganimomer

ganimomer Sep 7, 2015

Author Contributor

I did, and fixed it at a later commit. Good eye!

"lodash3/prefer-invoke": 1
}
}
```
Expand All @@ -59,6 +62,9 @@ Finally, enable all of the rules that you would like to use.
* [unwrap](docs/rules/unwrap.md): Prevent chaining without evaluation via `value()` or non-chainable methods like `max()`.,
* [prefer-compact](docs/rules/prefer-compact.md): Prefer `_.compact` over `_.filter` for only truthy values.
* [no-double-unwrap](docs/rules/no-double-unwrap.md): Do not use `.value()` on chains that have already ended (e.g. with `max()` or `reduce()`)
* [prefer-map](docs/rules/prefer-map.md): Prefer `_.map` over `_.forEach` with a `push` inside.
* [prefer-wrapper-methods](docs/rules/prefer-wrapper-methods.md): Prefer using array and string methods in the chain and not the initial value, e.g. `_(str).split(' ')...`
* [prefer-invoke](docs/rules/prefer-invoke.md): Prefer using `_.invoke` over `_.map` with a method call inside.


# License
Expand Down
30 changes: 30 additions & 0 deletions docs/rules/prefer-invoke.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# Prefer invoke

When using `_.map` with a method call of each item in the collection, it could improve readability by switching to `_.invoke`

## Rule Details

This rule takes no arguments.

The following patterns are considered warnings:

```js

_.map(arr, function(x) { return x.f(a, b)});

_(arr).filter(f).map(function(x) { return x.f()}).value();
```

The following patterns are not considered warnings:

```js

var x = _.invoke(arr, 'f');

var x = _.invoke(collection, 'split', ':');
```


## When Not To Use It

If you do not want to enforce using `_.invoke`, and prefer using `_.map` with a method call instead.
28 changes: 28 additions & 0 deletions docs/rules/prefer-map.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# Prefer map

When using `_.forEach` that pushes into an array, it could improve readability to use `_.map` instead.

## Rule Details

This rule takes no arguments.

The following patterns are considered warnings:

```js

_.forEach(arr, function(x) { newArr.push(f(x))}

```
The following patterns are not considered warnings:
```js

_.forEach(arr, function(x) { if (x.a) {a.push(x)}})

```
## When Not To Use It
If you do not want to enforce using `map`, you should not use this rule.
31 changes: 31 additions & 0 deletions docs/rules/prefer-wrapper-methods.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# Prefer wrapper methods

When starting a chain with an initial value that contains a call to an array or string method, it could be better to move that method to the chain itself.

## Rule Details

This rule takes no arguments.

The following patterns are considered warnings:

```js

_(str.split(' ')).map(f).reduce(g);

_.chain(str.split(' ')).map(f).reduce(g).value();

```

The following patterns are not considered warnings:

```js

_(str).split(' ').map(f).reduce(g);

_.chain(str).split(' ').map(f).reduce(g).value();
```


## When Not To Use It

If you do not want to enforce using `map`, you should not use this rule.
25 changes: 25 additions & 0 deletions lib/rules/prefer-invoke.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/**
* @fileoverview Rule to check if a call to map should be a call to invoke
*/
'use strict';

//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------

module.exports = function (context) {
var lodashUtil = require('../util/lodashUtil');
var astUtil = require('../util/astUtil');

function isFunctionMethodCallOfParam(func) {
return astUtil.isCallFromObject(astUtil.getValueReturnedInFirstLine(func), astUtil.getFirstParamName(func));
}

return {
CallExpression: function (node) {
if (lodashUtil.isCallToMethod(node, 'map') && isFunctionMethodCallOfParam(lodashUtil.getLodashIteratee(node))) {
context.report(node, 'Prefer _.invoke over map to a method call.');
}
}
};
};
27 changes: 27 additions & 0 deletions lib/rules/prefer-map.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/**
* @fileoverview Rule to check if a call to _.forEach should be a call to _.filter
*/
'use strict';

//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------

module.exports = function (context) {
var lodashUtil = require('../util/lodashUtil');
var astUtil = require('../util/astUtil');

function onlyHasPush(func) {
var firstLine = astUtil.getFirstFunctionLine(func);
var exp = func.type === 'ArrowFunctionExpression' ? firstLine : firstLine.expression;
return astUtil.getMethodName(exp) === 'push';
}

return {
CallExpression: function (node) {
if (lodashUtil.isCallToMethod(node, 'forEach') && onlyHasPush(lodashUtil.getLodashIteratee(node))) {
context.report(node, 'Prefer _.map over a _.forEach with a push to an array inside');
}
}
};
};
25 changes: 25 additions & 0 deletions lib/rules/prefer-wrapper-method.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/**
* @fileoverview Rule to check if there's a method in the chain start that can be in the chain
*/
'use strict';

//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------

module.exports = function (context) {
var lodashUtil = require('../util/lodashUtil');
var _ = require('lodash');
var WRAPPER_METHODS = ['concat', 'join', 'pop', 'push', 'reverse', 'shift', 'slice', 'sort', 'splice', 'unshift', 'replace', 'split'];
function isCallToWrapperMethod(node) {
return node && node.type === 'CallExpression' && _.includes(WRAPPER_METHODS, _.get(node, 'callee.property.name'));
}

return {
CallExpression: function (node) {
if (lodashUtil.isLodashChainStart(node) && isCallToWrapperMethod(node.arguments[0])) {
context.report(node, 'Prefer {{name}} with wrapper method over inside the chain start.', {name: node.arguments[0].callee.property.name});
}
}
};
};
33 changes: 33 additions & 0 deletions tests/lib/rules/prefer-invoke.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
'use strict';

// ------------------------------------------------------------------------------
// Requirements
// ------------------------------------------------------------------------------

var rule = require('../../../lib/rules/prefer-invoke');
var RuleTester = require('eslint').RuleTester;

// ------------------------------------------------------------------------------
// Tests
// ------------------------------------------------------------------------------

var ruleTester = new RuleTester();
var errors = [{message: 'Prefer _.invoke over map to a method call.'}];
ruleTester.run('prefer-invoke', rule, {
valid: [{
code: 'var x = _.invoke(arr, "f")'
}, {
code: 'var x = _.invoke(arr, "split", " ")'
}],
invalid: [{
code: '_.map(a, function(x) {return x.f()});',
errors: errors
}, {
code: '_(a).filter(f).map(function(x) { return x.split(" ")})',
errors: errors
}, {
code: '_.map(arr, x => x.f())',
ecmaFeatures: {arrowFunctions: true},
errors: errors
}]
});
33 changes: 33 additions & 0 deletions tests/lib/rules/prefer-map.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
'use strict';

// ------------------------------------------------------------------------------
// Requirements
// ------------------------------------------------------------------------------

var rule = require('../../../lib/rules/prefer-map');
var RuleTester = require('eslint').RuleTester;

// ------------------------------------------------------------------------------
// Tests
// ------------------------------------------------------------------------------

var ruleTester = new RuleTester();
var ruleError = {message: 'Prefer _.map over a _.forEach with a push to an array inside'};
ruleTester.run('prefer-map', rule, {
valid: [{
code: 'var x = _.map(arr, function(x) {return x + 7})'
}, {
code: '_.forEach(arr, function(x) { if (x.a) {a.push(x)}})'
}],
invalid: [{
code: '_(arr).forEach(function(x) { a.push(x)})',
errors: [ruleError]
}, {
code: '_(arr).forEach(function(x) { a.push(f(x))})',
errors: [ruleError]
}, {
code: '_(arr).forEach(x => a.push(f(x)))',
ecmaFeatures: {arrowFunctions: true},
errors: [ruleError]
}]
});
27 changes: 27 additions & 0 deletions tests/lib/rules/prefer-wrapper-method.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
'use strict';

// ------------------------------------------------------------------------------
// Requirements
// ------------------------------------------------------------------------------

var rule = require('../../../lib/rules/prefer-wrapper-method');
var RuleTester = require('eslint').RuleTester;

// ------------------------------------------------------------------------------
// Tests
// ------------------------------------------------------------------------------

var ruleTester = new RuleTester();
var errors = [{message: 'Prefer split with wrapper method over inside the chain start.'}];
ruleTester.run('prefer-wrapper-method', rule, {
valid: [{
code: 'var x = _(str).split(c).map(f).reduce(g)'
}],
invalid: [{
code: '_(str.split(c)).map(f).reduce(g)',
errors: errors
}, {
code: '_.chain(str.split(c)).map(f).reduce(g).value()',
errors: errors
}]
});

0 comments on commit 7ba1279

Please sign in to comment.