ts-codemod-lib 2.2.0 → 2.2.2

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