Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit 0fbd30e

Browse files
feat: Add prefer-array-from rule. Fixes #3
1 parent 9e93e54 commit 0fbd30e

File tree

8 files changed

+270
-9
lines changed

8 files changed

+270
-9
lines changed

‎README.md‎

Lines changed: 47 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,21 @@
44

55
Rules for Array functions and methods.
66

7+
## Contents
8+
- [Installation](#Installation)
9+
- [Rules](#Rules)
10+
- [`from-map`](#from-map)
11+
- [Examples](#Examples)
12+
- [`no-unnecessary-this-arg`](#no-unnecessary-this-arg)
13+
- [Checked Functions](#checked-functions)
14+
- [Checked Methods](#checked-methods)
15+
- [Examples](#Examples2)
16+
- [`prefer-array-from`](#prefer-array-from)
17+
- [Examples](#Examples3)
18+
- [`array-func/recommended` Configuration](#array-func-recommended-configuration)
19+
- [Using the Configuration](#using-the-configuration)
20+
- [License](#License)
21+
722
## Installation
823

924
Install [ESLint](https://www.github.com/eslint/eslint) either locally or globally.
@@ -52,10 +67,10 @@ The `this` parameter is useless when providing arrow functions, since the `this`
5267

5368
The fix is usually to omit the parameter. The Array methods can't be auto-fixed, since the detection of array methods is not confident enough to know that the method is being called on an array.
5469

55-
#### Checked functions
70+
#### Checked Functions
5671
- `from` (fixable)
5772

58-
#### Checked methods
73+
#### Checked Methods
5974
- `every`
6075
- `filter`
6176
- `find`
@@ -112,15 +127,40 @@ array.forEach(function(char) {
112127
array.filter(this.isGood, this);
113128
```
114129

130+
### `prefer-array-from`
131+
Use `Array.from` instead of `[...iterable]` for performance benefits.
132+
133+
This rule is auto fixable.
134+
135+
#### Examples
136+
Code that triggers this rule:
137+
```js
138+
const iterable = [..."string"];
139+
140+
const arrayCopy = [...iterable];
141+
```
142+
143+
Code that doesn't trigger this rule:
144+
```js
145+
const array = [1, 2, 3];
146+
147+
const extendedArray = [0, ...array];
148+
149+
const arrayCopy = Array.from(array);
150+
151+
const characterArray = Array.from("string");
152+
```
153+
115154
## `array-func/recommended` Configuration
116155
The recommended configuration will set your parser ECMA Version to 2015, since that's when the Array functions and methods were added.
117156

118-
Rule | Error level
119-
---- | -----------
120-
`from-map` | Error
121-
`no-unnecessary-this-arg` | Error
157+
Rule | Error level | Fixable
158+
---- | ----------- | -------
159+
`from-map` | Error | Yes
160+
`no-unnecessary-this-arg` | Error | Sometimes
161+
`prefer-array-from` | Error | Yes
122162

123-
### Using the configuration
163+
### Using the Configuration
124164
To enable this configuration use the `extends` property in your `.eslintrc.json` config file (may look different for other config file styles):
125165
```json
126166
{

‎index.js‎

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@
77
module.exports = {
88
rules: {
99
"from-map": require("./rules/from-map"),
10-
"no-unnecessary-this-arg": require("./rules/no-unnecessary-this-arg")
10+
"no-unnecessary-this-arg": require("./rules/no-unnecessary-this-arg"),
11+
"prefer-array-from": require("./rules/prefer-array-from")
1112
},
1213
configs: {
1314
recommended: {
@@ -17,7 +18,8 @@ module.exports = {
1718
plugins: [ 'array-func' ],
1819
rules: {
1920
"array-func/from-map": "error",
20-
"array-func/no-unnecessary-this-arg": "error"
21+
"array-func/no-unnecessary-this-arg": "error",
22+
"array-func/prefer-array-from": "error"
2123
}
2224
}
2325
}

‎lib/helpers/call-expression.js‎

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
/**
2+
* @author Martin Giger
3+
* @license MIT
4+
*/
5+
"use strict";
6+
7+
const {
8+
MEMBER_EXPRESSION,
9+
IDENTIFIER
10+
} = require("../type");
11+
12+
// Helper functions for call expression nodes.
13+
14+
exports.isMethod = (node, name) => "callee" in node && node.callee.type === MEMBER_EXPRESSION && node.callee.property.name === name;
15+
16+
exports.getParent = (node) => node.callee.object;
17+
18+
exports.isOnObject = (node, name) => "object" in node.callee && node.callee.object.type === IDENTIFIER && node.callee.object.name === name;

‎lib/type.js‎

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
/**
2+
* @author Martin Giger
3+
* @license MIT
4+
*/
5+
"use strict";
6+
7+
exports.MEMBER_EXPRESSION = "MemberExpression";
8+
exports.ARROW_FUNCTION_EXPRESSION = "ArrowFunctionExpression";
9+
exports.IDENTIFIER = "Identifier";
10+
exports.SPREAD_ELEMENT = "SpreadElement";

‎rules/prefer-array-from.js‎

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
/**
2+
* @author Martin Giger
3+
* @license MIT
4+
*/
5+
"use strict";
6+
7+
const { SPREAD_ELEMENT } = require("../lib/type");
8+
9+
const SINGLE_ELEMENT = 1,
10+
firstElement = (arr) => {
11+
const [ el ] = arr;
12+
return el;
13+
};
14+
15+
module.exports = {
16+
meta: {
17+
docs: {
18+
description: "Prefer using Array.from over spreading an interable in an array literal.",
19+
recommended: true
20+
},
21+
schema: [],
22+
fixable: "code"
23+
},
24+
create(context) {
25+
return {
26+
"ArrayExpression:exit"(node) {
27+
if(node.elements.length !== SINGLE_ELEMENT || firstElement(node.elements).type !== SPREAD_ELEMENT) {
28+
return;
29+
}
30+
context.report({
31+
node,
32+
loc: node.loc,
33+
message: "Use Array.from to convert from iterable to array",
34+
fix(fixer) {
35+
const sourceCode = context.getSourceCode();
36+
return fixer.replaceText(node, `Array.from(${sourceCode.getText(firstElement(node.elements).argument)})`);
37+
}
38+
});
39+
}
40+
};
41+
}
42+
};

‎test/helpers-call-expression.js‎

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
import test from 'ava';
2+
import { isMethod, getParent, isOnObject } from '../lib/helpers/call-expression';
3+
import { MEMBER_EXPRESSION, IDENTIFIER } from '../lib/type';
4+
5+
test('is method', (t) => {
6+
const name = "test";
7+
t.true(isMethod({
8+
callee: {
9+
type: MEMBER_EXPRESSION,
10+
property: {
11+
name
12+
}
13+
}
14+
}, name));
15+
});
16+
17+
test('not is method', (t) => {
18+
const name = 'test';
19+
t.false(isMethod({}, name));
20+
t.false(isMethod({
21+
callee: {
22+
type: IDENTIFIER,
23+
property: {
24+
name
25+
}
26+
}
27+
}, name));
28+
t.false(isMethod({
29+
callee: {
30+
type: MEMBER_EXPRESSION,
31+
property: {
32+
name: 'foo'
33+
}
34+
}
35+
}));
36+
});
37+
38+
test('get parent', (t) => {
39+
const parent = 'foo';
40+
t.is(getParent({
41+
callee: {
42+
object: parent
43+
}
44+
}), parent);
45+
});
46+
47+
test('is on object', (t) => {
48+
const name = 'test';
49+
t.true(isOnObject({
50+
callee: {
51+
object: {
52+
type: IDENTIFIER,
53+
name
54+
}
55+
}
56+
}, name));
57+
});
58+
59+
test('is not on object', (t) => {
60+
const name = 'test';
61+
t.false(isOnObject({
62+
callee: {}
63+
}, name));
64+
t.false(isOnObject({
65+
callee: {
66+
type: MEMBER_EXPRESSION,
67+
name
68+
}
69+
}, name));
70+
t.false(isOnObject({
71+
callee: {
72+
type: IDENTIFIER,
73+
name: 'foo'
74+
}
75+
}, name));
76+
});

‎test/rules/prefer-array-from.js‎

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import test from 'ava';
2+
import AvaRuleTester from 'eslint-ava-rule-tester';
3+
import rule from '../../rules/prefer-array-from';
4+
5+
const ruleTester = new AvaRuleTester(test, {
6+
parserOptions: {
7+
ecmaVersion: 2015
8+
}
9+
});
10+
11+
const message = "Use Array.from to convert from iterable to array";
12+
13+
ruleTester.run('from-map', rule, {
14+
valid: [
15+
'Array.from(new Set())',
16+
'Array.from(iterable)',
17+
'[1, ...iterable]',
18+
'[1, 2, 3]',
19+
'[iterable]'
20+
],
21+
invalid: [
22+
{
23+
code: '[...iterable]',
24+
errors: [ {
25+
message,
26+
column: 1,
27+
line: 1
28+
} ],
29+
output: 'Array.from(iterable)'
30+
},
31+
{
32+
code: '[...[1, 2]]',
33+
errors: [ {
34+
message,
35+
column: 1,
36+
line: 1
37+
} ],
38+
output: 'Array.from([1, 2])'
39+
},
40+
{
41+
code: '[..."test"]',
42+
errors: [ {
43+
message,
44+
column: 1,
45+
line: 1
46+
} ],
47+
output: 'Array.from("test")'
48+
}
49+
]
50+
});

‎test/type.js‎

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import test from 'ava';
2+
import {
3+
SPREAD_ELEMENT,
4+
ARROW_FUNCTION_EXPRESSION,
5+
MEMBER_EXPRESSION,
6+
IDENTIFIER
7+
} from '../lib/type';
8+
9+
test('Spread element', (t) => {
10+
t.is(SPREAD_ELEMENT, "SpreadElement");
11+
});
12+
13+
test('Arrow function expression', (t) => {
14+
t.is(ARROW_FUNCTION_EXPRESSION, "ArrowFunctionExpression");
15+
});
16+
17+
test('Member expression', (t) => {
18+
t.is(MEMBER_EXPRESSION, "MemberExpression");
19+
});
20+
21+
test('Identifier', (t) => {
22+
t.is(IDENTIFIER, "Identifier");
23+
});

0 commit comments

Comments
(0)

AltStyle によって変換されたページ (->オリジナル) /