Skip to content

Commit 70ded69

Browse files
committed
add prefer-lodash-method and prefer-lodash-typecheck (implements wix-incubator#3)
1 parent a91efe1 commit 70ded69

8 files changed

+224
-2
lines changed

README.md

+5-1
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,9 @@ Finally, enable all of the rules that you would like to use.
4545
"lodash3/prefer-map": 1,
4646
"lodash3/prefer-wrapper-method": 1,
4747
"lodash3/prefer-invoke": 1,
48-
"lodash3/prefer-thru": 1
48+
"lodash3/prefer-thru": 1,
49+
"lodash3/prefer-lodash-method": 1,
50+
"lodash3/prefer-lodash-typecheck": 1
4951
}
5052
}
5153
```
@@ -67,6 +69,8 @@ Finally, enable all of the rules that you would like to use.
6769
* [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(' ')...`
6870
* [prefer-invoke](docs/rules/prefer-invoke.md): Prefer using `_.invoke` over `_.map` with a method call inside.
6971
* [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)...`
72+
* [prefer-lodash-method](docs/rules/prefer-lodash-method.md): Prefer using Lodash collection methods (e.g. `_.map`) over native array methods.
73+
* [prefer-lodash-typecheck](docs/rules/prefer-lodash-typecheck.md): Prefer using `_.is*` methods over `typeof` and `instanceof` checks when applicable.
7074

7175

7276
# License

docs/rules/prefer-lodash-method.md

+34
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
# Prefer Lodash method
2+
3+
When using native functions like forEach and map, it's often better to use the Lodash implementation.
4+
5+
## Rule Details
6+
7+
This rule takes no arguments.
8+
9+
The following patterns are considered warnings:
10+
11+
```js
12+
13+
var b = a.map(f);
14+
15+
if (arr.some(f)) {
16+
// ...
17+
}
18+
19+
```
20+
21+
The following patterns are not considered warnings:
22+
23+
```js
24+
25+
_.map(a, f);
26+
27+
_(arr).map(f).reduce(g);
28+
29+
```
30+
31+
32+
## When Not To Use It
33+
34+
If you do not want to enforce using Lodash methods, you should not use this rule.

docs/rules/prefer-lodash-typecheck.md

+36
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
# Prefer Lodash typecheck
2+
3+
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
4+
5+
## Rule Details
6+
7+
This rule takes no arguments.
8+
9+
The following patterns are considered warnings:
10+
11+
```js
12+
13+
if (typeof a === 'number') {
14+
// ...
15+
}
16+
17+
var isNotString = typeof b !== 'string';
18+
19+
var isArray = a instanceof Array;
20+
21+
```
22+
23+
The following patterns are not considered warnings:
24+
25+
```js
26+
27+
var areSameType = typeof a === typeof b;
28+
29+
var isCar = truck instanceof Car;
30+
31+
```
32+
33+
34+
## When Not To Use It
35+
36+
If you do not want to enforce using Lodash methods for type checks, you should not use this rule.

lib/rules/prefer-lodash-method.js

+23
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
/**
2+
* @fileoverview Rule to check if there's a method in the chain start that can be in the chain
3+
*/
4+
'use strict';
5+
6+
//------------------------------------------------------------------------------
7+
// Rule Definition
8+
//------------------------------------------------------------------------------
9+
10+
module.exports = function (context) {
11+
var lodashUtil = require('../util/lodashUtil');
12+
var astUtil = require('../util/astUtil');
13+
14+
var REPORT_MESSAGE = 'Prefer \'_.{{method}}\' over the native function.';
15+
16+
return {
17+
CallExpression: function (node) {
18+
if (!lodashUtil.isLodashCall(node) && !lodashUtil.isLodashWrapper(astUtil.getCaller(node)) && lodashUtil.canBeLodashMethod(astUtil.getMethodName(node))) {
19+
context.report(node, REPORT_MESSAGE, {method: astUtil.getMethodName(node)});
20+
}
21+
}
22+
};
23+
};

lib/rules/prefer-lodash-typecheck.js

+47
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
/**
2+
* @fileoverview Rule to check if there's a method in the chain start that can be in the chain
3+
*/
4+
'use strict';
5+
6+
//------------------------------------------------------------------------------
7+
// Rule Definition
8+
//------------------------------------------------------------------------------
9+
10+
module.exports = function (context) {
11+
var lodashUtil = require('../util/lodashUtil');
12+
13+
function isTypeOf(node) {
14+
return node && node.type === 'UnaryExpression' && node.operator === 'typeof';
15+
}
16+
17+
function isLiteral(node) {
18+
return node && node.type === 'Literal';
19+
}
20+
21+
function isStrictComparison(node) {
22+
return (node.operator === '===' || node.operator === '!==');
23+
}
24+
25+
function isCompareTypeOfToLiteral(node) {
26+
return isStrictComparison(node) &&
27+
((isTypeOf(node.left) && isLiteral(node.right)) || (isTypeOf(node.right) && isLiteral(node.left)));
28+
}
29+
30+
var REPORT_MESSAGE = 'Prefer \'_.{{method}}\' over {{actual}}.';
31+
32+
return {
33+
BinaryExpression: function (node) {
34+
if (isCompareTypeOfToLiteral(node)) {
35+
context.report(node, REPORT_MESSAGE, {
36+
method: lodashUtil.getIsTypeMethod(isLiteral(node.left) ? node.left.value : node.right.value),
37+
actual: '\'typeof\' comparison'
38+
});
39+
} else if (node.operator === 'instanceof') {
40+
var lodashEquivalent = lodashUtil.getIsTypeMethod(node.right.name);
41+
if (node.right.type === 'Identifier' && lodashEquivalent) {
42+
context.report(node, REPORT_MESSAGE, {method: lodashEquivalent, actual: '\'instanceof ' + node.right.name + '\''});
43+
}
44+
}
45+
}
46+
};
47+
};

lib/util/lodashUtil.js

+10-1
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,14 @@ function isCallToMethod(node, method) {
5454
function isLodashWrapperMethod(node) {
5555
return _.includes(aliasMap.WRAPPER_METHODS, astUtil.getMethodName(node)) && node.type === 'CallExpression';
5656
}
57+
function getIsTypeMethod(name) {
58+
var types = ['number', 'boolean', 'function', 'Function', 'string', 'object', 'undefined', 'Date', 'Array', 'Error', 'Element'];
59+
return _.includes(types, name) ? 'is' + _.capitalize(name) : null;
60+
}
5761

62+
function canBeLodashMethod(name) {
63+
return _.includes(['forEach', 'map', 'reduce', 'filter', 'every', 'some'], name);
64+
}
5865
module.exports = {
5966
isLodashCall: isLodashCall,
6067
isLodashChainStart: isLodashChainStart,
@@ -65,5 +72,7 @@ module.exports = {
6572
isChainBreaker: isChainBreaker,
6673
isExplicitMethodChaining: isExplicitMethodChaining,
6774
isCallToMethod: isCallToMethod,
68-
isLodashWrapperMethod: isLodashWrapperMethod
75+
isLodashWrapperMethod: isLodashWrapperMethod,
76+
getIsTypeMethod: getIsTypeMethod,
77+
canBeLodashMethod: canBeLodashMethod
6978
};
+32
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
'use strict';
2+
3+
// ------------------------------------------------------------------------------
4+
// Requirements
5+
// ------------------------------------------------------------------------------
6+
7+
var rule = require('../../../lib/rules/prefer-lodash-method');
8+
var RuleTester = require('eslint').RuleTester;
9+
10+
// ------------------------------------------------------------------------------
11+
// Tests
12+
// ------------------------------------------------------------------------------
13+
14+
var ruleTester = new RuleTester();
15+
var errors = [{message: 'Prefer \'_.map\' over the native function.'}];
16+
ruleTester.run('prefer-lodash-method', rule, {
17+
valid: [{
18+
code: 'var x = _.map(arr, f)'
19+
}, {
20+
code: 'var x = _(arr).map(f).reduce(g)'
21+
}, {
22+
code: 'var x = _.chain(arr).map(f).reduce(g).value()'
23+
}],
24+
invalid: [{
25+
code: 'var x = a.map(function(x) {return x.f()});',
26+
errors: errors
27+
}, {
28+
code: 'var x = arr.map(x => x.f())',
29+
ecmaFeatures: {arrowFunctions: true},
30+
errors: errors
31+
}]
32+
});
+37
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
'use strict';
2+
3+
// ------------------------------------------------------------------------------
4+
// Requirements
5+
// ------------------------------------------------------------------------------
6+
7+
var rule = require('../../../lib/rules/prefer-lodash-typecheck');
8+
var RuleTester = require('eslint').RuleTester;
9+
10+
// ------------------------------------------------------------------------------
11+
// Tests
12+
// ------------------------------------------------------------------------------
13+
14+
var ruleTester = new RuleTester();
15+
var errors = {
16+
typeof: [{message: 'Prefer \'_.isNumber\' over \'typeof\' comparison.'}],
17+
instanceof: [{message: 'Prefer \'_.isArray\' over \'instanceof Array\'.'}]
18+
};
19+
ruleTester.run('prefer-lodash-typecheck', rule, {
20+
valid: [{
21+
code: 'var x = a instanceof B'
22+
}, {
23+
code: 'var x = a > b ? a : b'
24+
}, {
25+
code: 'var x = typeof a === typeof b'
26+
}],
27+
invalid: [{
28+
code: 'var x = typeof a === "number"',
29+
errors: errors.typeof
30+
}, {
31+
code: 'var x = "number" !== typeof a',
32+
errors: errors.typeof
33+
}, {
34+
code: 'var x = a instanceof Array',
35+
errors: errors.instanceof
36+
}]
37+
});

0 commit comments

Comments
 (0)