ts-codemod-lib 2.1.4 → 2.1.6

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 (41) hide show
  1. package/README.md +3 -1
  2. package/dist/cmd/append-as-const.mjs +1 -1
  3. package/dist/cmd/convert-interface-to-type.mjs +1 -1
  4. package/dist/cmd/convert-to-readonly.mjs +1 -1
  5. package/dist/cmd/replace-any-with-unknown.mjs +1 -1
  6. package/dist/cmd/replace-record-with-unknown-record.mjs +1 -1
  7. package/dist/functions/ast-transformers/append-as-const.d.mts +1 -0
  8. package/dist/functions/ast-transformers/append-as-const.d.mts.map +1 -1
  9. package/dist/functions/ast-transformers/append-as-const.mjs.map +1 -1
  10. package/dist/functions/ast-transformers/convert-to-readonly.d.mts +1 -0
  11. package/dist/functions/ast-transformers/convert-to-readonly.d.mts.map +1 -1
  12. package/dist/functions/ast-transformers/convert-to-readonly.mjs.map +1 -1
  13. package/dist/functions/ast-transformers/readonly-transformer-helpers/compare-union-types.d.mts.map +1 -1
  14. package/dist/functions/ast-transformers/readonly-transformer-helpers/compare-union-types.mjs.map +1 -1
  15. package/dist/functions/ast-transformers/readonly-transformer-helpers/readonly-context.d.mts +1 -0
  16. package/dist/functions/ast-transformers/readonly-transformer-helpers/readonly-context.d.mts.map +1 -1
  17. package/dist/functions/ast-transformers/readonly-transformer-helpers/readonly-context.mjs.map +1 -1
  18. package/dist/functions/ast-transformers/replace-record-with-unknown-record.d.mts +5 -1
  19. package/dist/functions/ast-transformers/replace-record-with-unknown-record.d.mts.map +1 -1
  20. package/dist/functions/ast-transformers/replace-record-with-unknown-record.mjs +134 -80
  21. package/dist/functions/ast-transformers/replace-record-with-unknown-record.mjs.map +1 -1
  22. package/dist/functions/functions/is-atomic-type-node.d.mts +1 -0
  23. package/dist/functions/functions/is-atomic-type-node.d.mts.map +1 -1
  24. package/dist/functions/functions/is-atomic-type-node.mjs.map +1 -1
  25. package/dist/functions/functions/is-readonly-node.d.mts +1 -0
  26. package/dist/functions/functions/is-readonly-node.d.mts.map +1 -1
  27. package/dist/functions/functions/is-readonly-node.mjs.map +1 -1
  28. package/package.json +6 -6
  29. package/src/cmd/append-as-const.mts +1 -1
  30. package/src/cmd/convert-interface-to-type.mts +1 -1
  31. package/src/cmd/convert-to-readonly.mts +1 -1
  32. package/src/cmd/replace-any-with-unknown.mts +1 -1
  33. package/src/cmd/replace-record-with-unknown-record.mts +1 -1
  34. package/src/functions/ast-transformers/append-as-const.mts +1 -0
  35. package/src/functions/ast-transformers/convert-to-readonly.mts +1 -0
  36. package/src/functions/ast-transformers/readonly-transformer-helpers/compare-union-types.mts +1 -0
  37. package/src/functions/ast-transformers/readonly-transformer-helpers/readonly-context.mts +1 -0
  38. package/src/functions/ast-transformers/replace-record-with-unknown-record.mts +169 -94
  39. package/src/functions/ast-transformers/replace-record-with-unknown-record.test.mts +312 -0
  40. package/src/functions/functions/is-atomic-type-node.mts +1 -0
  41. package/src/functions/functions/is-readonly-node.mts +1 -0
@@ -0,0 +1,312 @@
1
+ /* eslint-disable tree-shakable/import-star */
2
+ /* eslint-disable vitest/expect-expect */
3
+ import dedent from 'dedent';
4
+ import * as prettierPluginEstree from 'prettier/plugins/estree';
5
+ import * as prettierPluginTypeScript from 'prettier/plugins/typescript';
6
+ import * as prettier from 'prettier/standalone';
7
+ import { replaceRecordWithUnknownRecordTransformer } from './replace-record-with-unknown-record.mjs';
8
+ import { transformSourceCode } from './transform-source-code.mjs';
9
+
10
+ const testFn = async ({
11
+ source,
12
+ expected,
13
+ }: Readonly<{
14
+ source: string;
15
+ expected: string;
16
+ }>): Promise<void> => {
17
+ const transformed = await formatter(
18
+ transformSourceCode(source, false, [
19
+ replaceRecordWithUnknownRecordTransformer(),
20
+ ]),
21
+ );
22
+
23
+ const expectedFormatted = await formatter(expected);
24
+
25
+ expect(transformed).toBe(expectedFormatted);
26
+ };
27
+
28
+ const formatter = (code: string): Promise<string> =>
29
+ prettier.format(code, {
30
+ parser: 'typescript',
31
+ plugins: [prettierPluginTypeScript, prettierPluginEstree],
32
+ });
33
+
34
+ describe(replaceRecordWithUnknownRecordTransformer, () => {
35
+ test.each([
36
+ {
37
+ name: 'Simple Record<string, unknown> with import added',
38
+ source: 'type Foo = Record<string, unknown>;',
39
+ expected: dedent`
40
+ import { type UnknownRecord } from 'ts-type-forge';
41
+
42
+ type Foo = UnknownRecord;
43
+ `,
44
+ },
45
+ {
46
+ name: 'Readonly<Record<string, unknown>>',
47
+ source: 'type Foo = Readonly<Record<string, unknown>>;',
48
+ expected: dedent`
49
+ import { type UnknownRecord } from 'ts-type-forge';
50
+
51
+ type Foo = UnknownRecord;
52
+ `,
53
+ },
54
+ {
55
+ name: 'Type literal { [k: string]: unknown }',
56
+ source: 'type Foo = { [k: string]: unknown };',
57
+ expected: dedent`
58
+ import { type UnknownRecord } from 'ts-type-forge';
59
+
60
+ type Foo = UnknownRecord;
61
+ `,
62
+ },
63
+ {
64
+ name: 'Readonly type literal Readonly<{ [k: string]: unknown }>',
65
+ source: 'type Foo = Readonly<{ [k: string]: unknown }>;',
66
+ expected: dedent`
67
+ import { type UnknownRecord } from 'ts-type-forge';
68
+
69
+ type Foo = UnknownRecord;
70
+ `,
71
+ },
72
+ {
73
+ name: 'Exported interface with only [k: string]: unknown',
74
+ source: dedent`
75
+ export interface Foo {
76
+ [k: string]: unknown;
77
+ }
78
+ `,
79
+ expected: dedent`
80
+ import { type UnknownRecord } from 'ts-type-forge';
81
+
82
+ export type Foo = UnknownRecord;
83
+ `,
84
+ },
85
+ {
86
+ name: 'Non-exported interface with only [k: string]: unknown preserves missing export',
87
+ source: dedent`
88
+ interface Foo {
89
+ [k: string]: unknown;
90
+ }
91
+ `,
92
+ expected: dedent`
93
+ import { type UnknownRecord } from 'ts-type-forge';
94
+
95
+ type Foo = UnknownRecord;
96
+ `,
97
+ },
98
+ {
99
+ name: 'Generic interface with [k: string]: unknown is left untouched',
100
+ source: dedent`
101
+ export interface Foo<T> {
102
+ [k: string]: unknown;
103
+ }
104
+ `,
105
+ expected: dedent`
106
+ export interface Foo<T> {
107
+ [k: string]: unknown;
108
+ }
109
+ `,
110
+ },
111
+ {
112
+ name: 'Interface with extends + [k: string]: unknown is left untouched',
113
+ source: dedent`
114
+ interface Bar {
115
+ a: number;
116
+ }
117
+ export interface Foo extends Bar {
118
+ [k: string]: unknown;
119
+ }
120
+ `,
121
+ expected: dedent`
122
+ interface Bar {
123
+ a: number;
124
+ }
125
+ export interface Foo extends Bar {
126
+ [k: string]: unknown;
127
+ }
128
+ `,
129
+ },
130
+ {
131
+ name: 'declare interface with [k: string]: unknown is left untouched',
132
+ source: dedent`
133
+ declare interface Foo {
134
+ [k: string]: unknown;
135
+ }
136
+ `,
137
+ expected: dedent`
138
+ declare interface Foo {
139
+ [k: string]: unknown;
140
+ }
141
+ `,
142
+ },
143
+ {
144
+ name: 'Generic interface still has its property types transformed',
145
+ source: dedent`
146
+ export interface Foo<T> {
147
+ value: Record<string, unknown>;
148
+ tag: T;
149
+ }
150
+ `,
151
+ expected: dedent`
152
+ import { type UnknownRecord } from 'ts-type-forge';
153
+
154
+ export interface Foo<T> {
155
+ value: UnknownRecord;
156
+ tag: T;
157
+ }
158
+ `,
159
+ },
160
+ {
161
+ name: 'Record nested under readonly tuple (regression: TypeOperator)',
162
+ source: dedent`
163
+ type Foo =
164
+ | readonly [
165
+ 'always',
166
+ Readonly<{
167
+ patternOptions?: Readonly<Record<string, unknown>>;
168
+ }>,
169
+ ];
170
+ `,
171
+ expected: dedent`
172
+ import { type UnknownRecord } from 'ts-type-forge';
173
+
174
+ type Foo =
175
+ | readonly [
176
+ 'always',
177
+ Readonly<{
178
+ patternOptions?: UnknownRecord;
179
+ }>,
180
+ ];
181
+ `,
182
+ },
183
+ {
184
+ name: 'Record nested under readonly array (regression: TypeOperator)',
185
+ source: dedent`
186
+ type Foo = Readonly<{
187
+ forbid?: readonly (string | Record<string, unknown>)[];
188
+ }>;
189
+ `,
190
+ expected: dedent`
191
+ import { type UnknownRecord } from 'ts-type-forge';
192
+
193
+ type Foo = Readonly<{
194
+ forbid?: readonly (string | UnknownRecord)[];
195
+ }>;
196
+ `,
197
+ },
198
+ {
199
+ name: 'readonly Readonly<Record<...>>[] at type alias root (regression)',
200
+ source: 'type Foo = readonly Readonly<Record<string, unknown>>[];',
201
+ expected: dedent`
202
+ import { type UnknownRecord } from 'ts-type-forge';
203
+
204
+ type Foo = readonly UnknownRecord[];
205
+ `,
206
+ },
207
+ {
208
+ name: 'Record inside namespace',
209
+ source: dedent`
210
+ namespace Ns {
211
+ export type Foo = Record<string, unknown>;
212
+ }
213
+ `,
214
+ expected: dedent`
215
+ import { type UnknownRecord } from 'ts-type-forge';
216
+
217
+ namespace Ns {
218
+ export type Foo = UnknownRecord;
219
+ }
220
+ `,
221
+ },
222
+ {
223
+ name: 'Record nested in union inside namespace',
224
+ source: dedent`
225
+ namespace Ns {
226
+ export type Options = Readonly<{
227
+ targets:
228
+ | string
229
+ | readonly unknown[]
230
+ | Readonly<Record<string, unknown>>;
231
+ }>;
232
+ }
233
+ `,
234
+ expected: dedent`
235
+ import { type UnknownRecord } from 'ts-type-forge';
236
+
237
+ namespace Ns {
238
+ export type Options = Readonly<{
239
+ targets: string | readonly unknown[] | UnknownRecord;
240
+ }>;
241
+ }
242
+ `,
243
+ },
244
+ {
245
+ name: 'No transformation -> no import added',
246
+ source: 'type Foo = string;',
247
+ expected: 'type Foo = string;',
248
+ },
249
+ {
250
+ name: 'Existing UnknownRecord import is not duplicated',
251
+ source: dedent`
252
+ import { type UnknownRecord } from 'ts-type-forge';
253
+
254
+ type Foo = Record<string, unknown>;
255
+ `,
256
+ expected: dedent`
257
+ import { type UnknownRecord } from 'ts-type-forge';
258
+
259
+ type Foo = UnknownRecord;
260
+ `,
261
+ },
262
+ {
263
+ name: 'Merged into existing ts-type-forge import with other names',
264
+ source: dedent`
265
+ import { type DeepReadonly } from 'ts-type-forge';
266
+
267
+ type Foo = Record<string, unknown>;
268
+ `,
269
+ expected: dedent`
270
+ import { type DeepReadonly, type UnknownRecord } from 'ts-type-forge';
271
+
272
+ type Foo = UnknownRecord;
273
+ `,
274
+ },
275
+ {
276
+ name: 'Disable comment skips the type alias and does not add import',
277
+ source: dedent`
278
+ // transformer-ignore-next-line
279
+ type Foo = Record<string, unknown>;
280
+ `,
281
+ expected: dedent`
282
+ // transformer-ignore-next-line
283
+ type Foo = Record<string, unknown>;
284
+ `,
285
+ },
286
+ {
287
+ name: 'Transformer-specific disable comment is respected',
288
+ source: dedent`
289
+ // transformer-ignore-next-line replace-record-with-unknown-record
290
+ type A = Record<string, unknown>;
291
+ type B = Record<string, unknown>;
292
+ `,
293
+ expected: dedent`
294
+ import { type UnknownRecord } from 'ts-type-forge';
295
+
296
+ // transformer-ignore-next-line replace-record-with-unknown-record
297
+ type A = Record<string, unknown>;
298
+ type B = UnknownRecord;
299
+ `,
300
+ },
301
+ {
302
+ name: 'Record<string, string> (non-unknown value) is left alone',
303
+ source: 'type Foo = Record<string, string>;',
304
+ expected: 'type Foo = Record<string, string>;',
305
+ },
306
+ {
307
+ name: 'Record<number, unknown> (non-string key) is left alone',
308
+ source: 'type Foo = Record<number, unknown>;',
309
+ expected: 'type Foo = Record<number, unknown>;',
310
+ },
311
+ ])('$name', testFn);
312
+ });
@@ -1,5 +1,6 @@
1
1
  import { ISet } from 'ts-data-forge';
2
2
  import * as tsm from 'ts-morph';
3
+ import { type DeepReadonly } from 'ts-type-forge';
3
4
 
4
5
  // Define the set of SyntaxKinds that represent primitive type keywords
5
6
  const atomicTypeKeywordKinds = ISet.create<tsm.SyntaxKind>([
@@ -1,5 +1,6 @@
1
1
  import { Arr, expectType } from 'ts-data-forge';
2
2
  import * as tsm from 'ts-morph';
3
+ import { type DeepReadonly } from 'ts-type-forge';
3
4
  import { isAtomicTypeNode } from './is-atomic-type-node.mjs';
4
5
 
5
6
  export const isShallowReadonlyTypeNode = (node: tsm.Node): boolean =>