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
@@ -0,0 +1,23 @@
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 declare const DEFAULT_MUTABLE_PREFIXES: readonly string[];
13
+ /**
14
+ * The default `mutableTypePrefixes` of the transformers: a type alias, an
15
+ * interface or a class whose name starts with one of these is left mutable,
16
+ * and `append-as-const` adds no `as const` where the value has to be
17
+ * assignable to it (an annotated variable, `satisfies`, an arrow's return).
18
+ * `Mutable` suits the PascalCase of type names; `mut_` is accepted as for
19
+ * variables. No ESLint rule reads type names, so there is no counterpart to
20
+ * keep in step.
21
+ */
22
+ export declare const DEFAULT_MUTABLE_TYPE_PREFIXES: readonly string[];
23
+ //# sourceMappingURL=mutable-prefixes.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mutable-prefixes.d.mts","sourceRoot":"","sources":["../../../src/functions/constants/mutable-prefixes.mts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,eAAO,MAAM,wBAAwB,EAAE,SAAS,MAAM,EAEpD,CAAC;AAEH;;;;;;;;GAQG;AACH,eAAO,MAAM,6BAA6B,EAAE,SAAS,MAAM,EAGzD,CAAC"}
@@ -0,0 +1,28 @@
1
+
2
+
3
+
4
+
5
+
6
+
7
+
8
+
9
+
10
+
11
+
12
+ export const DEFAULT_MUTABLE_PREFIXES = Object.freeze([
13
+ 'mut_',
14
+ ]);
15
+
16
+
17
+
18
+
19
+
20
+
21
+
22
+
23
+
24
+ export const DEFAULT_MUTABLE_TYPE_PREFIXES = Object.freeze([
25
+ 'Mutable',
26
+ 'mut_',
27
+ ]);
28
+ //# sourceMappingURL=mutable-prefixes.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mutable-prefixes.mjs","sourceRoot":"","sources":["../../../src/functions/constants/mutable-prefixes.mts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,MAAM,CAAC,MAAM,wBAAwB,GAAsB,MAAM,CAAC,MAAM,CAAC;IACvE,MAAM;CACP,CAAC,CAAC;AAEH;;;;;;;;GAQG;AACH,MAAM,CAAC,MAAM,6BAA6B,GAAsB,MAAM,CAAC,MAAM,CAAC;IAC5E,SAAS;IACT,MAAM;CACP,CAAC,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ts-codemod-lib",
3
- "version": "3.5.0",
3
+ "version": "4.0.0",
4
4
  "private": false,
5
5
  "keywords": [
6
6
  "typescript",
@@ -38,27 +38,27 @@
38
38
  "LICENSE"
39
39
  ],
40
40
  "dependencies": {
41
- "ts-data-forge": "^14.8.0",
41
+ "ts-data-forge": "^14.10.0",
42
42
  "ts-morph": "^28.0.0",
43
43
  "ts-type-forge": "^9.2.2"
44
44
  },
45
45
  "devDependencies": {
46
- "@types/node": "26.6.4",
47
- "@vitest/browser-playwright": "5.0.3",
48
- "@vitest/coverage-v8": "5.0.3",
49
- "@vitest/ui": "5.0.3",
50
- "cspell": "10.3.6",
46
+ "@types/node": "26.6.5",
47
+ "@vitest/browser-playwright": "5.0.5",
48
+ "@vitest/coverage-v8": "5.0.5",
49
+ "@vitest/ui": "5.0.5",
50
+ "cspell": "10.4.0",
51
51
  "dedent": "1.7.2",
52
52
  "esbuild": "0.28.2",
53
- "eslint": "10.12.0",
54
- "eslint-config-typed": "5.19.0",
55
- "eslint-plugin-ts-data-forge": "0.9.0",
56
- "eslint-plugin-ts-fortress": "1.3.4",
57
- "eslint-plugin-ts-type-forge": "0.8.9",
53
+ "eslint": "10.13.0",
54
+ "eslint-config-typed": "6.0.0",
55
+ "eslint-plugin-ts-data-forge": "0.10.4",
56
+ "eslint-plugin-ts-fortress": "1.3.9",
57
+ "eslint-plugin-ts-type-forge": "0.8.14",
58
58
  "jiti": "2.7.0",
59
- "npm-run-all2": "9.0.3",
60
- "playwright": "1.64.0",
61
- "prettier": "3.9.9",
59
+ "npm-run-all2": "9.0.4",
60
+ "playwright": "1.64.1",
61
+ "prettier": "3.9.10",
62
62
  "prettier-plugin-organize-imports": "4.3.0",
63
63
  "prettier-plugin-packagejson": "3.0.2",
64
64
  "ts-repo-utils": "10.7.1",
@@ -67,15 +67,15 @@
67
67
  "typedoc-github-theme": "0.4.0",
68
68
  "typescript": "6.0.3",
69
69
  "typescript-native": "npm:typescript@7.0.2",
70
- "vite": "8.3.3",
71
- "vitest": "5.0.3"
70
+ "vite": "8.3.5",
71
+ "vitest": "5.0.5"
72
72
  },
73
73
  "engines": {
74
- "node": ">=22.22.2",
74
+ "node": ">=22.23.4",
75
75
  "pnpm": ">=9.0.0"
76
76
  },
77
77
  "volta": {
78
- "node": "26.12.0"
78
+ "node": "26.12.1"
79
79
  },
80
80
  "publishConfig": {
81
81
  "access": "public",
@@ -1,6 +1,10 @@
1
- import { Arr, ISet } from 'ts-data-forge';
1
+ import { Arr, ISet, isRecord } from 'ts-data-forge';
2
2
  import * as tsm from 'ts-morph';
3
3
  import type { DeepReadonly } from 'ts-type-forge';
4
+ import {
5
+ DEFAULT_MUTABLE_PREFIXES,
6
+ DEFAULT_MUTABLE_TYPE_PREFIXES,
7
+ } from '../constants/index.mjs';
4
8
  import {
5
9
  hasDisableNextLineComment,
6
10
  isAsConstNode,
@@ -16,13 +20,26 @@ const TRANSFORMER_NAME = 'append-as-const';
16
20
  export const appendAsConstTransformer = (
17
21
  options?: AppendAsConstTransformerOptions,
18
22
  ): TsMorphTransformer => {
19
- const ignorePrefixes = ISet.create(
20
- options?.ignorePrefixes ?? ['mut_', '#mut_', '_mut_', 'draft'],
21
- );
23
+ // Removed in 4.0.0; a caller without types would otherwise lose it silently.
24
+ const untypedOptions: unknown = options;
25
+
26
+ if (
27
+ isRecord(untypedOptions) &&
28
+ untypedOptions['ignorePrefixes'] !== undefined
29
+ ) {
30
+ throw new Error(
31
+ `The "ignorePrefixes" option of ${TRANSFORMER_NAME} was renamed to "mutableVariablePrefixes".`,
32
+ );
33
+ }
22
34
 
23
35
  const optionsInternal: AppendAsConstTransformerOptionsInternal = {
24
36
  applyLevel: options?.applyLevel ?? 'avoidInFunctionArgs',
25
- ignoredPrefixes: ignorePrefixes,
37
+ mutableVariablePrefixes: ISet.create(
38
+ options?.mutableVariablePrefixes ?? DEFAULT_MUTABLE_PREFIXES,
39
+ ),
40
+ mutableTypePrefixes: ISet.create(
41
+ options?.mutableTypePrefixes ?? DEFAULT_MUTABLE_TYPE_PREFIXES,
42
+ ),
26
43
  removeAsConstForConstTypeParameters:
27
44
  options?.removeAsConstForConstTypeParameters ?? true,
28
45
 
@@ -63,13 +80,23 @@ export type AppendAsConstTransformerOptions = DeepReadonly<{
63
80
  applyLevel?: 'all' | 'avoidInFunctionArgs';
64
81
 
65
82
  /**
66
- * A mute keywords to ignore the readonly conversion.
83
+ * Prefixes of the names of variables that are left mutable: their
84
+ * initializers get no `as const`, nor does a value annotated with their type
85
+ * (`typeof mut_x`).
67
86
  *
68
- * (e.g. `"mut_"`)
87
+ * @default ['mut_']
88
+ */
89
+ mutableVariablePrefixes?: string[];
90
+
91
+ /**
92
+ * Prefixes of the names of mutable types (see `convert-to-readonly`): a
93
+ * variable whose type annotation names such a type, an expression that
94
+ * `satisfies` one and an arrow function body returning one get no
95
+ * `as const`, which would make them unassignable to it.
69
96
  *
70
- * @default ['mut_', '#mut_', '_mut_', 'draft']
97
+ * @default ['Mutable', 'mut_']
71
98
  */
72
- ignorePrefixes?: string[];
99
+ mutableTypePrefixes?: string[];
73
100
 
74
101
  /**
75
102
  * Whether to remove redundant `as const` assertions from call arguments
@@ -93,7 +120,8 @@ export type AppendAsConstTransformerOptions = DeepReadonly<{
93
120
 
94
121
  type AppendAsConstTransformerOptionsInternal = DeepReadonly<{
95
122
  applyLevel: 'all' | 'avoidInFunctionArgs';
96
- ignoredPrefixes: ISet<string>;
123
+ mutableVariablePrefixes: ISet<string>;
124
+ mutableTypePrefixes: ISet<string>;
97
125
  removeAsConstForConstTypeParameters: boolean;
98
126
 
99
127
  debugPrint: (...args: readonly unknown[]) => void;
@@ -143,11 +171,13 @@ const transformNode = (
143
171
  if (node.isKind(tsm.SyntaxKind.VariableDeclaration)) {
144
172
  const nodeName = node.getName();
145
173
 
146
- // check for ignorePrefix
147
- if (options.ignoredPrefixes.some((p) => nodeName.startsWith(p))) {
174
+ // check for mutableVariablePrefixes
175
+ if (options.mutableVariablePrefixes.some((p) => nodeName.startsWith(p))) {
148
176
  // Skip conversion for variable declarations with ignored prefixes
149
177
  // Example: const mut_foo: string[] = []; -> remains as is, without appending `as const`
150
- options.debugPrint('skipped variable declaration by ignorePrefixes');
178
+ options.debugPrint(
179
+ 'skipped variable declaration by mutableVariablePrefixes',
180
+ );
151
181
 
152
182
  return;
153
183
  }
@@ -167,6 +197,18 @@ const transformNode = (
167
197
  return;
168
198
  }
169
199
 
200
+ // Example: const foo: MutableFoo = { a: [] }; -> remains as is, because
201
+ // `as const` would make the initializer unassignable to `MutableFoo`
202
+ const typeNode = node.getTypeNode();
203
+
204
+ if (refersToMutableType(typeNode, options)) {
205
+ options.debugPrint(
206
+ 'skipped variable declaration annotated with a mutable type',
207
+ );
208
+
209
+ return;
210
+ }
211
+
170
212
  const declarationKindKeywords = node
171
213
  .getVariableStatement()
172
214
  ?.getDeclarationKindKeywords()
@@ -189,13 +231,13 @@ const transformNode = (
189
231
  }
190
232
 
191
233
  // const [a, b] = ...;
192
- // TODO: Support ignoredPrefixes in ArrayBindingPattern
234
+ // TODO: Support mutableVariablePrefixes in ArrayBindingPattern
193
235
  // if (ts.isArrayBindingPattern(nodeName)) {
194
236
  // // for (const [i, el] of nodeName.elements.entries())
195
237
  // }
196
238
 
197
239
  // const { x, y } = ...;
198
- // TODO: Support ignoredPrefixes in ObjectBindingPattern
240
+ // TODO: Support mutableVariablePrefixes in ObjectBindingPattern
199
241
  // if (ts.isObjectBindingPattern(nodeName)) {
200
242
  // // for (const [i, el] of nodeName.elements.entries())
201
243
  // }
@@ -218,6 +260,14 @@ const transformNode = (
218
260
 
219
261
  // pass by ([X satisfies ...] -> X)
220
262
  if (tsm.Node.isSatisfiesExpression(node)) {
263
+ // Example: { a: [] } satisfies MutableFoo -> remains as is, because
264
+ // `as const` would make it fail to satisfy `MutableFoo`
265
+ if (refersToMutableType(node.getTypeNode(), options)) {
266
+ options.debugPrint('skipped satisfies expression with a mutable type');
267
+
268
+ return;
269
+ }
270
+
221
271
  transformNode(node.getExpression(), context, options);
222
272
 
223
273
  return;
@@ -236,6 +286,14 @@ const transformNode = (
236
286
 
237
287
  // pass by arrow function body ([() => X] -> X)
238
288
  if (tsm.Node.isArrowFunction(node)) {
289
+ // Example: (): MutableFoo => ({ a: [] }) -> remains as is, because
290
+ // `as const` would make the body unassignable to `MutableFoo`
291
+ if (refersToMutableType(node.getReturnTypeNode(), options)) {
292
+ options.debugPrint('skipped arrow function with a mutable return type');
293
+
294
+ return;
295
+ }
296
+
239
297
  const body = node.getBody();
240
298
 
241
299
  transformNode(body, context, options);
@@ -682,3 +740,82 @@ const isBareConstTypeParameterReference = (
682
740
  constTypeParameterNames.has(typeName.getText())
683
741
  );
684
742
  };
743
+
744
+ /** `ns.MutableFoo` -> `MutableFoo` */
745
+ const lastNameOf = (
746
+ // eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
747
+ entityName: tsm.EntityName,
748
+ ): string =>
749
+ (tsm.Node.isQualifiedName(entityName)
750
+ ? entityName.getRight()
751
+ : entityName
752
+ ).getText();
753
+
754
+ /** `mut_x.items` -> `mut_x` */
755
+ const firstNameOf = (
756
+ // eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
757
+ entityName: tsm.EntityName,
758
+ ): string =>
759
+ tsm.Node.isQualifiedName(entityName)
760
+ ? firstNameOf(entityName.getLeft())
761
+ : entityName.getText();
762
+
763
+ const startsWithAny = (prefixes: ISet<string>, name: string): boolean =>
764
+ prefixes.some((p) => name.startsWith(p));
765
+
766
+ /**
767
+ * The name a `typeof` starts from: a variable (`mut_x`) or a class, which
768
+ * `convert-to-readonly` leaves mutable by its type prefix (`MutableStore`).
769
+ */
770
+ const isMutableValueName = (
771
+ name: string,
772
+ options: AppendAsConstTransformerOptionsInternal,
773
+ ): boolean =>
774
+ startsWithAny(options.mutableVariablePrefixes, name) ||
775
+ startsWithAny(options.mutableTypePrefixes, name);
776
+
777
+ /**
778
+ * Whether `typeNode` names a type of a `mutableTypePrefixes` prefix anywhere
779
+ * in it (`MutableFoo`, `ns.MutableFoo[]`, `import('./m').MutableFoo | X`), or
780
+ * the type of a variable of a `mutableVariablePrefixes` prefix or a class of
781
+ * a `mutableTypePrefixes` one (`typeof mut_x`, `typeof MutableStore`). It reads names only, so an alias of another name for such
782
+ * a type is missed.
783
+ */
784
+ const refersToMutableType = (
785
+ // eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
786
+ typeNode: tsm.TypeNode | undefined,
787
+ options: AppendAsConstTransformerOptionsInternal,
788
+ ): boolean => {
789
+ if (typeNode === undefined) {
790
+ return false;
791
+ }
792
+
793
+ const refers = (n: tsm.Node): boolean => {
794
+ if (n.isKind(tsm.SyntaxKind.TypeReference)) {
795
+ return startsWithAny(
796
+ options.mutableTypePrefixes,
797
+ lastNameOf(n.getTypeName()),
798
+ );
799
+ }
800
+
801
+ if (n.isKind(tsm.SyntaxKind.ImportType)) {
802
+ const qualifier = n.getQualifier();
803
+
804
+ return (
805
+ qualifier !== undefined &&
806
+ // typeof import('./m').mut_x names a value
807
+ (n.compilerNode.isTypeOf
808
+ ? isMutableValueName(firstNameOf(qualifier), options)
809
+ : startsWithAny(options.mutableTypePrefixes, lastNameOf(qualifier)))
810
+ );
811
+ }
812
+
813
+ // typeof mut_x, typeof mut_x.items, typeof MutableClass
814
+ return (
815
+ n.isKind(tsm.SyntaxKind.TypeQuery) &&
816
+ isMutableValueName(firstNameOf(n.getExprName()), options)
817
+ );
818
+ };
819
+
820
+ return refers(typeNode) || typeNode.getFirstDescendant(refers) !== undefined;
821
+ };
@@ -1013,7 +1013,25 @@ describe(appendAsConstTransformer, () => {
1013
1013
  ])('$name', testFn);
1014
1014
  });
1015
1015
 
1016
- describe('ignorePrefixes option (mut_ prefix)', () => {
1016
+ describe('mutableVariablePrefixes option (mut_ prefix)', () => {
1017
+ test('the removed ignorePrefixes option is refused', () => {
1018
+ expect(() =>
1019
+ appendAsConstTransformer(
1020
+ // @ts-expect-error removed in 4.0.0
1021
+ { ignorePrefixes: ['mut_'] },
1022
+ ),
1023
+ ).toThrow('mutableVariablePrefixes');
1024
+ });
1025
+
1026
+ test('an unset ignorePrefixes option is accepted', () => {
1027
+ expect(() =>
1028
+ appendAsConstTransformer(
1029
+ // @ts-expect-error removed in 4.0.0
1030
+ { ignorePrefixes: undefined },
1031
+ ),
1032
+ ).not.toThrow();
1033
+ });
1034
+
1017
1035
  test.each([
1018
1036
  {
1019
1037
  name: 'single variable with mut_ prefix',
@@ -1025,6 +1043,66 @@ describe(appendAsConstTransformer, () => {
1025
1043
  source: 'const mut_a = [1, 2], mut_b = { c: 3 };',
1026
1044
  expected: 'const mut_a = [1, 2], mut_b = { c: 3 };',
1027
1045
  },
1046
+ {
1047
+ name: '"draft" and "_mut_" are not default prefixes',
1048
+ source: 'const draft = { a: [1] }, _mut_b = [2];',
1049
+ expected: 'const draft = { a: [1] } as const, _mut_b = [2] as const;',
1050
+ },
1051
+ {
1052
+ name: 'a variable annotated with a mutable type gets no as const',
1053
+ source: dedent`
1054
+ const a: MutableFoo = { b: [1] };
1055
+ const c: ns.MutableFoo | undefined = { b: [1] };
1056
+ const d: readonly MutableFoo[] = [{ b: [1] }];
1057
+ const f: mut_Foo = { b: [1] };
1058
+ const g: import('./m').MutableFoo = { b: [1] };
1059
+ const h = { b: [1] } satisfies MutableFoo;
1060
+ const i = (): MutableFoo => ({ b: [1] });
1061
+ const j: typeof mut_x = [1];
1062
+ const k: typeof mut_y.items = [1];
1063
+ const l: typeof import('./m').mut_z = [1];
1064
+ const m: InstanceType<typeof MutableStore> = { items: [] };
1065
+ const n: InstanceType<typeof import('./m').MutableStore> = { items: [] };
1066
+ const o = ((): MutableFoo => ({ b: [1] }));
1067
+ const p = cond ? ({ b: [1] } satisfies MutableFoo) : q;
1068
+ const e: Foo = { b: [1] };
1069
+ `,
1070
+ expected: dedent`
1071
+ const a: MutableFoo = { b: [1] };
1072
+ const c: ns.MutableFoo | undefined = { b: [1] };
1073
+ const d: readonly MutableFoo[] = [{ b: [1] }];
1074
+ const f: mut_Foo = { b: [1] };
1075
+ const g: import('./m').MutableFoo = { b: [1] };
1076
+ const h = { b: [1] } satisfies MutableFoo;
1077
+ const i = (): MutableFoo => ({ b: [1] });
1078
+ const j: typeof mut_x = [1];
1079
+ const k: typeof mut_y.items = [1];
1080
+ const l: typeof import('./m').mut_z = [1];
1081
+ const m: InstanceType<typeof MutableStore> = { items: [] };
1082
+ const n: InstanceType<typeof import('./m').MutableStore> = { items: [] };
1083
+ const o = ((): MutableFoo => ({ b: [1] }));
1084
+ const p = cond ? ({ b: [1] } satisfies MutableFoo) : q;
1085
+ const e: Foo = { b: [1] } as const;
1086
+ `,
1087
+ },
1088
+ {
1089
+ name: 'mutableTypePrefixes replaces the default',
1090
+ source: dedent`
1091
+ const a: MutableFoo = { b: [1] };
1092
+ const c: MyFoo = { b: [1] };
1093
+ `,
1094
+ expected: dedent`
1095
+ const a: MutableFoo = { b: [1] } as const;
1096
+ const c: MyFoo = { b: [1] };
1097
+ `,
1098
+ options: { mutableTypePrefixes: ['My'] },
1099
+ },
1100
+ {
1101
+ name: 'the prefixes named in the option replace the default',
1102
+ source: 'const draft = { a: [1] }, mut_b = [2];',
1103
+ expected: 'const draft = { a: [1] }, mut_b = [2] as const;',
1104
+ options: { mutableVariablePrefixes: ['draft'] },
1105
+ },
1028
1106
  ])('$name', testFn);
1029
1107
  });
1030
1108