ts-codemod-lib 3.5.0 → 3.5.1
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 +26 -7
- package/dist/functions/ast-transformers/append-as-const.d.mts.map +1 -1
- package/dist/functions/ast-transformers/append-as-const.mjs +3 -0
- package/dist/functions/ast-transformers/append-as-const.mjs.map +1 -1
- package/dist/functions/ast-transformers/convert-to-readonly.d.mts +10 -0
- package/dist/functions/ast-transformers/convert-to-readonly.d.mts.map +1 -1
- package/dist/functions/ast-transformers/convert-to-readonly.mjs +118 -14
- package/dist/functions/ast-transformers/convert-to-readonly.mjs.map +1 -1
- package/dist/functions/ast-transformers/readonly-transformer-helpers/group-union-types.d.mts +7 -1
- package/dist/functions/ast-transformers/readonly-transformer-helpers/group-union-types.d.mts.map +1 -1
- package/dist/functions/ast-transformers/readonly-transformer-helpers/group-union-types.mjs +25 -2
- package/dist/functions/ast-transformers/readonly-transformer-helpers/group-union-types.mjs.map +1 -1
- package/package.json +19 -19
- package/src/functions/ast-transformers/append-as-const.mts +3 -0
- package/src/functions/ast-transformers/convert-to-readonly.mts +182 -20
- package/src/functions/ast-transformers/convert-to-readonly.test.mts +421 -12
- package/src/functions/ast-transformers/readonly-transformer-helpers/group-union-types.mts +36 -5
|
@@ -3926,6 +3926,413 @@ describe(convertToReadonlyTransformer, () => {
|
|
|
3926
3926
|
])('$name', testFn);
|
|
3927
3927
|
});
|
|
3928
3928
|
|
|
3929
|
+
describe('Type literals with a property of an ignored prefix', () => {
|
|
3930
|
+
// `Readonly<T>` would make the ignored property readonly too, so such a
|
|
3931
|
+
// literal is left unwrapped and its other members are marked readonly in
|
|
3932
|
+
// place, as an interface's are.
|
|
3933
|
+
test.each([
|
|
3934
|
+
{
|
|
3935
|
+
name: 'Members other than the ignored one are marked readonly in place',
|
|
3936
|
+
source: dedent`
|
|
3937
|
+
type T = { mut_a: number[]; b: string[]; readonly c: number };
|
|
3938
|
+
`,
|
|
3939
|
+
expected: dedent`
|
|
3940
|
+
type T = { mut_a: number[]; readonly b: readonly string[]; readonly c: number };
|
|
3941
|
+
`,
|
|
3942
|
+
},
|
|
3943
|
+
{
|
|
3944
|
+
name: 'An ignored property already marked readonly is left as written',
|
|
3945
|
+
source: dedent`
|
|
3946
|
+
type T = { readonly mut_a: number[]; b: string[] };
|
|
3947
|
+
`,
|
|
3948
|
+
expected: dedent`
|
|
3949
|
+
type T = { readonly mut_a: number[]; readonly b: readonly string[] };
|
|
3950
|
+
`,
|
|
3951
|
+
},
|
|
3952
|
+
{
|
|
3953
|
+
name: 'A string literal name or a computed name of a literal is recognized',
|
|
3954
|
+
source: dedent`
|
|
3955
|
+
type T = { "mut_a": number[]; ['mut_b']: number[]; [\`mut_c\`]: number[]; d: string[] };
|
|
3956
|
+
`,
|
|
3957
|
+
expected: dedent`
|
|
3958
|
+
type T = { "mut_a": number[]; ['mut_b']: number[]; [\`mut_c\`]: number[]; readonly d: readonly string[] };
|
|
3959
|
+
`,
|
|
3960
|
+
},
|
|
3961
|
+
{
|
|
3962
|
+
name: 'An index signature alongside is marked readonly in place',
|
|
3963
|
+
source: dedent`
|
|
3964
|
+
type T = { mut_a: number[]; [key: string]: unknown };
|
|
3965
|
+
`,
|
|
3966
|
+
expected: dedent`
|
|
3967
|
+
type T = { mut_a: number[]; readonly [key: string]: unknown };
|
|
3968
|
+
`,
|
|
3969
|
+
},
|
|
3970
|
+
{
|
|
3971
|
+
name: 'Every configured prefix is recognized',
|
|
3972
|
+
source: dedent`
|
|
3973
|
+
type T = { draftItems: number[]; b: string[] };
|
|
3974
|
+
type U = { mutable_items: number[]; b: string[] };
|
|
3975
|
+
`,
|
|
3976
|
+
expected: dedent`
|
|
3977
|
+
type T = { draftItems: number[]; readonly b: readonly string[] };
|
|
3978
|
+
type U = { mutable_items: number[]; readonly b: readonly string[] };
|
|
3979
|
+
`,
|
|
3980
|
+
options: { ignorePrefixes: ['draft', 'mutable_'] },
|
|
3981
|
+
},
|
|
3982
|
+
{
|
|
3983
|
+
name: 'The "_mut_" prefix is recognized by default',
|
|
3984
|
+
source: dedent`
|
|
3985
|
+
type T = { _mut_items: number[]; b: string[] };
|
|
3986
|
+
`,
|
|
3987
|
+
expected: dedent`
|
|
3988
|
+
type T = { _mut_items: number[]; readonly b: readonly string[] };
|
|
3989
|
+
`,
|
|
3990
|
+
},
|
|
3991
|
+
{
|
|
3992
|
+
name: 'Ancestors are wrapped with the shallow Readonly only',
|
|
3993
|
+
source: dedent`
|
|
3994
|
+
type T = {
|
|
3995
|
+
a: {
|
|
3996
|
+
b: {
|
|
3997
|
+
mut_c: number[];
|
|
3998
|
+
d: string[];
|
|
3999
|
+
};
|
|
4000
|
+
};
|
|
4001
|
+
};
|
|
4002
|
+
`,
|
|
4003
|
+
expected: dedent`
|
|
4004
|
+
type T = Readonly<{
|
|
4005
|
+
a: Readonly<{
|
|
4006
|
+
b: {
|
|
4007
|
+
mut_c: number[];
|
|
4008
|
+
readonly d: readonly string[];
|
|
4009
|
+
};
|
|
4010
|
+
}>;
|
|
4011
|
+
}>;
|
|
4012
|
+
`,
|
|
4013
|
+
},
|
|
4014
|
+
{
|
|
4015
|
+
name: 'Nested in an interface',
|
|
4016
|
+
source: dedent`
|
|
4017
|
+
interface I {
|
|
4018
|
+
a: { mut_b: number[]; c: string[] };
|
|
4019
|
+
}
|
|
4020
|
+
`,
|
|
4021
|
+
expected: dedent`
|
|
4022
|
+
interface I {
|
|
4023
|
+
readonly a: { mut_b: number[]; readonly c: readonly string[] };
|
|
4024
|
+
}
|
|
4025
|
+
`,
|
|
4026
|
+
},
|
|
4027
|
+
{
|
|
4028
|
+
name: 'As an array element',
|
|
4029
|
+
source: dedent`
|
|
4030
|
+
type T = { mut_a: number[] }[];
|
|
4031
|
+
`,
|
|
4032
|
+
expected: dedent`
|
|
4033
|
+
type T = readonly { mut_a: number[] }[];
|
|
4034
|
+
`,
|
|
4035
|
+
},
|
|
4036
|
+
{
|
|
4037
|
+
name: 'In a union, it is not merged into the Readonly of the others',
|
|
4038
|
+
source: dedent`
|
|
4039
|
+
type T = { mut_a: number[]; b: string } | { c: number[] } | { d: string };
|
|
4040
|
+
`,
|
|
4041
|
+
expected: dedent`
|
|
4042
|
+
type T = { mut_a: number[]; readonly b: string } | Readonly<{ c: readonly number[] } | { d: string }>;
|
|
4043
|
+
`,
|
|
4044
|
+
},
|
|
4045
|
+
{
|
|
4046
|
+
name: 'In an intersection, it is not merged into the Readonly of the others',
|
|
4047
|
+
source: dedent`
|
|
4048
|
+
type T = { mut_a: number[]; b: string } & { c: number[] };
|
|
4049
|
+
`,
|
|
4050
|
+
expected: dedent`
|
|
4051
|
+
type T = { mut_a: number[]; readonly b: string } & Readonly<{ c: readonly number[] }>;
|
|
4052
|
+
`,
|
|
4053
|
+
},
|
|
4054
|
+
{
|
|
4055
|
+
name: 'In an intersection under Readonly, it is taken out',
|
|
4056
|
+
source: dedent`
|
|
4057
|
+
type T = Readonly<{ mut_a: number[]; b: string } & { c: string }>;
|
|
4058
|
+
`,
|
|
4059
|
+
expected: dedent`
|
|
4060
|
+
type T = { mut_a: number[]; readonly b: string } & Readonly<{ c: string }>;
|
|
4061
|
+
`,
|
|
4062
|
+
},
|
|
4063
|
+
{
|
|
4064
|
+
// Such a `Readonly` is also what earlier versions wrote.
|
|
4065
|
+
name: 'A Readonly written around it is taken off',
|
|
4066
|
+
source: dedent`
|
|
4067
|
+
type T = Readonly<{ mut_a: number[]; b: string[] }>;
|
|
4068
|
+
`,
|
|
4069
|
+
expected: dedent`
|
|
4070
|
+
type T = { mut_a: number[]; readonly b: readonly string[] };
|
|
4071
|
+
`,
|
|
4072
|
+
},
|
|
4073
|
+
{
|
|
4074
|
+
name: 'Nested Readonly written around it are taken off',
|
|
4075
|
+
source: dedent`
|
|
4076
|
+
type T = Readonly<Readonly<{ mut_a: number[]; b: string }>>;
|
|
4077
|
+
`,
|
|
4078
|
+
expected: dedent`
|
|
4079
|
+
type T = { mut_a: number[]; readonly b: string };
|
|
4080
|
+
`,
|
|
4081
|
+
},
|
|
4082
|
+
{
|
|
4083
|
+
name: 'A Readonly written around it in a union is taken off',
|
|
4084
|
+
source: dedent`
|
|
4085
|
+
type T = Readonly<{ mut_a: number[]; b: string }> | { c: string };
|
|
4086
|
+
`,
|
|
4087
|
+
expected: dedent`
|
|
4088
|
+
type T = { mut_a: number[]; readonly b: string } | Readonly<{ c: string }>;
|
|
4089
|
+
`,
|
|
4090
|
+
},
|
|
4091
|
+
{
|
|
4092
|
+
name: 'A Readonly written around it under a Readonly union is taken off',
|
|
4093
|
+
source: dedent`
|
|
4094
|
+
type T = Readonly<Readonly<{ mut_a: number[] }> | { c: string }>;
|
|
4095
|
+
`,
|
|
4096
|
+
expected: dedent`
|
|
4097
|
+
type T = { mut_a: number[] } | Readonly<{ c: string }>;
|
|
4098
|
+
`,
|
|
4099
|
+
},
|
|
4100
|
+
{
|
|
4101
|
+
// That member gets no `readonly` of its own, so the wrapper stays.
|
|
4102
|
+
name: 'A member skipped by the ignore comment keeps the Readonly',
|
|
4103
|
+
source: dedent`
|
|
4104
|
+
type T = Readonly<{
|
|
4105
|
+
// transformer-ignore-next-line
|
|
4106
|
+
b: string[];
|
|
4107
|
+
mut_a: number[];
|
|
4108
|
+
}>;
|
|
4109
|
+
`,
|
|
4110
|
+
expected: dedent`
|
|
4111
|
+
type T = Readonly<{
|
|
4112
|
+
// transformer-ignore-next-line
|
|
4113
|
+
b: string[];
|
|
4114
|
+
mut_a: number[];
|
|
4115
|
+
}>;
|
|
4116
|
+
`,
|
|
4117
|
+
},
|
|
4118
|
+
{
|
|
4119
|
+
name: 'A literal skipped by the ignore comment keeps the Readonly',
|
|
4120
|
+
source: dedent`
|
|
4121
|
+
type T = Readonly<
|
|
4122
|
+
// transformer-ignore-next-line
|
|
4123
|
+
{ mut_a: number[]; b: string[] }
|
|
4124
|
+
>;
|
|
4125
|
+
`,
|
|
4126
|
+
expected: dedent`
|
|
4127
|
+
type T = Readonly<
|
|
4128
|
+
// transformer-ignore-next-line
|
|
4129
|
+
{ mut_a: number[]; b: string[] }
|
|
4130
|
+
>;
|
|
4131
|
+
`,
|
|
4132
|
+
},
|
|
4133
|
+
{
|
|
4134
|
+
name: 'A literal skipped by the ignore comment in an intersection under Readonly stays under it',
|
|
4135
|
+
source: dedent`
|
|
4136
|
+
type T = Readonly<
|
|
4137
|
+
{ c: string } &
|
|
4138
|
+
// transformer-ignore-next-line
|
|
4139
|
+
{ mut_a: number[]; b: string[] }
|
|
4140
|
+
>;
|
|
4141
|
+
`,
|
|
4142
|
+
expected: dedent`
|
|
4143
|
+
type T = Readonly<
|
|
4144
|
+
{ c: string } &
|
|
4145
|
+
// transformer-ignore-next-line
|
|
4146
|
+
{ mut_a: number[]; b: string[] }
|
|
4147
|
+
>;
|
|
4148
|
+
`,
|
|
4149
|
+
},
|
|
4150
|
+
{
|
|
4151
|
+
name: 'An ignored property skipped by the ignore comment does not keep the Readonly',
|
|
4152
|
+
source: dedent`
|
|
4153
|
+
type T = {
|
|
4154
|
+
// transformer-ignore-next-line
|
|
4155
|
+
mut_a: number[];
|
|
4156
|
+
b: string[];
|
|
4157
|
+
};
|
|
4158
|
+
`,
|
|
4159
|
+
expected: dedent`
|
|
4160
|
+
type T = {
|
|
4161
|
+
// transformer-ignore-next-line
|
|
4162
|
+
mut_a: number[];
|
|
4163
|
+
readonly b: readonly string[];
|
|
4164
|
+
};
|
|
4165
|
+
`,
|
|
4166
|
+
},
|
|
4167
|
+
{
|
|
4168
|
+
// `Readonly` is what makes a method or an accessor readonly, and
|
|
4169
|
+
// neither takes a `readonly` modifier.
|
|
4170
|
+
name: 'A literal with a method signature or an accessor is wrapped as before',
|
|
4171
|
+
source: dedent`
|
|
4172
|
+
type T = { mut_a: number[]; f(): void; b: string[] };
|
|
4173
|
+
type U = Readonly<{ mut_a: number[]; get g(): string; set g(v: string) }>;
|
|
4174
|
+
`,
|
|
4175
|
+
expected: dedent`
|
|
4176
|
+
type T = Readonly<{ mut_a: number[]; f(): void; b: readonly string[] }>;
|
|
4177
|
+
type U = Readonly<{ mut_a: number[]; get g(): string; set g(v: string) }>;
|
|
4178
|
+
`,
|
|
4179
|
+
},
|
|
4180
|
+
{
|
|
4181
|
+
// `Readonly<A> & { ... }` would make an array `A` readonly.
|
|
4182
|
+
name: 'In an intersection under Readonly beside a type that may be an array, it stays under it',
|
|
4183
|
+
source: dedent`
|
|
4184
|
+
type T<A> = Readonly<A & { mut_a: number[]; b: string }>;
|
|
4185
|
+
`,
|
|
4186
|
+
expected: dedent`
|
|
4187
|
+
type T<A> = Readonly<A & { mut_a: number[]; readonly b: string }>;
|
|
4188
|
+
`,
|
|
4189
|
+
},
|
|
4190
|
+
{
|
|
4191
|
+
name: 'In an intersection under Readonly beside a primitive, it is taken out',
|
|
4192
|
+
source: dedent`
|
|
4193
|
+
type T = Readonly<string & { mut_a: number[]; b: string }>;
|
|
4194
|
+
`,
|
|
4195
|
+
expected: dedent`
|
|
4196
|
+
type T = string & { mut_a: number[]; readonly b: string };
|
|
4197
|
+
`,
|
|
4198
|
+
},
|
|
4199
|
+
{
|
|
4200
|
+
name: 'An accessor or a method of an ignored prefix is left as written',
|
|
4201
|
+
source: dedent`
|
|
4202
|
+
type T = { get mut_x(): number[]; set mut_x(v: number[]); mut_f(a: string[]): void; b: string[] };
|
|
4203
|
+
`,
|
|
4204
|
+
expected: dedent`
|
|
4205
|
+
type T = { get mut_x(): number[]; set mut_x(v: number[]); mut_f(a: string[]): void; readonly b: readonly string[] };
|
|
4206
|
+
`,
|
|
4207
|
+
},
|
|
4208
|
+
{
|
|
4209
|
+
name: 'A comment after the literal is kept when the Readonly is taken off',
|
|
4210
|
+
source: dedent`
|
|
4211
|
+
type T = Readonly<{ mut_a: number[]; b: string } /* note */>;
|
|
4212
|
+
type U = Readonly<{ mut_a: number[] } // note
|
|
4213
|
+
>[];
|
|
4214
|
+
`,
|
|
4215
|
+
expected: dedent`
|
|
4216
|
+
type T = /* note */ { mut_a: number[]; readonly b: string };
|
|
4217
|
+
type U =
|
|
4218
|
+
// note
|
|
4219
|
+
readonly { mut_a: number[] }[];
|
|
4220
|
+
`,
|
|
4221
|
+
},
|
|
4222
|
+
{
|
|
4223
|
+
name: 'A Readonly written around a single-member union of a literal with a call signature is kept',
|
|
4224
|
+
source: dedent`
|
|
4225
|
+
type T = Readonly<| { (): void; mut_a: number[] }>;
|
|
4226
|
+
`,
|
|
4227
|
+
expected: dedent`
|
|
4228
|
+
type T = Readonly<{ (): void; mut_a: number[] }>;
|
|
4229
|
+
`,
|
|
4230
|
+
},
|
|
4231
|
+
{
|
|
4232
|
+
name: 'A Readonly written around a literal with a call signature is kept',
|
|
4233
|
+
source: dedent`
|
|
4234
|
+
type T = Readonly<{ (): void; mut_a: number[]; b: string }>;
|
|
4235
|
+
`,
|
|
4236
|
+
expected: dedent`
|
|
4237
|
+
type T = Readonly<{ (): void; mut_a: number[]; b: string }>;
|
|
4238
|
+
`,
|
|
4239
|
+
},
|
|
4240
|
+
{
|
|
4241
|
+
name: 'A member that needs no wrapper does not keep the Readonly',
|
|
4242
|
+
source: dedent`
|
|
4243
|
+
type T = {
|
|
4244
|
+
mut_a: number[];
|
|
4245
|
+
mut_f(): void;
|
|
4246
|
+
// transformer-ignore-next-line
|
|
4247
|
+
readonly b: string;
|
|
4248
|
+
};
|
|
4249
|
+
`,
|
|
4250
|
+
expected: dedent`
|
|
4251
|
+
type T = {
|
|
4252
|
+
mut_a: number[];
|
|
4253
|
+
mut_f(): void;
|
|
4254
|
+
// transformer-ignore-next-line
|
|
4255
|
+
readonly b: string;
|
|
4256
|
+
};
|
|
4257
|
+
`,
|
|
4258
|
+
},
|
|
4259
|
+
{
|
|
4260
|
+
name: 'A Readonly written around it under an indexed access is taken off',
|
|
4261
|
+
source: dedent`
|
|
4262
|
+
type T = Readonly<{ mut_a: number[]; b: string[] }>['b'];
|
|
4263
|
+
`,
|
|
4264
|
+
expected: dedent`
|
|
4265
|
+
type T = { mut_a: number[]; b: readonly string[] }['b'];
|
|
4266
|
+
`,
|
|
4267
|
+
},
|
|
4268
|
+
{
|
|
4269
|
+
name: 'A DeepReadonly written explicitly is kept',
|
|
4270
|
+
source: dedent`
|
|
4271
|
+
type T = DeepReadonly<{ a: { mut_b: number[]; c: string[] } }>;
|
|
4272
|
+
`,
|
|
4273
|
+
expected: dedent`
|
|
4274
|
+
type T = DeepReadonly<{ a: { mut_b: number[]; c: string[] } }>;
|
|
4275
|
+
`,
|
|
4276
|
+
},
|
|
4277
|
+
{
|
|
4278
|
+
name: 'Under an indexed access',
|
|
4279
|
+
source: dedent`
|
|
4280
|
+
type T = { mut_a: number[]; b: string[] }['b'];
|
|
4281
|
+
`,
|
|
4282
|
+
expected: dedent`
|
|
4283
|
+
type T = { mut_a: number[]; b: readonly string[] }['b'];
|
|
4284
|
+
`,
|
|
4285
|
+
},
|
|
4286
|
+
{
|
|
4287
|
+
name: 'Without the prefix configured, the literal is wrapped',
|
|
4288
|
+
source: dedent`
|
|
4289
|
+
type T = { mut_a: number[]; b: string[] };
|
|
4290
|
+
`,
|
|
4291
|
+
expected: dedent`
|
|
4292
|
+
type T = Readonly<{ mut_a: readonly number[]; b: readonly string[] }>;
|
|
4293
|
+
`,
|
|
4294
|
+
options: { ignorePrefixes: [] },
|
|
4295
|
+
},
|
|
4296
|
+
])('$name', testFn);
|
|
4297
|
+
|
|
4298
|
+
test.each([
|
|
4299
|
+
dedent`
|
|
4300
|
+
type T = {
|
|
4301
|
+
a: { mut_b: number[]; c: string[] } | { d: number[] };
|
|
4302
|
+
mut_e: { f: string[] };
|
|
4303
|
+
};
|
|
4304
|
+
`,
|
|
4305
|
+
'type T = Readonly<{ mut_a: number[]; b: string }> | { c: string };',
|
|
4306
|
+
'type T = Readonly<{ mut_a: number[]; b: string }> & { c: string };',
|
|
4307
|
+
'type T = Readonly<{ mut_a: number[] }> | Readonly<{ c: string }>;',
|
|
4308
|
+
'type T = Readonly<Readonly<{ mut_a: number[] }> | { c: string }>;',
|
|
4309
|
+
'type T = Readonly<{ mut_a: number[]; b: string } & { c: string }>;',
|
|
4310
|
+
'type T = { a: string } | { mut_b: number[] } | { c: string };',
|
|
4311
|
+
'type T<A> = Readonly<A & { mut_a: number[]; b: string }>;',
|
|
4312
|
+
'type T = Readonly<string & { mut_a: number[]; b: string }>;',
|
|
4313
|
+
'type T = Readonly<string[] & { mut_a: number[]; b: string }>;',
|
|
4314
|
+
'type T = Readonly<(() => void) & { mut_a: number[]; b: string }>;',
|
|
4315
|
+
'type T = Readonly<{ mut_a: number[] } & { c: string } & string>;',
|
|
4316
|
+
'type T = Readonly<{ mut_a: number[]; b: string } /* note */>;',
|
|
4317
|
+
'type T = Readonly<{ mut_a: number[] } // note\n>[];',
|
|
4318
|
+
'type T = Readonly<{ mut_a: number[] } /* c */> | { d: string };',
|
|
4319
|
+
'type T = Readonly<\n // transformer-ignore-next-line\n { mut_a: number[]; b: string[] }\n>;',
|
|
4320
|
+
])(
|
|
4321
|
+
'a type literal with an ignored property reaches a fixed point: %s',
|
|
4322
|
+
(code) => {
|
|
4323
|
+
const once = transformSourceCode(code, false, [
|
|
4324
|
+
convertToReadonlyTransformer(),
|
|
4325
|
+
]);
|
|
4326
|
+
|
|
4327
|
+
const twice = transformSourceCode(once, false, [
|
|
4328
|
+
convertToReadonlyTransformer(),
|
|
4329
|
+
]);
|
|
4330
|
+
|
|
4331
|
+
expect(twice).toBe(once);
|
|
4332
|
+
},
|
|
4333
|
+
);
|
|
4334
|
+
});
|
|
4335
|
+
|
|
3929
4336
|
describe('Call and construct signatures', () => {
|
|
3930
4337
|
// `Readonly<T>` is a mapped type over `keyof T`, and a call or construct
|
|
3931
4338
|
// signature is not a property, so wrapping a type literal that carries one
|
|
@@ -4477,10 +4884,10 @@ describe(convertToReadonlyTransformer, () => {
|
|
|
4477
4884
|
type T = { mut_value: number[]; normalValue: string[] }
|
|
4478
4885
|
`,
|
|
4479
4886
|
expected: dedent`
|
|
4480
|
-
type T =
|
|
4887
|
+
type T = {
|
|
4481
4888
|
mut_value: number[];
|
|
4482
|
-
normalValue: readonly string[];
|
|
4483
|
-
}
|
|
4889
|
+
readonly normalValue: readonly string[];
|
|
4890
|
+
};
|
|
4484
4891
|
`,
|
|
4485
4892
|
options: { ignorePrefixes: ['mut_'] },
|
|
4486
4893
|
},
|
|
@@ -4490,11 +4897,11 @@ describe(convertToReadonlyTransformer, () => {
|
|
|
4490
4897
|
type T = { mut_value: number[]; mutable_data: string[]; normalValue: boolean[] };
|
|
4491
4898
|
`,
|
|
4492
4899
|
expected: dedent`
|
|
4493
|
-
type T =
|
|
4900
|
+
type T = {
|
|
4494
4901
|
mut_value: number[];
|
|
4495
4902
|
mutable_data: string[];
|
|
4496
|
-
normalValue: readonly boolean[];
|
|
4497
|
-
}
|
|
4903
|
+
readonly normalValue: readonly boolean[];
|
|
4904
|
+
};
|
|
4498
4905
|
`,
|
|
4499
4906
|
options: { ignorePrefixes: ['mut_', 'mutable_'] },
|
|
4500
4907
|
},
|
|
@@ -4510,10 +4917,10 @@ describe(convertToReadonlyTransformer, () => {
|
|
|
4510
4917
|
`,
|
|
4511
4918
|
expected: dedent`
|
|
4512
4919
|
type T = Readonly<{
|
|
4513
|
-
data:
|
|
4920
|
+
data: {
|
|
4514
4921
|
mut_items: number[];
|
|
4515
|
-
items: readonly string[];
|
|
4516
|
-
}
|
|
4922
|
+
readonly items: readonly string[];
|
|
4923
|
+
};
|
|
4517
4924
|
}>;
|
|
4518
4925
|
`,
|
|
4519
4926
|
options: { ignorePrefixes: ['mut_'] },
|
|
@@ -4566,11 +4973,11 @@ describe(convertToReadonlyTransformer, () => {
|
|
|
4566
4973
|
};
|
|
4567
4974
|
`,
|
|
4568
4975
|
expected: dedent`
|
|
4569
|
-
type T =
|
|
4976
|
+
type T = {
|
|
4570
4977
|
mut_items: {
|
|
4571
4978
|
[key: string]: number[];
|
|
4572
4979
|
};
|
|
4573
|
-
}
|
|
4980
|
+
};
|
|
4574
4981
|
`,
|
|
4575
4982
|
options: { ignorePrefixes: ['mut_'] },
|
|
4576
4983
|
},
|
|
@@ -4721,12 +5128,14 @@ describe(convertToReadonlyTransformer, () => {
|
|
|
4721
5128
|
options: { ignorePrefixes: ['mut_'] },
|
|
4722
5129
|
},
|
|
4723
5130
|
{
|
|
5131
|
+
// The binding pattern itself is not looked at (see the TODO in
|
|
5132
|
+
// `transformNode`); the type literal is left as is for its properties.
|
|
4724
5133
|
name: 'Object binding pattern in variable declaration (not implemented)',
|
|
4725
5134
|
source: dedent`
|
|
4726
5135
|
const { mut_arr, mut_obj }: { mut_arr: string[], mut_obj: number[] } = { mut_arr: [], mut_obj: [] };
|
|
4727
5136
|
`,
|
|
4728
5137
|
expected: dedent`
|
|
4729
|
-
const { mut_arr, mut_obj }:
|
|
5138
|
+
const { mut_arr, mut_obj }: { mut_arr: string[], mut_obj: number[] } = { mut_arr: [], mut_obj: [] };
|
|
4730
5139
|
`,
|
|
4731
5140
|
options: { ignorePrefixes: ['mut_'] },
|
|
4732
5141
|
},
|
|
@@ -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
|
|
147
|
-
// not grouped here: this group is
|
|
148
|
-
// `Readonly<...>`, which such a literal does
|
|
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
|
) {
|