eslint-config-typed 4.2.1 → 4.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -0
- package/dist/configs/immer.d.mts +3 -0
- package/dist/configs/immer.d.mts.map +1 -0
- package/dist/configs/immer.mjs +11 -0
- package/dist/configs/immer.mjs.map +1 -0
- package/dist/configs/index.d.mts +2 -0
- package/dist/configs/index.d.mts.map +1 -1
- package/dist/configs/index.mjs +2 -0
- package/dist/configs/index.mjs.map +1 -1
- package/dist/configs/plugins.d.mts +1 -1
- package/dist/configs/plugins.d.mts.map +1 -1
- package/dist/configs/plugins.mjs +2 -0
- package/dist/configs/plugins.mjs.map +1 -1
- package/dist/configs/ts-data-forge.d.mts +3 -0
- package/dist/configs/ts-data-forge.d.mts.map +1 -0
- package/dist/configs/ts-data-forge.mjs +11 -0
- package/dist/configs/ts-data-forge.mjs.map +1 -0
- package/dist/configs/typescript.d.mts.map +1 -1
- package/dist/configs/typescript.mjs +0 -2
- package/dist/configs/typescript.mjs.map +1 -1
- package/dist/entry-point.mjs +4 -0
- package/dist/entry-point.mjs.map +1 -1
- package/dist/index.mjs +4 -0
- package/dist/index.mjs.map +1 -1
- package/dist/plugins/index.d.mts +1 -0
- package/dist/plugins/index.d.mts.map +1 -1
- package/dist/plugins/index.mjs +1 -0
- package/dist/plugins/index.mjs.map +1 -1
- package/dist/plugins/ts-data-forge/index.d.mts +2 -0
- package/dist/plugins/ts-data-forge/index.d.mts.map +1 -0
- package/dist/plugins/ts-data-forge/index.mjs +2 -0
- package/dist/plugins/ts-data-forge/index.mjs.map +1 -0
- package/dist/plugins/ts-data-forge/plugin.d.mts +3 -0
- package/dist/plugins/ts-data-forge/plugin.d.mts.map +1 -0
- package/dist/plugins/ts-data-forge/plugin.mjs +8 -0
- package/dist/plugins/ts-data-forge/plugin.mjs.map +1 -0
- package/dist/plugins/ts-data-forge/rules/branded-number-types.d.mts +5 -0
- package/dist/plugins/ts-data-forge/rules/branded-number-types.d.mts.map +1 -0
- package/dist/plugins/ts-data-forge/rules/branded-number-types.mjs +35 -0
- package/dist/plugins/ts-data-forge/rules/branded-number-types.mjs.map +1 -0
- package/dist/plugins/ts-data-forge/rules/constants.d.mts +2 -0
- package/dist/plugins/ts-data-forge/rules/constants.d.mts.map +1 -0
- package/dist/plugins/ts-data-forge/rules/constants.mjs +4 -0
- package/dist/plugins/ts-data-forge/rules/constants.mjs.map +1 -0
- package/dist/plugins/ts-data-forge/rules/import-utils.d.mts +5 -0
- package/dist/plugins/ts-data-forge/rules/import-utils.d.mts.map +1 -0
- package/dist/plugins/ts-data-forge/rules/import-utils.mjs +31 -0
- package/dist/plugins/ts-data-forge/rules/import-utils.mjs.map +1 -0
- package/dist/plugins/ts-data-forge/rules/index.d.mts +2 -0
- package/dist/plugins/ts-data-forge/rules/index.d.mts.map +1 -0
- package/dist/plugins/ts-data-forge/rules/index.mjs +2 -0
- package/dist/plugins/ts-data-forge/rules/index.mjs.map +1 -0
- package/dist/plugins/ts-data-forge/rules/prefer-arr-is-array-at-least-length.d.mts +6 -0
- package/dist/plugins/ts-data-forge/rules/prefer-arr-is-array-at-least-length.d.mts.map +1 -0
- package/dist/plugins/ts-data-forge/rules/prefer-arr-is-array-at-least-length.mjs +94 -0
- package/dist/plugins/ts-data-forge/rules/prefer-arr-is-array-at-least-length.mjs.map +1 -0
- package/dist/plugins/ts-data-forge/rules/prefer-arr-is-array-of-length.d.mts +6 -0
- package/dist/plugins/ts-data-forge/rules/prefer-arr-is-array-of-length.d.mts.map +1 -0
- package/dist/plugins/ts-data-forge/rules/prefer-arr-is-array-of-length.mjs +95 -0
- package/dist/plugins/ts-data-forge/rules/prefer-arr-is-array-of-length.mjs.map +1 -0
- package/dist/plugins/ts-data-forge/rules/prefer-arr-is-array.d.mts +6 -0
- package/dist/plugins/ts-data-forge/rules/prefer-arr-is-array.d.mts.map +1 -0
- package/dist/plugins/ts-data-forge/rules/prefer-arr-is-array.mjs +72 -0
- package/dist/plugins/ts-data-forge/rules/prefer-arr-is-array.mjs.map +1 -0
- package/dist/plugins/ts-data-forge/rules/prefer-arr-is-non-empty.d.mts +6 -0
- package/dist/plugins/ts-data-forge/rules/prefer-arr-is-non-empty.d.mts.map +1 -0
- package/dist/plugins/ts-data-forge/rules/prefer-arr-is-non-empty.mjs +71 -0
- package/dist/plugins/ts-data-forge/rules/prefer-arr-is-non-empty.mjs.map +1 -0
- package/dist/plugins/ts-data-forge/rules/prefer-arr-sum.d.mts +6 -0
- package/dist/plugins/ts-data-forge/rules/prefer-arr-sum.d.mts.map +1 -0
- package/dist/plugins/ts-data-forge/rules/prefer-arr-sum.mjs +183 -0
- package/dist/plugins/ts-data-forge/rules/prefer-arr-sum.mjs.map +1 -0
- package/dist/plugins/ts-data-forge/rules/prefer-as-int.d.mts +6 -0
- package/dist/plugins/ts-data-forge/rules/prefer-as-int.d.mts.map +1 -0
- package/dist/plugins/ts-data-forge/rules/prefer-as-int.mjs +86 -0
- package/dist/plugins/ts-data-forge/rules/prefer-as-int.mjs.map +1 -0
- package/dist/plugins/ts-data-forge/rules/prefer-is-non-null-object.d.mts +6 -0
- package/dist/plugins/ts-data-forge/rules/prefer-is-non-null-object.d.mts.map +1 -0
- package/dist/plugins/ts-data-forge/rules/prefer-is-non-null-object.mjs +103 -0
- package/dist/plugins/ts-data-forge/rules/prefer-is-non-null-object.mjs.map +1 -0
- package/dist/plugins/ts-data-forge/rules/prefer-is-record-and-has-key.d.mts +6 -0
- package/dist/plugins/ts-data-forge/rules/prefer-is-record-and-has-key.d.mts.map +1 -0
- package/dist/plugins/ts-data-forge/rules/prefer-is-record-and-has-key.mjs +105 -0
- package/dist/plugins/ts-data-forge/rules/prefer-is-record-and-has-key.mjs.map +1 -0
- package/dist/plugins/ts-data-forge/rules/prefer-range-for-loop.d.mts +6 -0
- package/dist/plugins/ts-data-forge/rules/prefer-range-for-loop.d.mts.map +1 -0
- package/dist/plugins/ts-data-forge/rules/prefer-range-for-loop.mjs +130 -0
- package/dist/plugins/ts-data-forge/rules/prefer-range-for-loop.mjs.map +1 -0
- package/dist/plugins/ts-data-forge/rules/rules.d.mts +12 -0
- package/dist/plugins/ts-data-forge/rules/rules.d.mts.map +1 -0
- package/dist/plugins/ts-data-forge/rules/rules.mjs +24 -0
- package/dist/plugins/ts-data-forge/rules/rules.mjs.map +1 -0
- package/dist/plugins/ts-restrictions/rules/check-destructuring-completeness.d.mts.map +1 -1
- package/dist/plugins/ts-restrictions/rules/check-destructuring-completeness.mjs +6 -8
- package/dist/plugins/ts-restrictions/rules/check-destructuring-completeness.mjs.map +1 -1
- package/dist/plugins/ts-restrictions/rules/no-restricted-cast-name.d.mts.map +1 -1
- package/dist/plugins/ts-restrictions/rules/no-restricted-cast-name.mjs +11 -15
- package/dist/plugins/ts-restrictions/rules/no-restricted-cast-name.mjs.map +1 -1
- package/dist/rules/eslint-jest-rules.d.mts +3 -0
- package/dist/rules/eslint-jest-rules.d.mts.map +1 -1
- package/dist/rules/eslint-jest-rules.mjs +3 -0
- package/dist/rules/eslint-jest-rules.mjs.map +1 -1
- package/dist/rules/eslint-ts-data-forge-rules.d.mts +12 -0
- package/dist/rules/eslint-ts-data-forge-rules.d.mts.map +1 -0
- package/dist/rules/eslint-ts-data-forge-rules.mjs +14 -0
- package/dist/rules/eslint-ts-data-forge-rules.mjs.map +1 -0
- package/dist/rules/eslint-vitest-rules.d.mts +2 -2
- package/dist/rules/eslint-vitest-rules.mjs +2 -2
- package/dist/rules/eslint-vitest-rules.mjs.map +1 -1
- package/dist/rules/index.d.mts +1 -0
- package/dist/rules/index.d.mts.map +1 -1
- package/dist/rules/index.mjs +1 -0
- package/dist/rules/index.mjs.map +1 -1
- package/dist/types/define-known-rules.d.mts +2 -2
- package/dist/types/define-known-rules.d.mts.map +1 -1
- package/dist/types/define-known-rules.mjs.map +1 -1
- package/dist/types/rules/eslint-jest-rules.d.mts +119 -67
- package/dist/types/rules/eslint-jest-rules.d.mts.map +1 -1
- package/dist/types/rules/eslint-ts-data-forge-rules.d.mts +147 -0
- package/dist/types/rules/eslint-ts-data-forge-rules.d.mts.map +1 -0
- package/dist/types/rules/eslint-ts-data-forge-rules.mjs +2 -0
- package/dist/types/rules/eslint-ts-data-forge-rules.mjs.map +1 -0
- package/dist/types/rules/eslint-vitest-rules.d.mts +41 -20
- package/dist/types/rules/eslint-vitest-rules.d.mts.map +1 -1
- package/dist/types/rules/index.d.mts +1 -0
- package/dist/types/rules/index.d.mts.map +1 -1
- package/package.json +15 -15
- package/src/configs/immer.mts +9 -0
- package/src/configs/index.mts +2 -0
- package/src/configs/plugins.mts +3 -0
- package/src/configs/ts-data-forge.mts +11 -0
- package/src/configs/typescript.mts +0 -2
- package/src/plugins/index.mts +1 -0
- package/src/plugins/strict-dependencies/rules/resolve-import-path.test.mts +7 -9
- package/src/plugins/ts-data-forge/index.mts +1 -0
- package/src/plugins/ts-data-forge/plugin.mts +6 -0
- package/src/plugins/ts-data-forge/rules/branded-number-types.mts +36 -0
- package/src/plugins/ts-data-forge/rules/constants.mts +1 -0
- package/src/plugins/ts-data-forge/rules/import-utils.mts +56 -0
- package/src/plugins/ts-data-forge/rules/index.mts +1 -0
- package/src/plugins/ts-data-forge/rules/prefer-arr-is-array-at-least-length.mts +140 -0
- package/src/plugins/ts-data-forge/rules/prefer-arr-is-array-at-least-length.test.mts +175 -0
- package/src/plugins/ts-data-forge/rules/prefer-arr-is-array-of-length.mts +144 -0
- package/src/plugins/ts-data-forge/rules/prefer-arr-is-array-of-length.test.mts +183 -0
- package/src/plugins/ts-data-forge/rules/prefer-arr-is-array.mts +97 -0
- package/src/plugins/ts-data-forge/rules/prefer-arr-is-array.test.mts +62 -0
- package/src/plugins/ts-data-forge/rules/prefer-arr-is-non-empty.mts +106 -0
- package/src/plugins/ts-data-forge/rules/prefer-arr-is-non-empty.test.mts +83 -0
- package/src/plugins/ts-data-forge/rules/prefer-arr-sum.mts +269 -0
- package/src/plugins/ts-data-forge/rules/prefer-arr-sum.test.mts +171 -0
- package/src/plugins/ts-data-forge/rules/prefer-as-int.mts +130 -0
- package/src/plugins/ts-data-forge/rules/prefer-as-int.test.mts +267 -0
- package/src/plugins/ts-data-forge/rules/prefer-is-non-null-object.mts +144 -0
- package/src/plugins/ts-data-forge/rules/prefer-is-non-null-object.test.mts +156 -0
- package/src/plugins/ts-data-forge/rules/prefer-is-record-and-has-key.mts +158 -0
- package/src/plugins/ts-data-forge/rules/prefer-is-record-and-has-key.test.mts +191 -0
- package/src/plugins/ts-data-forge/rules/prefer-range-for-loop.mts +181 -0
- package/src/plugins/ts-data-forge/rules/prefer-range-for-loop.test.mts +156 -0
- package/src/plugins/ts-data-forge/rules/rules.mts +22 -0
- package/src/plugins/ts-restrictions/rules/check-destructuring-completeness.mts +6 -8
- package/src/plugins/ts-restrictions/rules/no-restricted-cast-name.mts +11 -15
- package/src/rules/eslint-jest-rules.mts +3 -0
- package/src/rules/eslint-ts-data-forge-rules.mts +13 -0
- package/src/rules/eslint-vitest-rules.mts +2 -2
- package/src/rules/index.mts +1 -0
- package/src/types/define-known-rules.mts +2 -0
- package/src/types/rules/eslint-jest-rules.mts +122 -67
- package/src/types/rules/eslint-ts-data-forge-rules.mts +156 -0
- package/src/types/rules/eslint-vitest-rules.mts +46 -21
- package/src/types/rules/index.mts +1 -0
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
import parser from '@typescript-eslint/parser';
|
|
2
|
+
import { RuleTester } from '@typescript-eslint/rule-tester';
|
|
3
|
+
import dedent from 'dedent';
|
|
4
|
+
import { preferArrIsArrayOfLength } from './prefer-arr-is-array-of-length.mjs';
|
|
5
|
+
|
|
6
|
+
const tester = new RuleTester({
|
|
7
|
+
languageOptions: {
|
|
8
|
+
parser,
|
|
9
|
+
parserOptions: {
|
|
10
|
+
ecmaVersion: 2020,
|
|
11
|
+
sourceType: 'module',
|
|
12
|
+
projectService: {
|
|
13
|
+
allowDefaultProject: ['*.ts*'],
|
|
14
|
+
},
|
|
15
|
+
tsconfigRootDir: `${import.meta.dirname}/../..`,
|
|
16
|
+
},
|
|
17
|
+
},
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
describe('prefer-arr-is-array-of-length', () => {
|
|
21
|
+
tester.run('prefer-arr-is-array-of-length', preferArrIsArrayOfLength, {
|
|
22
|
+
valid: [
|
|
23
|
+
{
|
|
24
|
+
name: 'ignores non-array types',
|
|
25
|
+
code: dedent`
|
|
26
|
+
const str = "hello";
|
|
27
|
+
const ok = str.length === 5;
|
|
28
|
+
`,
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
name: 'ignores other comparisons',
|
|
32
|
+
code: dedent`
|
|
33
|
+
const xs = [1, 2, 3];
|
|
34
|
+
const ok = xs.length > 0;
|
|
35
|
+
`,
|
|
36
|
+
},
|
|
37
|
+
],
|
|
38
|
+
invalid: [
|
|
39
|
+
{
|
|
40
|
+
name: 'replaces xs.length === n with Arr.isArrayOfLength',
|
|
41
|
+
code: dedent`
|
|
42
|
+
const xs: readonly number[] = [1, 2, 3];
|
|
43
|
+
const ok = xs.length === 3;
|
|
44
|
+
`,
|
|
45
|
+
output: dedent`
|
|
46
|
+
import { Arr } from 'ts-data-forge';
|
|
47
|
+
const xs: readonly number[] = [1, 2, 3];
|
|
48
|
+
const ok = Arr.isArrayOfLength(xs, 3);
|
|
49
|
+
`,
|
|
50
|
+
errors: [{ messageId: 'useIsArrayOfLength' }],
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
name: 'replaces n === xs.length with Arr.isArrayOfLength',
|
|
54
|
+
code: dedent`
|
|
55
|
+
const xs: readonly number[] = [1, 2, 3];
|
|
56
|
+
const ok = 3 === xs.length;
|
|
57
|
+
`,
|
|
58
|
+
output: dedent`
|
|
59
|
+
import { Arr } from 'ts-data-forge';
|
|
60
|
+
const xs: readonly number[] = [1, 2, 3];
|
|
61
|
+
const ok = Arr.isArrayOfLength(xs, 3);
|
|
62
|
+
`,
|
|
63
|
+
errors: [{ messageId: 'useIsArrayOfLength' }],
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
name: 'works with no type annotation',
|
|
67
|
+
code: dedent`
|
|
68
|
+
const xs = [1, 2, 3];
|
|
69
|
+
const ok = xs.length === 3;
|
|
70
|
+
`,
|
|
71
|
+
output: dedent`
|
|
72
|
+
import { Arr } from 'ts-data-forge';
|
|
73
|
+
const xs = [1, 2, 3];
|
|
74
|
+
const ok = Arr.isArrayOfLength(xs, 3);
|
|
75
|
+
`,
|
|
76
|
+
errors: [{ messageId: 'useIsArrayOfLength' }],
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
name: 'works with const assertion',
|
|
80
|
+
code: dedent`
|
|
81
|
+
const xs = [1, 2, 3] as const;
|
|
82
|
+
const ok = xs.length === 3;
|
|
83
|
+
`,
|
|
84
|
+
output: dedent`
|
|
85
|
+
import { Arr } from 'ts-data-forge';
|
|
86
|
+
const xs = [1, 2, 3] as const;
|
|
87
|
+
const ok = Arr.isArrayOfLength(xs, 3);
|
|
88
|
+
`,
|
|
89
|
+
errors: [{ messageId: 'useIsArrayOfLength' }],
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
name: 'works with variable length',
|
|
93
|
+
code: dedent`
|
|
94
|
+
const xs = [1, 2, 3];
|
|
95
|
+
const n = 3;
|
|
96
|
+
const ok = xs.length === n;
|
|
97
|
+
`,
|
|
98
|
+
output: dedent`
|
|
99
|
+
import { Arr } from 'ts-data-forge';
|
|
100
|
+
const xs = [1, 2, 3];
|
|
101
|
+
const n = 3;
|
|
102
|
+
const ok = Arr.isArrayOfLength(xs, n);
|
|
103
|
+
`,
|
|
104
|
+
errors: [{ messageId: 'useIsArrayOfLength' }],
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
name: 'keeps existing Arr import',
|
|
108
|
+
code: dedent`
|
|
109
|
+
import { Arr } from 'ts-data-forge';
|
|
110
|
+
|
|
111
|
+
const xs = [1, 2, 3];
|
|
112
|
+
const ok = xs.length === 3;
|
|
113
|
+
`,
|
|
114
|
+
output: dedent`
|
|
115
|
+
import { Arr } from 'ts-data-forge';
|
|
116
|
+
|
|
117
|
+
const xs = [1, 2, 3];
|
|
118
|
+
const ok = Arr.isArrayOfLength(xs, 3);
|
|
119
|
+
`,
|
|
120
|
+
errors: [{ messageId: 'useIsArrayOfLength' }],
|
|
121
|
+
},
|
|
122
|
+
{
|
|
123
|
+
name: 'replaces multiple checks',
|
|
124
|
+
code: dedent`
|
|
125
|
+
const xs = [1, 2, 3];
|
|
126
|
+
const ys = [4, 5];
|
|
127
|
+
const ok1 = xs.length === 3;
|
|
128
|
+
const ok2 = ys.length === 2;
|
|
129
|
+
`,
|
|
130
|
+
output: dedent`
|
|
131
|
+
import { Arr } from 'ts-data-forge';
|
|
132
|
+
const xs = [1, 2, 3];
|
|
133
|
+
const ys = [4, 5];
|
|
134
|
+
const ok1 = Arr.isArrayOfLength(xs, 3);
|
|
135
|
+
const ok2 = Arr.isArrayOfLength(ys, 2);
|
|
136
|
+
`,
|
|
137
|
+
errors: [
|
|
138
|
+
{ messageId: 'useIsArrayOfLength' },
|
|
139
|
+
{ messageId: 'useIsArrayOfLength' },
|
|
140
|
+
],
|
|
141
|
+
},
|
|
142
|
+
{
|
|
143
|
+
name: 'replaces xs.length !== n with !Arr.isArrayOfLength',
|
|
144
|
+
code: dedent`
|
|
145
|
+
const xs: readonly number[] = [1, 2, 3];
|
|
146
|
+
const ok = xs.length !== 3;
|
|
147
|
+
`,
|
|
148
|
+
output: dedent`
|
|
149
|
+
import { Arr } from 'ts-data-forge';
|
|
150
|
+
const xs: readonly number[] = [1, 2, 3];
|
|
151
|
+
const ok = !Arr.isArrayOfLength(xs, 3);
|
|
152
|
+
`,
|
|
153
|
+
errors: [{ messageId: 'useIsArrayOfLength' }],
|
|
154
|
+
},
|
|
155
|
+
{
|
|
156
|
+
name: 'replaces n !== xs.length with !Arr.isArrayOfLength',
|
|
157
|
+
code: dedent`
|
|
158
|
+
const xs: readonly number[] = [1, 2, 3];
|
|
159
|
+
const ok = 3 !== xs.length;
|
|
160
|
+
`,
|
|
161
|
+
output: dedent`
|
|
162
|
+
import { Arr } from 'ts-data-forge';
|
|
163
|
+
const xs: readonly number[] = [1, 2, 3];
|
|
164
|
+
const ok = !Arr.isArrayOfLength(xs, 3);
|
|
165
|
+
`,
|
|
166
|
+
errors: [{ messageId: 'useIsArrayOfLength' }],
|
|
167
|
+
},
|
|
168
|
+
{
|
|
169
|
+
name: 'works with !== and no type annotation',
|
|
170
|
+
code: dedent`
|
|
171
|
+
const xs = [1, 2, 3];
|
|
172
|
+
const ok = xs.length !== 5;
|
|
173
|
+
`,
|
|
174
|
+
output: dedent`
|
|
175
|
+
import { Arr } from 'ts-data-forge';
|
|
176
|
+
const xs = [1, 2, 3];
|
|
177
|
+
const ok = !Arr.isArrayOfLength(xs, 5);
|
|
178
|
+
`,
|
|
179
|
+
errors: [{ messageId: 'useIsArrayOfLength' }],
|
|
180
|
+
},
|
|
181
|
+
],
|
|
182
|
+
});
|
|
183
|
+
}, 20000);
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import {
|
|
2
|
+
AST_NODE_TYPES,
|
|
3
|
+
type TSESLint,
|
|
4
|
+
type TSESTree,
|
|
5
|
+
} from '@typescript-eslint/utils';
|
|
6
|
+
import {
|
|
7
|
+
buildImportFixes,
|
|
8
|
+
getNamedImports,
|
|
9
|
+
getTsDataForgeImport,
|
|
10
|
+
} from './import-utils.mjs';
|
|
11
|
+
|
|
12
|
+
type Options = readonly [];
|
|
13
|
+
|
|
14
|
+
type MessageIds = 'useArrIsArray';
|
|
15
|
+
|
|
16
|
+
export const preferArrIsArray: TSESLint.RuleModule<MessageIds, Options> = {
|
|
17
|
+
meta: {
|
|
18
|
+
type: 'suggestion',
|
|
19
|
+
docs: {
|
|
20
|
+
description:
|
|
21
|
+
'Replace `Array.isArray` with `Arr.isArray` from ts-data-forge.',
|
|
22
|
+
},
|
|
23
|
+
fixable: 'code',
|
|
24
|
+
schema: [],
|
|
25
|
+
messages: {
|
|
26
|
+
useArrIsArray:
|
|
27
|
+
'Replace `Array.isArray` with `Arr.isArray` from ts-data-forge.',
|
|
28
|
+
},
|
|
29
|
+
},
|
|
30
|
+
|
|
31
|
+
create: (context) => {
|
|
32
|
+
const sourceCode = context.sourceCode;
|
|
33
|
+
|
|
34
|
+
const program = sourceCode.ast;
|
|
35
|
+
|
|
36
|
+
const tsDataForgeImport = getTsDataForgeImport(program);
|
|
37
|
+
|
|
38
|
+
const mut_nodesToFix: TSESTree.CallExpression[] = [];
|
|
39
|
+
|
|
40
|
+
return {
|
|
41
|
+
CallExpression: (node) => {
|
|
42
|
+
if (!isArrayIsArrayCall(node)) return;
|
|
43
|
+
|
|
44
|
+
mut_nodesToFix.push(node);
|
|
45
|
+
},
|
|
46
|
+
'Program:exit': () => {
|
|
47
|
+
// Check if Arr is already imported
|
|
48
|
+
const hasArrImport = getNamedImports(tsDataForgeImport).includes('Arr');
|
|
49
|
+
|
|
50
|
+
// Note: We add import only for the first node to avoid conflicts when
|
|
51
|
+
// multiple fixes try to insert at the same position. This means if the
|
|
52
|
+
// first node is disabled via eslint-disable comment, no import will be
|
|
53
|
+
// added.
|
|
54
|
+
for (const [index, node] of mut_nodesToFix.entries()) {
|
|
55
|
+
context.report({
|
|
56
|
+
node,
|
|
57
|
+
messageId: 'useArrIsArray',
|
|
58
|
+
fix: (fixer) => {
|
|
59
|
+
const callee = node.callee;
|
|
60
|
+
|
|
61
|
+
if (callee.type !== AST_NODE_TYPES.MemberExpression) return [];
|
|
62
|
+
|
|
63
|
+
const replacement = `Arr.isArray${sourceCode.getText(node).slice(sourceCode.getText(callee).length)}`;
|
|
64
|
+
|
|
65
|
+
// Add import only for the first node and only if not already imported
|
|
66
|
+
const importFixes =
|
|
67
|
+
index === 0 && !hasArrImport
|
|
68
|
+
? buildImportFixes(fixer, program, tsDataForgeImport, ['Arr'])
|
|
69
|
+
: [];
|
|
70
|
+
|
|
71
|
+
return [...importFixes, fixer.replaceText(node, replacement)];
|
|
72
|
+
},
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
},
|
|
76
|
+
};
|
|
77
|
+
},
|
|
78
|
+
defaultOptions: [],
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
const isArrayIsArrayCall = (
|
|
82
|
+
node: DeepReadonly<TSESTree.CallExpression>,
|
|
83
|
+
): boolean => {
|
|
84
|
+
if (node.callee.type !== AST_NODE_TYPES.MemberExpression) return false;
|
|
85
|
+
|
|
86
|
+
const { object, property } = node.callee;
|
|
87
|
+
|
|
88
|
+
if (object.type !== AST_NODE_TYPES.Identifier) return false;
|
|
89
|
+
|
|
90
|
+
if (object.name !== 'Array') return false;
|
|
91
|
+
|
|
92
|
+
if (property.type !== AST_NODE_TYPES.Identifier) return false;
|
|
93
|
+
|
|
94
|
+
if (property.name !== 'isArray') return false;
|
|
95
|
+
|
|
96
|
+
return true;
|
|
97
|
+
};
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { RuleTester } from '@typescript-eslint/rule-tester';
|
|
2
|
+
import dedent from 'dedent';
|
|
3
|
+
import { preferArrIsArray } from './prefer-arr-is-array.mjs';
|
|
4
|
+
|
|
5
|
+
const ruleTester = new RuleTester();
|
|
6
|
+
|
|
7
|
+
ruleTester.run('prefer-arr-is-array', preferArrIsArray, {
|
|
8
|
+
valid: [
|
|
9
|
+
{
|
|
10
|
+
name: 'Already using Arr.isArray',
|
|
11
|
+
code: dedent`
|
|
12
|
+
import { Arr } from 'ts-data-forge';
|
|
13
|
+
const result = Arr.isArray(value);
|
|
14
|
+
`,
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
name: 'Not Array.isArray call',
|
|
18
|
+
code: dedent`
|
|
19
|
+
const obj = { isArray: () => true };
|
|
20
|
+
const result = obj.isArray(value);
|
|
21
|
+
`,
|
|
22
|
+
},
|
|
23
|
+
],
|
|
24
|
+
invalid: [
|
|
25
|
+
{
|
|
26
|
+
name: 'Replace Array.isArray with Arr.isArray',
|
|
27
|
+
code: dedent`
|
|
28
|
+
const result = Array.isArray(value);
|
|
29
|
+
`,
|
|
30
|
+
output: dedent`
|
|
31
|
+
import { Arr } from 'ts-data-forge';
|
|
32
|
+
const result = Arr.isArray(value);
|
|
33
|
+
`,
|
|
34
|
+
errors: [{ messageId: 'useArrIsArray' }],
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
name: 'Replace Array.isArray with Arr.isArray when Arr is already imported',
|
|
38
|
+
code: dedent`
|
|
39
|
+
import { Arr } from 'ts-data-forge';
|
|
40
|
+
const result = Array.isArray(value);
|
|
41
|
+
`,
|
|
42
|
+
output: dedent`
|
|
43
|
+
import { Arr } from 'ts-data-forge';
|
|
44
|
+
const result = Arr.isArray(value);
|
|
45
|
+
`,
|
|
46
|
+
errors: [{ messageId: 'useArrIsArray' }],
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
name: 'Replace multiple Array.isArray calls',
|
|
50
|
+
code: dedent`
|
|
51
|
+
const result1 = Array.isArray(value1);
|
|
52
|
+
const result2 = Array.isArray(value2);
|
|
53
|
+
`,
|
|
54
|
+
output: dedent`
|
|
55
|
+
import { Arr } from 'ts-data-forge';
|
|
56
|
+
const result1 = Arr.isArray(value1);
|
|
57
|
+
const result2 = Arr.isArray(value2);
|
|
58
|
+
`,
|
|
59
|
+
errors: [{ messageId: 'useArrIsArray' }, { messageId: 'useArrIsArray' }],
|
|
60
|
+
},
|
|
61
|
+
],
|
|
62
|
+
});
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import {
|
|
2
|
+
AST_NODE_TYPES,
|
|
3
|
+
type TSESLint,
|
|
4
|
+
type TSESTree,
|
|
5
|
+
} from '@typescript-eslint/utils';
|
|
6
|
+
import {
|
|
7
|
+
buildImportFixes,
|
|
8
|
+
getNamedImports,
|
|
9
|
+
getTsDataForgeImport,
|
|
10
|
+
} from './import-utils.mjs';
|
|
11
|
+
|
|
12
|
+
type Options = readonly [];
|
|
13
|
+
|
|
14
|
+
type MessageIds = 'useIsNonEmpty';
|
|
15
|
+
|
|
16
|
+
export const preferArrIsNonEmpty: TSESLint.RuleModule<MessageIds, Options> = {
|
|
17
|
+
meta: {
|
|
18
|
+
type: 'suggestion',
|
|
19
|
+
docs: {
|
|
20
|
+
description:
|
|
21
|
+
'Replace `xs.length > 0` with `Arr.isNonEmpty(xs)` from ts-data-forge.',
|
|
22
|
+
},
|
|
23
|
+
fixable: 'code',
|
|
24
|
+
schema: [],
|
|
25
|
+
messages: {
|
|
26
|
+
useIsNonEmpty:
|
|
27
|
+
'Replace `{{original}}` with `Arr.isNonEmpty({{arrayName}})` from ts-data-forge.',
|
|
28
|
+
},
|
|
29
|
+
},
|
|
30
|
+
|
|
31
|
+
create: (context) => {
|
|
32
|
+
const sourceCode = context.sourceCode;
|
|
33
|
+
|
|
34
|
+
const program = sourceCode.ast;
|
|
35
|
+
|
|
36
|
+
const tsDataForgeImport = getTsDataForgeImport(program);
|
|
37
|
+
|
|
38
|
+
const mut_nodesToFix: {
|
|
39
|
+
node: TSESTree.BinaryExpression;
|
|
40
|
+
arrayExpression: TSESTree.Expression;
|
|
41
|
+
}[] = [];
|
|
42
|
+
|
|
43
|
+
return {
|
|
44
|
+
BinaryExpression: (node) => {
|
|
45
|
+
// Check for `xs.length > 0` or `0 < xs.length`
|
|
46
|
+
if (node.operator !== '>' && node.operator !== '<') return;
|
|
47
|
+
|
|
48
|
+
const isLengthOnLeft = node.operator === '>';
|
|
49
|
+
|
|
50
|
+
const lengthSide = isLengthOnLeft ? node.left : node.right;
|
|
51
|
+
|
|
52
|
+
const numberSide = isLengthOnLeft ? node.right : node.left;
|
|
53
|
+
|
|
54
|
+
// Check if one side is `.length` and the other is 0
|
|
55
|
+
if (
|
|
56
|
+
lengthSide.type !== AST_NODE_TYPES.MemberExpression ||
|
|
57
|
+
lengthSide.property.type !== AST_NODE_TYPES.Identifier ||
|
|
58
|
+
lengthSide.property.name !== 'length' ||
|
|
59
|
+
numberSide.type !== AST_NODE_TYPES.Literal ||
|
|
60
|
+
numberSide.value !== 0
|
|
61
|
+
) {
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
mut_nodesToFix.push({
|
|
66
|
+
node,
|
|
67
|
+
arrayExpression: lengthSide.object,
|
|
68
|
+
});
|
|
69
|
+
},
|
|
70
|
+
'Program:exit': () => {
|
|
71
|
+
const namedImports = getNamedImports(tsDataForgeImport);
|
|
72
|
+
|
|
73
|
+
const hasArrImport = namedImports.includes('Arr');
|
|
74
|
+
|
|
75
|
+
for (const [
|
|
76
|
+
index,
|
|
77
|
+
{ node, arrayExpression },
|
|
78
|
+
] of mut_nodesToFix.entries()) {
|
|
79
|
+
const arrayText = sourceCode.getText(arrayExpression);
|
|
80
|
+
|
|
81
|
+
const originalText = sourceCode.getText(node);
|
|
82
|
+
|
|
83
|
+
context.report({
|
|
84
|
+
node,
|
|
85
|
+
messageId: 'useIsNonEmpty',
|
|
86
|
+
data: {
|
|
87
|
+
original: originalText,
|
|
88
|
+
arrayName: arrayText,
|
|
89
|
+
},
|
|
90
|
+
fix: (fixer) => {
|
|
91
|
+
const replacement = `Arr.isNonEmpty(${arrayText})`;
|
|
92
|
+
|
|
93
|
+
const importFixes =
|
|
94
|
+
index === 0 && !hasArrImport
|
|
95
|
+
? buildImportFixes(fixer, program, tsDataForgeImport, ['Arr'])
|
|
96
|
+
: [];
|
|
97
|
+
|
|
98
|
+
return [...importFixes, fixer.replaceText(node, replacement)];
|
|
99
|
+
},
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
},
|
|
103
|
+
};
|
|
104
|
+
},
|
|
105
|
+
defaultOptions: [],
|
|
106
|
+
};
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import parser from '@typescript-eslint/parser';
|
|
2
|
+
import { RuleTester } from '@typescript-eslint/rule-tester';
|
|
3
|
+
import dedent from 'dedent';
|
|
4
|
+
import { preferArrIsNonEmpty } from './prefer-arr-is-non-empty.mjs';
|
|
5
|
+
|
|
6
|
+
const tester = new RuleTester({
|
|
7
|
+
languageOptions: {
|
|
8
|
+
parser,
|
|
9
|
+
parserOptions: {
|
|
10
|
+
ecmaVersion: 2020,
|
|
11
|
+
sourceType: 'module',
|
|
12
|
+
projectService: {
|
|
13
|
+
allowDefaultProject: ['*.ts*'],
|
|
14
|
+
},
|
|
15
|
+
tsconfigRootDir: `${import.meta.dirname}/../..`,
|
|
16
|
+
},
|
|
17
|
+
},
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
describe('prefer-arr-is-non-empty', () => {
|
|
21
|
+
tester.run('prefer-arr-is-non-empty', preferArrIsNonEmpty, {
|
|
22
|
+
valid: [
|
|
23
|
+
{
|
|
24
|
+
name: 'ignores other comparisons',
|
|
25
|
+
code: dedent`
|
|
26
|
+
const xs = [1, 2, 3];
|
|
27
|
+
const ok = xs.length >= 1;
|
|
28
|
+
`,
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
name: 'ignores comparisons with non-zero',
|
|
32
|
+
code: dedent`
|
|
33
|
+
const xs = [1, 2, 3];
|
|
34
|
+
const ok = xs.length > 1;
|
|
35
|
+
`,
|
|
36
|
+
},
|
|
37
|
+
],
|
|
38
|
+
invalid: [
|
|
39
|
+
{
|
|
40
|
+
name: 'replaces xs.length > 0 with Arr.isNonEmpty',
|
|
41
|
+
code: dedent`
|
|
42
|
+
const xs = [1, 2, 3];
|
|
43
|
+
const ok = xs.length > 0;
|
|
44
|
+
`,
|
|
45
|
+
output: dedent`
|
|
46
|
+
import { Arr } from 'ts-data-forge';
|
|
47
|
+
const xs = [1, 2, 3];
|
|
48
|
+
const ok = Arr.isNonEmpty(xs);
|
|
49
|
+
`,
|
|
50
|
+
errors: [{ messageId: 'useIsNonEmpty' }],
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
name: 'replaces 0 < xs.length with Arr.isNonEmpty',
|
|
54
|
+
code: dedent`
|
|
55
|
+
const xs = [1, 2, 3];
|
|
56
|
+
const ok = 0 < xs.length;
|
|
57
|
+
`,
|
|
58
|
+
output: dedent`
|
|
59
|
+
import { Arr } from 'ts-data-forge';
|
|
60
|
+
const xs = [1, 2, 3];
|
|
61
|
+
const ok = Arr.isNonEmpty(xs);
|
|
62
|
+
`,
|
|
63
|
+
errors: [{ messageId: 'useIsNonEmpty' }],
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
name: 'keeps existing Arr import',
|
|
67
|
+
code: dedent`
|
|
68
|
+
import { Arr } from 'ts-data-forge';
|
|
69
|
+
|
|
70
|
+
const xs = [1, 2, 3];
|
|
71
|
+
const ok = xs.length > 0;
|
|
72
|
+
`,
|
|
73
|
+
output: dedent`
|
|
74
|
+
import { Arr } from 'ts-data-forge';
|
|
75
|
+
|
|
76
|
+
const xs = [1, 2, 3];
|
|
77
|
+
const ok = Arr.isNonEmpty(xs);
|
|
78
|
+
`,
|
|
79
|
+
errors: [{ messageId: 'useIsNonEmpty' }],
|
|
80
|
+
},
|
|
81
|
+
],
|
|
82
|
+
});
|
|
83
|
+
}, 20000);
|