Skip to content

Commit b13f039

Browse files
authored
feat: type-enhanced prefer-array-to-reversed (#84)
* feat: type-enhanced prefer-array-to-reversed Adds type-enhancement to the `prefer-array-to-reversed` rule such that non-arrays will not be converted to use `toReversed`. Fixes #69. * chore: update readme
1 parent c499c4b commit b13f039

4 files changed

Lines changed: 261 additions & 75 deletions

File tree

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,7 @@ Read more at the
141141
| [prefer-array-at](./src/rules/prefer-array-at.ts) | Prefer `Array.prototype.at()` over length-based indexing ||| 🔶 |
142142
| [prefer-array-fill](./src/rules/prefer-array-fill.ts) | Prefer `Array.prototype.fill()` over `Array.from()` or `map()` with constant values ||| ✖️ |
143143
| [prefer-includes](./src/rules/prefer-includes.ts) | Prefer `.includes()` over `indexOf()` comparisons for arrays and strings ||| ✖️ |
144-
| [prefer-array-to-reversed](./src/rules/prefer-array-to-reversed.ts) | Prefer `Array.prototype.toReversed()` over copying and reversing arrays ||| ✖️ |
144+
| [prefer-array-to-reversed](./src/rules/prefer-array-to-reversed.ts) | Prefer `Array.prototype.toReversed()` over copying and reversing arrays ||| 🔶 |
145145
| [prefer-array-to-sorted](./src/rules/prefer-array-to-sorted.ts) | Prefer `Array.prototype.toSorted()` over copying and sorting arrays ||| 🔶 |
146146
| [prefer-array-to-spliced](./src/rules/prefer-array-to-spliced.ts) | Prefer `Array.prototype.toSpliced()` over copying and splicing arrays ||| ✖️ |
147147
| [prefer-exponentiation-operator](./src/rules/prefer-exponentiation-operator.ts) | Prefer the exponentiation operator `**` over `Math.pow()` ||| ✖️ |

src/main.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,8 @@ const plugin: ESLint.Plugin = {
3535
'prefer-array-fill': preferArrayFill,
3636
'prefer-array-from-map': preferArrayFromMap,
3737
'prefer-includes': preferIncludes,
38-
'prefer-array-to-reversed': preferArrayToReversed,
38+
'prefer-array-to-reversed':
39+
preferArrayToReversed as never as Rule.RuleModule,
3940
'prefer-array-to-sorted': preferArrayToSorted as never as Rule.RuleModule,
4041
'prefer-array-to-spliced': preferArrayToSpliced,
4142
'prefer-exponentiation-operator': preferExponentiationOperator,
Lines changed: 246 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -1,99 +1,278 @@
11
import {RuleTester} from 'eslint';
2+
import {RuleTester as TSRuleTester} from '@typescript-eslint/rule-tester';
23
import {preferArrayToReversed} from './prefer-array-to-reversed.js';
4+
import * as path from 'node:path';
5+
import {fileURLToPath} from 'node:url';
36

7+
const rootDir = path.resolve(
8+
path.dirname(fileURLToPath(import.meta.url)),
9+
'../..'
10+
);
11+
const typedRuleTester = new TSRuleTester({
12+
languageOptions: {
13+
parserOptions: {
14+
projectService: {
15+
allowDefaultProject: ['*.ts'],
16+
defaultProject: './tsconfig.json'
17+
},
18+
tsconfigRootDir: rootDir
19+
}
20+
}
21+
});
422
const ruleTester = new RuleTester({
523
languageOptions: {
624
ecmaVersion: 2022,
725
sourceType: 'module'
826
}
927
});
1028

11-
ruleTester.run('prefer-array-to-reversed', preferArrayToReversed, {
12-
valid: [
13-
'const reversed = arr.reverse();',
14-
'arr.reverse();',
29+
ruleTester.run(
30+
'prefer-array-to-reversed (untyped)',
31+
preferArrayToReversed as never,
32+
{
33+
valid: [
34+
'const reversed = arr.reverse();',
35+
'arr.reverse();',
1536

16-
// slice with non-zero argument
17-
'const reversed = arr.slice(1).reverse();',
18-
'const reversed = arr.slice(0, 5).reverse();',
37+
// slice with non-zero argument
38+
'const reversed = arr.slice(1).reverse();',
39+
'const reversed = arr.slice(0, 5).reverse();',
1940

20-
// Already using toReversed
21-
'const reversed = arr.toReversed();',
41+
// Already using toReversed
42+
'const reversed = arr.toReversed();',
2243

23-
// reversing other results
24-
'const filtered = arr.filter(x => x > 0).reverse();',
25-
'const mapped = arr.map(x => x * 2).reverse();'
26-
],
44+
// reversing other results
45+
'const filtered = arr.filter(x => x > 0).reverse();',
46+
'const mapped = arr.map(x => x * 2).reverse();'
47+
],
2748

28-
invalid: [
29-
// concat().reverse()
30-
{
31-
code: 'const reversed = arr.concat().reverse();',
32-
output: 'const reversed = arr.toReversed();',
33-
errors: [{messageId: 'preferToReversed', data: {array: 'arr'}}]
34-
},
49+
invalid: [
50+
// concat().reverse()
51+
{
52+
code: 'const reversed = arr.concat().reverse();',
53+
output: 'const reversed = arr.toReversed();',
54+
errors: [
55+
{
56+
messageId: 'preferToReversed',
57+
data: {array: 'arr'},
58+
line: 1,
59+
column: 18
60+
}
61+
]
62+
},
3563

36-
// slice().reverse()
37-
{
38-
code: 'const reversed = arr.slice().reverse();',
39-
output: 'const reversed = arr.toReversed();',
40-
errors: [{messageId: 'preferToReversed', data: {array: 'arr'}}]
41-
},
64+
// slice().reverse()
65+
{
66+
code: 'const reversed = arr.slice().reverse();',
67+
output: 'const reversed = arr.toReversed();',
68+
errors: [
69+
{
70+
messageId: 'preferToReversed',
71+
data: {array: 'arr'},
72+
line: 1,
73+
column: 18
74+
}
75+
]
76+
},
4277

43-
// slice(0).reverse()
44-
{
45-
code: 'const reversed = arr.slice(0).reverse();',
46-
output: 'const reversed = arr.toReversed();',
47-
errors: [{messageId: 'preferToReversed', data: {array: 'arr'}}]
48-
},
78+
// slice(0).reverse()
79+
{
80+
code: 'const reversed = arr.slice(0).reverse();',
81+
output: 'const reversed = arr.toReversed();',
82+
errors: [
83+
{
84+
messageId: 'preferToReversed',
85+
data: {array: 'arr'},
86+
line: 1,
87+
column: 18
88+
}
89+
]
90+
},
4991

50-
// [...array].reverse()
51-
{
52-
code: 'const reversed = [...arr].reverse();',
53-
output: 'const reversed = arr.toReversed();',
54-
errors: [{messageId: 'preferToReversed', data: {array: 'arr'}}]
55-
},
92+
// [...array].reverse()
93+
{
94+
code: 'const reversed = [...arr].reverse();',
95+
output: 'const reversed = arr.toReversed();',
96+
errors: [
97+
{
98+
messageId: 'preferToReversed',
99+
data: {array: 'arr'},
100+
line: 1,
101+
column: 18
102+
}
103+
]
104+
},
56105

57-
// Member expressions
58-
{
59-
code: 'const reversed = obj.arr.concat().reverse();',
60-
output: 'const reversed = obj.arr.toReversed();',
61-
errors: [{messageId: 'preferToReversed', data: {array: 'obj.arr'}}]
62-
},
106+
// Member expressions
107+
{
108+
code: 'const reversed = obj.arr.concat().reverse();',
109+
output: 'const reversed = obj.arr.toReversed();',
110+
errors: [
111+
{
112+
messageId: 'preferToReversed',
113+
data: {array: 'obj.arr'},
114+
line: 1,
115+
column: 18
116+
}
117+
]
118+
},
119+
120+
// Member expressions with slice
121+
{
122+
code: 'const reversed = obj.arr.slice().reverse();',
123+
output: 'const reversed = obj.arr.toReversed();',
124+
errors: [
125+
{
126+
messageId: 'preferToReversed',
127+
data: {array: 'obj.arr'},
128+
line: 1,
129+
column: 18
130+
}
131+
]
132+
},
133+
134+
// Member expressions with spread
135+
{
136+
code: 'const reversed = [...obj.arr].reverse();',
137+
output: 'const reversed = obj.arr.toReversed();',
138+
errors: [
139+
{
140+
messageId: 'preferToReversed',
141+
data: {array: 'obj.arr'},
142+
line: 1,
143+
column: 18
144+
}
145+
]
146+
},
147+
148+
// Without assignment
149+
{
150+
code: 'arr.concat().reverse();',
151+
output: 'arr.toReversed();',
152+
errors: [
153+
{
154+
messageId: 'preferToReversed',
155+
data: {array: 'arr'},
156+
line: 1,
157+
column: 1
158+
}
159+
]
160+
},
63161

64-
// Member expressions with slice
162+
// Nested in expressions
163+
{
164+
code: 'console.log(arr.slice().reverse());',
165+
output: 'console.log(arr.toReversed());',
166+
errors: [
167+
{
168+
messageId: 'preferToReversed',
169+
data: {array: 'arr'},
170+
line: 1,
171+
column: 13
172+
}
173+
]
174+
},
175+
176+
// Complex expressions
177+
{
178+
code: 'const result = someFunc().slice(0).reverse();',
179+
output: 'const result = someFunc().toReversed();',
180+
errors: [
181+
{
182+
messageId: 'preferToReversed',
183+
data: {array: 'someFunc()'},
184+
line: 1,
185+
column: 16
186+
}
187+
]
188+
}
189+
]
190+
}
191+
);
192+
193+
typedRuleTester.run('prefer-array-to-reversed (typed)', preferArrayToReversed, {
194+
valid: [
195+
// Set spread - Set doesn't have toReversed()
65196
{
66-
code: 'const reversed = obj.arr.slice().reverse();',
67-
output: 'const reversed = obj.arr.toReversed();',
68-
errors: [{messageId: 'preferToReversed', data: {array: 'obj.arr'}}]
197+
code: `
198+
declare const mySet: Set<number>;
199+
const reversed = [...mySet].reverse();
200+
`
69201
},
70-
71-
// Member expressions with spread
202+
// Map spread - Map doesn't have toReversed()
72203
{
73-
code: 'const reversed = [...obj.arr].reverse();',
74-
output: 'const reversed = obj.arr.toReversed();',
75-
errors: [{messageId: 'preferToReversed', data: {array: 'obj.arr'}}]
204+
code: `
205+
declare const myMap: Map<string, number>;
206+
const reversed = [...myMap].reverse();
207+
`
76208
},
209+
// Iterable spread - generic iterables don't have toReversed()
210+
{
211+
code: `
212+
declare const iter: Iterable<number>;
213+
const reversed = [...iter].reverse();
214+
`
215+
}
216+
],
77217

78-
// Without assignment
218+
invalid: [
219+
// Typed array variable - spread
79220
{
80-
code: 'arr.concat().reverse();',
81-
output: 'arr.toReversed();',
82-
errors: [{messageId: 'preferToReversed', data: {array: 'arr'}}]
221+
code: `
222+
const arr: number[] = [1, 2, 3];
223+
const reversed = [...arr].reverse();
224+
`,
225+
output: `
226+
const arr: number[] = [1, 2, 3];
227+
const reversed = arr.toReversed();
228+
`,
229+
errors: [
230+
{
231+
messageId: 'preferToReversed' as const,
232+
line: 3,
233+
column: 26
234+
}
235+
]
83236
},
84-
85-
// Nested in expressions
237+
// Tuple type - concat
86238
{
87-
code: 'console.log(arr.slice().reverse());',
88-
output: 'console.log(arr.toReversed());',
89-
errors: [{messageId: 'preferToReversed', data: {array: 'arr'}}]
239+
code: `
240+
const tuple: [number, string] = [1, 'a'];
241+
const reversed = tuple.concat().reverse();
242+
`,
243+
output: `
244+
const tuple: [number, string] = [1, 'a'];
245+
const reversed = tuple.toReversed();
246+
`,
247+
errors: [
248+
{
249+
messageId: 'preferToReversed' as const,
250+
line: 3,
251+
column: 26
252+
}
253+
]
90254
},
91-
92-
// Complex expressions
255+
// Function returning array
93256
{
94-
code: 'const result = someFunc().slice(0).reverse();',
95-
output: 'const result = someFunc().toReversed();',
96-
errors: [{messageId: 'preferToReversed', data: {array: 'someFunc()'}}]
257+
code: `
258+
function getItems(): string[] {
259+
return ['a', 'b', 'c'];
260+
}
261+
const reversed = [...getItems()].reverse();
262+
`,
263+
output: `
264+
function getItems(): string[] {
265+
return ['a', 'b', 'c'];
266+
}
267+
const reversed = getItems().toReversed();
268+
`,
269+
errors: [
270+
{
271+
messageId: 'preferToReversed' as const,
272+
line: 5,
273+
column: 26
274+
}
275+
]
97276
}
98277
]
99278
});

0 commit comments

Comments
 (0)