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
@@ -1980,6 +1980,7 @@ describe(convertToReadonlyTransformer, () => {
1980
1980
  }
1981
1981
  }
1982
1982
  `,
1983
+ options: { mutablePropertyPrefixes: ['_mut_'] },
1983
1984
  },
1984
1985
  {
1985
1986
  name: 'Method with "draft" prefix',
@@ -1997,6 +1998,7 @@ describe(convertToReadonlyTransformer, () => {
1997
1998
  }
1998
1999
  }
1999
2000
  `,
2001
+ options: { mutablePropertyPrefixes: ['draft'] },
2000
2002
  },
2001
2003
  {
2002
2004
  name: 'Method without mut_ prefix should be transformed',
@@ -3926,6 +3928,418 @@ describe(convertToReadonlyTransformer, () => {
3926
3928
  ])('$name', testFn);
3927
3929
  });
3928
3930
 
3931
+ describe('Type literals with a property of an ignored prefix', () => {
3932
+ // `Readonly<T>` would make the ignored property readonly too, so such a
3933
+ // literal is left unwrapped and its other members are marked readonly in
3934
+ // place, as an interface's are.
3935
+ test.each([
3936
+ {
3937
+ name: 'Members other than the ignored one are marked readonly in place',
3938
+ source: dedent`
3939
+ type T = { mut_a: number[]; b: string[]; readonly c: number };
3940
+ `,
3941
+ expected: dedent`
3942
+ type T = { mut_a: number[]; readonly b: readonly string[]; readonly c: number };
3943
+ `,
3944
+ },
3945
+ {
3946
+ name: 'An ignored property already marked readonly is left as written',
3947
+ source: dedent`
3948
+ type T = { readonly mut_a: number[]; b: string[] };
3949
+ `,
3950
+ expected: dedent`
3951
+ type T = { readonly mut_a: number[]; readonly b: readonly string[] };
3952
+ `,
3953
+ },
3954
+ {
3955
+ name: 'A string literal name or a computed name of a literal is recognized',
3956
+ source: dedent`
3957
+ type T = { "mut_a": number[]; ['mut_b']: number[]; [\`mut_c\`]: number[]; d: string[] };
3958
+ `,
3959
+ expected: dedent`
3960
+ type T = { "mut_a": number[]; ['mut_b']: number[]; [\`mut_c\`]: number[]; readonly d: readonly string[] };
3961
+ `,
3962
+ },
3963
+ {
3964
+ name: 'An index signature alongside is marked readonly in place',
3965
+ source: dedent`
3966
+ type T = { mut_a: number[]; [key: string]: unknown };
3967
+ `,
3968
+ expected: dedent`
3969
+ type T = { mut_a: number[]; readonly [key: string]: unknown };
3970
+ `,
3971
+ },
3972
+ {
3973
+ name: 'Every configured prefix is recognized',
3974
+ source: dedent`
3975
+ type T = { draftItems: number[]; b: string[] };
3976
+ type U = { mutable_items: number[]; b: string[] };
3977
+ `,
3978
+ expected: dedent`
3979
+ type T = { draftItems: number[]; readonly b: readonly string[] };
3980
+ type U = { mutable_items: number[]; readonly b: readonly string[] };
3981
+ `,
3982
+ options: {
3983
+ mutableVariablePrefixes: ['draft', 'mutable_'],
3984
+ mutablePropertyPrefixes: ['draft', 'mutable_'],
3985
+ },
3986
+ },
3987
+ {
3988
+ name: '"mut_" alone is the default: it covers "#mut_" and no longer "_mut_"',
3989
+ source: dedent`
3990
+ type T = { _mut_items: number[]; b: string[] };
3991
+ class C { #mut_items: number[] = []; c: string[] = []; }
3992
+ `,
3993
+ expected: dedent`
3994
+ type T = Readonly<{ _mut_items: readonly number[]; b: readonly string[] }>;
3995
+ class C { #mut_items: number[] = []; readonly c: readonly string[] = []; }
3996
+ `,
3997
+ },
3998
+ {
3999
+ name: 'Ancestors are wrapped with the shallow Readonly only',
4000
+ source: dedent`
4001
+ type T = {
4002
+ a: {
4003
+ b: {
4004
+ mut_c: number[];
4005
+ d: string[];
4006
+ };
4007
+ };
4008
+ };
4009
+ `,
4010
+ expected: dedent`
4011
+ type T = Readonly<{
4012
+ a: Readonly<{
4013
+ b: {
4014
+ mut_c: number[];
4015
+ readonly d: readonly string[];
4016
+ };
4017
+ }>;
4018
+ }>;
4019
+ `,
4020
+ },
4021
+ {
4022
+ name: 'Nested in an interface',
4023
+ source: dedent`
4024
+ interface I {
4025
+ a: { mut_b: number[]; c: string[] };
4026
+ }
4027
+ `,
4028
+ expected: dedent`
4029
+ interface I {
4030
+ readonly a: { mut_b: number[]; readonly c: readonly string[] };
4031
+ }
4032
+ `,
4033
+ },
4034
+ {
4035
+ name: 'As an array element',
4036
+ source: dedent`
4037
+ type T = { mut_a: number[] }[];
4038
+ `,
4039
+ expected: dedent`
4040
+ type T = readonly { mut_a: number[] }[];
4041
+ `,
4042
+ },
4043
+ {
4044
+ name: 'In a union, it is not merged into the Readonly of the others',
4045
+ source: dedent`
4046
+ type T = { mut_a: number[]; b: string } | { c: number[] } | { d: string };
4047
+ `,
4048
+ expected: dedent`
4049
+ type T = { mut_a: number[]; readonly b: string } | Readonly<{ c: readonly number[] } | { d: string }>;
4050
+ `,
4051
+ },
4052
+ {
4053
+ name: 'In an intersection, it is not merged into the Readonly of the others',
4054
+ source: dedent`
4055
+ type T = { mut_a: number[]; b: string } & { c: number[] };
4056
+ `,
4057
+ expected: dedent`
4058
+ type T = { mut_a: number[]; readonly b: string } & Readonly<{ c: readonly number[] }>;
4059
+ `,
4060
+ },
4061
+ {
4062
+ name: 'In an intersection under Readonly, it is taken out',
4063
+ source: dedent`
4064
+ type T = Readonly<{ mut_a: number[]; b: string } & { c: string }>;
4065
+ `,
4066
+ expected: dedent`
4067
+ type T = { mut_a: number[]; readonly b: string } & Readonly<{ c: string }>;
4068
+ `,
4069
+ },
4070
+ {
4071
+ // Such a `Readonly` is also what earlier versions wrote.
4072
+ name: 'A Readonly written around it is taken off',
4073
+ source: dedent`
4074
+ type T = Readonly<{ mut_a: number[]; b: string[] }>;
4075
+ `,
4076
+ expected: dedent`
4077
+ type T = { mut_a: number[]; readonly b: readonly string[] };
4078
+ `,
4079
+ },
4080
+ {
4081
+ name: 'Nested Readonly written around it are taken off',
4082
+ source: dedent`
4083
+ type T = Readonly<Readonly<{ mut_a: number[]; b: string }>>;
4084
+ `,
4085
+ expected: dedent`
4086
+ type T = { mut_a: number[]; readonly b: string };
4087
+ `,
4088
+ },
4089
+ {
4090
+ name: 'A Readonly written around it in a union is taken off',
4091
+ source: dedent`
4092
+ type T = Readonly<{ mut_a: number[]; b: string }> | { c: string };
4093
+ `,
4094
+ expected: dedent`
4095
+ type T = { mut_a: number[]; readonly b: string } | Readonly<{ c: string }>;
4096
+ `,
4097
+ },
4098
+ {
4099
+ name: 'A Readonly written around it under a Readonly union is taken off',
4100
+ source: dedent`
4101
+ type T = Readonly<Readonly<{ mut_a: number[] }> | { c: string }>;
4102
+ `,
4103
+ expected: dedent`
4104
+ type T = { mut_a: number[] } | Readonly<{ c: string }>;
4105
+ `,
4106
+ },
4107
+ {
4108
+ // That member gets no `readonly` of its own, so the wrapper stays.
4109
+ name: 'A member skipped by the ignore comment keeps the Readonly',
4110
+ source: dedent`
4111
+ type T = Readonly<{
4112
+ // transformer-ignore-next-line
4113
+ b: string[];
4114
+ mut_a: number[];
4115
+ }>;
4116
+ `,
4117
+ expected: dedent`
4118
+ type T = Readonly<{
4119
+ // transformer-ignore-next-line
4120
+ b: string[];
4121
+ mut_a: number[];
4122
+ }>;
4123
+ `,
4124
+ },
4125
+ {
4126
+ name: 'A literal skipped by the ignore comment keeps the Readonly',
4127
+ source: dedent`
4128
+ type T = Readonly<
4129
+ // transformer-ignore-next-line
4130
+ { mut_a: number[]; b: string[] }
4131
+ >;
4132
+ `,
4133
+ expected: dedent`
4134
+ type T = Readonly<
4135
+ // transformer-ignore-next-line
4136
+ { mut_a: number[]; b: string[] }
4137
+ >;
4138
+ `,
4139
+ },
4140
+ {
4141
+ name: 'A literal skipped by the ignore comment in an intersection under Readonly stays under it',
4142
+ source: dedent`
4143
+ type T = Readonly<
4144
+ { c: string } &
4145
+ // transformer-ignore-next-line
4146
+ { mut_a: number[]; b: string[] }
4147
+ >;
4148
+ `,
4149
+ expected: dedent`
4150
+ type T = Readonly<
4151
+ { c: string } &
4152
+ // transformer-ignore-next-line
4153
+ { mut_a: number[]; b: string[] }
4154
+ >;
4155
+ `,
4156
+ },
4157
+ {
4158
+ name: 'An ignored property skipped by the ignore comment does not keep the Readonly',
4159
+ source: dedent`
4160
+ type T = {
4161
+ // transformer-ignore-next-line
4162
+ mut_a: number[];
4163
+ b: string[];
4164
+ };
4165
+ `,
4166
+ expected: dedent`
4167
+ type T = {
4168
+ // transformer-ignore-next-line
4169
+ mut_a: number[];
4170
+ readonly b: readonly string[];
4171
+ };
4172
+ `,
4173
+ },
4174
+ {
4175
+ // `Readonly` is what makes a method or an accessor readonly, and
4176
+ // neither takes a `readonly` modifier.
4177
+ name: 'A literal with a method signature or an accessor is wrapped as before',
4178
+ source: dedent`
4179
+ type T = { mut_a: number[]; f(): void; b: string[] };
4180
+ type U = Readonly<{ mut_a: number[]; get g(): string; set g(v: string) }>;
4181
+ `,
4182
+ expected: dedent`
4183
+ type T = Readonly<{ mut_a: number[]; f(): void; b: readonly string[] }>;
4184
+ type U = Readonly<{ mut_a: number[]; get g(): string; set g(v: string) }>;
4185
+ `,
4186
+ },
4187
+ {
4188
+ // `Readonly<A> & { ... }` would make an array `A` readonly.
4189
+ name: 'In an intersection under Readonly beside a type that may be an array, it stays under it',
4190
+ source: dedent`
4191
+ type T<A> = Readonly<A & { mut_a: number[]; b: string }>;
4192
+ `,
4193
+ expected: dedent`
4194
+ type T<A> = Readonly<A & { mut_a: number[]; readonly b: string }>;
4195
+ `,
4196
+ },
4197
+ {
4198
+ name: 'In an intersection under Readonly beside a primitive, it is taken out',
4199
+ source: dedent`
4200
+ type T = Readonly<string & { mut_a: number[]; b: string }>;
4201
+ `,
4202
+ expected: dedent`
4203
+ type T = string & { mut_a: number[]; readonly b: string };
4204
+ `,
4205
+ },
4206
+ {
4207
+ name: 'An accessor or a method of an ignored prefix is left as written',
4208
+ source: dedent`
4209
+ type T = { get mut_x(): number[]; set mut_x(v: number[]); mut_f(a: string[]): void; b: string[] };
4210
+ `,
4211
+ expected: dedent`
4212
+ type T = { get mut_x(): number[]; set mut_x(v: number[]); mut_f(a: string[]): void; readonly b: readonly string[] };
4213
+ `,
4214
+ },
4215
+ {
4216
+ name: 'A comment after the literal is kept when the Readonly is taken off',
4217
+ source: dedent`
4218
+ type T = Readonly<{ mut_a: number[]; b: string } /* note */>;
4219
+ type U = Readonly<{ mut_a: number[] } // note
4220
+ >[];
4221
+ `,
4222
+ expected: dedent`
4223
+ type T = /* note */ { mut_a: number[]; readonly b: string };
4224
+ type U =
4225
+ // note
4226
+ readonly { mut_a: number[] }[];
4227
+ `,
4228
+ },
4229
+ {
4230
+ name: 'A Readonly written around a single-member union of a literal with a call signature is kept',
4231
+ source: dedent`
4232
+ type T = Readonly<| { (): void; mut_a: number[] }>;
4233
+ `,
4234
+ expected: dedent`
4235
+ type T = Readonly<{ (): void; mut_a: number[] }>;
4236
+ `,
4237
+ },
4238
+ {
4239
+ name: 'A Readonly written around a literal with a call signature is kept',
4240
+ source: dedent`
4241
+ type T = Readonly<{ (): void; mut_a: number[]; b: string }>;
4242
+ `,
4243
+ expected: dedent`
4244
+ type T = Readonly<{ (): void; mut_a: number[]; b: string }>;
4245
+ `,
4246
+ },
4247
+ {
4248
+ name: 'A member that needs no wrapper does not keep the Readonly',
4249
+ source: dedent`
4250
+ type T = {
4251
+ mut_a: number[];
4252
+ mut_f(): void;
4253
+ // transformer-ignore-next-line
4254
+ readonly b: string;
4255
+ };
4256
+ `,
4257
+ expected: dedent`
4258
+ type T = {
4259
+ mut_a: number[];
4260
+ mut_f(): void;
4261
+ // transformer-ignore-next-line
4262
+ readonly b: string;
4263
+ };
4264
+ `,
4265
+ },
4266
+ {
4267
+ name: 'A Readonly written around it under an indexed access is taken off',
4268
+ source: dedent`
4269
+ type T = Readonly<{ mut_a: number[]; b: string[] }>['b'];
4270
+ `,
4271
+ expected: dedent`
4272
+ type T = { mut_a: number[]; b: readonly string[] }['b'];
4273
+ `,
4274
+ },
4275
+ {
4276
+ name: 'A DeepReadonly written explicitly is kept',
4277
+ source: dedent`
4278
+ type T = DeepReadonly<{ a: { mut_b: number[]; c: string[] } }>;
4279
+ `,
4280
+ expected: dedent`
4281
+ type T = DeepReadonly<{ a: { mut_b: number[]; c: string[] } }>;
4282
+ `,
4283
+ },
4284
+ {
4285
+ name: 'Under an indexed access',
4286
+ source: dedent`
4287
+ type T = { mut_a: number[]; b: string[] }['b'];
4288
+ `,
4289
+ expected: dedent`
4290
+ type T = { mut_a: number[]; b: readonly string[] }['b'];
4291
+ `,
4292
+ },
4293
+ {
4294
+ name: 'Without the prefix configured, the literal is wrapped',
4295
+ source: dedent`
4296
+ type T = { mut_a: number[]; b: string[] };
4297
+ `,
4298
+ expected: dedent`
4299
+ type T = Readonly<{ mut_a: readonly number[]; b: readonly string[] }>;
4300
+ `,
4301
+ options: { mutableVariablePrefixes: [], mutablePropertyPrefixes: [] },
4302
+ },
4303
+ ])('$name', testFn);
4304
+
4305
+ test.each([
4306
+ dedent`
4307
+ type T = {
4308
+ a: { mut_b: number[]; c: string[] } | { d: number[] };
4309
+ mut_e: { f: string[] };
4310
+ };
4311
+ `,
4312
+ 'type T = Readonly<{ mut_a: number[]; b: string }> | { c: string };',
4313
+ 'type T = Readonly<{ mut_a: number[]; b: string }> & { c: string };',
4314
+ 'type T = Readonly<{ mut_a: number[] }> | Readonly<{ c: string }>;',
4315
+ 'type T = Readonly<Readonly<{ mut_a: number[] }> | { c: string }>;',
4316
+ 'type T = Readonly<{ mut_a: number[]; b: string } & { c: string }>;',
4317
+ 'type T = { a: string } | { mut_b: number[] } | { c: string };',
4318
+ 'type T<A> = Readonly<A & { mut_a: number[]; b: string }>;',
4319
+ 'type T = Readonly<string & { mut_a: number[]; b: string }>;',
4320
+ 'type T = Readonly<string[] & { mut_a: number[]; b: string }>;',
4321
+ 'type T = Readonly<(() => void) & { mut_a: number[]; b: string }>;',
4322
+ 'type T = Readonly<{ mut_a: number[] } & { c: string } & string>;',
4323
+ 'type T = Readonly<{ mut_a: number[]; b: string } /* note */>;',
4324
+ 'type T = Readonly<{ mut_a: number[] } // note\n>[];',
4325
+ 'type T = Readonly<{ mut_a: number[] } /* c */> | { d: string };',
4326
+ 'type T = Readonly<\n // transformer-ignore-next-line\n { mut_a: number[]; b: string[] }\n>;',
4327
+ ])(
4328
+ 'a type literal with an ignored property reaches a fixed point: %s',
4329
+ (code) => {
4330
+ const once = transformSourceCode(code, false, [
4331
+ convertToReadonlyTransformer(),
4332
+ ]);
4333
+
4334
+ const twice = transformSourceCode(once, false, [
4335
+ convertToReadonlyTransformer(),
4336
+ ]);
4337
+
4338
+ expect(twice).toBe(once);
4339
+ },
4340
+ );
4341
+ });
4342
+
3929
4343
  describe('Call and construct signatures', () => {
3930
4344
  // `Readonly<T>` is a mapped type over `keyof T`, and a call or construct
3931
4345
  // signature is not a property, so wrapping a type literal that carries one
@@ -4442,7 +4856,7 @@ describe(convertToReadonlyTransformer, () => {
4442
4856
  ])('$name', testFn);
4443
4857
  });
4444
4858
 
4445
- describe('ignorePrefixes option', () => {
4859
+ describe('mutableVariablePrefixes and mutablePropertyPrefixes options', () => {
4446
4860
  test.each([
4447
4861
  {
4448
4862
  name: 'Variable declarations',
@@ -4465,10 +4879,10 @@ describe(convertToReadonlyTransformer, () => {
4465
4879
  {
4466
4880
  name: 'Type aliases',
4467
4881
  source: dedent`
4468
- type mut_Foo = number[]
4882
+ type MutableFoo = number[]
4469
4883
  `,
4470
4884
  expected: dedent`
4471
- type mut_Foo = number[]
4885
+ type MutableFoo = number[]
4472
4886
  `,
4473
4887
  },
4474
4888
  {
@@ -4477,12 +4891,15 @@ describe(convertToReadonlyTransformer, () => {
4477
4891
  type T = { mut_value: number[]; normalValue: string[] }
4478
4892
  `,
4479
4893
  expected: dedent`
4480
- type T = Readonly<{
4894
+ type T = {
4481
4895
  mut_value: number[];
4482
- normalValue: readonly string[];
4483
- }>;
4896
+ readonly normalValue: readonly string[];
4897
+ };
4484
4898
  `,
4485
- options: { ignorePrefixes: ['mut_'] },
4899
+ options: {
4900
+ mutableVariablePrefixes: ['mut_'],
4901
+ mutablePropertyPrefixes: ['mut_'],
4902
+ },
4486
4903
  },
4487
4904
  {
4488
4905
  name: 'Multiple ignore prefixes',
@@ -4490,13 +4907,16 @@ describe(convertToReadonlyTransformer, () => {
4490
4907
  type T = { mut_value: number[]; mutable_data: string[]; normalValue: boolean[] };
4491
4908
  `,
4492
4909
  expected: dedent`
4493
- type T = Readonly<{
4910
+ type T = {
4494
4911
  mut_value: number[];
4495
4912
  mutable_data: string[];
4496
- normalValue: readonly boolean[];
4497
- }>;
4913
+ readonly normalValue: readonly boolean[];
4914
+ };
4498
4915
  `,
4499
- options: { ignorePrefixes: ['mut_', 'mutable_'] },
4916
+ options: {
4917
+ mutableVariablePrefixes: ['mut_', 'mutable_'],
4918
+ mutablePropertyPrefixes: ['mut_', 'mutable_'],
4919
+ },
4500
4920
  },
4501
4921
  {
4502
4922
  name: 'Ignore prefix in nested properties',
@@ -4510,13 +4930,16 @@ describe(convertToReadonlyTransformer, () => {
4510
4930
  `,
4511
4931
  expected: dedent`
4512
4932
  type T = Readonly<{
4513
- data: Readonly<{
4933
+ data: {
4514
4934
  mut_items: number[];
4515
- items: readonly string[];
4516
- }>;
4935
+ readonly items: readonly string[];
4936
+ };
4517
4937
  }>;
4518
4938
  `,
4519
- options: { ignorePrefixes: ['mut_'] },
4939
+ options: {
4940
+ mutableVariablePrefixes: ['mut_'],
4941
+ mutablePropertyPrefixes: ['mut_'],
4942
+ },
4520
4943
  },
4521
4944
  {
4522
4945
  name: 'Ignore prefix in interface',
@@ -4532,7 +4955,10 @@ describe(convertToReadonlyTransformer, () => {
4532
4955
  readonly roles: readonly string[];
4533
4956
  }
4534
4957
  `,
4535
- options: { ignorePrefixes: ['mut_'] },
4958
+ options: {
4959
+ mutableVariablePrefixes: ['mut_'],
4960
+ mutablePropertyPrefixes: ['mut_'],
4961
+ },
4536
4962
  },
4537
4963
  {
4538
4964
  name: 'Ignore prefixes for various name node types in interface',
@@ -4554,7 +4980,10 @@ describe(convertToReadonlyTransformer, () => {
4554
4980
  readonly ['mu' + 't_d']: readonly string[]; // ComputedPropertyName
4555
4981
  }
4556
4982
  `,
4557
- options: { ignorePrefixes: ['mut_'] },
4983
+ options: {
4984
+ mutableVariablePrefixes: ['mut_'],
4985
+ mutablePropertyPrefixes: ['mut_'],
4986
+ },
4558
4987
  },
4559
4988
  {
4560
4989
  name: 'Ignore prefix with index signature',
@@ -4566,13 +4995,16 @@ describe(convertToReadonlyTransformer, () => {
4566
4995
  };
4567
4996
  `,
4568
4997
  expected: dedent`
4569
- type T = Readonly<{
4998
+ type T = {
4570
4999
  mut_items: {
4571
5000
  [key: string]: number[];
4572
5001
  };
4573
- }>;
5002
+ };
4574
5003
  `,
4575
- options: { ignorePrefixes: ['mut_'] },
5004
+ options: {
5005
+ mutableVariablePrefixes: ['mut_'],
5006
+ mutablePropertyPrefixes: ['mut_'],
5007
+ },
4576
5008
  },
4577
5009
  {
4578
5010
  name: 'Index signature in ignored property',
@@ -4590,21 +5022,27 @@ describe(convertToReadonlyTransformer, () => {
4590
5022
  };
4591
5023
  }
4592
5024
  `,
4593
- options: { ignorePrefixes: ['mut_'] },
5025
+ options: {
5026
+ mutableVariablePrefixes: ['mut_'],
5027
+ mutablePropertyPrefixes: ['mut_'],
5028
+ },
4594
5029
  },
4595
5030
  {
4596
5031
  name: 'Type alias with ignored prefix',
4597
5032
  source: dedent`
4598
- type mut_Config = {
5033
+ type MutableConfig = {
4599
5034
  items: string[];
4600
5035
  };
4601
5036
  `,
4602
5037
  expected: dedent`
4603
- type mut_Config = {
5038
+ type MutableConfig = {
4604
5039
  items: string[];
4605
5040
  };
4606
5041
  `,
4607
- options: { ignorePrefixes: ['mut_'] },
5042
+ options: {
5043
+ mutableVariablePrefixes: ['mut_'],
5044
+ mutablePropertyPrefixes: ['mut_'],
5045
+ },
4608
5046
  },
4609
5047
  {
4610
5048
  name: 'Function with ignored prefix',
@@ -4614,21 +5052,27 @@ describe(convertToReadonlyTransformer, () => {
4614
5052
  expected: dedent`
4615
5053
  function mut_process(data: string[]): number[] { return []; }
4616
5054
  `,
4617
- options: { ignorePrefixes: ['mut_'] },
5055
+ options: {
5056
+ mutableVariablePrefixes: ['mut_'],
5057
+ mutablePropertyPrefixes: ['mut_'],
5058
+ },
4618
5059
  },
4619
5060
  {
4620
5061
  name: 'Class with ignored prefix',
4621
5062
  source: dedent`
4622
- class mut_Store {
5063
+ class MutableStore {
4623
5064
  items: string[] = [];
4624
5065
  }
4625
5066
  `,
4626
5067
  expected: dedent`
4627
- class mut_Store {
5068
+ class MutableStore {
4628
5069
  items: string[] = [];
4629
5070
  }
4630
5071
  `,
4631
- options: { ignorePrefixes: ['mut_'] },
5072
+ options: {
5073
+ mutableVariablePrefixes: ['mut_'],
5074
+ mutablePropertyPrefixes: ['mut_'],
5075
+ },
4632
5076
  },
4633
5077
  {
4634
5078
  name: 'Variable declaration with ignored prefix',
@@ -4638,37 +5082,46 @@ describe(convertToReadonlyTransformer, () => {
4638
5082
  expected: dedent`
4639
5083
  const mut_items: string[] = []
4640
5084
  `,
4641
- options: { ignorePrefixes: ['mut_'] },
5085
+ options: {
5086
+ mutableVariablePrefixes: ['mut_'],
5087
+ mutablePropertyPrefixes: ['mut_'],
5088
+ },
4642
5089
  },
4643
5090
  {
4644
5091
  name: 'Generic type with ignored prefix',
4645
5092
  source: dedent`
4646
- type mut_List<T> = {
5093
+ type MutableList<T> = {
4647
5094
  items: T[]
4648
5095
  }
4649
5096
  `,
4650
5097
  expected: dedent`
4651
- type mut_List<T> = {
5098
+ type MutableList<T> = {
4652
5099
  items: T[];
4653
5100
  };
4654
5101
  `,
4655
- options: { ignorePrefixes: ['mut_'] },
5102
+ options: {
5103
+ mutableVariablePrefixes: ['mut_'],
5104
+ mutablePropertyPrefixes: ['mut_'],
5105
+ },
4656
5106
  },
4657
5107
  {
4658
5108
  name: 'Union type with ignored prefix members',
4659
5109
  source: dedent`
4660
- type Result = mut_Success | Error;
4661
- type mut_Success = {
5110
+ type Result = MutableSuccess | Error;
5111
+ type MutableSuccess = {
4662
5112
  data: string[]
4663
5113
  };
4664
5114
  `,
4665
5115
  expected: dedent`
4666
- type Result = mut_Success | Error;
4667
- type mut_Success = {
5116
+ type Result = MutableSuccess | Error;
5117
+ type MutableSuccess = {
4668
5118
  data: string[];
4669
5119
  };
4670
5120
  `,
4671
- options: { ignorePrefixes: ['mut_'] },
5121
+ options: {
5122
+ mutableVariablePrefixes: ['mut_'],
5123
+ mutablePropertyPrefixes: ['mut_'],
5124
+ },
4672
5125
  },
4673
5126
  {
4674
5127
  name: 'Property with ignored prefix in class',
@@ -4684,7 +5137,10 @@ describe(convertToReadonlyTransformer, () => {
4684
5137
  readonly items: readonly number[] = [];
4685
5138
  }
4686
5139
  `,
4687
- options: { ignorePrefixes: ['mut_'] },
5140
+ options: {
5141
+ mutableVariablePrefixes: ['mut_'],
5142
+ mutablePropertyPrefixes: ['mut_'],
5143
+ },
4688
5144
  },
4689
5145
  {
4690
5146
  name: 'Method parameter with ignored prefix',
@@ -4698,7 +5154,10 @@ describe(convertToReadonlyTransformer, () => {
4698
5154
  process(mut_data: string[], data: readonly number[]) {}
4699
5155
  }
4700
5156
  `,
4701
- options: { ignorePrefixes: ['mut_'] },
5157
+ options: {
5158
+ mutableVariablePrefixes: ['mut_'],
5159
+ mutablePropertyPrefixes: ['mut_'],
5160
+ },
4702
5161
  },
4703
5162
  {
4704
5163
  name: 'Function expression with ignored prefix',
@@ -4708,7 +5167,10 @@ describe(convertToReadonlyTransformer, () => {
4708
5167
  expected: dedent`
4709
5168
  const fn = function mut_process(data: string[]): number[] { return []; }
4710
5169
  `,
4711
- options: { ignorePrefixes: ['mut_'] },
5170
+ options: {
5171
+ mutableVariablePrefixes: ['mut_'],
5172
+ mutablePropertyPrefixes: ['mut_'],
5173
+ },
4712
5174
  },
4713
5175
  {
4714
5176
  name: 'Array binding pattern in variable declaration (not implemented)',
@@ -4718,17 +5180,25 @@ describe(convertToReadonlyTransformer, () => {
4718
5180
  expected: dedent`
4719
5181
  const [mut_first, mut_second]: readonly [readonly string[], readonly number[]] = [[], []];
4720
5182
  `,
4721
- options: { ignorePrefixes: ['mut_'] },
5183
+ options: {
5184
+ mutableVariablePrefixes: ['mut_'],
5185
+ mutablePropertyPrefixes: ['mut_'],
5186
+ },
4722
5187
  },
4723
5188
  {
5189
+ // The binding pattern itself is not looked at (see the TODO in
5190
+ // `transformNode`); the type literal is left as is for its properties.
4724
5191
  name: 'Object binding pattern in variable declaration (not implemented)',
4725
5192
  source: dedent`
4726
5193
  const { mut_arr, mut_obj }: { mut_arr: string[], mut_obj: number[] } = { mut_arr: [], mut_obj: [] };
4727
5194
  `,
4728
5195
  expected: dedent`
4729
- const { mut_arr, mut_obj }: Readonly<{ mut_arr: string[], mut_obj: number[] }> = { mut_arr: [], mut_obj: [] };
5196
+ const { mut_arr, mut_obj }: { mut_arr: string[], mut_obj: number[] } = { mut_arr: [], mut_obj: [] };
4730
5197
  `,
4731
- options: { ignorePrefixes: ['mut_'] },
5198
+ options: {
5199
+ mutableVariablePrefixes: ['mut_'],
5200
+ mutablePropertyPrefixes: ['mut_'],
5201
+ },
4732
5202
  },
4733
5203
  {
4734
5204
  name: 'Parameter with ignored prefix in class constructor',
@@ -4742,7 +5212,10 @@ describe(convertToReadonlyTransformer, () => {
4742
5212
  constructor(mut_config: string[]) {}
4743
5213
  }
4744
5214
  `,
4745
- options: { ignorePrefixes: ['mut_'] },
5215
+ options: {
5216
+ mutableVariablePrefixes: ['mut_'],
5217
+ mutablePropertyPrefixes: ['mut_'],
5218
+ },
4746
5219
  },
4747
5220
  {
4748
5221
  name: 'Parameter with ignored prefix in type literal method',
@@ -4756,7 +5229,10 @@ describe(convertToReadonlyTransformer, () => {
4756
5229
  process(mut_data: string[]): void;
4757
5230
  }>;
4758
5231
  `,
4759
- options: { ignorePrefixes: ['mut_'] },
5232
+ options: {
5233
+ mutableVariablePrefixes: ['mut_'],
5234
+ mutablePropertyPrefixes: ['mut_'],
5235
+ },
4760
5236
  },
4761
5237
  {
4762
5238
  name: 'Multiple parameters with ignored prefix',
@@ -4770,7 +5246,10 @@ describe(convertToReadonlyTransformer, () => {
4770
5246
  update(mut_id: string, mut_data: object[], data: readonly string[]): void;
4771
5247
  }
4772
5248
  `,
4773
- options: { ignorePrefixes: ['mut_'] },
5249
+ options: {
5250
+ mutableVariablePrefixes: ['mut_'],
5251
+ mutablePropertyPrefixes: ['mut_'],
5252
+ },
4774
5253
  },
4775
5254
  {
4776
5255
  name: 'Arrow function with ignored prefix parameters',
@@ -4780,18 +5259,228 @@ describe(convertToReadonlyTransformer, () => {
4780
5259
  expected: dedent`
4781
5260
  const handler = (mut_event: Event[], event: readonly CustomEvent[]) => {};
4782
5261
  `,
4783
- options: { ignorePrefixes: ['mut_'] },
5262
+ options: {
5263
+ mutableVariablePrefixes: ['mut_'],
5264
+ mutablePropertyPrefixes: ['mut_'],
5265
+ },
4784
5266
  },
4785
5267
  {
4786
- name: 'Arrow function parameters with "draft" prefix (default)',
5268
+ name: '"draft" is not a default prefix',
4787
5269
  source: dedent`
4788
5270
  const handler = (draft: Event[], event: CustomEvent[]) => {};
5271
+ type T = { draft: boolean; items: string[] };
5272
+ `,
5273
+ expected: dedent`
5274
+ const handler = (draft: readonly Event[], event: readonly CustomEvent[]) => {};
5275
+ type T = Readonly<{ draft: boolean; items: readonly string[] }>;
5276
+ `,
5277
+ },
5278
+ {
5279
+ name: '"Mutable" and "mut_" are the default type prefixes',
5280
+ source: dedent`
5281
+ type MutableFoo = { a: string[] };
5282
+ interface MutableBar { a: string[] }
5283
+ class MutableBaz { a: string[] = []; }
5284
+ const Q = class MutableQ { a: string[] = []; };
5285
+ type mut_Qux = { a: string[] };
5286
+ type Foo = { a: MutableFoo[] };
4789
5287
  `,
4790
5288
  expected: dedent`
4791
- const handler = (draft: Event[], event: readonly CustomEvent[]) => {};
5289
+ type MutableFoo = { a: string[] };
5290
+ interface MutableBar { a: string[] }
5291
+ class MutableBaz { a: string[] = []; }
5292
+ const Q = class MutableQ { a: string[] = []; };
5293
+ type mut_Qux = { a: string[] };
5294
+ type Foo = Readonly<{ a: readonly MutableFoo[] }>;
4792
5295
  `,
4793
5296
  },
5297
+ {
5298
+ name: 'Variable and property prefixes are separate',
5299
+ source: dedent`
5300
+ const handler = (draft: Event[]) => {};
5301
+ type T = { draft: boolean; items: string[] };
5302
+ `,
5303
+ expected: dedent`
5304
+ const handler = (draft: Event[]) => {};
5305
+ type T = Readonly<{ draft: boolean; items: readonly string[] }>;
5306
+ `,
5307
+ options: { mutableVariablePrefixes: ['draft'] },
5308
+ },
5309
+ {
5310
+ name: 'Each kind of name is matched against its own list: variables',
5311
+ source: dedent`
5312
+ type mut_T = { a: string[] };
5313
+ interface mut_I { a: string[] }
5314
+ class mut_C { a: string[] = []; }
5315
+ function mut_f(a: string[]): void {}
5316
+ const mut_x: string[] = [];
5317
+ type U = [mut_a: string[], b: string[]];
5318
+ class D { #mut_p: string[] = []; 'mut_q': string[] = []; }
5319
+ type V = { mut_r: string[]; s: string[] };
5320
+ interface J { mut_a: string[]; f(mut_p: string[]): void }
5321
+ type W = { mut_m(): string[]; b: string[] };
5322
+ class E { mut_m(x: string[]): string[] { return []; } get mut_g(): string[] { return []; } }
5323
+ `,
5324
+ expected: dedent`
5325
+ type mut_T = Readonly<{ a: readonly string[] }>;
5326
+ interface mut_I { readonly a: readonly string[] }
5327
+ class mut_C { readonly a: readonly string[] = []; }
5328
+ function mut_f(a: string[]): void {}
5329
+ const mut_x: string[] = [];
5330
+ type U = readonly [mut_a: readonly string[], b: readonly string[]];
5331
+ class D { readonly #mut_p: readonly string[] = []; readonly 'mut_q': readonly string[] = []; }
5332
+ type V = Readonly<{ mut_r: readonly string[]; s: readonly string[] }>;
5333
+ interface J { readonly mut_a: readonly string[]; f(mut_p: string[]): void }
5334
+ type W = Readonly<{ mut_m(): readonly string[]; b: readonly string[] }>;
5335
+ class E { mut_m(x: readonly string[]): readonly string[] { return []; } get mut_g(): readonly string[] { return []; } }
5336
+ `,
5337
+ options: {
5338
+ mutableVariablePrefixes: ['mut_'],
5339
+ mutableTypePrefixes: [],
5340
+ mutablePropertyPrefixes: [],
5341
+ },
5342
+ },
5343
+ {
5344
+ name: 'Each kind of name is matched against its own list: types',
5345
+ source: dedent`
5346
+ type mut_T = { a: string[] };
5347
+ interface mut_I { a: string[] }
5348
+ class mut_C { a: string[] = []; }
5349
+ function mut_f(a: string[]): void {}
5350
+ const mut_x: string[] = [];
5351
+ type U = [mut_a: string[], b: string[]];
5352
+ class D { #mut_p: string[] = []; 'mut_q': string[] = []; }
5353
+ type V = { mut_r: string[]; s: string[] };
5354
+ interface J { mut_a: string[]; f(mut_p: string[]): void }
5355
+ type W = { mut_m(): string[]; b: string[] };
5356
+ class E { mut_m(x: string[]): string[] { return []; } get mut_g(): string[] { return []; } }
5357
+ `,
5358
+ expected: dedent`
5359
+ type mut_T = { a: string[] };
5360
+ interface mut_I { a: string[] }
5361
+ class mut_C { a: string[] = []; }
5362
+ function mut_f(a: readonly string[]): void {}
5363
+ const mut_x: readonly string[] = [];
5364
+ type U = readonly [mut_a: readonly string[], b: readonly string[]];
5365
+ class D { readonly #mut_p: readonly string[] = []; readonly 'mut_q': readonly string[] = []; }
5366
+ type V = Readonly<{ mut_r: readonly string[]; s: readonly string[] }>;
5367
+ interface J { readonly mut_a: readonly string[]; f(mut_p: readonly string[]): void }
5368
+ type W = Readonly<{ mut_m(): readonly string[]; b: readonly string[] }>;
5369
+ class E { mut_m(x: readonly string[]): readonly string[] { return []; } get mut_g(): readonly string[] { return []; } }
5370
+ `,
5371
+ options: {
5372
+ mutableVariablePrefixes: [],
5373
+ mutableTypePrefixes: ['mut_'],
5374
+ mutablePropertyPrefixes: [],
5375
+ },
5376
+ },
5377
+ {
5378
+ name: 'Each kind of name is matched against its own list: properties',
5379
+ source: dedent`
5380
+ type mut_T = { a: string[] };
5381
+ interface mut_I { a: string[] }
5382
+ class mut_C { a: string[] = []; }
5383
+ function mut_f(a: string[]): void {}
5384
+ const mut_x: string[] = [];
5385
+ type U = [mut_a: string[], b: string[]];
5386
+ class D { #mut_p: string[] = []; 'mut_q': string[] = []; }
5387
+ type V = { mut_r: string[]; s: string[] };
5388
+ interface J { mut_a: string[]; f(mut_p: string[]): void }
5389
+ type W = { mut_m(): string[]; b: string[] };
5390
+ class E { mut_m(x: string[]): string[] { return []; } get mut_g(): string[] { return []; } }
5391
+ `,
5392
+ expected: dedent`
5393
+ type mut_T = Readonly<{ a: readonly string[] }>;
5394
+ interface mut_I { readonly a: readonly string[] }
5395
+ class mut_C { readonly a: readonly string[] = []; }
5396
+ function mut_f(a: readonly string[]): void {}
5397
+ const mut_x: readonly string[] = [];
5398
+ type U = readonly [mut_a: string[], b: readonly string[]];
5399
+ class D { #mut_p: string[] = []; 'mut_q': string[] = []; }
5400
+ type V = { mut_r: string[]; readonly s: readonly string[] };
5401
+ interface J { mut_a: string[]; f(mut_p: readonly string[]): void }
5402
+ type W = { mut_m(): string[]; readonly b: readonly string[] };
5403
+ class E { mut_m(x: string[]): string[] { return []; } get mut_g(): string[] { return []; } }
5404
+ `,
5405
+ options: {
5406
+ mutableVariablePrefixes: [],
5407
+ mutableTypePrefixes: [],
5408
+ mutablePropertyPrefixes: ['mut_'],
5409
+ },
5410
+ },
5411
+ {
5412
+ name: 'A property prefix with a "#" covers private names only',
5413
+ source: dedent`
5414
+ class C { #mut_a: string[] = []; mut_b: string[] = []; }
5415
+ `,
5416
+ expected: dedent`
5417
+ class C { #mut_a: string[] = []; readonly mut_b: readonly string[] = []; }
5418
+ `,
5419
+ options: { mutablePropertyPrefixes: ['#mut_'] },
5420
+ },
5421
+ {
5422
+ name: 'A property prefix does not apply to variables',
5423
+ source: dedent`
5424
+ const handler = (draft: Event[]) => {};
5425
+ type T = { draft: boolean; items: string[] };
5426
+ `,
5427
+ expected: dedent`
5428
+ const handler = (draft: readonly Event[]) => {};
5429
+ type T = { draft: boolean; readonly items: readonly string[] };
5430
+ `,
5431
+ options: { mutablePropertyPrefixes: ['draft'] },
5432
+ },
5433
+ {
5434
+ name: 'A constructor parameter property is a property',
5435
+ source: dedent`
5436
+ class C {
5437
+ draftA: string[] = [];
5438
+ constructor(private draftB: string[], readonly draftD: string[], draftC: string[]) {}
5439
+ }
5440
+ `,
5441
+ expected: dedent`
5442
+ class C {
5443
+ draftA: string[] = [];
5444
+ constructor(private draftB: string[], readonly draftD: string[], draftC: readonly string[]) {}
5445
+ }
5446
+ `,
5447
+ options: { mutablePropertyPrefixes: ['draft'] },
5448
+ },
5449
+ {
5450
+ name: 'A constructor parameter that is not a property is a variable',
5451
+ source: dedent`
5452
+ class C {
5453
+ draftA: string[] = [];
5454
+ constructor(private draftB: string[], draftC: string[]) {}
5455
+ }
5456
+ `,
5457
+ expected: dedent`
5458
+ class C {
5459
+ readonly draftA: readonly string[] = [];
5460
+ constructor(private readonly draftB: readonly string[], draftC: string[]) {}
5461
+ }
5462
+ `,
5463
+ options: { mutableVariablePrefixes: ['draft'] },
5464
+ },
4794
5465
  ])('$name', testFn);
5466
+
5467
+ test('the removed ignorePrefixes option is refused', () => {
5468
+ expect(() =>
5469
+ convertToReadonlyTransformer(
5470
+ // @ts-expect-error removed in 4.0.0
5471
+ { ignorePrefixes: ['mut_'] },
5472
+ ),
5473
+ ).toThrow('mutableVariablePrefixes');
5474
+ });
5475
+
5476
+ test('an unset ignorePrefixes option is accepted', () => {
5477
+ expect(() =>
5478
+ convertToReadonlyTransformer(
5479
+ // @ts-expect-error removed in 4.0.0
5480
+ { ignorePrefixes: undefined },
5481
+ ),
5482
+ ).not.toThrow();
5483
+ });
4795
5484
  });
4796
5485
 
4797
5486
  describe('Disable comment handling', () => {