eslint-plugin-ts-type-forge 0.2.0

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.
Files changed (54) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +96 -0
  3. package/dist/index.d.mts +5 -0
  4. package/dist/index.d.mts.map +1 -0
  5. package/dist/index.mjs +3 -0
  6. package/dist/index.mjs.map +1 -0
  7. package/dist/plugin.d.mts +3 -0
  8. package/dist/plugin.d.mts.map +1 -0
  9. package/dist/plugin.mjs +11 -0
  10. package/dist/plugin.mjs.map +1 -0
  11. package/dist/rule-types.d.mts +58 -0
  12. package/dist/rule-types.d.mts.map +1 -0
  13. package/dist/rule-types.mjs +2 -0
  14. package/dist/rule-types.mjs.map +1 -0
  15. package/dist/rules/ast-utils.d.mts +17 -0
  16. package/dist/rules/ast-utils.d.mts.map +1 -0
  17. package/dist/rules/ast-utils.mjs +26 -0
  18. package/dist/rules/ast-utils.mjs.map +1 -0
  19. package/dist/rules/constants.d.mts +3 -0
  20. package/dist/rules/constants.d.mts.map +1 -0
  21. package/dist/rules/constants.mjs +5 -0
  22. package/dist/rules/constants.mjs.map +1 -0
  23. package/dist/rules/import-utils.d.mts +26 -0
  24. package/dist/rules/import-utils.d.mts.map +1 -0
  25. package/dist/rules/import-utils.mjs +35 -0
  26. package/dist/rules/import-utils.mjs.map +1 -0
  27. package/dist/rules/index.d.mts +2 -0
  28. package/dist/rules/index.d.mts.map +1 -0
  29. package/dist/rules/index.mjs +2 -0
  30. package/dist/rules/index.mjs.map +1 -0
  31. package/dist/rules/prefer-non-empty-array.d.mts +11 -0
  32. package/dist/rules/prefer-non-empty-array.d.mts.map +1 -0
  33. package/dist/rules/prefer-non-empty-array.mjs +81 -0
  34. package/dist/rules/prefer-non-empty-array.mjs.map +1 -0
  35. package/dist/rules/rules.d.mts +6 -0
  36. package/dist/rules/rules.d.mts.map +1 -0
  37. package/dist/rules/rules.mjs +8 -0
  38. package/dist/rules/rules.mjs.map +1 -0
  39. package/dist/types.d.mts +4 -0
  40. package/dist/types.d.mts.map +1 -0
  41. package/dist/types.mjs +2 -0
  42. package/dist/types.mjs.map +1 -0
  43. package/package.json +77 -0
  44. package/src/index.mts +7 -0
  45. package/src/plugin.mts +9 -0
  46. package/src/rule-types.mts +89 -0
  47. package/src/rules/ast-utils.mts +76 -0
  48. package/src/rules/constants.mts +3 -0
  49. package/src/rules/import-utils.mts +105 -0
  50. package/src/rules/index.mts +1 -0
  51. package/src/rules/prefer-non-empty-array.mts +129 -0
  52. package/src/rules/prefer-non-empty-array.test.mts +192 -0
  53. package/src/rules/rules.mts +6 -0
  54. package/src/types.mts +4 -0
@@ -0,0 +1,192 @@
1
+ import parser from '@typescript-eslint/parser';
2
+ import { RuleTester } from '@typescript-eslint/rule-tester';
3
+ import dedent from 'dedent';
4
+ import { preferNonEmptyArray } from './prefer-non-empty-array.mjs';
5
+
6
+ const tester = new RuleTester({
7
+ languageOptions: {
8
+ parser,
9
+ parserOptions: {
10
+ ecmaVersion: 2020,
11
+ sourceType: 'module',
12
+ },
13
+ },
14
+ });
15
+
16
+ describe('prefer-non-empty-array', () => {
17
+ tester.run('prefer-non-empty-array', preferNonEmptyArray, {
18
+ valid: [
19
+ {
20
+ name: 'ignores mutable tuples (that spelling is MutableNonEmptyArray)',
21
+ code: 'type T = [number, ...number[]];',
22
+ },
23
+ {
24
+ name: 'ignores plain readonly arrays',
25
+ code: 'type T = readonly number[];',
26
+ },
27
+ {
28
+ name: 'ignores tuples whose rest element type differs from the head',
29
+ code: 'type T = readonly [string, ...number[]];',
30
+ },
31
+ {
32
+ name: 'ignores min-length-2 tuples',
33
+ code: 'type T = readonly [number, number, ...number[]];',
34
+ },
35
+ {
36
+ name: 'ignores fixed-length tuples',
37
+ code: 'type T = readonly [number];',
38
+ },
39
+ {
40
+ name: 'ignores leading-rest tuples',
41
+ code: 'type T = readonly [...number[], number];',
42
+ },
43
+ {
44
+ name: 'ignores optional heads',
45
+ code: 'type T = readonly [number?, ...number[]];',
46
+ },
47
+ {
48
+ name: 'ignores labelled tuple members (the labels would be lost)',
49
+ code: 'type T = readonly [head: number, ...tail: number[]];',
50
+ },
51
+ {
52
+ name: 'ignores readonly operators applied to non-tuples',
53
+ code: 'type T = readonly string[];',
54
+ },
55
+ {
56
+ name: 'stays silent when NonEmptyArray is declared locally',
57
+ code: dedent`
58
+ type NonEmptyArray<A> = readonly [A, ...A[]];
59
+ type T = readonly [number, ...number[]];
60
+ `,
61
+ },
62
+ {
63
+ name: 'stays silent when NonEmptyArray is exported locally',
64
+ code: 'export type NonEmptyArray<A> = readonly [A, ...A[]];',
65
+ },
66
+ {
67
+ name: 'stays silent when NonEmptyArray is imported from elsewhere',
68
+ code: dedent`
69
+ import { type NonEmptyArray } from './my-types.mjs';
70
+
71
+ type T = readonly [number, ...number[]];
72
+ type U = NonEmptyArray<string>;
73
+ `,
74
+ },
75
+ ],
76
+ invalid: [
77
+ {
78
+ name: 'replaces readonly [V, ...V[]] with NonEmptyArray<V>',
79
+ code: 'type T = readonly [number, ...number[]];',
80
+ output: 'type T = NonEmptyArray<number>;',
81
+ errors: [{ messageId: 'useNonEmptyArray' }],
82
+ },
83
+ {
84
+ name: 'replaces readonly [V, ...(readonly V[])] with NonEmptyArray<V>',
85
+ code: 'type T = readonly [number, ...(readonly number[])];',
86
+ output: 'type T = NonEmptyArray<number>;',
87
+ errors: [{ messageId: 'useNonEmptyArray' }],
88
+ },
89
+ {
90
+ name: 'keeps a composite element type intact',
91
+ code: 'type T = readonly [string | number, ...(string | number)[]];',
92
+ output: 'type T = NonEmptyArray<string | number>;',
93
+ errors: [{ messageId: 'useNonEmptyArray' }],
94
+ },
95
+ {
96
+ name: 'handles generic element types',
97
+ code: 'type T<A> = readonly [A, ...A[]];',
98
+ output: 'type T<A> = NonEmptyArray<A>;',
99
+ errors: [{ messageId: 'useNonEmptyArray' }],
100
+ },
101
+ {
102
+ name: 'rewrites occurrences nested inside another tuple',
103
+ code: 'type T = readonly [readonly [number, ...number[]], ...string[]];',
104
+ output: 'type T = readonly [NonEmptyArray<number>, ...string[]];',
105
+ errors: [{ messageId: 'useNonEmptyArray' }],
106
+ },
107
+ {
108
+ name: 'rewrites parameter and return type positions',
109
+ code: dedent`
110
+ declare const f: (xs: readonly [number, ...number[]]) => readonly [string, ...string[]];
111
+ `,
112
+ output: dedent`
113
+ declare const f: (xs: NonEmptyArray<number>) => NonEmptyArray<string>;
114
+ `,
115
+ errors: [
116
+ { messageId: 'useNonEmptyArray' },
117
+ { messageId: 'useNonEmptyArray' },
118
+ ],
119
+ },
120
+ {
121
+ name: 'reuses the local alias when NonEmptyArray is imported aliased',
122
+ code: dedent`
123
+ import { type NonEmptyArray as NEA } from 'ts-type-forge';
124
+
125
+ type T = readonly [number, ...number[]];
126
+ `,
127
+ output: dedent`
128
+ import { type NonEmptyArray as NEA } from 'ts-type-forge';
129
+
130
+ type T = NEA<number>;
131
+ `,
132
+ errors: [{ messageId: 'useNonEmptyArray' }],
133
+ },
134
+ {
135
+ name: 'does not add an import with the default (global) import style',
136
+ code: dedent`
137
+ import { type StrictOmit } from 'ts-type-forge';
138
+
139
+ type T = readonly [number, ...number[]];
140
+ `,
141
+ output: dedent`
142
+ import { type StrictOmit } from 'ts-type-forge';
143
+
144
+ type T = NonEmptyArray<number>;
145
+ `,
146
+ errors: [{ messageId: 'useNonEmptyArray' }],
147
+ },
148
+ {
149
+ name: 'adds the named import when importStyle is "named"',
150
+ options: [{ importStyle: 'named' }],
151
+ code: 'type T = readonly [number, ...number[]];',
152
+ output: dedent`
153
+ import { type NonEmptyArray } from 'ts-type-forge';
154
+ type T = NonEmptyArray<number>;
155
+ `,
156
+ errors: [{ messageId: 'useNonEmptyArray' }],
157
+ },
158
+ {
159
+ name: 'adds the named import only once per file',
160
+ options: [{ importStyle: 'named' }],
161
+ code: dedent`
162
+ type T = readonly [number, ...number[]];
163
+ type U = readonly [string, ...string[]];
164
+ `,
165
+ output: dedent`
166
+ import { type NonEmptyArray } from 'ts-type-forge';
167
+ type T = NonEmptyArray<number>;
168
+ type U = NonEmptyArray<string>;
169
+ `,
170
+ errors: [
171
+ { messageId: 'useNonEmptyArray' },
172
+ { messageId: 'useNonEmptyArray' },
173
+ ],
174
+ },
175
+ {
176
+ name: 'keeps an existing NonEmptyArray import untouched',
177
+ options: [{ importStyle: 'named' }],
178
+ code: dedent`
179
+ import { type NonEmptyArray } from 'ts-type-forge';
180
+
181
+ type T = readonly [number, ...number[]];
182
+ `,
183
+ output: dedent`
184
+ import { type NonEmptyArray } from 'ts-type-forge';
185
+
186
+ type T = NonEmptyArray<number>;
187
+ `,
188
+ errors: [{ messageId: 'useNonEmptyArray' }],
189
+ },
190
+ ],
191
+ });
192
+ });
@@ -0,0 +1,6 @@
1
+ import { type ESLintPlugin } from '../types.mjs';
2
+ import { preferNonEmptyArray } from './prefer-non-empty-array.mjs';
3
+
4
+ export const tsTypeForgeRules = {
5
+ 'prefer-non-empty-array': preferNonEmptyArray,
6
+ } as const satisfies ESLintPlugin['rules'];
package/src/types.mts ADDED
@@ -0,0 +1,4 @@
1
+ import { type TSESLint } from '@typescript-eslint/utils';
2
+ import { type DeepReadonly } from 'ts-type-forge';
3
+
4
+ export type ESLintPlugin = DeepReadonly<TSESLint.FlatConfig.Plugin>;