Skip to content

Commit

Permalink
add prefer-lodash-method and prefer-lodash-typecheck (implements #3)
Browse files Browse the repository at this point in the history
  • Loading branch information
ganimomer committed Sep 9, 2015
1 parent a91efe1 commit 70ded69
Show file tree
Hide file tree
Showing 8 changed files with 224 additions and 2 deletions.
6 changes: 5 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,9 @@ Finally, enable all of the rules that you would like to use.
"lodash3/prefer-map": 1,
"lodash3/prefer-wrapper-method": 1,
"lodash3/prefer-invoke": 1,
"lodash3/prefer-thru": 1
"lodash3/prefer-thru": 1,
"lodash3/prefer-lodash-method": 1,
"lodash3/prefer-lodash-typecheck": 1
}
}
```
Expand All @@ -67,6 +69,8 @@ Finally, enable all of the rules that you would like to use.
* [prefer-wrapper-method](docs/rules/prefer-wrapper-method.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.
* [prefer-thru](docs/rules/prefer-thru.md): Prefer using `_.prototype.thru` in the chain and not call functions in the initial value, e.g. `_(x).thru(f).map(g)...`
* [prefer-lodash-method](docs/rules/prefer-lodash-method.md): Prefer using Lodash collection methods (e.g. `_.map`) over native array methods.
* [prefer-lodash-typecheck](docs/rules/prefer-lodash-typecheck.md): Prefer using `_.is*` methods over `typeof` and `instanceof` checks when applicable.


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

When using native functions like forEach and map, it's often better to use the Lodash implementation.

## Rule Details

This rule takes no arguments.

The following patterns are considered warnings:

```js

var b = a.map(f);

if (arr.some(f)) {
// ...
}

```

The following patterns are not considered warnings:

```js

_.map(a, f);

_(arr).map(f).reduce(g);

```


## When Not To Use It

If you do not want to enforce using Lodash methods, you should not use this rule.
36 changes: 36 additions & 0 deletions docs/rules/prefer-lodash-typecheck.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# Prefer Lodash typecheck

Getting the specific type of a variable or expression can be done with `typeof` or `instanceof`. However, it's often more expressive to use the Lodash equivalent function

## Rule Details

This rule takes no arguments.

The following patterns are considered warnings:

```js

if (typeof a === 'number') {
// ...
}

var isNotString = typeof b !== 'string';

var isArray = a instanceof Array;

```

The following patterns are not considered warnings:

```js

var areSameType = typeof a === typeof b;

var isCar = truck instanceof Car;

```


## When Not To Use It

If you do not want to enforce using Lodash methods for type checks, you should not use this rule.
23 changes: 23 additions & 0 deletions lib/rules/prefer-lodash-method.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/**
* @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 astUtil = require('../util/astUtil');

var REPORT_MESSAGE = 'Prefer \'_.{{method}}\' over the native function.';

return {
CallExpression: function (node) {
if (!lodashUtil.isLodashCall(node) && !lodashUtil.isLodashWrapper(astUtil.getCaller(node)) && lodashUtil.canBeLodashMethod(astUtil.getMethodName(node))) {
context.report(node, REPORT_MESSAGE, {method: astUtil.getMethodName(node)});
}
}
};
};
47 changes: 47 additions & 0 deletions lib/rules/prefer-lodash-typecheck.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/**
* @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');

function isTypeOf(node) {
return node && node.type === 'UnaryExpression' && node.operator === 'typeof';
}

function isLiteral(node) {
return node && node.type === 'Literal';
}

function isStrictComparison(node) {
return (node.operator === '===' || node.operator === '!==');
}

function isCompareTypeOfToLiteral(node) {
return isStrictComparison(node) &&
((isTypeOf(node.left) && isLiteral(node.right)) || (isTypeOf(node.right) && isLiteral(node.left)));
}

var REPORT_MESSAGE = 'Prefer \'_.{{method}}\' over {{actual}}.';

return {
BinaryExpression: function (node) {
if (isCompareTypeOfToLiteral(node)) {
context.report(node, REPORT_MESSAGE, {
method: lodashUtil.getIsTypeMethod(isLiteral(node.left) ? node.left.value : node.right.value),
actual: '\'typeof\' comparison'
});
} else if (node.operator === 'instanceof') {
var lodashEquivalent = lodashUtil.getIsTypeMethod(node.right.name);
if (node.right.type === 'Identifier' && lodashEquivalent) {
context.report(node, REPORT_MESSAGE, {method: lodashEquivalent, actual: '\'instanceof ' + node.right.name + '\''});
}
}
}
};
};
11 changes: 10 additions & 1 deletion lib/util/lodashUtil.js
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,14 @@ function isCallToMethod(node, method) {
function isLodashWrapperMethod(node) {
return _.includes(aliasMap.WRAPPER_METHODS, astUtil.getMethodName(node)) && node.type === 'CallExpression';
}
function getIsTypeMethod(name) {
var types = ['number', 'boolean', 'function', 'Function', 'string', 'object', 'undefined', 'Date', 'Array', 'Error', 'Element'];
return _.includes(types, name) ? 'is' + _.capitalize(name) : null;
}

function canBeLodashMethod(name) {
return _.includes(['forEach', 'map', 'reduce', 'filter', 'every', 'some'], name);
}
module.exports = {
isLodashCall: isLodashCall,
isLodashChainStart: isLodashChainStart,
Expand All @@ -65,5 +72,7 @@ module.exports = {
isChainBreaker: isChainBreaker,
isExplicitMethodChaining: isExplicitMethodChaining,
isCallToMethod: isCallToMethod,
isLodashWrapperMethod: isLodashWrapperMethod
isLodashWrapperMethod: isLodashWrapperMethod,
getIsTypeMethod: getIsTypeMethod,
canBeLodashMethod: canBeLodashMethod
};
32 changes: 32 additions & 0 deletions tests/lib/rules/prefer-lodash-method.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
'use strict';

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

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

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

var ruleTester = new RuleTester();
var errors = [{message: 'Prefer \'_.map\' over the native function.'}];
ruleTester.run('prefer-lodash-method', rule, {
valid: [{
code: 'var x = _.map(arr, f)'
}, {
code: 'var x = _(arr).map(f).reduce(g)'
}, {
code: 'var x = _.chain(arr).map(f).reduce(g).value()'
}],
invalid: [{
code: 'var x = a.map(function(x) {return x.f()});',
errors: errors
}, {
code: 'var x = arr.map(x => x.f())',
ecmaFeatures: {arrowFunctions: true},
errors: errors
}]
});
37 changes: 37 additions & 0 deletions tests/lib/rules/prefer-lodash-typecheck.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
'use strict';

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

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

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

var ruleTester = new RuleTester();
var errors = {
typeof: [{message: 'Prefer \'_.isNumber\' over \'typeof\' comparison.'}],
instanceof: [{message: 'Prefer \'_.isArray\' over \'instanceof Array\'.'}]
};
ruleTester.run('prefer-lodash-typecheck', rule, {
valid: [{
code: 'var x = a instanceof B'
}, {
code: 'var x = a > b ? a : b'
}, {
code: 'var x = typeof a === typeof b'
}],
invalid: [{
code: 'var x = typeof a === "number"',
errors: errors.typeof
}, {
code: 'var x = "number" !== typeof a',
errors: errors.typeof
}, {
code: 'var x = a instanceof Array',
errors: errors.instanceof
}]
});

0 comments on commit 70ded69

Please sign in to comment.