8000 feat(eslint-plugin) [prefer-at] Create rule #6401 · MillerSvt/typescript-eslint@125a83f · GitHub
[go: up one dir, main page]

Skip to content

Commit 125a83f

Browse files
sviat9440Святослав Зайцев
authored andcommitted
feat(eslint-plugin) [prefer-at] Create rule typescript-eslint#6401
1 parent b14d3be commit 125a83f

File tree

4 files changed

+186
-0
lines changed

4 files changed

+186
-0
lines changed
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
---
2+
description: Enforce the use of `array.at(-1)` instead of `array[array.length - 1]`
3+
---
4+
5+
> 🛑 This file is source code, not the primary documentation location! 🛑
6+
>
7+
> See **https://typescript-eslint.io/rules/prefer-at** for documentation.
8+
9+
There are two ways to get the last item of the array:
10+
11+
- `arr[arr.length - 1]`: getting an item by index calculated relative to the length of the array.
12+
- `arr.at(-1)`: getting an item by the `at` method. [Docs](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/at)
13+
14+
`arr.at(-1)` is a cleaner equivalent to `arr[arr.length - 1]`.
15+
16+
## Examples
17+
18+
<!--tabs-->
19+
20+
### ❌ Incorrect
21+
22+
```ts
23+
let arr = [1, 2, 3];
24+
let a = arr[arr.length - 1];
25+
```
26+
27+
### ✅ Correct
28+
29+
```ts
30+
let arr = [1, 2, 3];
31+
let a = arr.at(-1);
32+
```
33+
34+
<!--/tabs-->
35+
36+
## When Not To Use It
37+
38+
If you support a browser that does not match
39+
[this table](https://caniuse.com/mdn-javascript_builtins_array_at)
40+
or use `node.js < 16.6.0` and don't include the polyfill

packages/eslint-plugin/src/rules/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,7 @@ import objectCurlySpacing from './object-curly-spacing';
9494
import paddingLineBetweenStatements from './padding-line-between-statements';
9595
import parameterProperties from './parameter-properties';
9696
import preferAsConst from './prefer-as-const';
97+
import preferAt from './prefer-at';
9798
import preferEnumInitializers from './prefer-enum-initializers';
9899
import preferForOf from './prefer-for-of';
99100
import preferFunctionType from './prefer-function-type';
@@ -227,6 +228,7 @@ export default {
227228
'padding-line-between-statements': paddingLineBetweenStatements,
228229
'parameter-properties': parameterProperties,
229230
'prefer-as-const': preferAsConst,
231+
'prefer-at': preferAt,
230232
'prefer-enum-initializers': preferEnumInitializers,
231233
'prefer-for-of': preferForOf,
232234
'prefer-function-type': preferFunctionType,
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
import type { TSESTree } from '@typescript-eslint/utils';
2+
import { AST_NODE_TYPES } from '@typescript-eslint/utils';
3+
4+
import * as util from '../util';
5+
6+
export default util.createRule({
7+
name: 'prefer-at',
8+
meta: {
9+
type: 'suggestion',
10+
fixable: 'code',
11+
docs: {
12+
description:
13+
'Enforce the use of `array.at(-1)` instead of `array[array.length - 1]`',
14+
recommended: false,
15+
},
16+
messages: {
17+
preferAt:
18+
'Expected a `{{name}}.at(-1)` instead of `{{name}}[{{name}}.length - 1]`.',
19+
},
20+
schema: [],
21+
},
22+
defaultOptions: [],
23+
create(context) {
24+
class UnknownNodeError extends Error {
25+
public constructor(node: TSESTree.Node) {
26+
super(`UnknownNode ${node.type}`);
27+
}
28+
}
29+
30+
function getName(node: TSESTree.Node): string {
31+
switch (node.type) {
32+
case AST_NODE_TYPES.PrivateIdentifier:
33+
return `#${node.name}`;
34+
case AST_NODE_TYPES.Identifier:
35+
return node.name;
36+
case AST_NODE_TYPES.ThisExpression:
37+
return 'this';
38+
case AST_NODE_TYPES.MemberExpression:
39+
return `${getName(node.object)}.${getName(node.property)}`;
40+
default:
41+
throw new UnknownNodeError(node);
42+
}
43+
}
44+
45+
return {
46+
MemberExpression(node: TSESTree.MemberExpression): void {
47+
try {
48+
if (
49+
node.property.type !== AST_NODE_TYPES.BinaryExpression ||
50+
node.property.operator !== '-' ||
51+
node.property.right.type !== AST_NODE_TYPES.Literal ||
52+
node.property.right.value !== 1
53+
) {
54+
return;
55+
}
56+
const objectName = getName(node.object);
57+
const propertyLeftName = getName(node.property.left);
58+
if (`${objectName}.length` === propertyLeftName) {
59+
context.report({
60+
messageId: 'preferAt',
61+
data: {
62+
name: objectName,
63+
},
64+
node,
65+
fix: fixer => fixer.replaceText(node, `${objectName}.at(-1)`),
66+
});
67+
}
68+
} catch (error) {
69+
if (error instanceof UnknownNodeError) {
70+
return;
71+
}
72+
throw error;
73+
}
74+
},
75+
};
76+
},
77+
});
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
import rule from '../../src/rules/prefer-at';
2+
import { RuleTester } from '../RuleTester';
3+
4+
const ruleTester = new RuleTester({
5+
parser: '@typescript-eslint/parser',
6+
});
7+
8+
ruleTester.run('prefer-at', rule, {
9+
valid: [
10+
'const a = arr.at(-1);',
11+
'const a = this.arr.at(-1);',
12+
'const a = this.#arr.at(-1);',
13+
'const a = this.#prop.arr.at(-1);',
14+
'const a = arr[arr.length + 1];',
15+
'const a = (arr ? b : c)[arr.length - 1];',
16+
],
17+
invalid: [
18+
{
19+
code: 'const a = arr[arr.length - 1];',
20+
errors: [
21+
{
22+
messageId: 'preferAt',
23+
data: {
24+
name: 'arr',
25+
},
26+
},
27+
],
28+
output: 'const a = arr.at(-1);',
29+
},
30+
{
31+
code: 'const a = this.arr[this.arr.length - 1];',
32+
errors: [
33+
{
34+
messageId: 'preferAt',
35+
data: {
36+
name: 'this.arr',
37+
},
38+
},
39+
],
40+
output: 'const a = this.arr.at(-1);',
41+
},
42+
{
43+
code: 'const a = this.#arr[this.#arr.length - 1];',
44+
errors: [
45+
{
46+
messageId: 'preferAt',
47+
data: {
48+
name: 'this.#arr',
49+
},
50+
},
51+
],
52+
output: 'const a = this.#arr.at(-1);',
53+
},
54+
{
55+
code: 'const a = this.#prop.arr[this.#prop.arr.length - 1];',
56+
errors: [
57+
{
58+
messageId: 'preferAt',
59+
data: {
60+
name: 'this.#prop.arr',
61+
},
62+
},
63+
],
64+
output: 'const a = this.#prop.arr.at(-1);',
65+
},
66+
],
67+
});

0 commit comments

Comments
 (0)
0