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.
Files changed (170) hide show
  1. package/README.md +4 -0
  2. package/dist/configs/immer.d.mts +3 -0
  3. package/dist/configs/immer.d.mts.map +1 -0
  4. package/dist/configs/immer.mjs +11 -0
  5. package/dist/configs/immer.mjs.map +1 -0
  6. package/dist/configs/index.d.mts +2 -0
  7. package/dist/configs/index.d.mts.map +1 -1
  8. package/dist/configs/index.mjs +2 -0
  9. package/dist/configs/index.mjs.map +1 -1
  10. package/dist/configs/plugins.d.mts +1 -1
  11. package/dist/configs/plugins.d.mts.map +1 -1
  12. package/dist/configs/plugins.mjs +2 -0
  13. package/dist/configs/plugins.mjs.map +1 -1
  14. package/dist/configs/ts-data-forge.d.mts +3 -0
  15. package/dist/configs/ts-data-forge.d.mts.map +1 -0
  16. package/dist/configs/ts-data-forge.mjs +11 -0
  17. package/dist/configs/ts-data-forge.mjs.map +1 -0
  18. package/dist/configs/typescript.d.mts.map +1 -1
  19. package/dist/configs/typescript.mjs +0 -2
  20. package/dist/configs/typescript.mjs.map +1 -1
  21. package/dist/entry-point.mjs +4 -0
  22. package/dist/entry-point.mjs.map +1 -1
  23. package/dist/index.mjs +4 -0
  24. package/dist/index.mjs.map +1 -1
  25. package/dist/plugins/index.d.mts +1 -0
  26. package/dist/plugins/index.d.mts.map +1 -1
  27. package/dist/plugins/index.mjs +1 -0
  28. package/dist/plugins/index.mjs.map +1 -1
  29. package/dist/plugins/ts-data-forge/index.d.mts +2 -0
  30. package/dist/plugins/ts-data-forge/index.d.mts.map +1 -0
  31. package/dist/plugins/ts-data-forge/index.mjs +2 -0
  32. package/dist/plugins/ts-data-forge/index.mjs.map +1 -0
  33. package/dist/plugins/ts-data-forge/plugin.d.mts +3 -0
  34. package/dist/plugins/ts-data-forge/plugin.d.mts.map +1 -0
  35. package/dist/plugins/ts-data-forge/plugin.mjs +8 -0
  36. package/dist/plugins/ts-data-forge/plugin.mjs.map +1 -0
  37. package/dist/plugins/ts-data-forge/rules/branded-number-types.d.mts +5 -0
  38. package/dist/plugins/ts-data-forge/rules/branded-number-types.d.mts.map +1 -0
  39. package/dist/plugins/ts-data-forge/rules/branded-number-types.mjs +35 -0
  40. package/dist/plugins/ts-data-forge/rules/branded-number-types.mjs.map +1 -0
  41. package/dist/plugins/ts-data-forge/rules/constants.d.mts +2 -0
  42. package/dist/plugins/ts-data-forge/rules/constants.d.mts.map +1 -0
  43. package/dist/plugins/ts-data-forge/rules/constants.mjs +4 -0
  44. package/dist/plugins/ts-data-forge/rules/constants.mjs.map +1 -0
  45. package/dist/plugins/ts-data-forge/rules/import-utils.d.mts +5 -0
  46. package/dist/plugins/ts-data-forge/rules/import-utils.d.mts.map +1 -0
  47. package/dist/plugins/ts-data-forge/rules/import-utils.mjs +31 -0
  48. package/dist/plugins/ts-data-forge/rules/import-utils.mjs.map +1 -0
  49. package/dist/plugins/ts-data-forge/rules/index.d.mts +2 -0
  50. package/dist/plugins/ts-data-forge/rules/index.d.mts.map +1 -0
  51. package/dist/plugins/ts-data-forge/rules/index.mjs +2 -0
  52. package/dist/plugins/ts-data-forge/rules/index.mjs.map +1 -0
  53. package/dist/plugins/ts-data-forge/rules/prefer-arr-is-array-at-least-length.d.mts +6 -0
  54. package/dist/plugins/ts-data-forge/rules/prefer-arr-is-array-at-least-length.d.mts.map +1 -0
  55. package/dist/plugins/ts-data-forge/rules/prefer-arr-is-array-at-least-length.mjs +94 -0
  56. package/dist/plugins/ts-data-forge/rules/prefer-arr-is-array-at-least-length.mjs.map +1 -0
  57. package/dist/plugins/ts-data-forge/rules/prefer-arr-is-array-of-length.d.mts +6 -0
  58. package/dist/plugins/ts-data-forge/rules/prefer-arr-is-array-of-length.d.mts.map +1 -0
  59. package/dist/plugins/ts-data-forge/rules/prefer-arr-is-array-of-length.mjs +95 -0
  60. package/dist/plugins/ts-data-forge/rules/prefer-arr-is-array-of-length.mjs.map +1 -0
  61. package/dist/plugins/ts-data-forge/rules/prefer-arr-is-array.d.mts +6 -0
  62. package/dist/plugins/ts-data-forge/rules/prefer-arr-is-array.d.mts.map +1 -0
  63. package/dist/plugins/ts-data-forge/rules/prefer-arr-is-array.mjs +72 -0
  64. package/dist/plugins/ts-data-forge/rules/prefer-arr-is-array.mjs.map +1 -0
  65. package/dist/plugins/ts-data-forge/rules/prefer-arr-is-non-empty.d.mts +6 -0
  66. package/dist/plugins/ts-data-forge/rules/prefer-arr-is-non-empty.d.mts.map +1 -0
  67. package/dist/plugins/ts-data-forge/rules/prefer-arr-is-non-empty.mjs +71 -0
  68. package/dist/plugins/ts-data-forge/rules/prefer-arr-is-non-empty.mjs.map +1 -0
  69. package/dist/plugins/ts-data-forge/rules/prefer-arr-sum.d.mts +6 -0
  70. package/dist/plugins/ts-data-forge/rules/prefer-arr-sum.d.mts.map +1 -0
  71. package/dist/plugins/ts-data-forge/rules/prefer-arr-sum.mjs +183 -0
  72. package/dist/plugins/ts-data-forge/rules/prefer-arr-sum.mjs.map +1 -0
  73. package/dist/plugins/ts-data-forge/rules/prefer-as-int.d.mts +6 -0
  74. package/dist/plugins/ts-data-forge/rules/prefer-as-int.d.mts.map +1 -0
  75. package/dist/plugins/ts-data-forge/rules/prefer-as-int.mjs +86 -0
  76. package/dist/plugins/ts-data-forge/rules/prefer-as-int.mjs.map +1 -0
  77. package/dist/plugins/ts-data-forge/rules/prefer-is-non-null-object.d.mts +6 -0
  78. package/dist/plugins/ts-data-forge/rules/prefer-is-non-null-object.d.mts.map +1 -0
  79. package/dist/plugins/ts-data-forge/rules/prefer-is-non-null-object.mjs +103 -0
  80. package/dist/plugins/ts-data-forge/rules/prefer-is-non-null-object.mjs.map +1 -0
  81. package/dist/plugins/ts-data-forge/rules/prefer-is-record-and-has-key.d.mts +6 -0
  82. package/dist/plugins/ts-data-forge/rules/prefer-is-record-and-has-key.d.mts.map +1 -0
  83. package/dist/plugins/ts-data-forge/rules/prefer-is-record-and-has-key.mjs +105 -0
  84. package/dist/plugins/ts-data-forge/rules/prefer-is-record-and-has-key.mjs.map +1 -0
  85. package/dist/plugins/ts-data-forge/rules/prefer-range-for-loop.d.mts +6 -0
  86. package/dist/plugins/ts-data-forge/rules/prefer-range-for-loop.d.mts.map +1 -0
  87. package/dist/plugins/ts-data-forge/rules/prefer-range-for-loop.mjs +130 -0
  88. package/dist/plugins/ts-data-forge/rules/prefer-range-for-loop.mjs.map +1 -0
  89. package/dist/plugins/ts-data-forge/rules/rules.d.mts +12 -0
  90. package/dist/plugins/ts-data-forge/rules/rules.d.mts.map +1 -0
  91. package/dist/plugins/ts-data-forge/rules/rules.mjs +24 -0
  92. package/dist/plugins/ts-data-forge/rules/rules.mjs.map +1 -0
  93. package/dist/plugins/ts-restrictions/rules/check-destructuring-completeness.d.mts.map +1 -1
  94. package/dist/plugins/ts-restrictions/rules/check-destructuring-completeness.mjs +6 -8
  95. package/dist/plugins/ts-restrictions/rules/check-destructuring-completeness.mjs.map +1 -1
  96. package/dist/plugins/ts-restrictions/rules/no-restricted-cast-name.d.mts.map +1 -1
  97. package/dist/plugins/ts-restrictions/rules/no-restricted-cast-name.mjs +11 -15
  98. package/dist/plugins/ts-restrictions/rules/no-restricted-cast-name.mjs.map +1 -1
  99. package/dist/rules/eslint-jest-rules.d.mts +3 -0
  100. package/dist/rules/eslint-jest-rules.d.mts.map +1 -1
  101. package/dist/rules/eslint-jest-rules.mjs +3 -0
  102. package/dist/rules/eslint-jest-rules.mjs.map +1 -1
  103. package/dist/rules/eslint-ts-data-forge-rules.d.mts +12 -0
  104. package/dist/rules/eslint-ts-data-forge-rules.d.mts.map +1 -0
  105. package/dist/rules/eslint-ts-data-forge-rules.mjs +14 -0
  106. package/dist/rules/eslint-ts-data-forge-rules.mjs.map +1 -0
  107. package/dist/rules/eslint-vitest-rules.d.mts +2 -2
  108. package/dist/rules/eslint-vitest-rules.mjs +2 -2
  109. package/dist/rules/eslint-vitest-rules.mjs.map +1 -1
  110. package/dist/rules/index.d.mts +1 -0
  111. package/dist/rules/index.d.mts.map +1 -1
  112. package/dist/rules/index.mjs +1 -0
  113. package/dist/rules/index.mjs.map +1 -1
  114. package/dist/types/define-known-rules.d.mts +2 -2
  115. package/dist/types/define-known-rules.d.mts.map +1 -1
  116. package/dist/types/define-known-rules.mjs.map +1 -1
  117. package/dist/types/rules/eslint-jest-rules.d.mts +119 -67
  118. package/dist/types/rules/eslint-jest-rules.d.mts.map +1 -1
  119. package/dist/types/rules/eslint-ts-data-forge-rules.d.mts +147 -0
  120. package/dist/types/rules/eslint-ts-data-forge-rules.d.mts.map +1 -0
  121. package/dist/types/rules/eslint-ts-data-forge-rules.mjs +2 -0
  122. package/dist/types/rules/eslint-ts-data-forge-rules.mjs.map +1 -0
  123. package/dist/types/rules/eslint-vitest-rules.d.mts +41 -20
  124. package/dist/types/rules/eslint-vitest-rules.d.mts.map +1 -1
  125. package/dist/types/rules/index.d.mts +1 -0
  126. package/dist/types/rules/index.d.mts.map +1 -1
  127. package/package.json +15 -15
  128. package/src/configs/immer.mts +9 -0
  129. package/src/configs/index.mts +2 -0
  130. package/src/configs/plugins.mts +3 -0
  131. package/src/configs/ts-data-forge.mts +11 -0
  132. package/src/configs/typescript.mts +0 -2
  133. package/src/plugins/index.mts +1 -0
  134. package/src/plugins/strict-dependencies/rules/resolve-import-path.test.mts +7 -9
  135. package/src/plugins/ts-data-forge/index.mts +1 -0
  136. package/src/plugins/ts-data-forge/plugin.mts +6 -0
  137. package/src/plugins/ts-data-forge/rules/branded-number-types.mts +36 -0
  138. package/src/plugins/ts-data-forge/rules/constants.mts +1 -0
  139. package/src/plugins/ts-data-forge/rules/import-utils.mts +56 -0
  140. package/src/plugins/ts-data-forge/rules/index.mts +1 -0
  141. package/src/plugins/ts-data-forge/rules/prefer-arr-is-array-at-least-length.mts +140 -0
  142. package/src/plugins/ts-data-forge/rules/prefer-arr-is-array-at-least-length.test.mts +175 -0
  143. package/src/plugins/ts-data-forge/rules/prefer-arr-is-array-of-length.mts +144 -0
  144. package/src/plugins/ts-data-forge/rules/prefer-arr-is-array-of-length.test.mts +183 -0
  145. package/src/plugins/ts-data-forge/rules/prefer-arr-is-array.mts +97 -0
  146. package/src/plugins/ts-data-forge/rules/prefer-arr-is-array.test.mts +62 -0
  147. package/src/plugins/ts-data-forge/rules/prefer-arr-is-non-empty.mts +106 -0
  148. package/src/plugins/ts-data-forge/rules/prefer-arr-is-non-empty.test.mts +83 -0
  149. package/src/plugins/ts-data-forge/rules/prefer-arr-sum.mts +269 -0
  150. package/src/plugins/ts-data-forge/rules/prefer-arr-sum.test.mts +171 -0
  151. package/src/plugins/ts-data-forge/rules/prefer-as-int.mts +130 -0
  152. package/src/plugins/ts-data-forge/rules/prefer-as-int.test.mts +267 -0
  153. package/src/plugins/ts-data-forge/rules/prefer-is-non-null-object.mts +144 -0
  154. package/src/plugins/ts-data-forge/rules/prefer-is-non-null-object.test.mts +156 -0
  155. package/src/plugins/ts-data-forge/rules/prefer-is-record-and-has-key.mts +158 -0
  156. package/src/plugins/ts-data-forge/rules/prefer-is-record-and-has-key.test.mts +191 -0
  157. package/src/plugins/ts-data-forge/rules/prefer-range-for-loop.mts +181 -0
  158. package/src/plugins/ts-data-forge/rules/prefer-range-for-loop.test.mts +156 -0
  159. package/src/plugins/ts-data-forge/rules/rules.mts +22 -0
  160. package/src/plugins/ts-restrictions/rules/check-destructuring-completeness.mts +6 -8
  161. package/src/plugins/ts-restrictions/rules/no-restricted-cast-name.mts +11 -15
  162. package/src/rules/eslint-jest-rules.mts +3 -0
  163. package/src/rules/eslint-ts-data-forge-rules.mts +13 -0
  164. package/src/rules/eslint-vitest-rules.mts +2 -2
  165. package/src/rules/index.mts +1 -0
  166. package/src/types/define-known-rules.mts +2 -0
  167. package/src/types/rules/eslint-jest-rules.mts +122 -67
  168. package/src/types/rules/eslint-ts-data-forge-rules.mts +156 -0
  169. package/src/types/rules/eslint-vitest-rules.mts +46 -21
  170. package/src/types/rules/index.mts +1 -0
@@ -0,0 +1,267 @@
1
+ import parser from '@typescript-eslint/parser';
2
+ import { RuleTester } from '@typescript-eslint/rule-tester';
3
+ import dedent from 'dedent';
4
+ import { preferAsInt } from './prefer-as-int.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-as-int', () => {
21
+ tester.run('prefer-as-int', preferAsInt, {
22
+ valid: [
23
+ {
24
+ name: 'ignores non-Int assertions',
25
+ code: dedent`
26
+ const n = 1;
27
+ const result = n as number;
28
+ `,
29
+ },
30
+ ],
31
+ invalid: [
32
+ ...([
33
+ {
34
+ name: 'replaces as Int with asInt import',
35
+ code: dedent`
36
+ const n = 1;
37
+ const result = n as Int;
38
+ `,
39
+ output: dedent`
40
+ import { asInt } from 'ts-data-forge';
41
+ const n = 1;
42
+ const result = asInt(n);
43
+ `,
44
+ errors: [{ messageId: 'useBrandedNumberCastFunction' }],
45
+ },
46
+ {
47
+ name: 'replaces as Int with asInt import when other imports exist',
48
+ code: dedent`
49
+ import { noop } from './noop.mjs';
50
+
51
+ const n = 1;
52
+ noop(n as Int);
53
+ `,
54
+ output: dedent`
55
+ import { asInt } from 'ts-data-forge';
56
+ import { noop } from './noop.mjs';
57
+
58
+ const n = 1;
59
+ noop(asInt(n));
60
+ `,
61
+ errors: [{ messageId: 'useBrandedNumberCastFunction' }],
62
+ },
63
+ {
64
+ name: 'replaces multiple as Int with asInt imports',
65
+ code: dedent`
66
+ const n = 1;
67
+ const result = n as Int;
68
+ const another = (n + 1) as Int;
69
+ `,
70
+ output: dedent`
71
+ import { asInt } from 'ts-data-forge';
72
+ const n = 1;
73
+ const result = asInt(n);
74
+ const another = asInt(n + 1);
75
+ `,
76
+ errors: [
77
+ { messageId: 'useBrandedNumberCastFunction' },
78
+ { messageId: 'useBrandedNumberCastFunction' },
79
+ ],
80
+ },
81
+ // {
82
+ // name: 'replaces as Int with asInt import with another disabled line',
83
+ // code: dedent`
84
+ // const n = 1;
85
+
86
+ // // eslint-disable-next-line
87
+ // const result = n as Int;
88
+
89
+ // const another = (n + 1) as Int;
90
+ // `,
91
+ // output: dedent`
92
+ // import { asInt } from 'ts-data-forge';
93
+ // const n = 1;
94
+
95
+ // // eslint-disable-next-line
96
+ // const result = n as Int;
97
+
98
+ // const another = asInt(n + 1);
99
+ // `,
100
+ // errors: [{ messageId: 'useBrandedNumberCastFunction' }],
101
+ // },
102
+ ] as const),
103
+
104
+ ...([
105
+ {
106
+ name: 'keeps existing asInt import',
107
+ code: dedent`
108
+ import { asInt } from 'ts-data-forge';
109
+
110
+ const n = 1;
111
+ const result = n as Int;
112
+ `,
113
+ output: dedent`
114
+ import { asInt } from 'ts-data-forge';
115
+
116
+ const n = 1;
117
+ const result = asInt(n);
118
+ `,
119
+ errors: [{ messageId: 'useBrandedNumberCastFunction' }],
120
+ },
121
+ {
122
+ name: 'keeps existing asInt import when other imports exist',
123
+ code: dedent`
124
+ import { noop } from './noop.mjs';
125
+ import { asInt } from 'ts-data-forge';
126
+
127
+ const n = 1;
128
+ noop(n as Int);
129
+ `,
130
+ output: dedent`
131
+ import { noop } from './noop.mjs';
132
+ import { asInt } from 'ts-data-forge';
133
+
134
+ const n = 1;
135
+ noop(asInt(n));
136
+ `,
137
+ errors: [{ messageId: 'useBrandedNumberCastFunction' }],
138
+ },
139
+ ] as const),
140
+
141
+ ...([
142
+ {
143
+ name: 'adds import even if ts-data-forge import exists',
144
+ code: dedent`
145
+ import { isNonNullObject } from 'ts-data-forge';
146
+
147
+ const n = 1;
148
+ const result = n as Int;
149
+ `,
150
+ output: dedent`
151
+ import { asInt } from 'ts-data-forge';
152
+ import { isNonNullObject } from 'ts-data-forge';
153
+
154
+ const n = 1;
155
+ const result = asInt(n);
156
+ `,
157
+ errors: [{ messageId: 'useBrandedNumberCastFunction' }],
158
+ },
159
+ {
160
+ name: 'adds import even if ts-data-forge import exists (with other imports)',
161
+ code: dedent`
162
+ import { noop } from './noop.mjs';
163
+ import { isNonNullObject } from 'ts-data-forge';
164
+
165
+ const n = 1;
166
+ const result = n as Int;
167
+ `,
168
+ output: dedent`
169
+ import { asInt } from 'ts-data-forge';
170
+ import { noop } from './noop.mjs';
171
+ import { isNonNullObject } from 'ts-data-forge';
172
+
173
+ const n = 1;
174
+ const result = asInt(n);
175
+ `,
176
+ errors: [{ messageId: 'useBrandedNumberCastFunction' }],
177
+ },
178
+ ] as const),
179
+ ],
180
+ });
181
+
182
+ // Test cases for other branded number types
183
+ tester.run('prefer-as-int (other types)', preferAsInt, {
184
+ valid: [],
185
+ invalid: [
186
+ {
187
+ name: 'replaces as Uint32 with asUint32',
188
+ code: dedent`
189
+ const n = 1;
190
+ const result = n as Uint32;
191
+ `,
192
+ output: dedent`
193
+ import { asUint32 } from 'ts-data-forge';
194
+ const n = 1;
195
+ const result = asUint32(n);
196
+ `,
197
+ errors: [{ messageId: 'useBrandedNumberCastFunction' }],
198
+ },
199
+ {
200
+ name: 'replaces as SafeInt with asSafeInt',
201
+ code: dedent`
202
+ const n = 1;
203
+ const result = n as SafeInt;
204
+ `,
205
+ output: dedent`
206
+ import { asSafeInt } from 'ts-data-forge';
207
+ const n = 1;
208
+ const result = asSafeInt(n);
209
+ `,
210
+ errors: [{ messageId: 'useBrandedNumberCastFunction' }],
211
+ },
212
+ {
213
+ name: 'replaces as PositiveInt with asPositiveInt',
214
+ code: dedent`
215
+ const n = 1;
216
+ const result = n as PositiveInt;
217
+ `,
218
+ output: dedent`
219
+ import { asPositiveInt } from 'ts-data-forge';
220
+ const n = 1;
221
+ const result = asPositiveInt(n);
222
+ `,
223
+ errors: [{ messageId: 'useBrandedNumberCastFunction' }],
224
+ },
225
+ {
226
+ name: 'replaces multiple different branded types',
227
+ code: dedent`
228
+ const n = 1;
229
+ const a = n as Int;
230
+ const b = n as Uint32;
231
+ const c = n as SafeInt;
232
+ `,
233
+ output: [
234
+ dedent`
235
+ import { asInt } from 'ts-data-forge';
236
+ const n = 1;
237
+ const a = asInt(n);
238
+ const b = n as Uint32;
239
+ const c = n as SafeInt;
240
+ `,
241
+ dedent`
242
+ import { asUint32 } from 'ts-data-forge';
243
+ import { asInt } from 'ts-data-forge';
244
+ const n = 1;
245
+ const a = asInt(n);
246
+ const b = asUint32(n);
247
+ const c = n as SafeInt;
248
+ `,
249
+ dedent`
250
+ import { asSafeInt } from 'ts-data-forge';
251
+ import { asUint32 } from 'ts-data-forge';
252
+ import { asInt } from 'ts-data-forge';
253
+ const n = 1;
254
+ const a = asInt(n);
255
+ const b = asUint32(n);
256
+ const c = asSafeInt(n);
257
+ `,
258
+ ],
259
+ errors: [
260
+ { messageId: 'useBrandedNumberCastFunction' },
261
+ { messageId: 'useBrandedNumberCastFunction' },
262
+ { messageId: 'useBrandedNumberCastFunction' },
263
+ ],
264
+ },
265
+ ],
266
+ });
267
+ }, 20000);
@@ -0,0 +1,144 @@
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 = 'useIsNonNullObject';
15
+
16
+ export const preferIsNonNullObject: TSESLint.RuleModule<MessageIds, Options> = {
17
+ meta: {
18
+ type: 'suggestion',
19
+ docs: {
20
+ description:
21
+ 'Replace `typeof u === "object" && u !== null` with `isNonNullObject(u)` from ts-data-forge.',
22
+ },
23
+ fixable: 'code',
24
+ schema: [],
25
+ messages: {
26
+ useIsNonNullObject:
27
+ 'Replace the object/null check with `isNonNullObject()` 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.LogicalExpression;
40
+ identifierName: string;
41
+ }[] = [];
42
+
43
+ return {
44
+ LogicalExpression: (node) => {
45
+ const identifierName = getNonNullObjectIdentifierName(node);
46
+
47
+ if (identifierName === undefined) return;
48
+
49
+ mut_nodesToFix.push({ node, identifierName });
50
+ },
51
+ 'Program:exit': () => {
52
+ // Check if isNonNullObject is already imported
53
+ const hasIsNonNullObjectImport =
54
+ getNamedImports(tsDataForgeImport).includes('isNonNullObject');
55
+
56
+ // Note: We add import only for the first node to avoid conflicts when
57
+ // multiple fixes try to insert at the same position. This means if the
58
+ // first node is disabled via eslint-disable comment, no import will be
59
+ // added.
60
+ for (const [
61
+ index,
62
+ { node, identifierName },
63
+ ] of mut_nodesToFix.entries()) {
64
+ context.report({
65
+ node,
66
+ messageId: 'useIsNonNullObject',
67
+ fix: (fixer) => {
68
+ const replacement = `isNonNullObject(${identifierName})`;
69
+
70
+ // Add import only for the first node and only if not already imported
71
+ const importFixes =
72
+ index === 0 && !hasIsNonNullObjectImport
73
+ ? buildImportFixes(fixer, program, tsDataForgeImport, [
74
+ 'isNonNullObject',
75
+ ])
76
+ : [];
77
+
78
+ return [...importFixes, fixer.replaceText(node, replacement)];
79
+ },
80
+ });
81
+ }
82
+ },
83
+ };
84
+ },
85
+ defaultOptions: [],
86
+ };
87
+
88
+ const getNonNullObjectIdentifierName = (
89
+ node: DeepReadonly<TSESTree.LogicalExpression>,
90
+ ): string | undefined => {
91
+ if (node.operator !== '&&') return undefined;
92
+
93
+ const leftIdentifierName = getTypeofObjectIdentifierName(node.left);
94
+
95
+ const rightIdentifierName = getNonNullCheckIdentifierName(node.right);
96
+
97
+ if (leftIdentifierName === undefined) return undefined;
98
+
99
+ if (rightIdentifierName === undefined) return undefined;
100
+
101
+ if (leftIdentifierName !== rightIdentifierName) return undefined;
102
+
103
+ return leftIdentifierName;
104
+ };
105
+
106
+ const getTypeofObjectIdentifierName = (
107
+ node: DeepReadonly<TSESTree.Expression>,
108
+ ): string | undefined => {
109
+ if (node.type !== AST_NODE_TYPES.BinaryExpression) return undefined;
110
+
111
+ if (node.operator !== '===') return undefined;
112
+
113
+ const { left, right } = node;
114
+
115
+ if (left.type !== AST_NODE_TYPES.UnaryExpression) return undefined;
116
+
117
+ if (left.operator !== 'typeof') return undefined;
118
+
119
+ if (right.type !== AST_NODE_TYPES.Literal) return undefined;
120
+
121
+ if (right.value !== 'object') return undefined;
122
+
123
+ if (left.argument.type !== AST_NODE_TYPES.Identifier) return undefined;
124
+
125
+ return left.argument.name;
126
+ };
127
+
128
+ const getNonNullCheckIdentifierName = (
129
+ node: DeepReadonly<TSESTree.Expression>,
130
+ ): string | undefined => {
131
+ if (node.type !== AST_NODE_TYPES.BinaryExpression) return undefined;
132
+
133
+ if (node.operator !== '!==') return undefined;
134
+
135
+ const { left, right } = node;
136
+
137
+ if (left.type !== AST_NODE_TYPES.Identifier) return undefined;
138
+
139
+ if (right.type !== AST_NODE_TYPES.Literal) return undefined;
140
+
141
+ if (right.value !== null) return undefined;
142
+
143
+ return left.name;
144
+ };
@@ -0,0 +1,156 @@
1
+ import parser from '@typescript-eslint/parser';
2
+ import { RuleTester } from '@typescript-eslint/rule-tester';
3
+ import dedent from 'dedent';
4
+ import { preferIsNonNullObject } from './prefer-is-non-null-object.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-is-non-null-object', () => {
21
+ tester.run('prefer-is-non-null-object', preferIsNonNullObject, {
22
+ valid: [
23
+ {
24
+ name: 'ignores non-matching typeof checks',
25
+ code: dedent`
26
+ const u = {};
27
+ const ok = typeof u === "object" && v !== null;
28
+ `,
29
+ },
30
+ ],
31
+ invalid: [
32
+ ...([
33
+ {
34
+ name: 'replaces typeof object check with isNonNullObject import',
35
+ code: dedent`
36
+ const u = {};
37
+ const ok = typeof u === "object" && u !== null;
38
+ `,
39
+ output: dedent`
40
+ import { isNonNullObject } from 'ts-data-forge';
41
+ const u = {};
42
+ const ok = isNonNullObject(u);
43
+ `,
44
+ errors: [{ messageId: 'useIsNonNullObject' }],
45
+ },
46
+ {
47
+ name: 'replaces typeof object check with isNonNullObject import when other imports exist',
48
+ code: dedent`
49
+ import { noop } from './noop.mjs';
50
+
51
+ const u = {};
52
+ noop(typeof u === "object" && u !== null);
53
+ `,
54
+ output: dedent`
55
+ import { isNonNullObject } from 'ts-data-forge';
56
+ import { noop } from './noop.mjs';
57
+
58
+ const u = {};
59
+ noop(isNonNullObject(u));
60
+ `,
61
+ errors: [{ messageId: 'useIsNonNullObject' }],
62
+ },
63
+ {
64
+ name: 'replaces multiple typeof object checks with isNonNullObject imports',
65
+ code: dedent`
66
+ const u = {};
67
+ const ok = typeof u === "object" && u !== null;
68
+ const ok2 = typeof u === "object" && u !== null;
69
+ `,
70
+ output: dedent`
71
+ import { isNonNullObject } from 'ts-data-forge';
72
+ const u = {};
73
+ const ok = isNonNullObject(u);
74
+ const ok2 = isNonNullObject(u);
75
+ `,
76
+ errors: [
77
+ { messageId: 'useIsNonNullObject' },
78
+ { messageId: 'useIsNonNullObject' },
79
+ ],
80
+ },
81
+ ] as const),
82
+
83
+ ...([
84
+ {
85
+ name: 'keeps existing isNonNullObject import',
86
+ code: dedent`
87
+ import { isNonNullObject } from 'ts-data-forge';
88
+
89
+ const ok = typeof u === "object" && u !== null;
90
+ `,
91
+ output: dedent`
92
+ import { isNonNullObject } from 'ts-data-forge';
93
+
94
+ const ok = isNonNullObject(u);
95
+ `,
96
+ errors: [{ messageId: 'useIsNonNullObject' }],
97
+ },
98
+ {
99
+ name: 'keeps existing isNonNullObject import when other imports exist',
100
+ code: dedent`
101
+ import { noop } from './noop.mjs';
102
+ import { isNonNullObject } from 'ts-data-forge';
103
+
104
+ const u = {};
105
+ noop(typeof u === "object" && u !== null);
106
+ `,
107
+ output: dedent`
108
+ import { noop } from './noop.mjs';
109
+ import { isNonNullObject } from 'ts-data-forge';
110
+
111
+ const u = {};
112
+ noop(isNonNullObject(u));
113
+ `,
114
+ errors: [{ messageId: 'useIsNonNullObject' }],
115
+ },
116
+ ] as const),
117
+
118
+ ...([
119
+ {
120
+ name: 'adds import even if ts-data-forge import exists',
121
+ code: dedent`
122
+ import { asInt } from 'ts-data-forge';
123
+
124
+ const ok = typeof u === "object" && u !== null;
125
+ `,
126
+ output: dedent`
127
+ import { isNonNullObject } from 'ts-data-forge';
128
+ import { asInt } from 'ts-data-forge';
129
+
130
+ const ok = isNonNullObject(u);
131
+ `,
132
+ errors: [{ messageId: 'useIsNonNullObject' }],
133
+ },
134
+ {
135
+ name: 'adds import even if ts-data-forge import exists (with other imports)',
136
+ code: dedent`
137
+ import { noop } from './noop.mjs';
138
+ import { asInt } from 'ts-data-forge';
139
+
140
+ const u = {};
141
+ noop(typeof u === "object" && u !== null);
142
+ `,
143
+ output: dedent`
144
+ import { isNonNullObject } from 'ts-data-forge';
145
+ import { noop } from './noop.mjs';
146
+ import { asInt } from 'ts-data-forge';
147
+
148
+ const u = {};
149
+ noop(isNonNullObject(u));
150
+ `,
151
+ errors: [{ messageId: 'useIsNonNullObject' }],
152
+ },
153
+ ] as const),
154
+ ],
155
+ });
156
+ }, 20000);