ts-codemod-lib 2.2.1 → 2.2.3

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 (94) hide show
  1. package/dist/cmd/append-as-const.mjs +51 -48
  2. package/dist/cmd/append-as-const.mjs.map +1 -1
  3. package/dist/cmd/convert-interface-to-type.mjs +51 -48
  4. package/dist/cmd/convert-interface-to-type.mjs.map +1 -1
  5. package/dist/cmd/convert-to-readonly.mjs +51 -48
  6. package/dist/cmd/convert-to-readonly.mjs.map +1 -1
  7. package/dist/cmd/replace-any-with-unknown.mjs +51 -48
  8. package/dist/cmd/replace-any-with-unknown.mjs.map +1 -1
  9. package/dist/cmd/replace-record-with-unknown-record.mjs +51 -48
  10. package/dist/cmd/replace-record-with-unknown-record.mjs.map +1 -1
  11. package/dist/cmd/run-transformer-cli.d.mts.map +1 -1
  12. package/dist/cmd/run-transformer-cli.mjs +138 -127
  13. package/dist/cmd/run-transformer-cli.mjs.map +1 -1
  14. package/dist/functions/ast-transformers/append-as-const.d.mts.map +1 -1
  15. package/dist/functions/ast-transformers/append-as-const.mjs +205 -191
  16. package/dist/functions/ast-transformers/append-as-const.mjs.map +1 -1
  17. package/dist/functions/ast-transformers/convert-interface-to-type.mjs +62 -82
  18. package/dist/functions/ast-transformers/convert-interface-to-type.mjs.map +1 -1
  19. package/dist/functions/ast-transformers/convert-to-readonly.d.mts.map +1 -1
  20. package/dist/functions/ast-transformers/convert-to-readonly.mjs +742 -834
  21. package/dist/functions/ast-transformers/convert-to-readonly.mjs.map +1 -1
  22. package/dist/functions/ast-transformers/readonly-transformer-helpers/compare-union-types.d.mts.map +1 -1
  23. package/dist/functions/ast-transformers/readonly-transformer-helpers/compare-union-types.mjs +2 -14
  24. package/dist/functions/ast-transformers/readonly-transformer-helpers/compare-union-types.mjs.map +1 -1
  25. package/dist/functions/ast-transformers/readonly-transformer-helpers/constants.d.mts +2 -1
  26. package/dist/functions/ast-transformers/readonly-transformer-helpers/constants.d.mts.map +1 -1
  27. package/dist/functions/ast-transformers/readonly-transformer-helpers/constants.mjs +8 -8
  28. package/dist/functions/ast-transformers/readonly-transformer-helpers/constants.mjs.map +1 -1
  29. package/dist/functions/ast-transformers/readonly-transformer-helpers/group-union-types.d.mts.map +1 -1
  30. package/dist/functions/ast-transformers/readonly-transformer-helpers/group-union-types.mjs +51 -62
  31. package/dist/functions/ast-transformers/readonly-transformer-helpers/group-union-types.mjs.map +1 -1
  32. package/dist/functions/ast-transformers/readonly-transformer-helpers/readonly-context.d.mts +12 -12
  33. package/dist/functions/ast-transformers/readonly-transformer-helpers/readonly-context.d.mts.map +1 -1
  34. package/dist/functions/ast-transformers/readonly-transformer-helpers/readonly-context.mjs +16 -14
  35. package/dist/functions/ast-transformers/readonly-transformer-helpers/readonly-context.mjs.map +1 -1
  36. package/dist/functions/ast-transformers/replace-any-with-unknown.mjs +25 -27
  37. package/dist/functions/ast-transformers/replace-any-with-unknown.mjs.map +1 -1
  38. package/dist/functions/ast-transformers/replace-record-with-unknown-record.mjs +149 -208
  39. package/dist/functions/ast-transformers/replace-record-with-unknown-record.mjs.map +1 -1
  40. package/dist/functions/ast-transformers/transform-source-code.d.mts.map +1 -1
  41. package/dist/functions/ast-transformers/transform-source-code.mjs +70 -72
  42. package/dist/functions/ast-transformers/transform-source-code.mjs.map +1 -1
  43. package/dist/functions/constants/ignore-comment-text.d.mts +2 -2
  44. package/dist/functions/constants/ignore-comment-text.d.mts.map +1 -1
  45. package/dist/functions/constants/ignore-comment-text.mjs +8 -8
  46. package/dist/functions/constants/ignore-comment-text.mjs.map +1 -1
  47. package/dist/functions/functions/has-disable-next-line-comment.d.mts.map +1 -1
  48. package/dist/functions/functions/has-disable-next-line-comment.mjs +33 -65
  49. package/dist/functions/functions/has-disable-next-line-comment.mjs.map +1 -1
  50. package/dist/functions/functions/is-as-const-node.d.mts +1 -1
  51. package/dist/functions/functions/is-as-const-node.d.mts.map +1 -1
  52. package/dist/functions/functions/is-as-const-node.mjs +15 -23
  53. package/dist/functions/functions/is-as-const-node.mjs.map +1 -1
  54. package/dist/functions/functions/is-atomic-type-node.d.mts.map +1 -1
  55. package/dist/functions/functions/is-atomic-type-node.mjs +22 -36
  56. package/dist/functions/functions/is-atomic-type-node.mjs.map +1 -1
  57. package/dist/functions/functions/is-conditional-type-distribution-guard.d.mts.map +1 -1
  58. package/dist/functions/functions/is-conditional-type-distribution-guard.mjs +9 -33
  59. package/dist/functions/functions/is-conditional-type-distribution-guard.mjs.map +1 -1
  60. package/dist/functions/functions/is-readonly-node.d.mts.map +1 -1
  61. package/dist/functions/functions/is-readonly-node.mjs +11 -21
  62. package/dist/functions/functions/is-readonly-node.mjs.map +1 -1
  63. package/dist/functions/functions/is-spread-parameter-node.d.mts.map +1 -1
  64. package/dist/functions/functions/is-spread-parameter-node.mjs +2 -4
  65. package/dist/functions/functions/is-spread-parameter-node.mjs.map +1 -1
  66. package/dist/functions/functions/remove-parentheses.d.mts.map +1 -1
  67. package/dist/functions/functions/remove-parentheses.mjs +1 -4
  68. package/dist/functions/functions/remove-parentheses.mjs.map +1 -1
  69. package/dist/functions/functions/should-avoid-parentheses-for-readonly.d.mts.map +1 -1
  70. package/dist/functions/functions/should-avoid-parentheses-for-readonly.mjs +7 -24
  71. package/dist/functions/functions/should-avoid-parentheses-for-readonly.mjs.map +1 -1
  72. package/dist/functions/functions/unwrap-readonly.d.mts.map +1 -1
  73. package/dist/functions/functions/unwrap-readonly.mjs +1 -3
  74. package/dist/functions/functions/unwrap-readonly.mjs.map +1 -1
  75. package/dist/functions/functions/wrap-with-parentheses.d.mts.map +1 -1
  76. package/dist/functions/functions/wrap-with-parentheses.mjs +26 -51
  77. package/dist/functions/functions/wrap-with-parentheses.mjs.map +1 -1
  78. package/dist/functions/utils/replace-with-debug.d.mts.map +1 -1
  79. package/dist/functions/utils/replace-with-debug.mjs +2 -2
  80. package/dist/functions/utils/replace-with-debug.mjs.map +1 -1
  81. package/package.json +40 -29
  82. package/src/cmd/append-as-const.mts +3 -3
  83. package/src/cmd/convert-interface-to-type.mts +3 -3
  84. package/src/cmd/convert-to-readonly.mts +3 -3
  85. package/src/cmd/replace-any-with-unknown.mts +3 -3
  86. package/src/cmd/replace-record-with-unknown-record.mts +3 -3
  87. package/src/cmd/run-transformer-cli.mts +21 -17
  88. package/src/functions/ast-transformers/convert-interface-to-type.mts +1 -1
  89. package/src/functions/ast-transformers/convert-to-readonly.mts +64 -35
  90. package/src/functions/ast-transformers/convert-to-readonly.test.mts +67 -1
  91. package/src/functions/ast-transformers/readonly-transformer-helpers/readonly-context.mts +6 -6
  92. package/src/functions/ast-transformers/replace-record-with-unknown-record.mts +4 -4
  93. package/src/functions/functions/has-disable-next-line-comment.mts +5 -5
  94. package/src/functions/functions/wrap-with-parentheses.mts +17 -23
@@ -13,869 +13,777 @@ import { invalidDeepReadonlyTypeName } from './readonly-transformer-helpers/cons
13
13
  import { groupUnionIntersectionTypes } from './readonly-transformer-helpers/group-union-types.mjs';
14
14
  import { nextReadonlyContext } from './readonly-transformer-helpers/readonly-context.mjs';
15
15
 
16
- const TRANSFORMER_NAME = 'convert-to-readonly';
16
+ const TRANSFORMER_NAME = "convert-to-readonly";
17
17
  const convertToReadonlyTransformer = (options) => {
18
- if (options?.DeepReadonly?.typeName !== undefined &&
19
- invalidDeepReadonlyTypeName.has(options.DeepReadonly.typeName)) {
20
- throw new Error(`Invalid DeepReadonly typeName "${options.DeepReadonly.typeName}" passed to convertToReadonlyType`);
21
- }
22
- const DeepReadonlyTypeName = options?.DeepReadonly?.typeName ?? 'DeepReadonly';
23
- const ignorePrefixes = ISet.create(options?.ignorePrefixes ?? ['mut_', '#mut_', '_mut_', 'draft']);
24
- const optionsInternal = {
25
- DeepReadonly: {
26
- typeName: DeepReadonlyTypeName,
27
- applyLevel: 'keep',
28
- },
29
- ignoreEmptyObjectTypes: options?.ignoreEmptyObjectTypes ?? true,
30
- ignoredPrefixes: ignorePrefixes,
31
- debugPrint: options?.debug === true ? console.debug : () => { },
32
- replaceNode: options?.debug === true
33
- ? replaceNodeWithDebugPrint
34
- : (node, newNodeText) => node.replaceWithText(newNodeText),
35
- };
36
- return {
37
- name: TRANSFORMER_NAME,
38
- transform: (sourceAst) => {
39
- for (const node of sourceAst.getChildren()) {
40
- transformNode(node, initialReadonlyContext, optionsInternal);
41
- }
42
- },
43
- };
18
+ if (options?.DeepReadonly?.typeName !== void 0 && invalidDeepReadonlyTypeName.has(options.DeepReadonly.typeName)) {
19
+ throw new Error(
20
+ `Invalid DeepReadonly typeName "${options.DeepReadonly.typeName}" passed to convertToReadonlyType`
21
+ );
22
+ }
23
+ const DeepReadonlyTypeName = options?.DeepReadonly?.typeName ?? "DeepReadonly";
24
+ const ignorePrefixes = ISet.create(
25
+ options?.ignorePrefixes ?? ["mut_", "#mut_", "_mut_", "draft"]
26
+ );
27
+ const optionsInternal = {
28
+ DeepReadonly: {
29
+ typeName: DeepReadonlyTypeName,
30
+ applyLevel: "keep"
31
+ },
32
+ ignoreEmptyObjectTypes: options?.ignoreEmptyObjectTypes ?? true,
33
+ ignoredPrefixes: ignorePrefixes,
34
+ debugPrint: options?.debug === true ? console.debug : () => {
35
+ },
36
+ replaceNode: options?.debug === true ? replaceNodeWithDebugPrint : (node, newNodeText) => node.replaceWithText(newNodeText)
37
+ };
38
+ return {
39
+ name: TRANSFORMER_NAME,
40
+ transform: (sourceAst) => {
41
+ for (const node of sourceAst.getChildren()) {
42
+ transformNode(node, initialReadonlyContext, optionsInternal);
43
+ }
44
+ }
45
+ };
44
46
  };
45
47
  const initialReadonlyContext = {
46
- type: 'none',
47
- indexedAccessDepth: 0,
48
+ type: "none",
49
+ indexedAccessDepth: 0
48
50
  };
49
51
  const transformNode = (node, readonlyContext, options) => {
50
- if (tsm.Node.isTypeNode(node) ||
51
- node.isKind(tsm.SyntaxKind.InterfaceDeclaration) ||
52
- node.isKind(tsm.SyntaxKind.ClassDeclaration)) {
53
- options.debugPrint();
54
- options.debugPrint(`transformNode [${node.getKindName()}] `);
55
- options.debugPrint(node.getFullText());
56
- options.debugPrint({ readonlyContext });
57
- options.debugPrint();
58
- }
59
- if (hasDisableNextLineComment(node, TRANSFORMER_NAME)) {
60
- options.debugPrint('skipped by disable-next-line comment');
61
- return;
62
- }
63
- // check for ignorePrefix
64
- if (node.isKind(tsm.SyntaxKind.VariableDeclaration)) {
65
- const nodeName = node.getName();
66
- if (options.ignoredPrefixes.some((p) => nodeName.startsWith(p))) {
67
- // Skip conversion for variable declarations with ignored prefixes
68
- // Example: const mut_foo: string[] = []; -> remains as is, without appending `as const`
69
- options.debugPrint('skipped variable declaration by ignorePrefixes');
70
- return;
71
- }
72
- // TODO: Support ignoredPrefixes in ArrayBindingPattern
73
- // if (ts.isArrayBindingPattern(nodeName)) {
74
- // // for (const [i, el] of nodeName.elements.entries())
75
- // }
76
- // TODO: Support ignoredPrefixes in ObjectBindingPattern
77
- // if (ts.isObjectBindingPattern(nodeName)) {
78
- // // for (const [i, el] of nodeName.elements.entries())
79
- // }
80
- }
81
- // Skip readonly conversion for function declarations with ignored prefixes
82
- // Example: function mut_foo() {} -> parameters and return types remain as is
83
- if ((node.isKind(tsm.SyntaxKind.FunctionDeclaration) ||
84
- node.isKind(tsm.SyntaxKind.FunctionExpression)) &&
85
- options.ignoredPrefixes.some((p) => node.getName()?.startsWith(p) === true)) {
86
- return;
87
- }
88
- if (node.isKind(tsm.SyntaxKind.Parameter)) {
89
- const nodeName = node.getName();
90
- if (options.ignoredPrefixes.some((p) => nodeName.startsWith(p))) {
91
- // Skip readonly conversion for variable declarations with ignored prefixes
92
- // Example: const mut_foo: string[] = []; -> remains as is, without readonly conversion
93
- return;
94
- }
95
- // TODO: Support ignoredPrefixes in ArrayBindingPattern
96
- // if (ts.isArrayBindingPattern(nodeName)) {
97
- // }
98
- // TODO: Support ignoredPrefixes in ObjectBindingPattern
99
- // if (ts.isObjectBindingPattern(nodeName)) {
100
- // }
101
- }
102
- // Skip readonly conversion for type alias declarations with ignored prefixes
103
- // Example: type mut_Foo = number[]; -> remains as number[] without readonly
104
- if (node.isKind(tsm.SyntaxKind.TypeAliasDeclaration) &&
105
- options.ignoredPrefixes.some((p) => node.getName().startsWith(p))) {
106
- return;
107
- }
108
- if (node.isKind(tsm.SyntaxKind.TypeReference)) {
109
- if (readonlyContext.type === 'readonly') {
110
- throw new Error('Invalid readonlyContext "readonly" passed to TypeReferenceNode');
111
- }
112
- transformTypeReferenceNode(node, readonlyContext, options);
113
- return;
114
- }
115
- if (node.isKind(tsm.SyntaxKind.ArrayType)) {
116
- transformArrayTypeNode(node, readonlyContext, options);
117
- return;
118
- }
119
- if (node.isKind(tsm.SyntaxKind.TupleType)) {
120
- transformTupleTypeNode(node, readonlyContext, options);
121
- return;
122
- }
123
- if (node.isKind(tsm.SyntaxKind.RestType)) {
124
- transformRestTypeNode(node, readonlyContext, options);
125
- return;
126
- }
127
- if (node.isKind(tsm.SyntaxKind.TypeOperator)) {
128
- if (readonlyContext.type === 'readonly') {
129
- throw new Error('Invalid readonlyContext "readonly" passed to TypeOperatorNode');
130
- }
131
- transformTypeOperatorNode(node, readonlyContext, options);
132
- return;
133
- }
134
- if (node.isKind(tsm.SyntaxKind.TypeLiteral)) {
135
- if (readonlyContext.type === 'readonly') {
136
- throw new Error('Invalid readonlyContext "readonly" passed to TypeLiteralNode');
137
- }
138
- transformTypeLiteralNode(node, readonlyContext, options);
139
- return;
140
- }
141
- if (node.isKind(tsm.SyntaxKind.IndexedAccessType)) {
142
- transformIndexedAccessTypeNode(node, readonlyContext, options);
143
- return;
144
- }
145
- if (node.isKind(tsm.SyntaxKind.MappedType)) {
146
- if (readonlyContext.type === 'readonly') {
147
- throw new Error('Invalid readonlyContext "readonly" passed to MappedTypeNode');
148
- }
149
- transformMappedTypeNode(node, readonlyContext, options);
150
- return;
151
- }
152
- if (node.isKind(tsm.SyntaxKind.InterfaceDeclaration)) {
153
- // Skip readonly conversion for interface declarations with ignored prefixes
154
- // Example: interface mut_Interface {...} -> properties remain without readonly
155
- if (options.ignoredPrefixes.some((p) => node.getName().startsWith(p))) {
156
- return;
157
- }
158
- transformInterfaceDeclarationNode(node, options);
159
- return;
160
- }
161
- if (node.isKind(tsm.SyntaxKind.ClassDeclaration)) {
162
- // Skip readonly conversion for class declarations with ignored prefixes
163
- // Example: class mut_Class {...} -> properties remain without readonly
164
- if (options.ignoredPrefixes.some((p) => node.getName()?.startsWith(p) === true)) {
165
- return;
166
- }
167
- transformClassDeclarationNode(node, options);
168
- return;
169
- }
170
- if (node.isKind(tsm.SyntaxKind.IntersectionType)) {
171
- if (readonlyContext.type === 'readonly') {
172
- throw new Error('Invalid readonlyContext "readonly" passed to IntersectionTypeNode');
173
- }
174
- transformIntersectionTypeNode(node, readonlyContext, options);
175
- return;
176
- }
177
- if (node.isKind(tsm.SyntaxKind.UnionType)) {
178
- if (readonlyContext.type === 'readonly') {
179
- throw new Error('Invalid readonlyContext "readonly" passed to UnionTypeNode');
180
- }
181
- transformUnionTypeNode(node, readonlyContext, options);
182
- return;
183
- }
184
- if (node.isKind(tsm.SyntaxKind.ParenthesizedType)) {
185
- // NOTE: readonlyContext を不変のままバケツリレーさせるためにこのケースも必要
186
- transformParenthesizedTypeNode(node, readonlyContext, options);
187
- return;
188
- }
189
- for (const child of node.getChildren()) {
190
- transformNode(child, initialReadonlyContext, options);
191
- }
52
+ if (tsm.Node.isTypeNode(node) || node.isKind(tsm.SyntaxKind.InterfaceDeclaration) || node.isKind(tsm.SyntaxKind.ClassDeclaration)) {
53
+ options.debugPrint();
54
+ options.debugPrint(`transformNode [${node.getKindName()}] `);
55
+ options.debugPrint(node.getFullText());
56
+ options.debugPrint({ readonlyContext });
57
+ options.debugPrint();
58
+ }
59
+ if (hasDisableNextLineComment(node, TRANSFORMER_NAME)) {
60
+ options.debugPrint("skipped by disable-next-line comment");
61
+ return;
62
+ }
63
+ if (node.isKind(tsm.SyntaxKind.VariableDeclaration)) {
64
+ const nodeName = node.getName();
65
+ if (options.ignoredPrefixes.some((p) => nodeName.startsWith(p))) {
66
+ options.debugPrint("skipped variable declaration by ignorePrefixes");
67
+ return;
68
+ }
69
+ }
70
+ if ((node.isKind(tsm.SyntaxKind.FunctionDeclaration) || node.isKind(tsm.SyntaxKind.FunctionExpression)) && options.ignoredPrefixes.some((p) => node.getName()?.startsWith(p) === true)) {
71
+ return;
72
+ }
73
+ if (node.isKind(tsm.SyntaxKind.Parameter)) {
74
+ const nodeName = node.getName();
75
+ if (options.ignoredPrefixes.some((p) => nodeName.startsWith(p))) {
76
+ return;
77
+ }
78
+ }
79
+ if (node.isKind(tsm.SyntaxKind.TypeAliasDeclaration) && options.ignoredPrefixes.some((p) => node.getName().startsWith(p))) {
80
+ return;
81
+ }
82
+ if (node.isKind(tsm.SyntaxKind.TypeReference)) {
83
+ if (readonlyContext.type === "readonly") {
84
+ throw new Error(
85
+ 'Invalid readonlyContext "readonly" passed to TypeReferenceNode'
86
+ );
87
+ }
88
+ transformTypeReferenceNode(node, readonlyContext, options);
89
+ return;
90
+ }
91
+ if (node.isKind(tsm.SyntaxKind.ArrayType)) {
92
+ transformArrayTypeNode(node, readonlyContext, options);
93
+ return;
94
+ }
95
+ if (node.isKind(tsm.SyntaxKind.TupleType)) {
96
+ transformTupleTypeNode(node, readonlyContext, options);
97
+ return;
98
+ }
99
+ if (node.isKind(tsm.SyntaxKind.RestType)) {
100
+ transformRestTypeNode(node, readonlyContext, options);
101
+ return;
102
+ }
103
+ if (node.isKind(tsm.SyntaxKind.TypeOperator)) {
104
+ if (readonlyContext.type === "readonly") {
105
+ throw new Error(
106
+ 'Invalid readonlyContext "readonly" passed to TypeOperatorNode'
107
+ );
108
+ }
109
+ transformTypeOperatorNode(node, readonlyContext, options);
110
+ return;
111
+ }
112
+ if (node.isKind(tsm.SyntaxKind.TypeLiteral)) {
113
+ if (readonlyContext.type === "readonly") {
114
+ throw new Error(
115
+ 'Invalid readonlyContext "readonly" passed to TypeLiteralNode'
116
+ );
117
+ }
118
+ transformTypeLiteralNode(node, readonlyContext, options);
119
+ return;
120
+ }
121
+ if (node.isKind(tsm.SyntaxKind.IndexedAccessType)) {
122
+ transformIndexedAccessTypeNode(node, readonlyContext, options);
123
+ return;
124
+ }
125
+ if (node.isKind(tsm.SyntaxKind.MappedType)) {
126
+ if (readonlyContext.type === "readonly") {
127
+ throw new Error(
128
+ 'Invalid readonlyContext "readonly" passed to MappedTypeNode'
129
+ );
130
+ }
131
+ transformMappedTypeNode(node, readonlyContext, options);
132
+ return;
133
+ }
134
+ if (node.isKind(tsm.SyntaxKind.InterfaceDeclaration)) {
135
+ if (options.ignoredPrefixes.some((p) => node.getName().startsWith(p))) {
136
+ return;
137
+ }
138
+ transformInterfaceDeclarationNode(node, options);
139
+ return;
140
+ }
141
+ if (node.isKind(tsm.SyntaxKind.ClassDeclaration)) {
142
+ if (options.ignoredPrefixes.some(
143
+ (p) => node.getName()?.startsWith(p) === true
144
+ )) {
145
+ return;
146
+ }
147
+ transformClassDeclarationNode(node, options);
148
+ return;
149
+ }
150
+ if (node.isKind(tsm.SyntaxKind.IntersectionType)) {
151
+ if (readonlyContext.type === "readonly") {
152
+ throw new Error(
153
+ 'Invalid readonlyContext "readonly" passed to IntersectionTypeNode'
154
+ );
155
+ }
156
+ transformIntersectionTypeNode(node, readonlyContext, options);
157
+ return;
158
+ }
159
+ if (node.isKind(tsm.SyntaxKind.UnionType)) {
160
+ if (readonlyContext.type === "readonly") {
161
+ throw new Error(
162
+ 'Invalid readonlyContext "readonly" passed to UnionTypeNode'
163
+ );
164
+ }
165
+ transformUnionTypeNode(node, readonlyContext, options);
166
+ return;
167
+ }
168
+ if (node.isKind(tsm.SyntaxKind.ParenthesizedType)) {
169
+ transformParenthesizedTypeNode(node, readonlyContext, options);
170
+ return;
171
+ }
172
+ for (const child of node.getChildren()) {
173
+ transformNode(child, initialReadonlyContext, options);
174
+ }
192
175
  };
193
- //
194
- // Transformer implementation for each node type
195
- //
196
- /**
197
- * - `tr(ReadonlyArray<E>) |-> readonly tr(E)[]`
198
- * - `tr(Readonly<Readonly<E>>) |-> Readonly<tr(E)>`
199
- * - `tr(DeepReadonly<Readonly<E>>) |-> DeepReadonly<tr(E)>`
200
- * - `tr(Readonly<number>) |-> number`
201
- * - `tr(Readonly<E[]>) |-> readonly tr(E)[]`
202
- * - `tr(Readonly<[E1, E2, E3]>) |-> readonly [tr(E1), tr(E2), tr(E3)]`
203
- * - `tr(Readonly<readonly E[]>) |-> readonly tr(E)[]`
204
- * - `tr(Readonly<readonly [E1, E2, E3]>) |-> readonly [tr(E1), tr(E2), tr(E3)]`
205
- * - `tr(Readonly<A | readonly E[]>) |-> Readonly<tr(A)> | readonly tr(E)[]>`
206
- */
207
- const transformTypeReferenceNode = (
208
- // eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
209
- node, readonlyContext, options) => {
210
- const typeNameStr = node.getTypeName().getText();
211
- const typeArguments = node.getTypeArguments();
212
- // Array<T> / ReadonlyArray<T> to readonly T[]
213
- if (typeNameStr === 'Array' || typeNameStr === 'ReadonlyArray') {
214
- if (!Arr.isArrayOfLength(typeArguments, 1)) {
215
- throw new Error(`Unexpected number of type arguments "${typeArguments.length}" for ${typeNameStr}.`);
216
- }
217
- // Recursive processing
218
- transformNode(typeArguments[0], {
219
- type: 'none',
220
- indexedAccessDepth: readonlyContext.indexedAccessDepth,
221
- }, options);
222
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
223
- const T = node.getTypeArguments()[0];
224
- options.replaceNode(node, readonlyContext.type === 'DeepReadonly'
225
- ? wrapWithParentheses(`${wrapWithParentheses(T.getFullText())}[]`)
226
- : wrapWithParentheses(`readonly ${wrapWithParentheses(T.getFullText())}[]`));
227
- return;
228
- }
229
- // Set<T> to ReadonlySet<T>
230
- if (typeNameStr === 'Set') {
231
- if (!Arr.isArrayOfLength(typeArguments, 1)) {
232
- throw new Error(`Unexpected number of type arguments "${typeArguments.length}" for Set.`);
233
- }
234
- // Recursive processing
235
- transformNode(typeArguments[0], {
236
- type: 'none',
237
- indexedAccessDepth: readonlyContext.indexedAccessDepth,
238
- }, options);
239
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
240
- const T = node.getTypeArguments()[0];
241
- options.replaceNode(node, `ReadonlySet<${T.getFullText()}>`);
242
- return;
243
- }
244
- // Map<T> to ReadonlyMap<T>
245
- if (typeNameStr === 'Map') {
246
- if (!Arr.isArrayOfLength(typeArguments, 2)) {
247
- throw new Error(`Unexpected number of type arguments "${typeArguments.length}" for Map.`);
248
- }
249
- const nextReadonlyContextValue = nextReadonlyContext({
250
- currentReadonlyContext: readonlyContext,
251
- nextReadonlyContextType: 'none',
252
- indexedAccessDepthChange: 'keep',
253
- });
254
- // Recursive processing
255
- for (const t of typeArguments) {
256
- transformNode(t, nextReadonlyContextValue, options);
257
- }
258
- const [K, V] = node.getTypeArguments();
259
- options.replaceNode(node,
260
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
261
- `ReadonlyMap<${K.getFullText()}, ${V.getFullText()}>`);
262
- return;
263
- }
264
- // remove unnecessary `Readonly` wrapper or convert to readonly operator
265
- if (typeNameStr === 'Readonly') {
266
- if (!Arr.isArrayOfLength(typeArguments, 1)) {
267
- throw new Error(`Unexpected number of type arguments "${typeArguments.length}" for Readonly.`);
268
- }
269
- // Recursive processing
270
- transformNode(typeArguments[0], nextReadonlyContext({
271
- currentReadonlyContext: readonlyContext,
272
- nextReadonlyContextType: 'Readonly',
273
- indexedAccessDepthChange: 'keep',
274
- }), options);
275
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
276
- const T = removeParentheses(node.getTypeArguments()[0]);
277
- // DeepReadonly<Readonly<T>> -> DeepReadonly<T>
278
- if (readonlyContext.type === 'DeepReadonly') {
279
- options.replaceNode(node, T.getFullText());
280
- return;
281
- }
282
- // Readonly<{ ... }>[I] -> { ... }[I]
283
- if (readonlyContext.indexedAccessDepth > 0) {
284
- options.replaceNode(node, T.getFullText());
285
- return;
286
- }
287
- // Readonly<Readonly<T>> -> Readonly<T>
288
- if (isReadonlyTypeReferenceNode(T)) {
289
- options.replaceNode(node, T.getFullText());
290
- return;
291
- }
292
- // Readonly<number> -> number
293
- if (isAtomicTypeNode(T)) {
294
- options.replaceNode(node, T.getFullText());
295
- return;
296
- }
297
- // T = E[]
298
- // Readonly<E[]> -> readonly E[]
299
- //
300
- // T = [E1, E2, E3]
301
- // Readonly<[E1, E2, E3]> -> readonly [E1, E2, E3]
302
- if (T.isKind(tsm.SyntaxKind.ArrayType) ||
303
- T.isKind(tsm.SyntaxKind.TupleType)) {
304
- const readonlyText = `readonly ${T.getFullText()}`;
305
- options.replaceNode(node, shouldAvoidParenthesesForReadonly(node)
306
- ? readonlyText
307
- : wrapWithParentheses(readonlyText));
308
- return;
309
- }
310
- // T = readonly E[] or readonly [E1, E2, E3]
311
- // Readonly<readonly E[]> -> readonly E[]
312
- // Readonly<readonly [E1, E2, E3]> -> readonly [E1, E2, E3]
313
- if (isReadonlyTupleOrArrayTypeNode(T)) {
314
- options.replaceNode(node, T.getFullText());
315
- return;
316
- }
317
- // T = A | B | C
318
- // T = A & B & C
319
- if (T.isKind(tsm.SyntaxKind.UnionType) ||
320
- T.isKind(tsm.SyntaxKind.IntersectionType)) {
321
- options.replaceNode(node, T.getFullText());
322
- return;
323
- }
324
- return;
325
- }
326
- // DeepReadonly
327
- if (typeNameStr === options.DeepReadonly.typeName) {
328
- if (!Arr.isArrayOfLength(typeArguments, 1)) {
329
- throw new Error(`Unexpected number of type arguments "${typeArguments.length}" for Readonly.`);
330
- }
331
- // Recursive processing
332
- transformNode(typeArguments[0], nextReadonlyContext({
333
- currentReadonlyContext: readonlyContext,
334
- nextReadonlyContextType: 'DeepReadonly',
335
- }), options);
336
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
337
- const T = node.getTypeArguments()[0];
338
- // DeepReadonly<DeepReadonly<T>> -> DeepReadonly<T>
339
- if (readonlyContext.type === 'DeepReadonly') {
340
- options.replaceNode(node, T.getFullText());
341
- return;
342
- }
343
- // Readonly<number> -> number
344
- if (isAtomicTypeNode(T)) {
345
- options.replaceNode(node, T.getFullText());
346
- return;
347
- }
348
- // T = P[]
349
- // DeepReadonly<P[]> -> readonly P[] (for primitive type arrays, convert to readonly P[] form)
350
- // DeepReadonly<O[]> -> DeepReadonly<O[]> (for object type arrays, keep as is to recursively apply Readonly)
351
- // DeepReadonly<P[][]> -> DeepReadonly<P[][]>
352
- if (T.isKind(tsm.SyntaxKind.ArrayType) &&
353
- isAtomicTypeNode(T.getElementTypeNode())) {
354
- const readonlyText = `readonly ${T.getFullText()}`;
355
- options.replaceNode(node, shouldAvoidParenthesesForReadonly(node)
356
- ? readonlyText
357
- : wrapWithParentheses(readonlyText));
358
- return;
359
- }
360
- // T = [P1, P2, P3]
361
- // DeepReadonly<[P1, P2, P3]> -> readonly [P1, P2, P3]
362
- if (T.isKind(tsm.SyntaxKind.TupleType) &&
363
- T.getElements().every(isAtomicTypeNode)) {
364
- const readonlyText = `readonly ${T.getFullText()}`;
365
- options.replaceNode(node, shouldAvoidParenthesesForReadonly(node)
366
- ? readonlyText
367
- : wrapWithParentheses(readonlyText));
368
- return;
369
- }
370
- return;
371
- }
372
- {
373
- const nextReadonlyContextValue = nextReadonlyContext({
374
- currentReadonlyContext: readonlyContext,
375
- nextReadonlyContextType: 'none',
376
- indexedAccessDepthChange: 'keep',
377
- });
378
- // Recursive processing
379
- for (const t of node.getTypeArguments()) {
380
- transformNode(t, nextReadonlyContextValue, options);
381
- }
176
+ const transformTypeReferenceNode = (node, readonlyContext, options) => {
177
+ const typeNameStr = node.getTypeName().getText();
178
+ const typeArguments = node.getTypeArguments();
179
+ if (typeNameStr === "Array" || typeNameStr === "ReadonlyArray") {
180
+ if (!Arr.isArrayOfLength(typeArguments, 1)) {
181
+ throw new Error(
182
+ `Unexpected number of type arguments "${typeArguments.length}" for ${typeNameStr}.`
183
+ );
184
+ }
185
+ transformNode(
186
+ typeArguments[0],
187
+ {
188
+ type: "none",
189
+ indexedAccessDepth: readonlyContext.indexedAccessDepth
190
+ },
191
+ options
192
+ );
193
+ const T = node.getTypeArguments()[0];
194
+ options.replaceNode(
195
+ node,
196
+ readonlyContext.type === "DeepReadonly" ? wrapWithParentheses(`${wrapWithParentheses(T.getFullText())}[]`) : wrapWithParentheses(
197
+ `readonly ${wrapWithParentheses(T.getFullText())}[]`
198
+ )
199
+ );
200
+ return;
201
+ }
202
+ if (typeNameStr === "Set") {
203
+ if (!Arr.isArrayOfLength(typeArguments, 1)) {
204
+ throw new Error(
205
+ `Unexpected number of type arguments "${typeArguments.length}" for Set.`
206
+ );
207
+ }
208
+ transformNode(
209
+ typeArguments[0],
210
+ {
211
+ type: "none",
212
+ indexedAccessDepth: readonlyContext.indexedAccessDepth
213
+ },
214
+ options
215
+ );
216
+ const T = node.getTypeArguments()[0];
217
+ options.replaceNode(node, `ReadonlySet<${T.getFullText()}>`);
218
+ return;
219
+ }
220
+ if (typeNameStr === "Map") {
221
+ if (!Arr.isArrayOfLength(typeArguments, 2)) {
222
+ throw new Error(
223
+ `Unexpected number of type arguments "${typeArguments.length}" for Map.`
224
+ );
382
225
  }
383
- };
384
- /** `tr(E[]) |-> tr(E)[]` */
385
- const transformArrayTypeNode = (
386
- // eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
387
- node, readonlyContext, options) => {
388
- // Recursive processing
389
- transformNode(node.getElementTypeNode(), nextReadonlyContext({
226
+ const nextReadonlyContextValue = nextReadonlyContext({
227
+ currentReadonlyContext: readonlyContext,
228
+ nextReadonlyContextType: "none",
229
+ indexedAccessDepthChange: "keep"
230
+ });
231
+ for (const t of typeArguments) {
232
+ transformNode(t, nextReadonlyContextValue, options);
233
+ }
234
+ const [K, V] = node.getTypeArguments();
235
+ options.replaceNode(
236
+ node,
237
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
238
+ `ReadonlyMap<${K.getFullText()}, ${V.getFullText()}>`
239
+ );
240
+ return;
241
+ }
242
+ if (typeNameStr === "Readonly") {
243
+ if (!Arr.isArrayOfLength(typeArguments, 1)) {
244
+ throw new Error(
245
+ `Unexpected number of type arguments "${typeArguments.length}" for Readonly.`
246
+ );
247
+ }
248
+ transformNode(
249
+ typeArguments[0],
250
+ nextReadonlyContext({
251
+ currentReadonlyContext: readonlyContext,
252
+ nextReadonlyContextType: "Readonly",
253
+ indexedAccessDepthChange: "keep"
254
+ }),
255
+ options
256
+ );
257
+ const T = removeParentheses(node.getTypeArguments()[0]);
258
+ if (readonlyContext.type === "DeepReadonly") {
259
+ options.replaceNode(node, T.getFullText());
260
+ return;
261
+ }
262
+ if (readonlyContext.indexedAccessDepth > 0) {
263
+ options.replaceNode(node, T.getFullText());
264
+ return;
265
+ }
266
+ if (isReadonlyTypeReferenceNode(T)) {
267
+ options.replaceNode(node, T.getFullText());
268
+ return;
269
+ }
270
+ if (isAtomicTypeNode(T)) {
271
+ options.replaceNode(node, T.getFullText());
272
+ return;
273
+ }
274
+ if (T.isKind(tsm.SyntaxKind.ArrayType) || T.isKind(tsm.SyntaxKind.TupleType)) {
275
+ const readonlyText = `readonly ${T.getFullText()}`;
276
+ options.replaceNode(
277
+ node,
278
+ shouldAvoidParenthesesForReadonly(node) ? readonlyText : wrapWithParentheses(readonlyText)
279
+ );
280
+ return;
281
+ }
282
+ if (isReadonlyTupleOrArrayTypeNode(T)) {
283
+ options.replaceNode(node, T.getFullText());
284
+ return;
285
+ }
286
+ if (T.isKind(tsm.SyntaxKind.UnionType) || T.isKind(tsm.SyntaxKind.IntersectionType)) {
287
+ options.replaceNode(node, T.getFullText());
288
+ return;
289
+ }
290
+ return;
291
+ }
292
+ if (typeNameStr === options.DeepReadonly.typeName) {
293
+ if (!Arr.isArrayOfLength(typeArguments, 1)) {
294
+ throw new Error(
295
+ `Unexpected number of type arguments "${typeArguments.length}" for Readonly.`
296
+ );
297
+ }
298
+ transformNode(
299
+ typeArguments[0],
300
+ nextReadonlyContext({
390
301
  currentReadonlyContext: readonlyContext,
391
- nextReadonlyContextType: 'none',
392
- indexedAccessDepthChange: 'decr',
393
- }), options);
394
- switch (readonlyContext.type) {
395
- case 'DeepReadonly':
396
- case 'readonly':
397
- return; // remain tr(E)[] as is
398
- // Unnecessary `Readonly` wrapper will be remove in transformTypeReferenceNode
399
- case 'Readonly':
400
- case 'none':
401
- // Skip the `A[] extends B[] ? ...` union-distribution-guard idiom: adding
402
- // `readonly` there does not improve immutability and breaks the idiom.
403
- if (readonlyContext.indexedAccessDepth === 0 &&
404
- !isConditionalTypeDistributionGuard(node)) {
405
- const readonlyText = `readonly ${node.getFullText()}`;
406
- options.replaceNode(node, shouldAvoidParenthesesForReadonly(node)
407
- ? readonlyText
408
- : wrapWithParentheses(readonlyText));
409
- }
302
+ nextReadonlyContextType: "DeepReadonly"
303
+ }),
304
+ options
305
+ );
306
+ const T = node.getTypeArguments()[0];
307
+ if (readonlyContext.type === "DeepReadonly") {
308
+ options.replaceNode(node, T.getFullText());
309
+ return;
310
+ }
311
+ if (isAtomicTypeNode(T)) {
312
+ options.replaceNode(node, T.getFullText());
313
+ return;
314
+ }
315
+ if (T.isKind(tsm.SyntaxKind.ArrayType) && isAtomicTypeNode(T.getElementTypeNode())) {
316
+ const readonlyText = `readonly ${T.getFullText()}`;
317
+ options.replaceNode(
318
+ node,
319
+ shouldAvoidParenthesesForReadonly(node) ? readonlyText : wrapWithParentheses(readonlyText)
320
+ );
321
+ return;
322
+ }
323
+ if (T.isKind(tsm.SyntaxKind.TupleType) && T.getElements().every(isAtomicTypeNode)) {
324
+ const readonlyText = `readonly ${T.getFullText()}`;
325
+ options.replaceNode(
326
+ node,
327
+ shouldAvoidParenthesesForReadonly(node) ? readonlyText : wrapWithParentheses(readonlyText)
328
+ );
329
+ return;
330
+ }
331
+ return;
332
+ }
333
+ {
334
+ const nextReadonlyContextValue = nextReadonlyContext({
335
+ currentReadonlyContext: readonlyContext,
336
+ nextReadonlyContextType: "none",
337
+ indexedAccessDepthChange: "keep"
338
+ });
339
+ for (const t of node.getTypeArguments()) {
340
+ transformNode(t, nextReadonlyContextValue, options);
410
341
  }
342
+ }
411
343
  };
412
- /** `tr([E1, E2, E3])` |-> `[tr(E1), tr(E2), tr(E3)]` */
413
- const transformTupleTypeNode = (
414
- // eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
415
- node, readonlyContext, options) => {
416
- // Recursive processing
417
- {
418
- const nextReadonlyContextValue = nextReadonlyContext({
419
- currentReadonlyContext: readonlyContext,
420
- nextReadonlyContextType: 'none',
421
- indexedAccessDepthChange: 'decr',
422
- });
423
- for (const el of node.getElements()) {
424
- if (el.isKind(tsm.SyntaxKind.NamedTupleMember)) {
425
- if (options.ignoredPrefixes.every((p) => !el.getName().startsWith(p))) {
426
- transformNode(el.getTypeNode(), nextReadonlyContextValue, options);
427
- }
428
- }
429
- else {
430
- transformNode(el, nextReadonlyContextValue, options);
431
- }
432
- }
433
- }
434
- switch (readonlyContext.type) {
435
- case 'DeepReadonly':
436
- case 'readonly':
437
- return; // remain tr(E)[] as is
438
- // Unnecessary `Readonly` wrapper will be remove in transformTypeReferenceNode
439
- case 'Readonly':
440
- case 'none':
441
- // Skip the `[A] extends [B] ? ...` union-distribution-guard idiom: adding
442
- // `readonly` there does not improve immutability and breaks the idiom.
443
- if (readonlyContext.indexedAccessDepth === 0 &&
444
- !isConditionalTypeDistributionGuard(node)) {
445
- const readonlyText = `readonly ${node.getFullText()}`;
446
- options.replaceNode(node, shouldAvoidParenthesesForReadonly(node)
447
- ? readonlyText
448
- : wrapWithParentheses(readonlyText));
449
- }
450
- }
344
+ const transformArrayTypeNode = (node, readonlyContext, options) => {
345
+ transformNode(
346
+ node.getElementTypeNode(),
347
+ nextReadonlyContext({
348
+ currentReadonlyContext: readonlyContext,
349
+ nextReadonlyContextType: "none",
350
+ indexedAccessDepthChange: "decr"
351
+ }),
352
+ options
353
+ );
354
+ switch (readonlyContext.type) {
355
+ case "DeepReadonly":
356
+ case "readonly":
357
+ return;
358
+ // remain tr(E)[] as is
359
+ // Unnecessary `Readonly` wrapper will be remove in transformTypeReferenceNode
360
+ case "Readonly":
361
+ case "none":
362
+ if (readonlyContext.indexedAccessDepth === 0 && !isConditionalTypeDistributionGuard(node)) {
363
+ const readonlyText = `readonly ${node.getFullText()}`;
364
+ options.replaceNode(
365
+ node,
366
+ shouldAvoidParenthesesForReadonly(node) ? readonlyText : wrapWithParentheses(readonlyText)
367
+ );
368
+ }
369
+ }
451
370
  };
452
- /** `tr("...T")` |-> `...tr(T)` */
453
- const transformRestTypeNode = (
454
- // eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
455
- node, readonlyContext, options) => {
456
- // Recursive processing
457
- transformNode(node.getTypeNode() /* = T */, nextReadonlyContext({
458
- currentReadonlyContext: readonlyContext,
459
- nextReadonlyContextType: 'none',
460
- indexedAccessDepthChange: 'decr',
461
- }), options);
462
- const R = removeParentheses(node.getTypeNode());
463
- // `tr("...readonly E[]") |-> ...tr(E)[]`
464
- // `tr("...readonly [E1, E2]") |-> ...[tr(E1), tr(E2)]`
465
- if (isReadonlyArrayTypeNode(R) || isReadonlyTupleTypeNode(R)) {
466
- options.replaceNode(node, `...${R.getTypeNode().getFullText()}` /* = tr(E)[] or [tr(E1), tr(E2)] */);
467
- }
371
+ const transformTupleTypeNode = (node, readonlyContext, options) => {
372
+ {
373
+ const nextReadonlyContextValue = nextReadonlyContext({
374
+ currentReadonlyContext: readonlyContext,
375
+ nextReadonlyContextType: "none",
376
+ indexedAccessDepthChange: "decr"
377
+ });
378
+ for (const el of node.getElements()) {
379
+ if (el.isKind(tsm.SyntaxKind.NamedTupleMember)) {
380
+ if (options.ignoredPrefixes.every((p) => !el.getName().startsWith(p))) {
381
+ transformNode(el.getTypeNode(), nextReadonlyContextValue, options);
382
+ }
383
+ } else {
384
+ transformNode(el, nextReadonlyContextValue, options);
385
+ }
386
+ }
387
+ }
388
+ switch (readonlyContext.type) {
389
+ case "DeepReadonly":
390
+ case "readonly":
391
+ return;
392
+ // remain tr(E)[] as is
393
+ // Unnecessary `Readonly` wrapper will be remove in transformTypeReferenceNode
394
+ case "Readonly":
395
+ case "none":
396
+ if (readonlyContext.indexedAccessDepth === 0 && !isConditionalTypeDistributionGuard(node)) {
397
+ const readonlyText = `readonly ${node.getFullText()}`;
398
+ options.replaceNode(
399
+ node,
400
+ shouldAvoidParenthesesForReadonly(node) ? readonlyText : wrapWithParentheses(readonlyText)
401
+ );
402
+ }
403
+ }
468
404
  };
469
- /**
470
- * - `readonly T[][] |-> readonly (readonly T[])[]`
471
- * - `keyof { a: number[] } |-> keyof Readonly<{ a: readonly number[] }>`
472
- */
473
- const transformTypeOperatorNode = (
474
- // eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
475
- node, readonlyContext, options) => {
476
- // Recursive processing
477
- transformNode(node.getTypeNode(), nextReadonlyContext({
478
- currentReadonlyContext: readonlyContext,
479
- nextReadonlyContextType: node.getOperator() === tsm.SyntaxKind.ReadonlyKeyword
480
- ? 'readonly'
481
- : 'none',
482
- indexedAccessDepthChange: 'decr',
483
- }), options);
484
- switch (readonlyContext.type) {
485
- // DeepReadonly<readonly E[]> -> DeepReadonly<E[]>
486
- case 'DeepReadonly':
487
- options.replaceNode(node, node.getTypeNode().getFullText());
488
- return;
489
- case 'Readonly':
490
- case 'none':
491
- if (readonlyContext.indexedAccessDepth > 0) {
492
- options.replaceNode(node, node.getTypeNode().getFullText());
493
- }
494
- }
405
+ const transformRestTypeNode = (node, readonlyContext, options) => {
406
+ transformNode(
407
+ node.getTypeNode(),
408
+ nextReadonlyContext({
409
+ currentReadonlyContext: readonlyContext,
410
+ nextReadonlyContextType: "none",
411
+ indexedAccessDepthChange: "decr"
412
+ }),
413
+ options
414
+ );
415
+ const R = removeParentheses(node.getTypeNode());
416
+ if (isReadonlyArrayTypeNode(R) || isReadonlyTupleTypeNode(R)) {
417
+ options.replaceNode(
418
+ node,
419
+ `...${R.getTypeNode().getFullText()}`
420
+ );
421
+ }
495
422
  };
496
- // `{ readonly member: V } |-> Readonly<{ member: V }>`
497
- const transformTypeLiteralNode = (
498
- // eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
499
- node, readonlyContext, options) => {
500
- if (options.ignoreEmptyObjectTypes &&
501
- Arr.isArrayOfLength(node.getMembers(), 0)) {
502
- return;
503
- }
504
- // Recursive processing
505
- transformMembers(node.getMembers(), 'remove', nextReadonlyContext({
506
- currentReadonlyContext: readonlyContext,
507
- nextReadonlyContextType: 'none',
508
- indexedAccessDepthChange: 'decr',
509
- }), options);
510
- switch (readonlyContext.type) {
511
- case 'DeepReadonly':
512
- case 'Readonly':
513
- // Don't wrap with Readonly if already readonly
514
- return;
515
- case 'none': {
516
- if (readonlyContext.indexedAccessDepth === 0) {
517
- // `{ readonly x: X, readonly y: Y } |-> Readonly<{ x: X, y: Y }>`
518
- options.replaceNode(node, `Readonly<${node.getFullText()}>`);
519
- }
520
- }
521
- }
423
+ const transformTypeOperatorNode = (node, readonlyContext, options) => {
424
+ transformNode(
425
+ node.getTypeNode(),
426
+ nextReadonlyContext({
427
+ currentReadonlyContext: readonlyContext,
428
+ nextReadonlyContextType: node.getOperator() === tsm.SyntaxKind.ReadonlyKeyword ? "readonly" : "none",
429
+ indexedAccessDepthChange: "decr"
430
+ }),
431
+ options
432
+ );
433
+ switch (readonlyContext.type) {
434
+ // DeepReadonly<readonly E[]> -> DeepReadonly<E[]>
435
+ case "DeepReadonly":
436
+ options.replaceNode(node, node.getTypeNode().getFullText());
437
+ return;
438
+ case "Readonly":
439
+ case "none":
440
+ if (readonlyContext.indexedAccessDepth > 0) {
441
+ options.replaceNode(node, node.getTypeNode().getFullText());
442
+ }
443
+ }
522
444
  };
523
- /** `tr([A, B, C][I])` |-> `[tr(A), tr(B), tr(C)][I]` */
524
- const transformIndexedAccessTypeNode = (
525
- // eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
526
- node, readonlyContext, options) => {
527
- // Recursive processing
528
- transformNode(node.getObjectTypeNode(), nextReadonlyContext({
529
- currentReadonlyContext: readonlyContext,
530
- nextReadonlyContextType: 'none',
531
- indexedAccessDepthChange: 'incr',
532
- }), options);
533
- transformNode(node.getIndexTypeNode() /* = I */, nextReadonlyContext({
534
- currentReadonlyContext: readonlyContext,
535
- nextReadonlyContextType: 'Readonly',
536
- indexedAccessDepthChange: 'decr',
537
- }), options);
445
+ const transformTypeLiteralNode = (node, readonlyContext, options) => {
446
+ if (options.ignoreEmptyObjectTypes && Arr.isArrayOfLength(node.getMembers(), 0)) {
447
+ return;
448
+ }
449
+ transformMembers(
450
+ node.getMembers(),
451
+ "remove",
452
+ nextReadonlyContext({
453
+ currentReadonlyContext: readonlyContext,
454
+ nextReadonlyContextType: "none",
455
+ indexedAccessDepthChange: "decr"
456
+ }),
457
+ options
458
+ );
459
+ switch (readonlyContext.type) {
460
+ case "DeepReadonly":
461
+ case "Readonly":
462
+ return;
463
+ case "none": {
464
+ if (readonlyContext.indexedAccessDepth === 0) {
465
+ options.replaceNode(node, `Readonly<${node.getFullText()}>`);
466
+ }
467
+ }
468
+ }
538
469
  };
539
- /**
540
- * - `{ [key in Obj]: V }` -> `Readonly<{ [key in Obj]: V }>`
541
- * - `{ -readonly [key in Obj]: V }` -> `Readonly<{ [key in Obj]: V }>`
542
- * - `{ readonly [key in Obj]: V }` -> `Readonly<{ [key in Obj]: V }>`
543
- * - `{ +readonly [key in Obj]: V }` -> `Readonly<{ [key in Obj]: V }>`
544
- */
545
- const transformMappedTypeNode = (
546
- // eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
547
- node, readonlyContext, options) => {
548
- const typeNode = node.getTypeNode();
549
- if (typeNode !== undefined) {
550
- transformNode(typeNode, nextReadonlyContext({
551
- currentReadonlyContext: readonlyContext,
552
- nextReadonlyContextType: 'none',
553
- indexedAccessDepthChange: 'decr',
554
- }), options);
555
- }
556
- const questionTokenText = pipe(node.getQuestionToken()?.getText()).mapNullable((t) => t === '?' ? '?' : t === '-' ? '-?' : t === '+' ? '+?' : undefined).value ?? '';
557
- const result = pipe(
470
+ const transformIndexedAccessTypeNode = (node, readonlyContext, options) => {
471
+ transformNode(
472
+ node.getObjectTypeNode(),
473
+ nextReadonlyContext({
474
+ currentReadonlyContext: readonlyContext,
475
+ nextReadonlyContextType: "none",
476
+ indexedAccessDepthChange: "incr"
477
+ }),
478
+ options
479
+ );
480
+ transformNode(
481
+ node.getIndexTypeNode(),
482
+ nextReadonlyContext({
483
+ currentReadonlyContext: readonlyContext,
484
+ nextReadonlyContextType: "Readonly",
485
+ indexedAccessDepthChange: "decr"
486
+ }),
487
+ options
488
+ );
489
+ };
490
+ const transformMappedTypeNode = (node, readonlyContext, options) => {
491
+ const typeNode = node.getTypeNode();
492
+ if (typeNode !== void 0) {
493
+ transformNode(
494
+ typeNode,
495
+ nextReadonlyContext({
496
+ currentReadonlyContext: readonlyContext,
497
+ nextReadonlyContextType: "none",
498
+ indexedAccessDepthChange: "decr"
499
+ }),
500
+ options
501
+ );
502
+ }
503
+ const questionTokenText = pipe(node.getQuestionToken()?.getText()).mapNullable(
504
+ (t) => t === "?" ? "?" : t === "-" ? "-?" : t === "+" ? "+?" : void 0
505
+ ).value ?? "";
506
+ const result = pipe(
558
507
  // remove readonlyToken
559
- `{ [${node.getTypeParameter().getFullText()}]${questionTokenText}: ${node.getTypeNode()?.getFullText()} }`).map((mpt) => match(readonlyContext.type, {
560
- // Don't wrap with Readonly if already readonly or unnecessary
561
- DeepReadonly: mpt,
562
- Readonly: mpt,
563
- none: readonlyContext.indexedAccessDepth > 0 ? mpt : `Readonly<${mpt}>`,
564
- })).value;
565
- options.replaceNode(node, result);
508
+ `{ [${node.getTypeParameter().getFullText()}]${questionTokenText}: ${node.getTypeNode()?.getFullText()} }`
509
+ ).map(
510
+ (mpt) => match(readonlyContext.type, {
511
+ // Don't wrap with Readonly if already readonly or unnecessary
512
+ DeepReadonly: mpt,
513
+ Readonly: mpt,
514
+ none: readonlyContext.indexedAccessDepth > 0 ? mpt : `Readonly<${mpt}>`
515
+ })
516
+ ).value;
517
+ options.replaceNode(node, result);
566
518
  };
567
- // Making interface members readonly
568
- const transformInterfaceDeclarationNode = (
569
- // eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
570
- node, options) => {
571
- for (const param of node.getTypeParameters()) {
572
- transformNode(param, initialReadonlyContext, options);
573
- }
574
- for (const n of node.getHeritageClauses()) {
575
- transformNode(n, initialReadonlyContext, options);
576
- }
577
- transformMembers(node.getMembers(), 'add', initialReadonlyContext, options);
519
+ const transformInterfaceDeclarationNode = (node, options) => {
520
+ for (const param of node.getTypeParameters()) {
521
+ transformNode(param, initialReadonlyContext, options);
522
+ }
523
+ for (const n of node.getHeritageClauses()) {
524
+ transformNode(n, initialReadonlyContext, options);
525
+ }
526
+ transformMembers(node.getMembers(), "add", initialReadonlyContext, options);
578
527
  };
579
- const transformClassDeclarationNode = (
580
- // eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
581
- node, options) => {
582
- for (const n of node.getTypeParameters()) {
583
- transformNode(n, initialReadonlyContext, options);
584
- }
585
- for (const n of node.getHeritageClauses()) {
586
- transformNode(n, initialReadonlyContext, options);
587
- }
588
- for (const mb of node.getMembers()) {
589
- if (hasDisableNextLineComment(mb)) {
590
- options.debugPrint('skipped member by disable-next-line comment');
591
- continue;
592
- }
593
- if (mb.isKind(tsm.SyntaxKind.PropertyDeclaration)) {
594
- if (!checkIfPropertyNameShouldBeIgnored(mb.getNameNode(), options)) {
595
- mb.setIsReadonly(true);
596
- const type = mb.getTypeNode();
597
- if (type !== undefined) {
598
- transformNode(type, initialReadonlyContext, options);
599
- }
600
- const initializer = mb.getInitializer();
601
- if (initializer !== undefined) {
602
- transformNode(initializer, initialReadonlyContext, options);
603
- }
604
- }
605
- continue;
606
- }
607
- // MethodDeclaration, GetAccessorDeclaration, SetAccessorDeclaration
608
- // Check if method name has ignored prefix (e.g., mut_)
609
- if ((mb.isKind(tsm.SyntaxKind.MethodDeclaration) ||
610
- mb.isKind(tsm.SyntaxKind.GetAccessor) ||
611
- mb.isKind(tsm.SyntaxKind.SetAccessor)) &&
612
- checkIfPropertyNameShouldBeIgnored(mb.getNameNode(), options)) {
613
- options.debugPrint('skipped method by ignored prefix');
614
- continue;
615
- }
616
- transformNode(mb, initialReadonlyContext, options);
617
- }
618
- for (const mb of node.getChildrenOfKind(tsm.SyntaxKind.IndexSignature)) {
619
- transformIndexSignatureDeclaration(mb, 'add', initialReadonlyContext, options);
620
- }
621
- for (const ctor of node.getConstructors()) {
622
- for (const param of ctor.getParameters()) {
623
- if (hasDisableNextLineComment(param)) {
624
- options.debugPrint('skipped class constructor parameter by disable-next-line comment');
625
- continue;
626
- }
627
- {
628
- const name = param.getName();
629
- if (options.ignoredPrefixes.some((p) => name.startsWith(p))) {
630
- continue;
631
- }
632
- }
633
- // Check if parameter is a property declaration (public/protected/private)
634
- const scope = param.getScope();
635
- // public -> public readonly
636
- // protected -> protected readonly
637
- // private -> private readonly
638
- if (scope === tsm.Scope.Public ||
639
- scope === tsm.Scope.Protected ||
640
- scope === tsm.Scope.Private) {
641
- param.setIsReadonly(true);
642
- }
643
- const type = param.getTypeNode();
644
- if (type !== undefined) {
645
- transformNode(type, initialReadonlyContext, options);
646
- }
647
- }
648
- const body = ctor.getBody();
649
- if (body !== undefined) {
650
- transformNode(body, initialReadonlyContext, options);
651
- }
652
- continue;
653
- }
528
+ const transformClassDeclarationNode = (node, options) => {
529
+ for (const n of node.getTypeParameters()) {
530
+ transformNode(n, initialReadonlyContext, options);
531
+ }
532
+ for (const n of node.getHeritageClauses()) {
533
+ transformNode(n, initialReadonlyContext, options);
534
+ }
535
+ for (const mb of node.getMembers()) {
536
+ if (hasDisableNextLineComment(mb)) {
537
+ options.debugPrint("skipped member by disable-next-line comment");
538
+ continue;
539
+ }
540
+ if (mb.isKind(tsm.SyntaxKind.PropertyDeclaration)) {
541
+ if (!checkIfPropertyNameShouldBeIgnored(mb.getNameNode(), options)) {
542
+ mb.setIsReadonly(true);
543
+ const type = mb.getTypeNode();
544
+ if (type !== void 0) {
545
+ transformNode(type, initialReadonlyContext, options);
546
+ }
547
+ const initializer = mb.getInitializer();
548
+ if (initializer !== void 0) {
549
+ transformNode(initializer, initialReadonlyContext, options);
550
+ }
551
+ }
552
+ continue;
553
+ }
554
+ if ((mb.isKind(tsm.SyntaxKind.MethodDeclaration) || mb.isKind(tsm.SyntaxKind.GetAccessor) || mb.isKind(tsm.SyntaxKind.SetAccessor)) && checkIfPropertyNameShouldBeIgnored(mb.getNameNode(), options)) {
555
+ options.debugPrint("skipped method by ignored prefix");
556
+ continue;
557
+ }
558
+ transformNode(mb, initialReadonlyContext, options);
559
+ }
560
+ for (const mb of node.getChildrenOfKind(tsm.SyntaxKind.IndexSignature)) {
561
+ transformIndexSignatureDeclaration(
562
+ mb,
563
+ "add",
564
+ initialReadonlyContext,
565
+ options
566
+ );
567
+ }
568
+ for (const ctor of node.getConstructors()) {
569
+ for (const param of ctor.getParameters()) {
570
+ transformConstructorParameter(param, options);
571
+ }
572
+ const body = ctor.getBody();
573
+ if (body !== void 0) {
574
+ transformNode(body, initialReadonlyContext, options);
575
+ }
576
+ }
654
577
  };
655
- /**
656
- * - `tr(A & B) -> tr(A) & tr(B)`
657
- * - `tr(Readonly<A> & Readonly<B>) -> Readonly<tr(A) & tr(B)>`
658
- */
659
- const transformIntersectionTypeNode = (
660
- // eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
661
- node, readonlyContext, options) => {
662
- transformUnionOrIntersectionTypeNodeImpl(node, readonlyContext, options, '&');
578
+ const transformConstructorParameter = (param, options) => {
579
+ if (hasDisableNextLineComment(param)) {
580
+ options.debugPrint(
581
+ "skipped class constructor parameter by disable-next-line comment"
582
+ );
583
+ return;
584
+ }
585
+ const name = param.getName();
586
+ if (options.ignoredPrefixes.some((p) => name.startsWith(p))) {
587
+ return;
588
+ }
589
+ const scope = param.getScope();
590
+ if (scope === tsm.Scope.Public || scope === tsm.Scope.Protected || scope === tsm.Scope.Private) {
591
+ param.setIsReadonly(true);
592
+ }
593
+ const type = param.getTypeNode();
594
+ if (type !== void 0) {
595
+ transformNode(type, initialReadonlyContext, options);
596
+ }
663
597
  };
664
- /**
665
- * - `tr(A | B) |-> tr(A) | tr(B)`
666
- * - `tr(Readonly<A> | Readonly<B>) |-> Readonly<tr(A) | tr(B)>`
667
- */
668
- const transformUnionTypeNode = (
669
- // eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
670
- node, readonlyContext, options) => {
671
- transformUnionOrIntersectionTypeNodeImpl(node, readonlyContext, options, '|');
598
+ const transformIntersectionTypeNode = (node, readonlyContext, options) => {
599
+ transformUnionOrIntersectionTypeNodeImpl(node, readonlyContext, options, "&");
672
600
  };
673
- const transformUnionOrIntersectionTypeNodeImpl = (
674
- // eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
675
- node, readonlyContext, options, operator) => {
676
- const nextReadonlyContextValue = nextReadonlyContext({
677
- currentReadonlyContext: readonlyContext,
678
- nextReadonlyContextType: 'none',
679
- indexedAccessDepthChange: 'keep',
680
- });
681
- // Recursive processing
682
- for (const n of node.getTypeNodes() /* = [A, B] */) {
683
- transformNode(n, nextReadonlyContextValue, options);
684
- }
685
- const newTypes = node.getTypeNodes();
686
- const { primitives, arraysAndTuples, typeLiterals, others } = groupUnionIntersectionTypes(newTypes);
687
- options.debugPrint({ primitives, arraysAndTuples, typeLiterals, others });
688
- // Separate empty type literals from non-empty ones when ignoreEmptyObjectTypes is true
689
- const isEmptyTypeLiteral = (
690
- // eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
691
- n) => n.isKind(tsm.SyntaxKind.TypeLiteral) &&
692
- Arr.isArrayOfLength(n.getMembers(), 0);
693
- const nonEmptyTypeLiterals = typeLiterals === undefined
694
- ? undefined
695
- : options.ignoreEmptyObjectTypes
696
- ? typeLiterals.nodes.filter((n) => !isEmptyTypeLiteral(n))
697
- : typeLiterals.nodes;
698
- const emptyTypeLiterals = typeLiterals === undefined || !options.ignoreEmptyObjectTypes
699
- ? undefined
700
- : typeLiterals.nodes.filter((n) => isEmptyTypeLiteral(n));
701
- const typeLiteralsWrappedWithReadonly = nonEmptyTypeLiterals === undefined ||
702
- Arr.isArrayOfLength(nonEmptyTypeLiterals, 0)
703
- ? []
704
- : [
705
- unionToString({
706
- types: nonEmptyTypeLiterals.map((n) => isReadonlyTypeReferenceNode(n)
707
- ? // NOTE: Readonly<A & B> -> (A & B)
708
- unwrapReadonlyTypeArgText(n)
709
- : n.getFullText()),
710
- op: operator,
711
- wrapWithReadonly: nextReadonlyContextValue.type !== 'DeepReadonly' &&
712
- nextReadonlyContextValue.indexedAccessDepth === 0,
713
- }),
714
- ];
715
- // Sort by first occurrence (preserving the original union order as much as possible)
716
- const sorted = [
717
- mapNullable(primitives, (a) => [a.nodes.map((n) => n.getFullText()), a.firstPosition]),
718
- mapNullable(arraysAndTuples, (a) => [a.nodes.map((n) => n.getFullText()), a.firstPosition]),
719
- nonEmptyTypeLiterals !== undefined && Arr.isNonEmpty(nonEmptyTypeLiterals)
720
- ? mapNullable(typeLiterals, (a) => [typeLiteralsWrappedWithReadonly, a.firstPosition])
721
- : undefined,
722
- emptyTypeLiterals !== undefined && Arr.isNonEmpty(emptyTypeLiterals)
723
- ? mapNullable(typeLiterals, (a) => [
724
- emptyTypeLiterals.map((n) => n.getFullText()),
725
- // Use a position after non-empty type literals if they exist
726
- a.firstPosition +
727
- (nonEmptyTypeLiterals !== undefined &&
728
- Arr.isNonEmpty(nonEmptyTypeLiterals)
729
- ? 0.5
730
- : 0),
731
- ])
732
- : undefined,
733
- mapNullable(others, (a) => [
734
- a.nodes.map((n) => wrapWithParentheses(n.getFullText())),
735
- a.firstPosition,
736
- ]),
737
- ]
738
- .filter((x) => x !== undefined)
739
- .toSorted((a, b) => a[1] - b[1])
740
- .flatMap((a) => a[0]);
741
- // Readonly<number & { x: X } & { y: Y } & readonly E[]>
742
- // -> number & readonly E[] & Readonly<{ x: X } & { y: Y }>
743
- // Readonly<number | { x: X } | { y: Y } | readonly E[]>
744
- // -> number | readonly E[] | Readonly<{ x: X } | { y: Y }>
745
- options.replaceNode(node, unionToString({ types: sorted, op: operator, wrapWithReadonly: false }));
601
+ const transformUnionTypeNode = (node, readonlyContext, options) => {
602
+ transformUnionOrIntersectionTypeNodeImpl(node, readonlyContext, options, "|");
746
603
  };
747
- const unionToString = ({ types, op, wrapWithReadonly, }) => Arr.isArrayOfLength(types, 0)
748
- ? 'never'
749
- : Arr.isArrayOfLength(types, 1)
750
- ? wrapWithReadonly === false
751
- ? wrapWithParentheses(types[0])
752
- : `${isString(wrapWithReadonly) ? wrapWithReadonly : 'Readonly'}<${types[0]}>`
753
- : wrapWithReadonly === false
754
- ? wrapWithParentheses(types.join(` ${op} `))
755
- : `${isString(wrapWithReadonly) ? wrapWithReadonly : 'Readonly'}<${types.join(` ${op} `)}>`;
756
- /** Convert ((T)) -> (T) recursively */
757
- const transformParenthesizedTypeNode = (
758
- // eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
759
- node, readonlyContext, options) => {
760
- const typeNode = node.getTypeNode();
761
- if (typeNode.isKind(tsm.SyntaxKind.ParenthesizedType)) {
762
- // Recursive processing
763
- transformParenthesizedTypeNode(typeNode, readonlyContext, options);
764
- }
765
- transformNode(node.getTypeNode(), readonlyContext, options);
766
- const T = node.getTypeNode();
767
- if (
604
+ const transformUnionOrIntersectionTypeNodeImpl = (node, readonlyContext, options, operator) => {
605
+ const nextReadonlyContextValue = nextReadonlyContext({
606
+ currentReadonlyContext: readonlyContext,
607
+ nextReadonlyContextType: "none",
608
+ indexedAccessDepthChange: "keep"
609
+ });
610
+ for (const n of node.getTypeNodes()) {
611
+ transformNode(n, nextReadonlyContextValue, options);
612
+ }
613
+ const newTypes = node.getTypeNodes();
614
+ const { primitives, arraysAndTuples, typeLiterals, others } = groupUnionIntersectionTypes(newTypes);
615
+ options.debugPrint({ primitives, arraysAndTuples, typeLiterals, others });
616
+ const isEmptyTypeLiteral = (n) => n.isKind(tsm.SyntaxKind.TypeLiteral) && Arr.isArrayOfLength(n.getMembers(), 0);
617
+ const nonEmptyTypeLiterals = typeLiterals === void 0 ? void 0 : options.ignoreEmptyObjectTypes ? typeLiterals.nodes.filter((n) => !isEmptyTypeLiteral(n)) : typeLiterals.nodes;
618
+ const emptyTypeLiterals = typeLiterals === void 0 || !options.ignoreEmptyObjectTypes ? void 0 : typeLiterals.nodes.filter((n) => isEmptyTypeLiteral(n));
619
+ const typeLiteralsWrappedWithReadonly = nonEmptyTypeLiterals === void 0 || Arr.isArrayOfLength(nonEmptyTypeLiterals, 0) ? [] : [
620
+ unionToString({
621
+ types: nonEmptyTypeLiterals.map(
622
+ (n) => isReadonlyTypeReferenceNode(n) ? (
623
+ // NOTE: Readonly<A & B> -> (A & B)
624
+ unwrapReadonlyTypeArgText(n)
625
+ ) : n.getFullText()
626
+ ),
627
+ op: operator,
628
+ wrapWithReadonly: nextReadonlyContextValue.type !== "DeepReadonly" && nextReadonlyContextValue.indexedAccessDepth === 0
629
+ })
630
+ ];
631
+ const sorted = [
632
+ mapNullable(
633
+ primitives,
634
+ (a) => [a.nodes.map((n) => n.getFullText()), a.firstPosition]
635
+ ),
636
+ mapNullable(
637
+ arraysAndTuples,
638
+ (a) => [a.nodes.map((n) => n.getFullText()), a.firstPosition]
639
+ ),
640
+ nonEmptyTypeLiterals !== void 0 && Arr.isNonEmpty(nonEmptyTypeLiterals) ? mapNullable(
641
+ typeLiterals,
642
+ (a) => [typeLiteralsWrappedWithReadonly, a.firstPosition]
643
+ ) : void 0,
644
+ emptyTypeLiterals !== void 0 && Arr.isNonEmpty(emptyTypeLiterals) ? mapNullable(
645
+ typeLiterals,
646
+ (a) => [
647
+ emptyTypeLiterals.map((n) => n.getFullText()),
648
+ // Use a position after non-empty type literals if they exist
649
+ a.firstPosition + (nonEmptyTypeLiterals !== void 0 && Arr.isNonEmpty(nonEmptyTypeLiterals) ? 0.5 : 0)
650
+ ]
651
+ ) : void 0,
652
+ mapNullable(
653
+ others,
654
+ (a) => [
655
+ a.nodes.map((n) => wrapWithParentheses(n.getFullText())),
656
+ a.firstPosition
657
+ ]
658
+ )
659
+ ].filter((x) => x !== void 0).toSorted((a, b) => a[1] - b[1]).flatMap((a) => a[0]);
660
+ options.replaceNode(
661
+ node,
662
+ unionToString({ types: sorted, op: operator, wrapWithReadonly: false })
663
+ );
664
+ };
665
+ const unionToString = ({
666
+ types,
667
+ op,
668
+ wrapWithReadonly
669
+ }) => Arr.isArrayOfLength(types, 0) ? "never" : Arr.isArrayOfLength(types, 1) ? wrapWithReadonly === false ? wrapWithParentheses(types[0]) : `${isString(wrapWithReadonly) ? wrapWithReadonly : "Readonly"}<${types[0]}>` : wrapWithReadonly === false ? wrapWithParentheses(types.join(` ${op} `)) : `${isString(wrapWithReadonly) ? wrapWithReadonly : "Readonly"}<${types.join(` ${op} `)}>`;
670
+ const transformParenthesizedTypeNode = (node, readonlyContext, options) => {
671
+ const typeNode = node.getTypeNode();
672
+ if (typeNode.isKind(tsm.SyntaxKind.ParenthesizedType)) {
673
+ transformParenthesizedTypeNode(typeNode, readonlyContext, options);
674
+ }
675
+ transformNode(node.getTypeNode(), readonlyContext, options);
676
+ const T = node.getTypeNode();
677
+ if (
768
678
  // remove () if T is TypeReferenceNode
769
679
  // e.g. `(Readonly<A>) |-> Readonly<A>`
770
- T.isKind(tsm.SyntaxKind.TypeReference) ||
771
- // remove () if T is ArrayTypeNode
772
- // e.g. `(A[]) |-> A[]`
773
- T.isKind(tsm.SyntaxKind.ArrayType) ||
774
- // remove () if T is TupleTypeNode
775
- // e.g. `([A]) |-> [A]`
776
- T.isKind(tsm.SyntaxKind.TupleType) ||
777
- // remove () if T is PrimitiveTypeNode
778
- // e.g. `(number) |-> number`
779
- isAtomicTypeNode(T) ||
780
- // remove () if T is TypeLiteralNode
781
- // e.g. `({ member: V }) |-> { member: V }`
782
- T.isKind(tsm.SyntaxKind.TypeLiteral)) {
783
- options.replaceNode(node, T.getFullText());
784
- }
680
+ T.isKind(tsm.SyntaxKind.TypeReference) || // remove () if T is ArrayTypeNode
681
+ // e.g. `(A[]) |-> A[]`
682
+ T.isKind(tsm.SyntaxKind.ArrayType) || // remove () if T is TupleTypeNode
683
+ // e.g. `([A]) |-> [A]`
684
+ T.isKind(tsm.SyntaxKind.TupleType) || // remove () if T is PrimitiveTypeNode
685
+ // e.g. `(number) |-> number`
686
+ isAtomicTypeNode(T) || // remove () if T is TypeLiteralNode
687
+ // e.g. `({ member: V }) |-> { member: V }`
688
+ T.isKind(tsm.SyntaxKind.TypeLiteral) || // collapse redundant nested parentheses if T is itself a ParenthesizedType
689
+ // e.g. `((X)) |-> (X)`
690
+ //
691
+ // This is what makes the transform terminate on nested parentheses. The
692
+ // union/intersection handler defensively wraps its result in parentheses
693
+ // for precedence safety, which turns `(A | B)` into `((A | B))` when the
694
+ // union already sits inside a `ParenthesizedType`. Collapsing the redundant
695
+ // outer layer here means each pass removes exactly the layer the wrapping
696
+ // added, so the parentheses no longer grow without bound (which previously
697
+ // overflowed the parser stack on inputs like `type T = (('a' | 'b'))`).
698
+ T.isKind(tsm.SyntaxKind.ParenthesizedType)
699
+ ) {
700
+ options.replaceNode(node, T.getFullText());
701
+ }
785
702
  };
786
- /**
787
- * `tr(["member1: V1", "member2: V2", "member3: V3"])`
788
- *
789
- * -> `["member1: tr(V1)", "member2: tr(V2)", "member3: tr(V3)"]`
790
- */
791
- const transformMembers = (
792
- // eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
793
- members, readonlyModifier, readonlyContext, options) => {
794
- for (const mb of members) {
795
- if (hasDisableNextLineComment(mb)) {
796
- options.debugPrint('skipped by member disable-next-line comment');
797
- continue;
798
- }
799
- if (mb.isKind(tsm.SyntaxKind.PropertySignature)) {
800
- if (!checkIfPropertyNameShouldBeIgnored(mb.getNameNode(), options)) {
801
- transformPropertySignature(mb, readonlyModifier, readonlyContext, options);
802
- }
803
- continue;
804
- }
805
- if (mb.isKind(tsm.SyntaxKind.IndexSignature)) {
806
- transformIndexSignatureDeclaration(mb, readonlyModifier, readonlyContext, options);
807
- continue;
808
- }
809
- transformNode(mb, readonlyContext, options);
810
- continue;
811
- }
703
+ const transformMembers = (members, readonlyModifier, readonlyContext, options) => {
704
+ for (const mb of members) {
705
+ if (hasDisableNextLineComment(mb)) {
706
+ options.debugPrint("skipped by member disable-next-line comment");
707
+ continue;
708
+ }
709
+ if (mb.isKind(tsm.SyntaxKind.PropertySignature)) {
710
+ if (!checkIfPropertyNameShouldBeIgnored(mb.getNameNode(), options)) {
711
+ transformPropertySignature(
712
+ mb,
713
+ readonlyModifier,
714
+ readonlyContext,
715
+ options
716
+ );
717
+ }
718
+ continue;
719
+ }
720
+ if (mb.isKind(tsm.SyntaxKind.IndexSignature)) {
721
+ transformIndexSignatureDeclaration(
722
+ mb,
723
+ readonlyModifier,
724
+ readonlyContext,
725
+ options
726
+ );
727
+ continue;
728
+ }
729
+ transformNode(mb, readonlyContext, options);
730
+ continue;
731
+ }
812
732
  };
813
- const transformPropertySignature = (
814
- // eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
815
- node, readonlyModifier, readonlyContext, options) => {
816
- if (readonlyContext.type === 'DeepReadonly' ||
817
- readonlyModifier === 'remove') {
818
- node.setIsReadonly(false);
819
- }
820
- else {
821
- node.setIsReadonly(true);
822
- }
823
- {
824
- const type = node.getTypeNode();
825
- if (type !== undefined) {
826
- transformNode(type, readonlyContext, options);
827
- }
828
- }
733
+ const transformPropertySignature = (node, readonlyModifier, readonlyContext, options) => {
734
+ if (readonlyContext.type === "DeepReadonly" || readonlyModifier === "remove") {
735
+ node.setIsReadonly(false);
736
+ } else {
737
+ node.setIsReadonly(true);
738
+ }
739
+ {
740
+ const type = node.getTypeNode();
741
+ if (type !== void 0) {
742
+ transformNode(type, readonlyContext, options);
743
+ }
744
+ }
829
745
  };
830
- const transformIndexSignatureDeclaration = (
831
- // eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
832
- node, readonlyModifier, readonlyContext, options) => {
833
- if (hasDisableNextLineComment(node, TRANSFORMER_NAME)) {
834
- options.debugPrint('skipped index signature by disable-next-line comment');
835
- return;
836
- }
837
- if (readonlyContext.type === 'DeepReadonly' ||
838
- readonlyModifier === 'remove') {
839
- // node.setIsReadonly(false);
840
- node.toggleModifier('readonly', false);
841
- }
842
- else {
843
- node.toggleModifier('readonly', true);
844
- // node.setIsReadonly(true);
845
- }
846
- {
847
- const key = node.getKeyTypeNode();
848
- transformNode(key, readonlyContext, options);
849
- }
850
- {
851
- const ret = node.getReturnTypeNode();
852
- if (ret !== undefined) {
853
- transformNode(ret, readonlyContext, options);
854
- }
855
- }
746
+ const transformIndexSignatureDeclaration = (node, readonlyModifier, readonlyContext, options) => {
747
+ if (hasDisableNextLineComment(node, TRANSFORMER_NAME)) {
748
+ options.debugPrint("skipped index signature by disable-next-line comment");
749
+ return;
750
+ }
751
+ if (readonlyContext.type === "DeepReadonly" || readonlyModifier === "remove") {
752
+ node.toggleModifier("readonly", false);
753
+ } else {
754
+ node.toggleModifier("readonly", true);
755
+ }
756
+ {
757
+ const key = node.getKeyTypeNode();
758
+ transformNode(key, readonlyContext, options);
759
+ }
760
+ {
761
+ const ret = node.getReturnTypeNode();
762
+ if (ret !== void 0) {
763
+ transformNode(ret, readonlyContext, options);
764
+ }
765
+ }
856
766
  };
857
- const checkIfPropertyNameShouldBeIgnored = (
858
- // eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
859
- nameNode, options) => {
860
- return (
767
+ const checkIfPropertyNameShouldBeIgnored = (nameNode, options) => {
768
+ return (
861
769
  // mut_x: number[]
862
- (nameNode.isKind(tsm.SyntaxKind.Identifier) &&
863
- pipe(nameNode.getText()).map((nm) => options.ignoredPrefixes.some((p) => nm.startsWith(p))).value) ||
864
- // "mut_x": number[]
865
- (nameNode.isKind(tsm.SyntaxKind.StringLiteral) &&
866
- pipe(nameNode.getLiteralValue()).map((nm) => options.ignoredPrefixes.some((p) => nm.startsWith(p))).value) ||
867
- // #memberName: number[] (class only)
868
- (nameNode.isKind(tsm.SyntaxKind.PrivateIdentifier) &&
869
- pipe(nameNode.getText()).map((nm) => options.ignoredPrefixes.some((p) => nm.startsWith(`#${p}`))).value) ||
870
- // [`mut_x`]: number[]
871
- (nameNode.isKind(tsm.SyntaxKind.ComputedPropertyName) &&
872
- pipe(nameNode.getExpression()).map((exp) => {
873
- if (exp.isKind(tsm.SyntaxKind.StringLiteral)) {
874
- const nm = exp.getLiteralValue();
875
- return options.ignoredPrefixes.some((p) => nm.startsWith(p));
876
- }
877
- return false;
878
- }).value));
770
+ nameNode.isKind(tsm.SyntaxKind.Identifier) && pipe(nameNode.getText()).map(
771
+ (nm) => options.ignoredPrefixes.some((p) => nm.startsWith(p))
772
+ ).value || // "mut_x": number[]
773
+ nameNode.isKind(tsm.SyntaxKind.StringLiteral) && pipe(nameNode.getLiteralValue()).map(
774
+ (nm) => options.ignoredPrefixes.some((p) => nm.startsWith(p))
775
+ ).value || // #memberName: number[] (class only)
776
+ nameNode.isKind(tsm.SyntaxKind.PrivateIdentifier) && pipe(nameNode.getText()).map(
777
+ (nm) => options.ignoredPrefixes.some((p) => nm.startsWith(`#${p}`))
778
+ ).value || // [`mut_x`]: number[]
779
+ nameNode.isKind(tsm.SyntaxKind.ComputedPropertyName) && pipe(nameNode.getExpression()).map((exp) => {
780
+ if (exp.isKind(tsm.SyntaxKind.StringLiteral)) {
781
+ const nm = exp.getLiteralValue();
782
+ return options.ignoredPrefixes.some((p) => nm.startsWith(p));
783
+ }
784
+ return false;
785
+ }).value
786
+ );
879
787
  };
880
788
 
881
789
  export { convertToReadonlyTransformer };