ts-codemod-lib 1.3.0 → 1.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cmd/append-as-const.mjs +1 -1
- package/dist/cmd/convert-interface-to-type.mjs +1 -1
- package/dist/cmd/convert-to-readonly.mjs +1 -1
- package/dist/cmd/replace-any-with-unknown.mjs +1 -1
- package/dist/cmd/replace-record-with-unknown-record.mjs +1 -1
- package/dist/functions/ast-transformers/append-as-const.d.mts +2 -0
- package/dist/functions/ast-transformers/append-as-const.d.mts.map +1 -1
- package/dist/functions/ast-transformers/append-as-const.mjs +49 -4
- package/dist/functions/ast-transformers/append-as-const.mjs.map +1 -1
- package/package.json +2 -2
- package/src/cmd/append-as-const.mts +1 -1
- package/src/cmd/convert-interface-to-type.mts +1 -1
- package/src/cmd/convert-to-readonly.mts +1 -1
- package/src/cmd/replace-any-with-unknown.mts +1 -1
- package/src/cmd/replace-record-with-unknown-record.mts +1 -1
- package/src/functions/ast-transformers/append-as-const.mts +70 -4
- package/src/functions/ast-transformers/append-as-const.test.mts +117 -0
|
@@ -12,7 +12,7 @@ import { transformSourceCode } from '../functions/ast-transformers/transform-sou
|
|
|
12
12
|
/* eslint-disable no-await-in-loop */
|
|
13
13
|
const cmdDef = cmd.command({
|
|
14
14
|
name: 'append-as-const',
|
|
15
|
-
version: '1.3.
|
|
15
|
+
version: '1.3.1',
|
|
16
16
|
args: {
|
|
17
17
|
baseDir: cmd.positional({
|
|
18
18
|
type: cmd.string,
|
|
@@ -12,7 +12,7 @@ import { transformSourceCode } from '../functions/ast-transformers/transform-sou
|
|
|
12
12
|
/* eslint-disable no-await-in-loop */
|
|
13
13
|
const cmdDef = cmd.command({
|
|
14
14
|
name: 'convert-interface-to-type',
|
|
15
|
-
version: '1.3.
|
|
15
|
+
version: '1.3.1',
|
|
16
16
|
args: {
|
|
17
17
|
baseDir: cmd.positional({
|
|
18
18
|
type: cmd.string,
|
|
@@ -12,7 +12,7 @@ import { transformSourceCode } from '../functions/ast-transformers/transform-sou
|
|
|
12
12
|
/* eslint-disable no-await-in-loop */
|
|
13
13
|
const cmdDef = cmd.command({
|
|
14
14
|
name: 'convert-to-readonly',
|
|
15
|
-
version: '1.3.
|
|
15
|
+
version: '1.3.1',
|
|
16
16
|
args: {
|
|
17
17
|
baseDir: cmd.positional({
|
|
18
18
|
type: cmd.string,
|
|
@@ -12,7 +12,7 @@ import { transformSourceCode } from '../functions/ast-transformers/transform-sou
|
|
|
12
12
|
/* eslint-disable no-await-in-loop */
|
|
13
13
|
const cmdDef = cmd.command({
|
|
14
14
|
name: 'replace-any-with-unknown',
|
|
15
|
-
version: '1.3.
|
|
15
|
+
version: '1.3.1',
|
|
16
16
|
args: {
|
|
17
17
|
baseDir: cmd.positional({
|
|
18
18
|
type: cmd.string,
|
|
@@ -12,7 +12,7 @@ import { transformSourceCode } from '../functions/ast-transformers/transform-sou
|
|
|
12
12
|
/* eslint-disable no-await-in-loop */
|
|
13
13
|
const cmdDef = cmd.command({
|
|
14
14
|
name: 'replace-record-with-unknown-record',
|
|
15
|
-
version: '1.3.
|
|
15
|
+
version: '1.3.1',
|
|
16
16
|
args: {
|
|
17
17
|
baseDir: cmd.positional({
|
|
18
18
|
type: cmd.string,
|
|
@@ -6,6 +6,8 @@ export type AppendAsConstTransformerOptions = DeepReadonly<{
|
|
|
6
6
|
* A mute keywords to ignore the readonly conversion.
|
|
7
7
|
*
|
|
8
8
|
* (e.g. `"mut_"`)
|
|
9
|
+
*
|
|
10
|
+
* @default ['mut_', '#mut_', '_mut_', 'draft']
|
|
9
11
|
*/
|
|
10
12
|
ignorePrefixes?: string[];
|
|
11
13
|
ignoreConstTypeParameter?: boolean;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"append-as-const.d.mts","sourceRoot":"","sources":["../../../src/functions/ast-transformers/append-as-const.mts"],"names":[],"mappings":"AAMA,OAAO,EAAE,KAAK,kBAAkB,EAAE,MAAM,aAAa,CAAC;AAItD,eAAO,MAAM,wBAAwB,GACnC,UAAU,+BAA+B,KACxC,
|
|
1
|
+
{"version":3,"file":"append-as-const.d.mts","sourceRoot":"","sources":["../../../src/functions/ast-transformers/append-as-const.mts"],"names":[],"mappings":"AAMA,OAAO,EAAE,KAAK,kBAAkB,EAAE,MAAM,aAAa,CAAC;AAItD,eAAO,MAAM,wBAAwB,GACnC,UAAU,+BAA+B,KACxC,kBAoBF,CAAC;AAEF,MAAM,MAAM,+BAA+B,GAAG,YAAY,CAAC;IACzD,UAAU,CAAC,EAAE,KAAK,GAAG,qBAAqB,CAAC;IAE3C;;;;;;OAMG;IACH,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;IAE1B,wBAAwB,CAAC,EAAE,OAAO,CAAC;CACpC,CAAC,CAAC"}
|
|
@@ -6,7 +6,7 @@ import '../functions/is-primitive-type-node.mjs';
|
|
|
6
6
|
|
|
7
7
|
const TRANSFORMER_NAME = 'append-as-const';
|
|
8
8
|
const appendAsConstTransformer = (options) => {
|
|
9
|
-
const ignorePrefixes = ISet.create(options?.ignorePrefixes ?? ['mut_']);
|
|
9
|
+
const ignorePrefixes = ISet.create(options?.ignorePrefixes ?? ['mut_', '#mut_', '_mut_', 'draft']);
|
|
10
10
|
const optionsInternal = {
|
|
11
11
|
applyLevel: options?.applyLevel ?? 'avoidInFunctionArgs',
|
|
12
12
|
ignoredPrefixes: ignorePrefixes,
|
|
@@ -79,22 +79,67 @@ const transformNode = (node, options) => {
|
|
|
79
79
|
transformNode(child, options);
|
|
80
80
|
}
|
|
81
81
|
};
|
|
82
|
-
const removeAsConstRecursively = (node) => {
|
|
82
|
+
const removeAsConstRecursively = (node, insideSpreadWithConditional = false) => {
|
|
83
83
|
if (hasDisableNextLineComment(node)) {
|
|
84
84
|
console.debug('skipped by disable-next-line comment');
|
|
85
85
|
return;
|
|
86
86
|
}
|
|
87
87
|
if (isAsConstNode(node)) {
|
|
88
|
+
// If we're inside a spread element with conditional, keep the `as const`
|
|
89
|
+
if (insideSpreadWithConditional) {
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
88
92
|
// Extract node.expression to remove `as const` and recursively call the function
|
|
89
93
|
// to remove `as const` from nested nodes
|
|
90
94
|
// Example: `[[1,2] as const, [3,4]] as const` -> `[[1,2], [3,4]]`
|
|
91
|
-
removeAsConstRecursively(node.getExpression());
|
|
95
|
+
removeAsConstRecursively(node.getExpression(), insideSpreadWithConditional);
|
|
92
96
|
node.replaceWithText(node.getExpression().getText());
|
|
93
97
|
return;
|
|
94
98
|
}
|
|
99
|
+
// If we're inside a spread with conditional and encounter array/object literal without `as const`, add it
|
|
100
|
+
if (insideSpreadWithConditional) {
|
|
101
|
+
if (tsm.Node.isArrayLiteralExpression(node)) {
|
|
102
|
+
// Don't add `as const` to empty arrays
|
|
103
|
+
if (node.getElements().length === 0) {
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
// Add `as const` to the array itself, but don't recursively process elements
|
|
107
|
+
// Elements will be processed normally by the outer transform
|
|
108
|
+
node.replaceWithText(`${node.getText()} as const`);
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
if (tsm.Node.isObjectLiteralExpression(node)) {
|
|
112
|
+
// Don't add `as const` to empty objects
|
|
113
|
+
if (node.getProperties().length === 0) {
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
// Add `as const` to the object itself, but don't recursively process properties
|
|
117
|
+
node.replaceWithText(`${node.getText()} as const`);
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
// Mark that we're inside a spread element's expression only if it contains conditional
|
|
122
|
+
// Example: `...(flag ? [1, 2] as const : [])` keeps inner `as const`
|
|
123
|
+
// Example: `...[1, 2] as const` removes inner `as const`
|
|
124
|
+
if (tsm.Node.isSpreadElement(node)) {
|
|
125
|
+
const expression = node.getExpression();
|
|
126
|
+
const hasConditional = containsConditionalExpression(expression);
|
|
127
|
+
removeAsConstRecursively(expression, hasConditional);
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
95
130
|
for (const child of node.getChildren()) {
|
|
96
|
-
removeAsConstRecursively(child);
|
|
131
|
+
removeAsConstRecursively(child, insideSpreadWithConditional);
|
|
132
|
+
}
|
|
133
|
+
};
|
|
134
|
+
const containsConditionalExpression = (node) => {
|
|
135
|
+
if (tsm.Node.isConditionalExpression(node)) {
|
|
136
|
+
return true;
|
|
137
|
+
}
|
|
138
|
+
// Check children recursively, but stop at AsExpression boundaries
|
|
139
|
+
if (isAsConstNode(node)) {
|
|
140
|
+
return false;
|
|
97
141
|
}
|
|
142
|
+
return node.getChildren().some(containsConditionalExpression);
|
|
98
143
|
};
|
|
99
144
|
const removeParenthesis = (node) => tsm.Node.isParenthesizedExpression(node)
|
|
100
145
|
? removeParenthesis(node.getExpression())
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"append-as-const.mjs","sources":["../../../src/functions/ast-transformers/append-as-const.mts"],"sourcesContent":[null],"names":[],"mappings":";;;;;;AAQA,MAAM,gBAAgB,GAAG,iBAAiB;AAEnC,MAAM,wBAAwB,GAAG,CACtC,OAAyC,KACnB;
|
|
1
|
+
{"version":3,"file":"append-as-const.mjs","sources":["../../../src/functions/ast-transformers/append-as-const.mts"],"sourcesContent":[null],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ts-codemod-lib",
|
|
3
|
-
"version": "1.3.
|
|
3
|
+
"version": "1.3.1",
|
|
4
4
|
"private": false,
|
|
5
5
|
"keywords": [
|
|
6
6
|
"typescript",
|
|
@@ -118,7 +118,7 @@
|
|
|
118
118
|
"prettier": "3.8.0",
|
|
119
119
|
"prettier-plugin-organize-imports": "4.3.0",
|
|
120
120
|
"prettier-plugin-packagejson": "3.0.0",
|
|
121
|
-
"rollup": "4.
|
|
121
|
+
"rollup": "4.56.0",
|
|
122
122
|
"semantic-release": "25.0.2",
|
|
123
123
|
"ts-data-forge": "6.3.0",
|
|
124
124
|
"ts-repo-utils": "8.1.0",
|
|
@@ -11,7 +11,9 @@ const TRANSFORMER_NAME = 'append-as-const';
|
|
|
11
11
|
export const appendAsConstTransformer = (
|
|
12
12
|
options?: AppendAsConstTransformerOptions,
|
|
13
13
|
): TsMorphTransformer => {
|
|
14
|
-
const ignorePrefixes = ISet.create(
|
|
14
|
+
const ignorePrefixes = ISet.create(
|
|
15
|
+
options?.ignorePrefixes ?? ['mut_', '#mut_', '_mut_', 'draft'],
|
|
16
|
+
);
|
|
15
17
|
|
|
16
18
|
const optionsInternal: AppendAsConstTransformerOptionsInternal = {
|
|
17
19
|
applyLevel: options?.applyLevel ?? 'avoidInFunctionArgs',
|
|
@@ -37,6 +39,8 @@ export type AppendAsConstTransformerOptions = DeepReadonly<{
|
|
|
37
39
|
* A mute keywords to ignore the readonly conversion.
|
|
38
40
|
*
|
|
39
41
|
* (e.g. `"mut_"`)
|
|
42
|
+
*
|
|
43
|
+
* @default ['mut_', '#mut_', '_mut_', 'draft']
|
|
40
44
|
*/
|
|
41
45
|
ignorePrefixes?: string[];
|
|
42
46
|
|
|
@@ -134,7 +138,10 @@ const transformNode = (
|
|
|
134
138
|
}
|
|
135
139
|
};
|
|
136
140
|
|
|
137
|
-
const removeAsConstRecursively = (
|
|
141
|
+
const removeAsConstRecursively = (
|
|
142
|
+
node: tsm.Node,
|
|
143
|
+
insideSpreadWithConditional: boolean = false,
|
|
144
|
+
): void => {
|
|
138
145
|
if (hasDisableNextLineComment(node)) {
|
|
139
146
|
console.debug('skipped by disable-next-line comment');
|
|
140
147
|
|
|
@@ -142,21 +149,80 @@ const removeAsConstRecursively = (node: tsm.Node): void => {
|
|
|
142
149
|
}
|
|
143
150
|
|
|
144
151
|
if (isAsConstNode(node)) {
|
|
152
|
+
// If we're inside a spread element with conditional, keep the `as const`
|
|
153
|
+
if (insideSpreadWithConditional) {
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
|
|
145
157
|
// Extract node.expression to remove `as const` and recursively call the function
|
|
146
158
|
// to remove `as const` from nested nodes
|
|
147
159
|
// Example: `[[1,2] as const, [3,4]] as const` -> `[[1,2], [3,4]]`
|
|
148
|
-
removeAsConstRecursively(node.getExpression());
|
|
160
|
+
removeAsConstRecursively(node.getExpression(), insideSpreadWithConditional);
|
|
149
161
|
|
|
150
162
|
node.replaceWithText(node.getExpression().getText());
|
|
151
163
|
|
|
152
164
|
return;
|
|
153
165
|
}
|
|
154
166
|
|
|
167
|
+
// If we're inside a spread with conditional and encounter array/object literal without `as const`, add it
|
|
168
|
+
if (insideSpreadWithConditional) {
|
|
169
|
+
if (tsm.Node.isArrayLiteralExpression(node)) {
|
|
170
|
+
// Don't add `as const` to empty arrays
|
|
171
|
+
if (node.getElements().length === 0) {
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// Add `as const` to the array itself, but don't recursively process elements
|
|
176
|
+
// Elements will be processed normally by the outer transform
|
|
177
|
+
node.replaceWithText(`${node.getText()} as const`);
|
|
178
|
+
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
if (tsm.Node.isObjectLiteralExpression(node)) {
|
|
183
|
+
// Don't add `as const` to empty objects
|
|
184
|
+
if (node.getProperties().length === 0) {
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// Add `as const` to the object itself, but don't recursively process properties
|
|
189
|
+
node.replaceWithText(`${node.getText()} as const`);
|
|
190
|
+
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// Mark that we're inside a spread element's expression only if it contains conditional
|
|
196
|
+
// Example: `...(flag ? [1, 2] as const : [])` keeps inner `as const`
|
|
197
|
+
// Example: `...[1, 2] as const` removes inner `as const`
|
|
198
|
+
if (tsm.Node.isSpreadElement(node)) {
|
|
199
|
+
const expression = node.getExpression();
|
|
200
|
+
|
|
201
|
+
const hasConditional = containsConditionalExpression(expression);
|
|
202
|
+
|
|
203
|
+
removeAsConstRecursively(expression, hasConditional);
|
|
204
|
+
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
207
|
+
|
|
155
208
|
for (const child of node.getChildren()) {
|
|
156
|
-
removeAsConstRecursively(child);
|
|
209
|
+
removeAsConstRecursively(child, insideSpreadWithConditional);
|
|
157
210
|
}
|
|
158
211
|
};
|
|
159
212
|
|
|
213
|
+
const containsConditionalExpression = (node: tsm.Node): boolean => {
|
|
214
|
+
if (tsm.Node.isConditionalExpression(node)) {
|
|
215
|
+
return true;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// Check children recursively, but stop at AsExpression boundaries
|
|
219
|
+
if (isAsConstNode(node)) {
|
|
220
|
+
return false;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
return node.getChildren().some(containsConditionalExpression);
|
|
224
|
+
};
|
|
225
|
+
|
|
160
226
|
const removeParenthesis = (node: tsm.Node): tsm.Node =>
|
|
161
227
|
tsm.Node.isParenthesizedExpression(node)
|
|
162
228
|
? removeParenthesis(node.getExpression())
|
|
@@ -393,5 +393,122 @@ describe(appendAsConstTransformer, () => {
|
|
|
393
393
|
}) as const;
|
|
394
394
|
`,
|
|
395
395
|
},
|
|
396
|
+
{
|
|
397
|
+
name: 'Nested as const inside spread operator with conditional should be kept',
|
|
398
|
+
source: dedent`
|
|
399
|
+
const flag = true as boolean;
|
|
400
|
+
|
|
401
|
+
type Elem =
|
|
402
|
+
| Readonly<{ a: 'str0' }>
|
|
403
|
+
| Readonly<{ b: 'str1' }>
|
|
404
|
+
| Readonly<{ c: 'str2' }>;
|
|
405
|
+
|
|
406
|
+
const a = [
|
|
407
|
+
{ a: 'str0' },
|
|
408
|
+
...(flag ? ([{ b: 'str1' }, { c: 'str2' }] as const) : []),
|
|
409
|
+
] as const satisfies readonly Elem[];
|
|
410
|
+
`,
|
|
411
|
+
expected: dedent`
|
|
412
|
+
const flag = true as boolean;
|
|
413
|
+
|
|
414
|
+
type Elem =
|
|
415
|
+
| Readonly<{ a: 'str0' }>
|
|
416
|
+
| Readonly<{ b: 'str1' }>
|
|
417
|
+
| Readonly<{ c: 'str2' }>;
|
|
418
|
+
|
|
419
|
+
const a = [
|
|
420
|
+
{ a: 'str0' },
|
|
421
|
+
...(flag ? ([{ b: 'str1' }, { c: 'str2' }] as const) : []),
|
|
422
|
+
] as const satisfies readonly Elem[];
|
|
423
|
+
`,
|
|
424
|
+
},
|
|
425
|
+
{
|
|
426
|
+
name: 'Spread with conditional without as const should add as const to inner array',
|
|
427
|
+
source: dedent`
|
|
428
|
+
const flag = true as boolean;
|
|
429
|
+
|
|
430
|
+
type Elem =
|
|
431
|
+
| Readonly<{ a: 'str0' }>
|
|
432
|
+
| Readonly<{ b: 'str1' }>
|
|
433
|
+
| Readonly<{ c: 'str2' }>;
|
|
434
|
+
|
|
435
|
+
const a = [
|
|
436
|
+
{ a: 'str0' },
|
|
437
|
+
...(flag ? [{ b: 'str1' }, { c: 'str2' }] : []),
|
|
438
|
+
] as const satisfies readonly Elem[];
|
|
439
|
+
`,
|
|
440
|
+
expected: dedent`
|
|
441
|
+
const flag = true as boolean;
|
|
442
|
+
|
|
443
|
+
type Elem =
|
|
444
|
+
| Readonly<{ a: 'str0' }>
|
|
445
|
+
| Readonly<{ b: 'str1' }>
|
|
446
|
+
| Readonly<{ c: 'str2' }>;
|
|
447
|
+
|
|
448
|
+
const a = [
|
|
449
|
+
{ a: 'str0' },
|
|
450
|
+
...(flag ? [{ b: 'str1' }, { c: 'str2' }] as const : []),
|
|
451
|
+
] as const satisfies readonly Elem[];
|
|
452
|
+
`,
|
|
453
|
+
},
|
|
454
|
+
{
|
|
455
|
+
name: 'Simple spread without conditional should remove inner as const',
|
|
456
|
+
source: dedent`
|
|
457
|
+
const a = [
|
|
458
|
+
1,
|
|
459
|
+
...[2, 3] as const,
|
|
460
|
+
] as const;
|
|
461
|
+
`,
|
|
462
|
+
expected: dedent`
|
|
463
|
+
const a = [
|
|
464
|
+
1,
|
|
465
|
+
...[2, 3],
|
|
466
|
+
] as const;
|
|
467
|
+
`,
|
|
468
|
+
},
|
|
469
|
+
{
|
|
470
|
+
name: 'Multiple simple spreads should remove inner as const',
|
|
471
|
+
source: dedent`
|
|
472
|
+
const c = [
|
|
473
|
+
...[1, 2] as const,
|
|
474
|
+
...[3, 4] as const,
|
|
475
|
+
] as const;
|
|
476
|
+
`,
|
|
477
|
+
expected: dedent`
|
|
478
|
+
const c = [
|
|
479
|
+
...[1, 2],
|
|
480
|
+
...[3, 4],
|
|
481
|
+
] as const;
|
|
482
|
+
`,
|
|
483
|
+
},
|
|
484
|
+
{
|
|
485
|
+
name: 'Nested as const inside spread operator without conditional statement should be removed',
|
|
486
|
+
source: dedent`
|
|
487
|
+
const flag = true as boolean;
|
|
488
|
+
|
|
489
|
+
type Elem =
|
|
490
|
+
| Readonly<{ a: 'str0' }>
|
|
491
|
+
| Readonly<{ b: 'str1' }>
|
|
492
|
+
| Readonly<{ c: 'str2' }>;
|
|
493
|
+
|
|
494
|
+
const a = [
|
|
495
|
+
{ a: 'str0' },
|
|
496
|
+
...([{ b: 'str1' }, { c: 'str2' }] as const),
|
|
497
|
+
] as const satisfies readonly Elem[];
|
|
498
|
+
`,
|
|
499
|
+
expected: dedent`
|
|
500
|
+
const flag = true as boolean;
|
|
501
|
+
|
|
502
|
+
type Elem =
|
|
503
|
+
| Readonly<{ a: 'str0' }>
|
|
504
|
+
| Readonly<{ b: 'str1' }>
|
|
505
|
+
| Readonly<{ c: 'str2' }>;
|
|
506
|
+
|
|
507
|
+
const a = [
|
|
508
|
+
{ a: 'str0' },
|
|
509
|
+
...[{ b: 'str1' }, { c: 'str2' }],
|
|
510
|
+
] as const satisfies readonly Elem[];
|
|
511
|
+
`,
|
|
512
|
+
},
|
|
396
513
|
])('$name', testFn);
|
|
397
514
|
});
|