ts-codemod-lib 3.5.1 → 4.0.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.
- package/README.md +7 -3
- package/dist/functions/ast-transformers/append-as-const.d.mts +13 -4
- package/dist/functions/ast-transformers/append-as-const.d.mts.map +1 -1
- package/dist/functions/ast-transformers/append-as-const.mjs +80 -7
- package/dist/functions/ast-transformers/append-as-const.mjs.map +1 -1
- package/dist/functions/ast-transformers/convert-to-readonly.d.mts +23 -4
- package/dist/functions/ast-transformers/convert-to-readonly.d.mts.map +1 -1
- package/dist/functions/ast-transformers/convert-to-readonly.mjs +40 -19
- package/dist/functions/ast-transformers/convert-to-readonly.mjs.map +1 -1
- package/dist/functions/constants/index.d.mts +1 -0
- package/dist/functions/constants/index.d.mts.map +1 -1
- package/dist/functions/constants/index.mjs +1 -0
- package/dist/functions/constants/index.mjs.map +1 -1
- package/dist/functions/constants/mutable-prefixes.d.mts +23 -0
- package/dist/functions/constants/mutable-prefixes.d.mts.map +1 -0
- package/dist/functions/constants/mutable-prefixes.mjs +28 -0
- package/dist/functions/constants/mutable-prefixes.mjs.map +1 -0
- package/package.json +2 -2
- package/src/functions/ast-transformers/append-as-const.mts +152 -18
- package/src/functions/ast-transformers/append-as-const.test.mts +79 -1
- package/src/functions/ast-transformers/convert-to-readonly.mts +124 -41
- package/src/functions/ast-transformers/convert-to-readonly.test.mts +321 -41
- package/src/functions/constants/index.mts +1 -0
- package/src/functions/constants/mutable-prefixes.mts +28 -0
|
@@ -2,6 +2,7 @@ import {
|
|
|
2
2
|
Arr,
|
|
3
3
|
expectType,
|
|
4
4
|
ISet,
|
|
5
|
+
isRecord,
|
|
5
6
|
isString,
|
|
6
7
|
mapNullable,
|
|
7
8
|
match,
|
|
@@ -14,6 +15,10 @@ import type {
|
|
|
14
15
|
StrictExclude,
|
|
15
16
|
StrictExtract,
|
|
16
17
|
} from 'ts-type-forge';
|
|
18
|
+
import {
|
|
19
|
+
DEFAULT_MUTABLE_PREFIXES,
|
|
20
|
+
DEFAULT_MUTABLE_TYPE_PREFIXES,
|
|
21
|
+
} from '../constants/index.mjs';
|
|
17
22
|
import {
|
|
18
23
|
hasCallOrConstructSignature,
|
|
19
24
|
hasDisableNextLineComment,
|
|
@@ -59,13 +64,17 @@ export const convertToReadonlyTransformer = (
|
|
|
59
64
|
const DeepReadonlyTypeName =
|
|
60
65
|
options?.DeepReadonly?.typeName ?? 'DeepReadonly';
|
|
61
66
|
|
|
62
|
-
//
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
)
|
|
67
|
+
// Removed in 4.0.0; a caller without types would otherwise lose it silently.
|
|
68
|
+
const untypedOptions: unknown = options;
|
|
69
|
+
|
|
70
|
+
if (
|
|
71
|
+
isRecord(untypedOptions) &&
|
|
72
|
+
untypedOptions['ignorePrefixes'] !== undefined
|
|
73
|
+
) {
|
|
74
|
+
throw new Error(
|
|
75
|
+
`The "ignorePrefixes" option of ${TRANSFORMER_NAME} was replaced by "mutableVariablePrefixes", "mutableTypePrefixes" and "mutablePropertyPrefixes".`,
|
|
76
|
+
);
|
|
77
|
+
}
|
|
69
78
|
|
|
70
79
|
const optionsInternal: ReadonlyTransformerOptionsInternal = {
|
|
71
80
|
DeepReadonly: {
|
|
@@ -73,7 +82,15 @@ export const convertToReadonlyTransformer = (
|
|
|
73
82
|
applyLevel: 'keep',
|
|
74
83
|
},
|
|
75
84
|
ignoreEmptyObjectTypes: options?.ignoreEmptyObjectTypes ?? true,
|
|
76
|
-
|
|
85
|
+
mutableVariablePrefixes: ISet.create(
|
|
86
|
+
options?.mutableVariablePrefixes ?? DEFAULT_MUTABLE_PREFIXES,
|
|
87
|
+
),
|
|
88
|
+
mutableTypePrefixes: ISet.create(
|
|
89
|
+
options?.mutableTypePrefixes ?? DEFAULT_MUTABLE_TYPE_PREFIXES,
|
|
90
|
+
),
|
|
91
|
+
mutablePropertyPrefixes: ISet.create(
|
|
92
|
+
options?.mutablePropertyPrefixes ?? DEFAULT_MUTABLE_PREFIXES,
|
|
93
|
+
),
|
|
77
94
|
recordStyle: options?.recordStyle ?? 'ReadonlyRecord',
|
|
78
95
|
debugPrint: options?.debug === true ? console.debug : () => {},
|
|
79
96
|
replaceNode:
|
|
@@ -139,9 +156,30 @@ export type ReadonlyTransformerOptions = DeepReadonly<{
|
|
|
139
156
|
ignoreEmptyObjectTypes?: boolean;
|
|
140
157
|
|
|
141
158
|
/**
|
|
142
|
-
*
|
|
159
|
+
* Prefixes of the names of variables, parameters (but for a constructor
|
|
160
|
+
* parameter property, which `mutablePropertyPrefixes` decides) and
|
|
161
|
+
* functions that are left mutable: their types are not made readonly.
|
|
143
162
|
*
|
|
144
|
-
*
|
|
163
|
+
* @default ['mut_']
|
|
164
|
+
*/
|
|
165
|
+
mutableVariablePrefixes?: string[];
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Prefixes of the names of type aliases, interfaces, class declarations and
|
|
169
|
+
* named class expressions that are left mutable: their declarations are not
|
|
170
|
+
* made readonly.
|
|
171
|
+
*
|
|
172
|
+
* @default ['Mutable', 'mut_']
|
|
173
|
+
*/
|
|
174
|
+
mutableTypePrefixes?: string[];
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Prefixes of the names of properties, methods and accessors (of type
|
|
178
|
+
* literals, interfaces and classes, constructor parameter properties
|
|
179
|
+
* included) that are left mutable: they get no `readonly`, and their types
|
|
180
|
+
* are not made readonly. A private name is matched with or after its `#`, so
|
|
181
|
+
* both `mut_` and `#mut_` cover `#mut_x`. Of a named tuple member with such a prefix, only the type
|
|
182
|
+
* is kept; the tuple is still made `readonly`.
|
|
145
183
|
*
|
|
146
184
|
* A type literal with a property, a method or an accessor of such a prefix
|
|
147
185
|
* is not wrapped with `Readonly<...>`, which would make that member readonly
|
|
@@ -153,9 +191,9 @@ export type ReadonlyTransformerOptions = DeepReadonly<{
|
|
|
153
191
|
* it is a member of a `Readonly<...>` intersection beside a type that may be
|
|
154
192
|
* an array.
|
|
155
193
|
*
|
|
156
|
-
* @default ['mut_'
|
|
194
|
+
* @default ['mut_']
|
|
157
195
|
*/
|
|
158
|
-
|
|
196
|
+
mutablePropertyPrefixes?: string[];
|
|
159
197
|
|
|
160
198
|
/**
|
|
161
199
|
* The output style used when making `Record<K, V>` readonly.
|
|
@@ -190,7 +228,11 @@ type ReadonlyTransformerOptionsInternal = Readonly<{
|
|
|
190
228
|
|
|
191
229
|
ignoreEmptyObjectTypes: boolean;
|
|
192
230
|
|
|
193
|
-
|
|
231
|
+
mutableVariablePrefixes: ISet<string>;
|
|
232
|
+
|
|
233
|
+
mutableTypePrefixes: ISet<string>;
|
|
234
|
+
|
|
235
|
+
mutablePropertyPrefixes: ISet<string>;
|
|
194
236
|
|
|
195
237
|
recordStyle: 'Readonly<Record>' | 'ReadonlyRecord';
|
|
196
238
|
|
|
@@ -230,24 +272,26 @@ const transformNode = (
|
|
|
230
272
|
return;
|
|
231
273
|
}
|
|
232
274
|
|
|
233
|
-
// check for
|
|
275
|
+
// check for mutableVariablePrefixes
|
|
234
276
|
if (node.isKind(tsm.SyntaxKind.VariableDeclaration)) {
|
|
235
277
|
const nodeName = node.getName();
|
|
236
278
|
|
|
237
|
-
if (options.
|
|
279
|
+
if (options.mutableVariablePrefixes.some((p) => nodeName.startsWith(p))) {
|
|
238
280
|
// Skip conversion for variable declarations with ignored prefixes
|
|
239
281
|
// Example: const mut_foo: string[] = []; -> remains as is, without appending `as const`
|
|
240
|
-
options.debugPrint(
|
|
282
|
+
options.debugPrint(
|
|
283
|
+
'skipped variable declaration by mutableVariablePrefixes',
|
|
284
|
+
);
|
|
241
285
|
|
|
242
286
|
return;
|
|
243
287
|
}
|
|
244
288
|
|
|
245
|
-
// TODO: Support
|
|
289
|
+
// TODO: Support mutableVariablePrefixes in ArrayBindingPattern
|
|
246
290
|
// if (ts.isArrayBindingPattern(nodeName)) {
|
|
247
291
|
// // for (const [i, el] of nodeName.elements.entries())
|
|
248
292
|
// }
|
|
249
293
|
|
|
250
|
-
// TODO: Support
|
|
294
|
+
// TODO: Support mutableVariablePrefixes in ObjectBindingPattern
|
|
251
295
|
// if (ts.isObjectBindingPattern(nodeName)) {
|
|
252
296
|
// // for (const [i, el] of nodeName.elements.entries())
|
|
253
297
|
// }
|
|
@@ -258,7 +302,9 @@ const transformNode = (
|
|
|
258
302
|
if (
|
|
259
303
|
(node.isKind(tsm.SyntaxKind.FunctionDeclaration) ||
|
|
260
304
|
node.isKind(tsm.SyntaxKind.FunctionExpression)) &&
|
|
261
|
-
options.
|
|
305
|
+
options.mutableVariablePrefixes.some(
|
|
306
|
+
(p) => node.getName()?.startsWith(p) === true,
|
|
307
|
+
)
|
|
262
308
|
) {
|
|
263
309
|
return;
|
|
264
310
|
}
|
|
@@ -266,26 +312,30 @@ const transformNode = (
|
|
|
266
312
|
if (node.isKind(tsm.SyntaxKind.Parameter)) {
|
|
267
313
|
const nodeName = node.getName();
|
|
268
314
|
|
|
269
|
-
if (
|
|
270
|
-
|
|
271
|
-
|
|
315
|
+
if (
|
|
316
|
+
mutablePrefixesOfParameter(node, options).some((p) =>
|
|
317
|
+
nodeName.startsWith(p),
|
|
318
|
+
)
|
|
319
|
+
) {
|
|
320
|
+
// Skip readonly conversion for parameters with mutable prefixes
|
|
321
|
+
// Example: (mut_foo: string[]) => {} -> remains as is, without readonly conversion
|
|
272
322
|
return;
|
|
273
323
|
}
|
|
274
324
|
|
|
275
|
-
// TODO: Support
|
|
325
|
+
// TODO: Support mutableVariablePrefixes in ArrayBindingPattern
|
|
276
326
|
// if (ts.isArrayBindingPattern(nodeName)) {
|
|
277
327
|
// }
|
|
278
328
|
|
|
279
|
-
// TODO: Support
|
|
329
|
+
// TODO: Support mutableVariablePrefixes in ObjectBindingPattern
|
|
280
330
|
// if (ts.isObjectBindingPattern(nodeName)) {
|
|
281
331
|
// }
|
|
282
332
|
}
|
|
283
333
|
|
|
284
334
|
// Skip readonly conversion for type alias declarations with ignored prefixes
|
|
285
|
-
// Example: type
|
|
335
|
+
// Example: type MutableFoo = number[]; -> remains as number[] without readonly
|
|
286
336
|
if (
|
|
287
337
|
node.isKind(tsm.SyntaxKind.TypeAliasDeclaration) &&
|
|
288
|
-
|
|
338
|
+
hasMutableTypeName(node.getName(), options)
|
|
289
339
|
) {
|
|
290
340
|
return;
|
|
291
341
|
}
|
|
@@ -364,8 +414,8 @@ const transformNode = (
|
|
|
364
414
|
|
|
365
415
|
if (node.isKind(tsm.SyntaxKind.InterfaceDeclaration)) {
|
|
366
416
|
// Skip readonly conversion for interface declarations with ignored prefixes
|
|
367
|
-
// Example: interface
|
|
368
|
-
if (
|
|
417
|
+
// Example: interface MutableInterface {...} -> properties remain without readonly
|
|
418
|
+
if (hasMutableTypeName(node.getName(), options)) {
|
|
369
419
|
return;
|
|
370
420
|
}
|
|
371
421
|
|
|
@@ -374,14 +424,18 @@ const transformNode = (
|
|
|
374
424
|
return;
|
|
375
425
|
}
|
|
376
426
|
|
|
427
|
+
// Example: const C = class MutableClass {...} -> properties remain without readonly
|
|
428
|
+
if (
|
|
429
|
+
node.isKind(tsm.SyntaxKind.ClassExpression) &&
|
|
430
|
+
hasMutableTypeName(node.getName(), options)
|
|
431
|
+
) {
|
|
432
|
+
return;
|
|
433
|
+
}
|
|
434
|
+
|
|
377
435
|
if (node.isKind(tsm.SyntaxKind.ClassDeclaration)) {
|
|
378
436
|
// Skip readonly conversion for class declarations with ignored prefixes
|
|
379
|
-
// Example: class
|
|
380
|
-
if (
|
|
381
|
-
options.ignoredPrefixes.some(
|
|
382
|
-
(p) => node.getName()?.startsWith(p) === true,
|
|
383
|
-
)
|
|
384
|
-
) {
|
|
437
|
+
// Example: class MutableClass {...} -> properties remain without readonly
|
|
438
|
+
if (hasMutableTypeName(node.getName(), options)) {
|
|
385
439
|
return;
|
|
386
440
|
}
|
|
387
441
|
|
|
@@ -1002,7 +1056,11 @@ const transformTupleTypeNode = (
|
|
|
1002
1056
|
|
|
1003
1057
|
for (const el of node.getElements()) {
|
|
1004
1058
|
if (el.isKind(tsm.SyntaxKind.NamedTupleMember)) {
|
|
1005
|
-
if (
|
|
1059
|
+
if (
|
|
1060
|
+
options.mutablePropertyPrefixes.every(
|
|
1061
|
+
(p) => !el.getName().startsWith(p),
|
|
1062
|
+
)
|
|
1063
|
+
) {
|
|
1006
1064
|
transformNode(el.getTypeNode(), nextReadonlyContextValue, options);
|
|
1007
1065
|
}
|
|
1008
1066
|
} else {
|
|
@@ -1362,7 +1420,9 @@ const transformConstructorParameter = (
|
|
|
1362
1420
|
|
|
1363
1421
|
const name = param.getName();
|
|
1364
1422
|
|
|
1365
|
-
if (
|
|
1423
|
+
if (
|
|
1424
|
+
mutablePrefixesOfParameter(param, options).some((p) => name.startsWith(p))
|
|
1425
|
+
) {
|
|
1366
1426
|
return;
|
|
1367
1427
|
}
|
|
1368
1428
|
|
|
@@ -2097,17 +2157,19 @@ const checkIfPropertyNameShouldBeIgnored = (
|
|
|
2097
2157
|
// mut_x: number[]
|
|
2098
2158
|
(nameNode.isKind(tsm.SyntaxKind.Identifier) &&
|
|
2099
2159
|
pipe(nameNode.getText()).map((nm) =>
|
|
2100
|
-
options.
|
|
2160
|
+
options.mutablePropertyPrefixes.some((p) => nm.startsWith(p)),
|
|
2101
2161
|
).value) ||
|
|
2102
2162
|
// "mut_x": number[]
|
|
2103
2163
|
(nameNode.isKind(tsm.SyntaxKind.StringLiteral) &&
|
|
2104
2164
|
pipe(nameNode.getLiteralValue()).map((nm) =>
|
|
2105
|
-
options.
|
|
2165
|
+
options.mutablePropertyPrefixes.some((p) => nm.startsWith(p)),
|
|
2106
2166
|
).value) ||
|
|
2107
|
-
// #memberName: number[] (class only)
|
|
2167
|
+
// #memberName: number[] (class only), matched with or after its `#`
|
|
2108
2168
|
(nameNode.isKind(tsm.SyntaxKind.PrivateIdentifier) &&
|
|
2109
2169
|
pipe(nameNode.getText()).map((nm) =>
|
|
2110
|
-
options.
|
|
2170
|
+
options.mutablePropertyPrefixes.some(
|
|
2171
|
+
(p) => nm.startsWith(p) || nm.startsWith(`#${p}`),
|
|
2172
|
+
),
|
|
2111
2173
|
).value) ||
|
|
2112
2174
|
// ['mut_x']: number[], [`mut_x`]: number[]
|
|
2113
2175
|
(nameNode.isKind(tsm.SyntaxKind.ComputedPropertyName) &&
|
|
@@ -2118,7 +2180,7 @@ const checkIfPropertyNameShouldBeIgnored = (
|
|
|
2118
2180
|
) {
|
|
2119
2181
|
const nm = exp.getLiteralValue();
|
|
2120
2182
|
|
|
2121
|
-
return options.
|
|
2183
|
+
return options.mutablePropertyPrefixes.some((p) => nm.startsWith(p));
|
|
2122
2184
|
}
|
|
2123
2185
|
|
|
2124
2186
|
return false;
|
|
@@ -2209,3 +2271,24 @@ const takesOffReadonly = (
|
|
|
2209
2271
|
isLeftUnwrapped(node, options) &&
|
|
2210
2272
|
!hasCallOrConstructSignature(node) &&
|
|
2211
2273
|
!hasDisableNextLineCommentAround(node);
|
|
2274
|
+
|
|
2275
|
+
/** Whether a type alias, interface or class name starts with a `mutableTypePrefixes` entry. */
|
|
2276
|
+
const hasMutableTypeName = (
|
|
2277
|
+
name: string | undefined,
|
|
2278
|
+
options: ReadonlyTransformerOptionsInternal,
|
|
2279
|
+
): boolean =>
|
|
2280
|
+
name !== undefined &&
|
|
2281
|
+
options.mutableTypePrefixes.some((p) => name.startsWith(p));
|
|
2282
|
+
|
|
2283
|
+
/**
|
|
2284
|
+
* A parameter property is a property of the class, as a `PropertyDeclaration`
|
|
2285
|
+
* is; any other parameter is a variable.
|
|
2286
|
+
*/
|
|
2287
|
+
const mutablePrefixesOfParameter = (
|
|
2288
|
+
// eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
|
|
2289
|
+
param: tsm.ParameterDeclaration,
|
|
2290
|
+
options: ReadonlyTransformerOptionsInternal,
|
|
2291
|
+
): ISet<string> =>
|
|
2292
|
+
param.isParameterProperty()
|
|
2293
|
+
? options.mutablePropertyPrefixes
|
|
2294
|
+
: options.mutableVariablePrefixes;
|