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.
@@ -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.0',
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.0',
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.0',
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.0',
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.0',
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,kBAkBF,CAAC;AAEF,MAAM,MAAM,+BAA+B,GAAG,YAAY,CAAC;IACzD,UAAU,CAAC,EAAE,KAAK,GAAG,qBAAqB,CAAC;IAE3C;;;;OAIG;IACH,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;IAE1B,wBAAwB,CAAC,EAAE,OAAO,CAAC;CACpC,CAAC,CAAC"}
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":"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"}
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.0",
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.55.2",
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",
@@ -12,7 +12,7 @@ import {
12
12
 
13
13
  const cmdDef = cmd.command({
14
14
  name: 'append-as-const',
15
- version: '1.3.0',
15
+ version: '1.3.1',
16
16
  args: {
17
17
  baseDir: cmd.positional({
18
18
  type: cmd.string,
@@ -12,7 +12,7 @@ import {
12
12
 
13
13
  const cmdDef = cmd.command({
14
14
  name: 'convert-interface-to-type',
15
- version: '1.3.0',
15
+ version: '1.3.1',
16
16
  args: {
17
17
  baseDir: cmd.positional({
18
18
  type: cmd.string,
@@ -12,7 +12,7 @@ import {
12
12
 
13
13
  const cmdDef = cmd.command({
14
14
  name: 'convert-to-readonly',
15
- version: '1.3.0',
15
+ version: '1.3.1',
16
16
  args: {
17
17
  baseDir: cmd.positional({
18
18
  type: cmd.string,
@@ -12,7 +12,7 @@ import {
12
12
 
13
13
  const cmdDef = cmd.command({
14
14
  name: 'replace-any-with-unknown',
15
- version: '1.3.0',
15
+ version: '1.3.1',
16
16
  args: {
17
17
  baseDir: cmd.positional({
18
18
  type: cmd.string,
@@ -12,7 +12,7 @@ import {
12
12
 
13
13
  const cmdDef = cmd.command({
14
14
  name: 'replace-record-with-unknown-record',
15
- version: '1.3.0',
15
+ version: '1.3.1',
16
16
  args: {
17
17
  baseDir: cmd.positional({
18
18
  type: cmd.string,
@@ -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(options?.ignorePrefixes ?? ['mut_']);
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 = (node: tsm.Node): void => {
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
  });