Skip to content

Commit 3c33820

Browse files
fiskersindresorhus
andauthored
Add no-length-as-slice-end rule (#2400)
Co-authored-by: Sindre Sorhus <[email protected]>
1 parent 1deb9bb commit 3c33820

6 files changed

+248
-0
lines changed

docs/rules/no-length-as-slice-end.md

+30
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
# Disallow using `.length` as the `end` argument of `{Array,String,TypedArray}#slice()`
2+
3+
💼 This rule is enabled in the ✅ `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs-eslintconfigjs).
4+
5+
🔧 This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix).
6+
7+
<!-- end auto-generated rule header -->
8+
<!-- Do not manually modify this header. Run: `npm run fix:eslint-docs` -->
9+
10+
When calling `{String,Array,TypedArray}#slice(start, end)`, omitting the `end` argument defaults it to the object's `.length`. Passing it explicitly is unnecessary.
11+
12+
## Fail
13+
14+
```js
15+
const foo = string.slice(1, string.length);
16+
```
17+
18+
```js
19+
const foo = array.slice(1, array.length);
20+
```
21+
22+
## Pass
23+
24+
```js
25+
const foo = string.slice(1);
26+
```
27+
28+
```js
29+
const foo = bar.slice(1, baz.length);
30+
```

readme.md

+1
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,7 @@ If you don't use the preset, ensure you use the same `env` and `parserOptions` c
142142
| [no-invalid-fetch-options](docs/rules/no-invalid-fetch-options.md) | Disallow invalid options in `fetch()` and `new Request()`. || | |
143143
| [no-invalid-remove-event-listener](docs/rules/no-invalid-remove-event-listener.md) | Prevent calling `EventTarget#removeEventListener()` with the result of an expression. || | |
144144
| [no-keyword-prefix](docs/rules/no-keyword-prefix.md) | Disallow identifiers starting with `new` or `class`. | | | |
145+
| [no-length-as-slice-end](docs/rules/no-length-as-slice-end.md) | Disallow using `.length` as the `end` argument of `{Array,String,TypedArray}#slice()`. || 🔧 | |
145146
| [no-lonely-if](docs/rules/no-lonely-if.md) | Disallow `if` statements as the only statement in `if` blocks without `else`. || 🔧 | |
146147
| [no-magic-array-flat-depth](docs/rules/no-magic-array-flat-depth.md) | Disallow a magic number as the `depth` argument in `Array#flat(…).` || | |
147148
| [no-negated-condition](docs/rules/no-negated-condition.md) | Disallow negated conditions. || 🔧 | |

rules/no-length-as-slice-end.js

+53
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
'use strict';
2+
const {isMethodCall, isMemberExpression} = require('./ast/index.js');
3+
const {removeArgument} = require('./fix/index.js');
4+
const {isSameReference} = require('./utils/index.js');
5+
6+
const MESSAGE_ID = 'no-length-as-slice-end';
7+
const messages = {
8+
[MESSAGE_ID]: 'Passing `….length` as the `end` argument is unnecessary.',
9+
};
10+
11+
/** @param {import('eslint').Rule.RuleContext} context */
12+
const create = context => {
13+
context.on('CallExpression', callExpression => {
14+
if (!isMethodCall(callExpression, {
15+
method: 'slice',
16+
argumentsLength: 2,
17+
optionalCall: false,
18+
})) {
19+
return;
20+
}
21+
22+
const secondArgument = callExpression.arguments[1];
23+
const node = secondArgument.type === 'ChainExpression' ? secondArgument.expression : secondArgument;
24+
25+
if (
26+
!isMemberExpression(node, {property: 'length', computed: false})
27+
|| !isSameReference(callExpression.callee.object, node.object)
28+
) {
29+
return;
30+
}
31+
32+
return {
33+
node,
34+
messageId: MESSAGE_ID,
35+
/** @param {import('eslint').Rule.RuleFixer} fixer */
36+
fix: fixer => removeArgument(fixer, secondArgument, context.sourceCode),
37+
};
38+
});
39+
};
40+
41+
/** @type {import('eslint').Rule.RuleModule} */
42+
module.exports = {
43+
create,
44+
meta: {
45+
type: 'suggestion',
46+
docs: {
47+
description: 'Disallow using `.length` as the `end` argument of `{Array,String,TypedArray}#slice()`.',
48+
recommended: true,
49+
},
50+
fixable: 'code',
51+
messages,
52+
},
53+
};

test/no-length-as-slice-end.mjs

+33
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import {getTester} from './utils/test.mjs';
2+
3+
const {test} = getTester(import.meta);
4+
5+
test.snapshot({
6+
valid: [
7+
'foo.slice?.(1, foo.length)',
8+
'foo.slice(foo.length, 1)',
9+
'foo.slice()',
10+
'foo.slice(1)',
11+
'foo.slice(1, foo.length - 1)',
12+
'foo.slice(1, foo.length, extraArgument)',
13+
'foo.slice(...[1], foo.length)',
14+
'foo.notSlice(1, foo.length)',
15+
'new foo.slice(1, foo.length)',
16+
'slice(1, foo.length)',
17+
'foo.slice(1, foo.notLength)',
18+
'foo.slice(1, length)',
19+
'foo[slice](1, foo.length)',
20+
'foo.slice(1, foo[length])',
21+
'foo.slice(1, bar.length)',
22+
// `isSameReference` consider they are not the same reference
23+
'foo().slice(1, foo().length)',
24+
],
25+
invalid: [
26+
'foo.slice(1, foo.length)',
27+
'foo?.slice(1, foo.length)',
28+
'foo.slice(1, foo.length,)',
29+
'foo.slice(1, (( foo.length )))',
30+
'foo.slice(1, foo?.length)',
31+
'foo?.slice(1, foo?.length)',
32+
],
33+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
# Snapshot report for `test/no-length-as-slice-end.mjs`
2+
3+
The actual snapshot is saved in `no-length-as-slice-end.mjs.snap`.
4+
5+
Generated by [AVA](https://avajs.dev).
6+
7+
## invalid(1): foo.slice(1, foo.length)
8+
9+
> Input
10+
11+
`␊
12+
1 | foo.slice(1, foo.length)␊
13+
`
14+
15+
> Output
16+
17+
`␊
18+
1 | foo.slice(1)␊
19+
`
20+
21+
> Error 1/1
22+
23+
`␊
24+
> 1 | foo.slice(1, foo.length)␊
25+
| ^^^^^^^^^^ Passing \`….length\` as the \`end\` argument is unnecessary.␊
26+
`
27+
28+
## invalid(2): foo?.slice(1, foo.length)
29+
30+
> Input
31+
32+
`␊
33+
1 | foo?.slice(1, foo.length)␊
34+
`
35+
36+
> Output
37+
38+
`␊
39+
1 | foo?.slice(1)␊
40+
`
41+
42+
> Error 1/1
43+
44+
`␊
45+
> 1 | foo?.slice(1, foo.length)␊
46+
| ^^^^^^^^^^ Passing \`….length\` as the \`end\` argument is unnecessary.␊
47+
`
48+
49+
## invalid(3): foo.slice(1, foo.length,)
50+
51+
> Input
52+
53+
`␊
54+
1 | foo.slice(1, foo.length,)␊
55+
`
56+
57+
> Output
58+
59+
`␊
60+
1 | foo.slice(1,)␊
61+
`
62+
63+
> Error 1/1
64+
65+
`␊
66+
> 1 | foo.slice(1, foo.length,)␊
67+
| ^^^^^^^^^^ Passing \`….length\` as the \`end\` argument is unnecessary.␊
68+
`
69+
70+
## invalid(4): foo.slice(1, (( foo.length )))
71+
72+
> Input
73+
74+
`␊
75+
1 | foo.slice(1, (( foo.length )))␊
76+
`
77+
78+
> Output
79+
80+
`␊
81+
1 | foo.slice(1)␊
82+
`
83+
84+
> Error 1/1
85+
86+
`␊
87+
> 1 | foo.slice(1, (( foo.length )))␊
88+
| ^^^^^^^^^^ Passing \`….length\` as the \`end\` argument is unnecessary.␊
89+
`
90+
91+
## invalid(5): foo.slice(1, foo?.length)
92+
93+
> Input
94+
95+
`␊
96+
1 | foo.slice(1, foo?.length)␊
97+
`
98+
99+
> Output
100+
101+
`␊
102+
1 | foo.slice(1)␊
103+
`
104+
105+
> Error 1/1
106+
107+
`␊
108+
> 1 | foo.slice(1, foo?.length)␊
109+
| ^^^^^^^^^^^ Passing \`….length\` as the \`end\` argument is unnecessary.␊
110+
`
111+
112+
## invalid(6): foo?.slice(1, foo?.length)
113+
114+
> Input
115+
116+
`␊
117+
1 | foo?.slice(1, foo?.length)␊
118+
`
119+
120+
> Output
121+
122+
`␊
123+
1 | foo?.slice(1)␊
124+
`
125+
126+
> Error 1/1
127+
128+
`␊
129+
> 1 | foo?.slice(1, foo?.length)␊
130+
| ^^^^^^^^^^^ Passing \`….length\` as the \`end\` argument is unnecessary.␊
131+
`
445 Bytes
Binary file not shown.

0 commit comments

Comments
 (0)