ts-codemod-lib 3.4.0 → 3.4.2

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 (50) hide show
  1. package/README.md +4 -4
  2. package/dist/functions/ast-transformers/append-as-const.d.mts +2 -2
  3. package/dist/functions/ast-transformers/append-as-const.d.mts.map +1 -1
  4. package/dist/functions/ast-transformers/append-as-const.mjs +13 -3
  5. package/dist/functions/ast-transformers/append-as-const.mjs.map +1 -1
  6. package/dist/functions/ast-transformers/convert-interface-to-type.d.mts +1 -1
  7. package/dist/functions/ast-transformers/convert-interface-to-type.d.mts.map +1 -1
  8. package/dist/functions/ast-transformers/convert-to-readonly.d.mts +2 -2
  9. package/dist/functions/ast-transformers/convert-to-readonly.d.mts.map +1 -1
  10. package/dist/functions/ast-transformers/convert-to-readonly.mjs +157 -33
  11. package/dist/functions/ast-transformers/convert-to-readonly.mjs.map +1 -1
  12. package/dist/functions/ast-transformers/enable-no-unchecked-indexed-access.d.mts +2 -2
  13. package/dist/functions/ast-transformers/enable-no-unchecked-indexed-access.d.mts.map +1 -1
  14. package/dist/functions/ast-transformers/enable-no-unchecked-indexed-access.mjs +24 -6
  15. package/dist/functions/ast-transformers/enable-no-unchecked-indexed-access.mjs.map +1 -1
  16. package/dist/functions/ast-transformers/readonly-transformer-helpers/group-union-types.d.mts +32 -2
  17. package/dist/functions/ast-transformers/readonly-transformer-helpers/group-union-types.d.mts.map +1 -1
  18. package/dist/functions/ast-transformers/readonly-transformer-helpers/group-union-types.mjs +88 -2
  19. package/dist/functions/ast-transformers/readonly-transformer-helpers/group-union-types.mjs.map +1 -1
  20. package/dist/functions/ast-transformers/readonly-transformer-helpers/readonly-context.d.mts +1 -1
  21. package/dist/functions/ast-transformers/readonly-transformer-helpers/readonly-context.d.mts.map +1 -1
  22. package/dist/functions/ast-transformers/replace-any-with-unknown.d.mts +1 -1
  23. package/dist/functions/ast-transformers/replace-any-with-unknown.d.mts.map +1 -1
  24. package/dist/functions/ast-transformers/replace-record-with-unknown-record.d.mts +1 -1
  25. package/dist/functions/ast-transformers/replace-record-with-unknown-record.d.mts.map +1 -1
  26. package/dist/functions/ast-transformers/transform-source-code.d.mts +1 -1
  27. package/dist/functions/ast-transformers/transform-source-code.d.mts.map +1 -1
  28. package/dist/functions/functions/is-atomic-type-node.d.mts +1 -1
  29. package/dist/functions/functions/is-atomic-type-node.d.mts.map +1 -1
  30. package/dist/functions/functions/is-readonly-node.d.mts +1 -1
  31. package/dist/functions/functions/is-readonly-node.d.mts.map +1 -1
  32. package/dist/functions/functions/unwrap-readonly.d.mts +1 -1
  33. package/dist/functions/functions/unwrap-readonly.d.mts.map +1 -1
  34. package/package.json +14 -14
  35. package/src/functions/ast-transformers/append-as-const.mts +21 -5
  36. package/src/functions/ast-transformers/append-as-const.test.mts +24 -0
  37. package/src/functions/ast-transformers/convert-interface-to-type.mts +1 -1
  38. package/src/functions/ast-transformers/convert-to-readonly.mts +249 -52
  39. package/src/functions/ast-transformers/convert-to-readonly.test.mts +421 -7
  40. package/src/functions/ast-transformers/enable-no-unchecked-indexed-access.mts +30 -8
  41. package/src/functions/ast-transformers/enable-no-unchecked-indexed-access.test.mts +111 -0
  42. package/src/functions/ast-transformers/readonly-transformer-helpers/compare-union-types.mts +1 -1
  43. package/src/functions/ast-transformers/readonly-transformer-helpers/group-union-types.mts +124 -3
  44. package/src/functions/ast-transformers/readonly-transformer-helpers/readonly-context.mts +1 -1
  45. package/src/functions/ast-transformers/replace-any-with-unknown.mts +1 -1
  46. package/src/functions/ast-transformers/replace-record-with-unknown-record.mts +1 -1
  47. package/src/functions/ast-transformers/transform-source-code.mts +1 -1
  48. package/src/functions/functions/is-atomic-type-node.mts +1 -1
  49. package/src/functions/functions/is-readonly-node.mts +1 -1
  50. package/src/functions/functions/unwrap-readonly.mts +1 -1
@@ -11,9 +11,30 @@ import {
11
11
  type ReadonlyTypeReferenceNode,
12
12
  } from '../../functions/index.mjs';
13
13
 
14
+ /**
15
+ * Sorts the members of a union or intersection into the groups
16
+ * `convert-to-readonly` writes back separately.
17
+ *
18
+ * `typeLiterals` is the group joined and wrapped in a single `Readonly<...>`.
19
+ * It takes type literals, the `Readonly<...>` members that may be merged (see
20
+ * `isMergeableReadonlyTypeReferenceNode`), and, when the union or intersection
21
+ * is itself the argument of a `Readonly<...>` (`underReadonly`), every other
22
+ * member that `Readonly` still means something to (see `staysOutsideReadonly`):
23
+ * taking such a member out of the `Readonly` it was written under would drop
24
+ * its readonly-ness.
25
+ */
14
26
  export const groupUnionIntersectionTypes = (
15
27
  // eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
16
28
  types: readonly tsm.TypeNode[],
29
+ {
30
+ operator,
31
+ underReadonly,
32
+ deepReadonlyTypeName,
33
+ }: Readonly<{
34
+ operator: '&' | '|';
35
+ underReadonly: boolean;
36
+ deepReadonlyTypeName: string;
37
+ }>,
17
38
  ): Readonly<{
18
39
  primitives:
19
40
  | Readonly<{
@@ -37,7 +58,7 @@ export const groupUnionIntersectionTypes = (
37
58
  typeLiterals:
38
59
  | Readonly<{
39
60
  firstPosition: number;
40
- nodes: readonly (tsm.TypeLiteralNode | ReadonlyTypeReferenceNode)[];
61
+ nodes: readonly tsm.TypeNode[];
41
62
  }>
42
63
  | undefined;
43
64
 
@@ -71,7 +92,7 @@ export const groupUnionIntersectionTypes = (
71
92
  typeLiterals:
72
93
  | Readonly<{
73
94
  firstPosition: number;
74
- nodes: (tsm.TypeLiteralNode | ReadonlyTypeReferenceNode)[];
95
+ nodes: tsm.TypeNode[];
75
96
  }>
76
97
  | undefined;
77
98
 
@@ -130,7 +151,8 @@ export const groupUnionIntersectionTypes = (
130
151
  if (
131
152
  (t.isKind(tsm.SyntaxKind.TypeLiteral) &&
132
153
  !hasCallOrConstructSignature(t)) ||
133
- isReadonlyTypeReferenceNode(t)
154
+ isMergeableReadonlyTypeReferenceNode(t, operator) ||
155
+ (underReadonly && !staysOutsideReadonly(t, deepReadonlyTypeName))
134
156
  ) {
135
157
  if (mut_grouped.typeLiterals === undefined) {
136
158
  mut_grouped.typeLiterals = {
@@ -157,3 +179,102 @@ export const groupUnionIntersectionTypes = (
157
179
 
158
180
  return mut_grouped;
159
181
  };
182
+
183
+ /**
184
+ * Whether a `Readonly<T>` member of a union or intersection may be merged with
185
+ * the others into one `Readonly<...>`.
186
+ *
187
+ * - In a union, any `T`: `Readonly` distributes over `|`, so `Readonly<A> |
188
+ * Readonly<B>` and `Readonly<A | B>` are the same type.
189
+ * - In an intersection, only a `T` known to be an object type (see
190
+ * `isObjectTypeLiteral`). Over object types `Readonly<A> & Readonly<B>` and
191
+ * `Readonly<A & B>` agree, but `Readonly` maps an array to a readonly array
192
+ * only when its argument _is_ an array, so `Readonly<string[] & X>` leaves the
193
+ * array mutable where `Readonly<string[]> & Readonly<X>` does not. A type
194
+ * parameter or an alias may be such an array.
195
+ */
196
+ export const isMergeableReadonlyTypeReferenceNode = (
197
+ // eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
198
+ node: tsm.TypeNode,
199
+ operator: '&' | '|',
200
+ ): node is ReadonlyTypeReferenceNode =>
201
+ isReadonlyTypeReferenceNode(node) &&
202
+ (operator === '|' || isObjectTypeLiteral(node.getTypeArguments()[0]));
203
+
204
+ /**
205
+ * A type literal or a mapped type known to be an object type, or a union,
206
+ * intersection or `Readonly<...>` of them.
207
+ */
208
+ const isObjectTypeLiteral = (
209
+ // eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
210
+ node: tsm.TypeNode,
211
+ ): boolean => {
212
+ const t = removeParentheses(node);
213
+
214
+ if (t.isKind(tsm.SyntaxKind.TypeLiteral)) {
215
+ return true;
216
+ }
217
+
218
+ // A mapped type is an object type unless it maps over `keyof T`, which maps
219
+ // an array `T` to an array.
220
+ if (t.isKind(tsm.SyntaxKind.MappedType)) {
221
+ const constraint = t.getTypeParameter().getConstraint();
222
+
223
+ return !(
224
+ constraint?.isKind(tsm.SyntaxKind.TypeOperator) === true &&
225
+ constraint.getOperator() === tsm.SyntaxKind.KeyOfKeyword
226
+ );
227
+ }
228
+
229
+ if (isReadonlyTypeReferenceNode(t)) {
230
+ return isObjectTypeLiteral(t.getTypeArguments()[0]);
231
+ }
232
+
233
+ if (
234
+ t.isKind(tsm.SyntaxKind.UnionType) ||
235
+ t.isKind(tsm.SyntaxKind.IntersectionType)
236
+ ) {
237
+ return t.getTypeNodes().every(isObjectTypeLiteral);
238
+ }
239
+
240
+ return false;
241
+ };
242
+
243
+ /**
244
+ * Whether a member of a union or intersection under `Readonly<...>` may be
245
+ * taken out of it as it is.
246
+ *
247
+ * - A function or constructor type, or a type literal with a call or construct
248
+ * signature, loses its signatures under `Readonly`; see
249
+ * `hasCallOrConstructSignature`.
250
+ * - `keyof T` and `unique symbol` are not object types.
251
+ * - `DeepReadonly<T>`, `ReadonlyMap`, `ReadonlySet` and `ReadonlyRecord` are
252
+ * readonly already.
253
+ */
254
+ const staysOutsideReadonly = (
255
+ // eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
256
+ node: tsm.TypeNode,
257
+ deepReadonlyTypeName: string,
258
+ ): boolean => {
259
+ if (
260
+ node.isKind(tsm.SyntaxKind.FunctionType) ||
261
+ node.isKind(tsm.SyntaxKind.ConstructorType) ||
262
+ node.isKind(tsm.SyntaxKind.TypeLiteral) ||
263
+ node.isKind(tsm.SyntaxKind.TypeOperator)
264
+ ) {
265
+ return true;
266
+ }
267
+
268
+ if (node.isKind(tsm.SyntaxKind.TypeReference)) {
269
+ const typeName = node.getTypeName().getText();
270
+
271
+ return (
272
+ typeName === deepReadonlyTypeName ||
273
+ typeName === 'ReadonlyMap' ||
274
+ typeName === 'ReadonlySet' ||
275
+ typeName === 'ReadonlyRecord'
276
+ );
277
+ }
278
+
279
+ return false;
280
+ };
@@ -1,5 +1,5 @@
1
1
  import { SafeUint, match } from 'ts-data-forge';
2
- import { type SafeUintWithSmallInt, type StrictExtract } from 'ts-type-forge';
2
+ import type { SafeUintWithSmallInt, StrictExtract } from 'ts-type-forge';
3
3
 
4
4
  /**
5
5
  * Controls whether to make a layer mutable during recursive transformation
@@ -4,7 +4,7 @@ import {
4
4
  isSpreadNamedTupleMemberNode,
5
5
  isSpreadParameterNode,
6
6
  } from '../functions/index.mjs';
7
- import { type TsMorphTransformer } from './types.mjs';
7
+ import type { TsMorphTransformer } from './types.mjs';
8
8
 
9
9
  const TRANSFORMER_NAME = 'replace-any-with-unknown';
10
10
 
@@ -2,7 +2,7 @@
2
2
  import { Arr } from 'ts-data-forge';
3
3
  import * as tsm from 'ts-morph';
4
4
  import { hasDisableNextLineComment } from '../functions/index.mjs';
5
- import { type TsMorphTransformer } from './types.mjs';
5
+ import type { TsMorphTransformer } from './types.mjs';
6
6
 
7
7
  const TRANSFORMER_NAME = 'replace-record-with-unknown-record';
8
8
 
@@ -1,6 +1,6 @@
1
1
  import { Arr } from 'ts-data-forge';
2
2
  import * as tsm from 'ts-morph';
3
- import { type TsMorphTransformer } from './types.mjs';
3
+ import type { TsMorphTransformer } from './types.mjs';
4
4
 
5
5
  const fileIgnorePrefixes = [
6
6
  'transformer-ignore',
@@ -1,6 +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
+ import type { DeepReadonly } from 'ts-type-forge';
4
4
 
5
5
  // Define the set of SyntaxKinds that represent primitive type keywords
6
6
  const atomicTypeKeywordKinds = ISet.create<tsm.SyntaxKind>([
@@ -1,6 +1,6 @@
1
1
  import { Arr, expectType } from 'ts-data-forge';
2
2
  import * as tsm from 'ts-morph';
3
- import { type DeepReadonly, type StrictOmit } from 'ts-type-forge';
3
+ import type { DeepReadonly, StrictOmit } from 'ts-type-forge';
4
4
  import { isAtomicTypeNode } from './is-atomic-type-node.mjs';
5
5
 
6
6
  export const isShallowReadonlyTypeNode = (node: tsm.Node): boolean =>
@@ -1,4 +1,4 @@
1
- import { type ReadonlyTypeReferenceNode } from './is-readonly-node.mjs';
1
+ import type { ReadonlyTypeReferenceNode } from './is-readonly-node.mjs';
2
2
  import { wrapWithParentheses } from './wrap-with-parentheses.mjs';
3
3
 
4
4
  export const unwrapReadonlyTypeArgText = (