Skip to content

Commit

Permalink
feat: add avoid-reverse rule (fixes #12)
Browse files Browse the repository at this point in the history
  • Loading branch information
freaktechnik committed Jan 15, 2018
1 parent f104f4c commit 15d020b
Show file tree
Hide file tree
Showing 4 changed files with 160 additions and 2 deletions.
39 changes: 39 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ Rules for Array functions and methods.
- [Examples](#examples-1)
- [`prefer-array-from`](#prefer-array-from)
- [Examples](#examples-2)
- [`avoid-reverse`](#avoid-reverse)
- [Examples](#examples-3)
- [`array-func/recommended` Configuration](#array-funcrecommended-configuration)
- [Using the Configuration](#using-the-configuration)
- [License](#license)
Expand Down Expand Up @@ -151,6 +153,42 @@ const arrayCopy = Array.from(array);
const characterArray = Array.from("string");
```

### `avoid-reverse`
Avoid reversing the array and running a method on it if there is an equivalent
of the method operating on the array from the other end.

There are two operations with such equivalents: `indexOf` with `lastIndexOf` and
`reduce` with `reduceRight`.

This rule is auto fixable.

#### Examples
Code that triggers this rule:
```js
const lastIndex = array.reverse().indexOf(1);

const firstIndex = array.reverse().lastIndexOf(1);

const sum = array.reverse().reduce((p, c) => p + c, 0);

const reverseSum = array.reverse().reduceRight((p, c) => p + c, 0);
```

Code that doesn't trigger this rule:
```js
const lastIndex = array.lastIndexOf(1);

const firstIndex = array.indexOf(1);

const sum = array.reduce((p, c) => p + c, 0);

const reverseSum = array.reduceRight((p, c) => p + c, 0);

const reverseArray = array.reverse();

const reverseMap = array.reverse().map((r) => r + 1);
```

## `array-func/recommended` Configuration
The recommended configuration will set your parser ECMA Version to 2015, since that's when the Array functions and methods were added.

Expand All @@ -159,6 +197,7 @@ Rule | Error level | Fixable
`from-map` | Error | Yes
`no-unnecessary-this-arg` | Error | Sometimes
`prefer-array-from` | Error | Yes
`avoid-reverse` | Error | Yes

### Using the Configuration
To enable this configuration use the `extends` property in your `.eslintrc.json` config file (may look different for other config file styles):
Expand Down
6 changes: 4 additions & 2 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ module.exports = {
rules: {
"from-map": require("./rules/from-map"),
"no-unnecessary-this-arg": require("./rules/no-unnecessary-this-arg"),
"prefer-array-from": require("./rules/prefer-array-from")
"prefer-array-from": require("./rules/prefer-array-from"),
"avoid-reverse": require("./rules/avoid-reverse")
},
configs: {
recommended: {
Expand All @@ -19,7 +20,8 @@ module.exports = {
rules: {
"array-func/from-map": "error",
"array-func/no-unnecessary-this-arg": "error",
"array-func/prefer-array-from": "error"
"array-func/prefer-array-from": "error",
"array-func/avoid-reverse": "error"
}
}
}
Expand Down
59 changes: 59 additions & 0 deletions rules/avoid-reverse.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
/**
* @author Martin Giger
* @license MIT
*/
"use strict";

const {
isMethod,
getParent
} = require("../lib/helpers/call-expression");

const REPLACEMENTS = {
indexOf: "lastIndexOf",
reduce: "reduceRight",
lastIndexOf: "indexOf",
reduceRight: "reduce"
};

module.exports = {
meta: {
docs: {
description: "Prefer methods operating from the right over reversing the array",
recommended: true
},
schema: [],
fixable: "code"
},
create(context) {
return {
"CallExpression:exit"(node) {
if(Object.keys(REPLACEMENTS).every((m) => !isMethod(node, m))) {
return;
}

const parent = getParent(node);
if(!isMethod(parent, 'reverse')) {
return;
}

const reversed = REPLACEMENTS[node.callee.property.name];

context.report({
node: node.callee.property,
loc: {
start: parent.callee.property.loc.start,
end: node.callee.property.loc.end
},
message: `Prefer using ${reversed} over reversing the array and ${node.callee.property.name}`,
fix(fixer) {
return fixer.replaceTextRange([
parent.callee.property.start,
node.callee.property.end
], reversed);
}
});
}
};
}
};
58 changes: 58 additions & 0 deletions test/rules/avoid-reverse.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import test from 'ava';
import AvaRuleTester from 'eslint-ava-rule-tester';
import rule from '../../rules/avoid-reverse';

const ruleTester = new AvaRuleTester(test, {
parserOptions: {
ecmaVersion: 2015
}
});

ruleTester.run('from-map', rule, {
valid: [
'array.lastIndexOf(1)',
'array.indexOf(1)',
'array.reduce((p, c) => p + c, 0)',
'array.reduceRight((p, c) => p + c, 0)',
'array.reverse()',
'array.reverse().map((r) => r + 1)'
],
invalid: [
{
code: 'array.reverse().indexOf(1)',
errors: [ {
message: 'Prefer using lastIndexOf over reversing the array and indexOf',
column: 7,
line: 1
} ],
output: 'array.lastIndexOf(1)'
},
{
code: 'array.reverse().lastIndexOf(1)',
errors: [ {
message: 'Prefer using indexOf over reversing the array and lastIndexOf',
column: 7,
line: 1
} ],
output: 'array.indexOf(1)'
},
{
code: 'array.reverse().reduce((p, c) => p + c, 0)',
errors: [ {
message: 'Prefer using reduceRight over reversing the array and reduce',
column: 7,
line: 1
} ],
output: 'array.reduceRight((p, c) => p + c, 0)'
},
{
code: 'array.reverse().reduceRight((p, c) => p + c, 0)',
errors: [ {
message: 'Prefer using reduce over reversing the array and reduceRight',
column: 7,
line: 1
} ],
output: 'array.reduce((p, c) => p + c, 0)'
}
]
});

0 comments on commit 15d020b

Please sign in to comment.