Skip to content

Commit

Permalink
add rule chain-style
Browse files Browse the repository at this point in the history
  • Loading branch information
ganimomer committed Dec 24, 2015
1 parent 8807f52 commit 59a3b25
Show file tree
Hide file tree
Showing 4 changed files with 189 additions and 1 deletion.
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,8 @@ Finally, enable all of the rules that you would like to use.
"lodash3/prefer-times": 1,
"lodash3/prefer-startswith": 1,
"lodash3/prefer-noop": 1,
"lodash3/prefer-constant": 1
"lodash3/prefer-constant": 1,
"lodash3/chain-style": [1, "as-needed"]
}
}
```
Expand Down Expand Up @@ -91,6 +92,7 @@ Finally, enable all of the rules that you would like to use.
* [prefer-startswth](docs/rules/prefer-startswith.md): Prefer `_.startsWith` over `a.indexOf(b) === 0`.
* [prefer-noop](docs/rules/prefer-noop.md): Prefer `_.noop` over empty functions.
* [prefer-constant](docs/rules/prefer-constant.md): Prefer `_.constant` over functions returning literals.
* [chain-style](docs/rules/chain-style.md): Enforce a specific chain style: explicit, implicit, or explicit only when necessary.

# License

Expand Down
79 changes: 79 additions & 0 deletions docs/rules/chain-style.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
# Chain Style

There are two ways to create a lodash chain: implicit chaining and explicit chaining with the `_.chain` method.

To use implicit chaining, you can call `_(value)` on your value and, and then finish the chain with the `.value()` method or with any method that returns a single value (e.g. `.first()`, `.max()`)
For example:
```js
var maxFiltered = _(arr).filter(someFilter).max(someCallback);
```
In order to keep the value wrapped in the chain after any single-value method, chaining needs to be explicit, with the `_.chain()` method.
For example:
```js
var mergedFilteredMax = _.chain(arr).filter(someFilter).max(someCallback).assign(obj).value();
```

For more information, check out the [Lodash documentation for chaining](https://lodash.com/docs#_).

## Rule Details

This rule takes one argument, the preferred style: `implicit`, `explicit` or `as-needed`. (default is `as-needed`).

The following patterns are considered problems:

```js
/*eslint lodash3/chain-style: [2, "as-needed"]*/

_.chain(val).map(f).filter(g).value(); // Unnecessary explicit chaining

_.chain(val).map(f).join(c).value(); // Unnecessary explicit chaining, the chain-breaking method join() is last in the chain.

```

```js
/*eslint lodash3/chain-style: [2, "implicit"]*/

_.chain(val).map(f).filter(g).value(); // Do not use explicit chaining

_.chain(val).map(f).first().assign(obj).value(); // Do not use explicit chaining

```

```js
/*eslint lodash3/chain-style: [2, "explicit"]*/

_(val).map(f).filter(g).value(); // Do not use implicit chaining


```



The following patterns are not considered warnings:

```js
/*eslint lodash3/chain-style: [2, "as-needed"]*/

_(val).map(f).filter(g).value();

_.chain(val).map(f).first().assign(obj).value();

```

```js
/*eslint lodash3/chain-style: [2, "implicit"]*/

_(val).map(f).filter(g).value();

```

```js
/*eslint lodash3/chain-style: [2, "explicit"]*/

_.chain(val).map(f).filter(g).value();

```

## When Not To Use It

If you do not want to enforce a specific chain style, then you can disable this rule.
54 changes: 54 additions & 0 deletions lib/rules/chain-style.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
/**
* @fileoverview Rule to enforce a specific chain style
*/
'use strict';

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

function isExplicitChainStart(node) {
return lodashUtil.isLodashCall(node) && lodashUtil.isCallToMethod(node, 'chain');
}

var callExpressionVisitors = {
'as-needed': function (node) {
if (isExplicitChainStart(node)) {
var curr = node.parent.parent;
var needed = false;
while (astUtil.isMethodCall(curr) && !lodashUtil.isChainBreaker(curr)) {
if (!lodashUtil.isChainable(curr) && !lodashUtil.isChainBreaker(curr.parent.parent)) {
needed = true;
}
curr = curr.parent.parent;
}
if (astUtil.isMethodCall(curr) && !needed) {
context.report(node, 'Unnecessary explicit chaining');
}
}
},
implicit: function (node) {
if (isExplicitChainStart(node)) {
context.report(node, 'Do not use explicit chaining');
}
},
explicit: function (node) {
if (node.callee.name === '_') {
context.report(node, 'Do not use implicit chaining');
}
}
};

return {
CallExpression: callExpressionVisitors[context.options[0] || 'as-needed']
};
};

module.exports.schema = [
{
enum: ['as-needed', 'implicit', 'explicit']
}
];
53 changes: 53 additions & 0 deletions tests/lib/rules/chain-style.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
'use strict';

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

var rule = require('../../../lib/rules/chain-style');
var RuleTester = require('eslint').RuleTester;

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

var ruleTester = new RuleTester();
var errors = {
asNeeded: [{message: 'Unnecessary explicit chaining'}],
implicit: [{message: 'Do not use explicit chaining'}],
explicit: [{message: 'Do not use implicit chaining'}]
};

ruleTester.run('chain-style', rule, {
valid: [
'_(a).map(f).filter(g).value()',
'_(a).map(f).join(" ")',
'_.chain(a).map(f).first().assign(obj).value()',
{code: '_(a).map(f).filter(g).value();', options: ['as-needed']},
{code: '_(a).map(f).join(" ")', options: ['implicit']},
{code: '_.chain(a).map(f).filter(b).value()', options: ['explicit']}
],
invalid: [{
code: '_.chain(a).map(f).filter(g).value()',
errors: errors.asNeeded
}, {
code: '_.chain(a).map(f).max(g).value()',
errors: errors.asNeeded
}, {
code: '_.chain(a).map(f).filter(g).value()',
errors: errors.asNeeded,
options: ['as-needed']
}, {
code: '_(a).map(f).max(g)',
errors: errors.explicit,
options: ['explicit']
}, {
code: '_.chain(a).map(f).max(g).assign(obj).value()',
errors: errors.implicit,
options: ['implicit']
}, {
code: '_.chain(a).map(f).max(g).value()',
errors: errors.implicit,
options: ['implicit']
}]
});

0 comments on commit 59a3b25

Please sign in to comment.