ts-codemod-lib 3.5.0 → 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.
Files changed (29) hide show
  1. package/README.md +32 -9
  2. package/dist/functions/ast-transformers/append-as-const.d.mts +13 -4
  3. package/dist/functions/ast-transformers/append-as-const.d.mts.map +1 -1
  4. package/dist/functions/ast-transformers/append-as-const.mjs +81 -5
  5. package/dist/functions/ast-transformers/append-as-const.mjs.map +1 -1
  6. package/dist/functions/ast-transformers/convert-to-readonly.d.mts +33 -4
  7. package/dist/functions/ast-transformers/convert-to-readonly.d.mts.map +1 -1
  8. package/dist/functions/ast-transformers/convert-to-readonly.mjs +155 -30
  9. package/dist/functions/ast-transformers/convert-to-readonly.mjs.map +1 -1
  10. package/dist/functions/ast-transformers/readonly-transformer-helpers/group-union-types.d.mts +7 -1
  11. package/dist/functions/ast-transformers/readonly-transformer-helpers/group-union-types.d.mts.map +1 -1
  12. package/dist/functions/ast-transformers/readonly-transformer-helpers/group-union-types.mjs +25 -2
  13. package/dist/functions/ast-transformers/readonly-transformer-helpers/group-union-types.mjs.map +1 -1
  14. package/dist/functions/constants/index.d.mts +1 -0
  15. package/dist/functions/constants/index.d.mts.map +1 -1
  16. package/dist/functions/constants/index.mjs +1 -0
  17. package/dist/functions/constants/index.mjs.map +1 -1
  18. package/dist/functions/constants/mutable-prefixes.d.mts +23 -0
  19. package/dist/functions/constants/mutable-prefixes.d.mts.map +1 -0
  20. package/dist/functions/constants/mutable-prefixes.mjs +28 -0
  21. package/dist/functions/constants/mutable-prefixes.mjs.map +1 -0
  22. package/package.json +19 -19
  23. package/src/functions/ast-transformers/append-as-const.mts +152 -15
  24. package/src/functions/ast-transformers/append-as-const.test.mts +79 -1
  25. package/src/functions/ast-transformers/convert-to-readonly.mts +302 -57
  26. package/src/functions/ast-transformers/convert-to-readonly.test.mts +738 -49
  27. package/src/functions/ast-transformers/readonly-transformer-helpers/group-union-types.mts +36 -5
  28. package/src/functions/constants/index.mts +1 -0
  29. package/src/functions/constants/mutable-prefixes.mts +28 -0
@@ -22,6 +22,11 @@ import {
22
22
  * member that `Readonly` still means something to (see `staysOutsideReadonly`):
23
23
  * taking such a member out of the `Readonly` it was written under would drop
24
24
  * its readonly-ness.
25
+ *
26
+ * `keptUnwrapped` names the further type literals the caller leaves unwrapped
27
+ * (one with a property of an ignored prefix, such as `mut_`), which stay out of
28
+ * that group as one with a call or construct signature does — except under a
29
+ * `Readonly` intersection with a member that may be an array.
25
30
  */
26
31
  export const groupUnionIntersectionTypes = (
27
32
  // eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
@@ -30,10 +35,13 @@ export const groupUnionIntersectionTypes = (
30
35
  operator,
31
36
  underReadonly,
32
37
  deepReadonlyTypeName,
38
+ keptUnwrapped = () => false,
33
39
  }: Readonly<{
34
40
  operator: '&' | '|';
35
41
  underReadonly: boolean;
36
42
  deepReadonlyTypeName: string;
43
+ // eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
44
+ keptUnwrapped?: (node: tsm.TypeLiteralNode) => boolean;
37
45
  }>,
38
46
  ): Readonly<{
39
47
  primitives:
@@ -109,6 +117,28 @@ export const groupUnionIntersectionTypes = (
109
117
  others: undefined,
110
118
  };
111
119
 
120
+ // Taking a literal out of `Readonly<A & { mut_x: X }>` leaves
121
+ // `Readonly<A> & { mut_x: X }`, which is another type when `A` may be an
122
+ // array (see `isMergeableReadonlyTypeReferenceNode`). So `keptUnwrapped` is
123
+ // honoured under `Readonly<... & ...>` only when every member that stays
124
+ // under it is an object type.
125
+ const honoursKeptUnwrapped =
126
+ !underReadonly ||
127
+ operator === '|' ||
128
+ types.every((t_) => {
129
+ const t = removeParentheses(t_);
130
+
131
+ return (
132
+ isAtomicTypeNode(t) ||
133
+ t.isKind(tsm.SyntaxKind.ArrayType) ||
134
+ t.isKind(tsm.SyntaxKind.TupleType) ||
135
+ isReadonlyTupleOrArrayTypeNode(t) ||
136
+ t.isKind(tsm.SyntaxKind.TypeLiteral) ||
137
+ isMergeableReadonlyTypeReferenceNode(t, operator) ||
138
+ staysOutsideReadonly(t, deepReadonlyTypeName)
139
+ );
140
+ });
141
+
112
142
  for (const [i, t_] of types.entries()) {
113
143
  const t = removeParentheses(t_);
114
144
 
@@ -143,14 +173,15 @@ export const groupUnionIntersectionTypes = (
143
173
  continue;
144
174
  }
145
175
 
146
- // A type literal that declares a call or construct signature is deliberately
147
- // not grouped here: this group is joined and wrapped in a single
148
- // `Readonly<...>`, which such a literal does not survive. It falls through
149
- // to `others`, which leaves it as written. See
176
+ // A type literal that declares a call or construct signature, or one the
177
+ // caller keeps unwrapped, is deliberately not grouped here: this group is
178
+ // joined and wrapped in a single `Readonly<...>`, which such a literal does
179
+ // not survive. It falls through to `others`, which leaves it as written. See
150
180
  // `hasCallOrConstructSignature`.
151
181
  if (
152
182
  (t.isKind(tsm.SyntaxKind.TypeLiteral) &&
153
- !hasCallOrConstructSignature(t)) ||
183
+ !hasCallOrConstructSignature(t) &&
184
+ !(honoursKeptUnwrapped && keptUnwrapped(t))) ||
154
185
  isMergeableReadonlyTypeReferenceNode(t, operator) ||
155
186
  (underReadonly && !staysOutsideReadonly(t, deepReadonlyTypeName))
156
187
  ) {
@@ -1 +1,2 @@
1
1
  export * from './ignore-comment-text.mjs';
2
+ export * from './mutable-prefixes.mjs';
@@ -0,0 +1,28 @@
1
+ /**
2
+ * The default `mutableVariablePrefixes` and `mutablePropertyPrefixes` of the
3
+ * transformers: a name that starts with one of these is left mutable. A
4
+ * private name (`#mut_x`) is matched with or after its `#`.
5
+ *
6
+ * The ESLint counterpart is `ignoredMutablePattern` in
7
+ * `libs/eslint-config-typed/src/rules/eslint-functional-rules.mts` (and, for a
8
+ * property, the `ignoreAccessorPattern` of `functional/immutable-data` there);
9
+ * `libs/eslint-config-typed/src/rules/eslint-functional-rules.test.mts` fails
10
+ * when the two differ; it reads this package's `dist/`, so build first.
11
+ */
12
+ export const DEFAULT_MUTABLE_PREFIXES: readonly string[] = Object.freeze([
13
+ 'mut_',
14
+ ]);
15
+
16
+ /**
17
+ * The default `mutableTypePrefixes` of the transformers: a type alias, an
18
+ * interface or a class whose name starts with one of these is left mutable,
19
+ * and `append-as-const` adds no `as const` where the value has to be
20
+ * assignable to it (an annotated variable, `satisfies`, an arrow's return).
21
+ * `Mutable` suits the PascalCase of type names; `mut_` is accepted as for
22
+ * variables. No ESLint rule reads type names, so there is no counterpart to
23
+ * keep in step.
24
+ */
25
+ export const DEFAULT_MUTABLE_TYPE_PREFIXES: readonly string[] = Object.freeze([
26
+ 'Mutable',
27
+ 'mut_',
28
+ ]);