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