ts-codemod-lib 3.3.0 → 3.3.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 (86) hide show
  1. package/README.md +22 -0
  2. package/dist/entry-point.mjs +2 -24
  3. package/dist/entry-point.mjs.map +1 -1
  4. package/dist/functions/ast-transformers/append-as-const.mjs +330 -292
  5. package/dist/functions/ast-transformers/append-as-const.mjs.map +1 -1
  6. package/dist/functions/ast-transformers/convert-interface-to-type.mjs +85 -70
  7. package/dist/functions/ast-transformers/convert-interface-to-type.mjs.map +1 -1
  8. package/dist/functions/ast-transformers/convert-to-readonly.d.mts.map +1 -1
  9. package/dist/functions/ast-transformers/convert-to-readonly.mjs +971 -838
  10. package/dist/functions/ast-transformers/convert-to-readonly.mjs.map +1 -1
  11. package/dist/functions/ast-transformers/enable-no-unchecked-indexed-access.d.mts +32 -9
  12. package/dist/functions/ast-transformers/enable-no-unchecked-indexed-access.d.mts.map +1 -1
  13. package/dist/functions/ast-transformers/enable-no-unchecked-indexed-access.mjs +299 -134
  14. package/dist/functions/ast-transformers/enable-no-unchecked-indexed-access.mjs.map +1 -1
  15. package/dist/functions/ast-transformers/index.mjs +10 -12
  16. package/dist/functions/ast-transformers/index.mjs.map +1 -1
  17. package/dist/functions/ast-transformers/readonly-transformer-helpers/compare-union-types.mjs +14 -7
  18. package/dist/functions/ast-transformers/readonly-transformer-helpers/compare-union-types.mjs.map +1 -1
  19. package/dist/functions/ast-transformers/readonly-transformer-helpers/constants.mjs +12 -15
  20. package/dist/functions/ast-transformers/readonly-transformer-helpers/constants.mjs.map +1 -1
  21. package/dist/functions/ast-transformers/readonly-transformer-helpers/group-union-types.d.mts.map +1 -1
  22. package/dist/functions/ast-transformers/readonly-transformer-helpers/group-union-types.mjs +70 -59
  23. package/dist/functions/ast-transformers/readonly-transformer-helpers/group-union-types.mjs.map +1 -1
  24. package/dist/functions/ast-transformers/readonly-transformer-helpers/index.mjs +5 -5
  25. package/dist/functions/ast-transformers/readonly-transformer-helpers/index.mjs.map +1 -1
  26. package/dist/functions/ast-transformers/readonly-transformer-helpers/readonly-context.mjs +15 -20
  27. package/dist/functions/ast-transformers/readonly-transformer-helpers/readonly-context.mjs.map +1 -1
  28. package/dist/functions/ast-transformers/replace-any-with-unknown.mjs +30 -34
  29. package/dist/functions/ast-transformers/replace-any-with-unknown.mjs.map +1 -1
  30. package/dist/functions/ast-transformers/replace-record-with-unknown-record.mjs +197 -156
  31. package/dist/functions/ast-transformers/replace-record-with-unknown-record.mjs.map +1 -1
  32. package/dist/functions/ast-transformers/transform-source-code.mjs +74 -75
  33. package/dist/functions/ast-transformers/transform-source-code.mjs.map +1 -1
  34. package/dist/functions/ast-transformers/types.mjs +2 -2
  35. package/dist/functions/ast-transformers/types.mjs.map +1 -1
  36. package/dist/functions/constants/ignore-comment-text.mjs +11 -13
  37. package/dist/functions/constants/ignore-comment-text.mjs.map +1 -1
  38. package/dist/functions/constants/index.mjs +2 -2
  39. package/dist/functions/constants/index.mjs.map +1 -1
  40. package/dist/functions/functions/has-call-or-construct-signature.d.mts +18 -0
  41. package/dist/functions/functions/has-call-or-construct-signature.d.mts.map +1 -0
  42. package/dist/functions/functions/has-call-or-construct-signature.mjs +87 -0
  43. package/dist/functions/functions/has-call-or-construct-signature.mjs.map +1 -0
  44. package/dist/functions/functions/has-disable-next-line-comment.mjs +67 -38
  45. package/dist/functions/functions/has-disable-next-line-comment.mjs.map +1 -1
  46. package/dist/functions/functions/index.d.mts +1 -0
  47. package/dist/functions/functions/index.d.mts.map +1 -1
  48. package/dist/functions/functions/index.mjs +12 -11
  49. package/dist/functions/functions/index.mjs.map +1 -1
  50. package/dist/functions/functions/is-as-const-node.mjs +25 -20
  51. package/dist/functions/functions/is-as-const-node.mjs.map +1 -1
  52. package/dist/functions/functions/is-atomic-type-node.mjs +245 -25
  53. package/dist/functions/functions/is-atomic-type-node.mjs.map +1 -1
  54. package/dist/functions/functions/is-conditional-type-distribution-guard.mjs +34 -13
  55. package/dist/functions/functions/is-conditional-type-distribution-guard.mjs.map +1 -1
  56. package/dist/functions/functions/is-readonly-node.mjs +120 -14
  57. package/dist/functions/functions/is-readonly-node.mjs.map +1 -1
  58. package/dist/functions/functions/is-spread-parameter-node.mjs +5 -6
  59. package/dist/functions/functions/is-spread-parameter-node.mjs.map +1 -1
  60. package/dist/functions/functions/remove-parentheses.mjs +4 -4
  61. package/dist/functions/functions/remove-parentheses.mjs.map +1 -1
  62. package/dist/functions/functions/should-avoid-parentheses-for-readonly.mjs +23 -9
  63. package/dist/functions/functions/should-avoid-parentheses-for-readonly.mjs.map +1 -1
  64. package/dist/functions/functions/unwrap-readonly.mjs +3 -4
  65. package/dist/functions/functions/unwrap-readonly.mjs.map +1 -1
  66. package/dist/functions/functions/wrap-with-parentheses.d.mts +14 -5
  67. package/dist/functions/functions/wrap-with-parentheses.d.mts.map +1 -1
  68. package/dist/functions/functions/wrap-with-parentheses.mjs +67 -30
  69. package/dist/functions/functions/wrap-with-parentheses.mjs.map +1 -1
  70. package/dist/functions/index.mjs +5 -24
  71. package/dist/functions/index.mjs.map +1 -1
  72. package/dist/functions/utils/index.mjs +2 -2
  73. package/dist/functions/utils/index.mjs.map +1 -1
  74. package/dist/functions/utils/replace-with-debug.mjs +4 -6
  75. package/dist/functions/utils/replace-with-debug.mjs.map +1 -1
  76. package/dist/index.mjs +2 -24
  77. package/dist/index.mjs.map +1 -1
  78. package/package.json +18 -18
  79. package/src/functions/ast-transformers/convert-to-readonly.mts +19 -13
  80. package/src/functions/ast-transformers/convert-to-readonly.test.mts +129 -1
  81. package/src/functions/ast-transformers/enable-no-unchecked-indexed-access.mts +32 -9
  82. package/src/functions/ast-transformers/enable-no-unchecked-indexed-access.test.mts +76 -22
  83. package/src/functions/ast-transformers/readonly-transformer-helpers/group-union-types.mts +8 -1
  84. package/src/functions/functions/has-call-or-construct-signature.mts +120 -0
  85. package/src/functions/functions/index.mts +1 -0
  86. package/src/functions/functions/wrap-with-parentheses.mts +32 -17
@@ -1 +1 @@
1
- {"version":3,"file":"unwrap-readonly.mjs","sources":["../../../src/functions/functions/unwrap-readonly.mts"],"sourcesContent":["import { type ReadonlyTypeReferenceNode } from './is-readonly-node.mjs';\nimport { wrapWithParentheses } from './wrap-with-parentheses.mjs';\n\nexport const unwrapReadonlyTypeArgText = (\n // eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types\n node: ReadonlyTypeReferenceNode,\n): string => wrapWithParentheses(node.getTypeArguments()[0].getFullText());\n"],"names":[],"mappings":";;AAGO,MAAM,yBAAA,GAA4B,CAEvC,IAAA,KACW,mBAAA,CAAoB,IAAA,CAAK,kBAAiB,CAAE,CAAC,CAAA,CAAE,WAAA,EAAa;;;;"}
1
+ {"version":3,"file":"unwrap-readonly.mjs","sourceRoot":"","sources":["../../../src/functions/functions/unwrap-readonly.mts"],"names":[],"mappings":"AACA,OAAO,EAAE,mBAAmB,EAAE,MAAM,6BAA6B,CAAC;AAElE,MAAM,CAAC,MAAM,yBAAyB,GAAG;AACvC,8EAA8E;AAC9E,IAA+B,EACvB,EAAE,CAAC,mBAAmB,CAAC,IAAI,CAAC,gBAAgB,EAAE,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC,CAAC"}
@@ -4,11 +4,20 @@
4
4
  * fully wrapped with balanced parentheses.
5
5
  *
6
6
  * @example
7
- * wrapWithParentheses('A') // '(A)'
8
- * wrapWithParentheses('(A)') // '(A)' (not '((A))')
9
- * wrapWithParentheses('A | B') // '(A | B)'
10
- * wrapWithParentheses('(A | B)') // '(A | B)' (not '((A | B))')
11
- * wrapWithParentheses('(A) | (B)') // '((A) | (B))' (needs outer parens)
7
+ *
8
+ * ```ts
9
+ * assert.deepStrictEqual(wrapWithParentheses('A'), '(A)');
10
+ *
11
+ * // already wrapped, so not '((A))'
12
+ * assert.deepStrictEqual(wrapWithParentheses('(A)'), '(A)');
13
+ *
14
+ * assert.deepStrictEqual(wrapWithParentheses('A | B'), '(A | B)');
15
+ *
16
+ * assert.deepStrictEqual(wrapWithParentheses('(A | B)'), '(A | B)');
17
+ *
18
+ * // the outer parentheses do not span everything, so a pair is added
19
+ * assert.deepStrictEqual(wrapWithParentheses('(A) | (B)'), '((A) | (B))');
20
+ * ```
12
21
  */
13
22
  export declare const wrapWithParentheses: (nodeStr: string) => string;
14
23
  //# sourceMappingURL=wrap-with-parentheses.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"wrap-with-parentheses.d.mts","sourceRoot":"","sources":["../../../src/functions/functions/wrap-with-parentheses.mts"],"names":[],"mappings":"AAgDA;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,mBAAmB,YAAa,MAAM,KAAG,MAQrD,CAAC"}
1
+ {"version":3,"file":"wrap-with-parentheses.d.mts","sourceRoot":"","sources":["../../../src/functions/functions/wrap-with-parentheses.mts"],"names":[],"mappings":"AAsDA;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,eAAO,MAAM,mBAAmB,YAAa,MAAM,KAAG,MAQrD,CAAC"}
@@ -1,36 +1,73 @@
1
- import { range, asUint32 } from 'ts-data-forge';
2
-
1
+ import { asUint32, range } from 'ts-data-forge';
3
2
  const parenDepthDelta = (char) => {
4
- switch (char) {
5
- case "(":
6
- return 1;
7
- case ")":
8
- return -1;
9
- default:
10
- return 0;
11
- }
3
+ switch (char) {
4
+ case '(':
5
+ return 1;
6
+ case ')':
7
+ return -1;
8
+
9
+
10
+ case undefined:
11
+ default:
12
+ return 0;
13
+ }
12
14
  };
15
+
16
+
17
+
18
+
19
+
20
+
21
+
22
+
23
+
24
+
25
+
26
+
27
+
13
28
  const isWrappedWithParentheses = (str) => {
14
- const trimmed = str.trim();
15
- if (!trimmed.startsWith("(") || !trimmed.endsWith(")")) {
16
- return false;
17
- }
18
- let mut_depth = 0;
19
- for (const mut_i of range(0, asUint32(trimmed.length))) {
20
- mut_depth += parenDepthDelta(trimmed.charAt(mut_i));
21
- if (mut_depth === 0 && mut_i < trimmed.length - 1) {
22
- return false;
29
+ const trimmed = str.trim();
30
+ if (!trimmed.startsWith('(') || !trimmed.endsWith(')')) {
31
+ return false;
23
32
  }
24
- }
25
- return true;
26
- };
27
- const wrapWithParentheses = (nodeStr) => {
28
- const trimmed = nodeStr.trim();
29
- if (isWrappedWithParentheses(trimmed)) {
30
- return trimmed;
31
- }
32
- return `(${trimmed})`;
33
+
34
+
35
+ let mut_depth = 0;
36
+ for (const mut_i of range(0, asUint32(trimmed.length))) {
37
+ mut_depth += parenDepthDelta(trimmed.at(mut_i));
38
+
39
+ if (mut_depth === 0 && mut_i < trimmed.length - 1) {
40
+ return false;
41
+ }
42
+ }
43
+ return true;
33
44
  };
34
45
 
35
- export { wrapWithParentheses };
36
- //# sourceMappingURL=wrap-with-parentheses.mjs.map
46
+
47
+
48
+
49
+
50
+
51
+
52
+
53
+
54
+
55
+
56
+
57
+
58
+
59
+
60
+
61
+
62
+
63
+
64
+
65
+
66
+ export const wrapWithParentheses = (nodeStr) => {
67
+ const trimmed = nodeStr.trim();
68
+ if (isWrappedWithParentheses(trimmed)) {
69
+ return trimmed;
70
+ }
71
+ return `(${trimmed})`;
72
+ };
73
+ //# sourceMappingURL=wrap-with-parentheses.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"wrap-with-parentheses.mjs","sources":["../../../src/functions/functions/wrap-with-parentheses.mts"],"sourcesContent":["/**\n * Checks if a string is already wrapped with a single pair of balanced parentheses\n * that encompasses the entire expression.\n *\n * @example\n * isWrappedWithParentheses('(A)') // true\n * isWrappedWithParentheses('((A))') // true (outer pair wraps everything)\n * isWrappedWithParentheses('(A) | (B)') // false (outer parens don't wrap everything)\n * isWrappedWithParentheses('A') // false\n */\nimport { asUint32, range } from 'ts-data-forge';\n\nconst parenDepthDelta = (char: string): number => {\n switch (char) {\n case '(':\n return 1;\n\n case ')':\n return -1;\n\n default:\n return 0;\n }\n};\n\nconst isWrappedWithParentheses = (str: string): boolean => {\n const trimmed = str.trim();\n\n if (!trimmed.startsWith('(') || !trimmed.endsWith(')')) {\n return false;\n }\n\n // Check if the opening and closing parentheses are balanced\n // and the opening parenthesis corresponds to the closing one\n let mut_depth = 0;\n\n for (const mut_i of range(0, asUint32(trimmed.length))) {\n mut_depth += parenDepthDelta(trimmed.charAt(mut_i));\n\n // If we reach depth 0 before the end, the outer parentheses don't wrap everything\n if (mut_depth === 0 && mut_i < trimmed.length - 1) {\n return false;\n }\n }\n\n return true;\n};\n\n/**\n * Wraps a string with parentheses if not already wrapped.\n * Avoids adding redundant parentheses when the expression is already\n * fully wrapped with balanced parentheses.\n *\n * @example\n * wrapWithParentheses('A') // '(A)'\n * wrapWithParentheses('(A)') // '(A)' (not '((A))')\n * wrapWithParentheses('A | B') // '(A | B)'\n * wrapWithParentheses('(A | B)') // '(A | B)' (not '((A | B))')\n * wrapWithParentheses('(A) | (B)') // '((A) | (B))' (needs outer parens)\n */\nexport const wrapWithParentheses = (nodeStr: string): string => {\n const trimmed = nodeStr.trim();\n\n if (isWrappedWithParentheses(trimmed)) {\n return trimmed;\n }\n\n return `(${trimmed})` as const;\n};\n"],"names":[],"mappings":";;AAYA,MAAM,eAAA,GAAkB,CAAC,IAAA,KAAyB;AAChD,EAAA,QAAQ,IAAA;AAAM,IACZ,KAAK,GAAA;AACH,MAAA,OAAO,CAAA;AAAA,IAET,KAAK,GAAA;AACH,MAAA,OAAO,EAAA;AAAA,IAET;AACE,MAAA,OAAO,CAAA;AAAA;AAEb,CAAA;AAEA,MAAM,wBAAA,GAA2B,CAAC,GAAA,KAAyB;AACzD,EAAA,MAAM,OAAA,GAAU,IAAI,IAAA,EAAK;AAEzB,EAAA,IAAI,CAAC,QAAQ,UAAA,CAAW,GAAG,KAAK,CAAC,OAAA,CAAQ,QAAA,CAAS,GAAG,CAAA,EAAG;AACtD,IAAA,OAAO,KAAA;AAAA,EACT;AAIA,EAAA,IAAI,SAAA,GAAY,CAAA;AAEhB,EAAA,KAAA,MAAW,SAAS,KAAA,CAAM,CAAA,EAAG,SAAS,OAAA,CAAQ,MAAM,CAAC,CAAA,EAAG;AACtD,IAAA,SAAA,IAAa,eAAA,CAAgB,OAAA,CAAQ,MAAA,CAAO,KAAK,CAAC,CAAA;AAGlD,IAAA,IAAI,SAAA,KAAc,CAAA,IAAK,KAAA,GAAQ,OAAA,CAAQ,SAAS,CAAA,EAAG;AACjD,MAAA,OAAO,KAAA;AAAA,IACT;AAAA,EACF;AAEA,EAAA,OAAO,IAAA;AACT,CAAA;AAcO,MAAM,mBAAA,GAAsB,CAAC,OAAA,KAA4B;AAC9D,EAAA,MAAM,OAAA,GAAU,QAAQ,IAAA,EAAK;AAE7B,EAAA,IAAI,wBAAA,CAAyB,OAAO,CAAA,EAAG;AACrC,IAAA,OAAO,OAAA;AAAA,EACT;AAEA,EAAA,OAAO,IAAI,OAAO,CAAA,CAAA,CAAA;AACpB;;;;"}
1
+ {"version":3,"file":"wrap-with-parentheses.mjs","sourceRoot":"","sources":["../../../src/functions/functions/wrap-with-parentheses.mts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,eAAe,CAAC;AAEhD,MAAM,eAAe,GAAG,CAAC,IAAwB,EAAU,EAAE;IAC3D,QAAQ,IAAI,EAAE,CAAC;QACb,KAAK,GAAG;YACN,OAAO,CAAC,CAAC;QAEX,KAAK,GAAG;YACN,OAAO,CAAC,CAAC,CAAC;QAEZ,sEAAsE;QACtE,0DAA0D;QAC1D,KAAK,SAAS,CAAC;QACf;YACE,OAAO,CAAC,CAAC;IACb,CAAC;AACH,CAAC,CAAC;AAEF;;;;;;;;;;;;GAYG;AACH,MAAM,wBAAwB,GAAG,CAAC,GAAW,EAAW,EAAE;IACxD,MAAM,OAAO,GAAG,GAAG,CAAC,IAAI,EAAE,CAAC;IAE3B,IAAI,CAAC,OAAO,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;QACvD,OAAO,KAAK,CAAC;IACf,CAAC;IAED,4DAA4D;IAC5D,6DAA6D;IAC7D,IAAI,SAAS,GAAG,CAAC,CAAC;IAElB,KAAK,MAAM,KAAK,IAAI,KAAK,CAAC,CAAC,EAAE,QAAQ,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC;QACvD,SAAS,IAAI,eAAe,CAAC,OAAO,CAAC,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC;QAEhD,kFAAkF;QAClF,IAAI,SAAS,KAAK,CAAC,IAAI,KAAK,GAAG,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YAClD,OAAO,KAAK,CAAC;QACf,CAAC;IACH,CAAC;IAED,OAAO,IAAI,CAAC;AACd,CAAC,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,MAAM,CAAC,MAAM,mBAAmB,GAAG,CAAC,OAAe,EAAU,EAAE;IAC7D,MAAM,OAAO,GAAG,OAAO,CAAC,IAAI,EAAE,CAAC;IAE/B,IAAI,wBAAwB,CAAC,OAAO,CAAC,EAAE,CAAC;QACtC,OAAO,OAAO,CAAC;IACjB,CAAC;IAED,OAAO,IAAI,OAAO,GAAY,CAAC;AACjC,CAAC,CAAC"}
@@ -1,24 +1,5 @@
1
- export { appendAsConstTransformer } from './ast-transformers/append-as-const.mjs';
2
- export { convertInterfaceToTypeTransformer } from './ast-transformers/convert-interface-to-type.mjs';
3
- export { convertToReadonlyTransformer } from './ast-transformers/convert-to-readonly.mjs';
4
- export { enableNoUncheckedIndexedAccessTransformer } from './ast-transformers/enable-no-unchecked-indexed-access.mjs';
5
- export { compareUnionIntersectionTypes } from './ast-transformers/readonly-transformer-helpers/compare-union-types.mjs';
6
- export { invalidDeepReadonlyTypeName } from './ast-transformers/readonly-transformer-helpers/constants.mjs';
7
- export { groupUnionIntersectionTypes } from './ast-transformers/readonly-transformer-helpers/group-union-types.mjs';
8
- export { nextReadonlyContext } from './ast-transformers/readonly-transformer-helpers/readonly-context.mjs';
9
- export { replaceAnyWithUnknownTransformer } from './ast-transformers/replace-any-with-unknown.mjs';
10
- export { replaceRecordWithUnknownRecordTransformer } from './ast-transformers/replace-record-with-unknown-record.mjs';
11
- export { transformSourceCode } from './ast-transformers/transform-source-code.mjs';
12
- export { IGNORE_FILE_COMMENT_PREFIXES, IGNORE_LINE_COMMENT_PREFIXES } from './constants/ignore-comment-text.mjs';
13
- export { hasDisableNextLineComment } from './functions/has-disable-next-line-comment.mjs';
14
- export { isAsConstNode } from './functions/is-as-const-node.mjs';
15
- export { isAtomicTypeNode } from './functions/is-atomic-type-node.mjs';
16
- export { isConditionalTypeDistributionGuard } from './functions/is-conditional-type-distribution-guard.mjs';
17
- export { isReadonlyArrayTypeNode, isReadonlyTupleOrArrayTypeNode, isReadonlyTupleTypeNode, isReadonlyTypeReferenceNode, isShallowReadonlyTypeNode } from './functions/is-readonly-node.mjs';
18
- export { isSpreadNamedTupleMemberNode, isSpreadParameterNode } from './functions/is-spread-parameter-node.mjs';
19
- export { removeParentheses } from './functions/remove-parentheses.mjs';
20
- export { shouldAvoidParenthesesForReadonly } from './functions/should-avoid-parentheses-for-readonly.mjs';
21
- export { unwrapReadonlyTypeArgText } from './functions/unwrap-readonly.mjs';
22
- export { wrapWithParentheses } from './functions/wrap-with-parentheses.mjs';
23
- export { replaceNodeWithDebugPrint } from './utils/replace-with-debug.mjs';
24
- //# sourceMappingURL=index.mjs.map
1
+ export * from './ast-transformers/index.mjs';
2
+ export * from './constants/index.mjs';
3
+ export * from './functions/index.mjs';
4
+ export * from './utils/index.mjs';
5
+ //# sourceMappingURL=index.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;"}
1
+ {"version":3,"file":"index.mjs","sourceRoot":"","sources":["../../src/functions/index.mts"],"names":[],"mappings":"AAAA,cAAc,8BAA8B,CAAC;AAC7C,cAAc,uBAAuB,CAAC;AACtC,cAAc,uBAAuB,CAAC;AACtC,cAAc,mBAAmB,CAAC"}
@@ -1,2 +1,2 @@
1
- export { replaceNodeWithDebugPrint } from './replace-with-debug.mjs';
2
- //# sourceMappingURL=index.mjs.map
1
+ export * from './replace-with-debug.mjs';
2
+ //# sourceMappingURL=index.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","sources":[],"sourcesContent":[],"names":[],"mappings":""}
1
+ {"version":3,"file":"index.mjs","sourceRoot":"","sources":["../../../src/functions/utils/index.mts"],"names":[],"mappings":"AAAA,cAAc,0BAA0B,CAAC"}
@@ -1,7 +1,5 @@
1
- const replaceNodeWithDebugPrint = (node, newNodeText) => {
2
- console.debug(`${node.getText()} -> ${newNodeText}`);
3
- node.replaceWithText(newNodeText);
1
+ export const replaceNodeWithDebugPrint = (node, newNodeText) => {
2
+ console.debug(`${node.getText()} -> ${newNodeText}`);
3
+ node.replaceWithText(newNodeText);
4
4
  };
5
-
6
- export { replaceNodeWithDebugPrint };
7
- //# sourceMappingURL=replace-with-debug.mjs.map
5
+ //# sourceMappingURL=replace-with-debug.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"replace-with-debug.mjs","sources":["../../../src/functions/utils/replace-with-debug.mts"],"sourcesContent":["import type * as tsm from 'ts-morph';\n\nexport const replaceNodeWithDebugPrint = (\n node: tsm.Node,\n newNodeText: string,\n): void => {\n console.debug(`${node.getText()} -> ${newNodeText}`);\n\n node.replaceWithText(newNodeText);\n};\n"],"names":[],"mappings":"AAEO,MAAM,yBAAA,GAA4B,CACvC,IAAA,EACA,WAAA,KACS;AACT,EAAA,OAAA,CAAQ,MAAM,CAAA,EAAG,IAAA,CAAK,SAAS,CAAA,IAAA,EAAO,WAAW,CAAA,CAAE,CAAA;AAEnD,EAAA,IAAA,CAAK,gBAAgB,WAAW,CAAA;AAClC;;;;"}
1
+ {"version":3,"file":"replace-with-debug.mjs","sourceRoot":"","sources":["../../../src/functions/utils/replace-with-debug.mts"],"names":[],"mappings":"AAEA,MAAM,CAAC,MAAM,yBAAyB,GAAG,CACvC,IAAc,EACd,WAAmB,EACb,EAAE;IACR,OAAO,CAAC,KAAK,CAAC,GAAG,IAAI,CAAC,OAAO,EAAE,OAAO,WAAW,EAAE,CAAC,CAAC;IAErD,IAAI,CAAC,eAAe,CAAC,WAAW,CAAC,CAAC;AACpC,CAAC,CAAC"}
package/dist/index.mjs CHANGED
@@ -1,24 +1,2 @@
1
- export { appendAsConstTransformer } from './functions/ast-transformers/append-as-const.mjs';
2
- export { convertInterfaceToTypeTransformer } from './functions/ast-transformers/convert-interface-to-type.mjs';
3
- export { convertToReadonlyTransformer } from './functions/ast-transformers/convert-to-readonly.mjs';
4
- export { enableNoUncheckedIndexedAccessTransformer } from './functions/ast-transformers/enable-no-unchecked-indexed-access.mjs';
5
- export { compareUnionIntersectionTypes } from './functions/ast-transformers/readonly-transformer-helpers/compare-union-types.mjs';
6
- export { invalidDeepReadonlyTypeName } from './functions/ast-transformers/readonly-transformer-helpers/constants.mjs';
7
- export { groupUnionIntersectionTypes } from './functions/ast-transformers/readonly-transformer-helpers/group-union-types.mjs';
8
- export { nextReadonlyContext } from './functions/ast-transformers/readonly-transformer-helpers/readonly-context.mjs';
9
- export { replaceAnyWithUnknownTransformer } from './functions/ast-transformers/replace-any-with-unknown.mjs';
10
- export { replaceRecordWithUnknownRecordTransformer } from './functions/ast-transformers/replace-record-with-unknown-record.mjs';
11
- export { transformSourceCode } from './functions/ast-transformers/transform-source-code.mjs';
12
- export { IGNORE_FILE_COMMENT_PREFIXES, IGNORE_LINE_COMMENT_PREFIXES } from './functions/constants/ignore-comment-text.mjs';
13
- export { hasDisableNextLineComment } from './functions/functions/has-disable-next-line-comment.mjs';
14
- export { isAsConstNode } from './functions/functions/is-as-const-node.mjs';
15
- export { isAtomicTypeNode } from './functions/functions/is-atomic-type-node.mjs';
16
- export { isConditionalTypeDistributionGuard } from './functions/functions/is-conditional-type-distribution-guard.mjs';
17
- export { isReadonlyArrayTypeNode, isReadonlyTupleOrArrayTypeNode, isReadonlyTupleTypeNode, isReadonlyTypeReferenceNode, isShallowReadonlyTypeNode } from './functions/functions/is-readonly-node.mjs';
18
- export { isSpreadNamedTupleMemberNode, isSpreadParameterNode } from './functions/functions/is-spread-parameter-node.mjs';
19
- export { removeParentheses } from './functions/functions/remove-parentheses.mjs';
20
- export { shouldAvoidParenthesesForReadonly } from './functions/functions/should-avoid-parentheses-for-readonly.mjs';
21
- export { unwrapReadonlyTypeArgText } from './functions/functions/unwrap-readonly.mjs';
22
- export { wrapWithParentheses } from './functions/functions/wrap-with-parentheses.mjs';
23
- export { replaceNodeWithDebugPrint } from './functions/utils/replace-with-debug.mjs';
24
- //# sourceMappingURL=index.mjs.map
1
+ export * from './functions/index.mjs';
2
+ //# sourceMappingURL=index.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;"}
1
+ {"version":3,"file":"index.mjs","sourceRoot":"","sources":["../src/index.mts"],"names":[],"mappings":"AAAA,cAAc,uBAAuB,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ts-codemod-lib",
3
- "version": "3.3.0",
3
+ "version": "3.3.2",
4
4
  "private": false,
5
5
  "keywords": [
6
6
  "typescript",
@@ -38,45 +38,44 @@
38
38
  "LICENSE"
39
39
  ],
40
40
  "dependencies": {
41
- "ts-data-forge": "^14.4.0",
41
+ "ts-data-forge": "^14.7.1",
42
42
  "ts-morph": "^28.0.0",
43
43
  "ts-type-forge": "^9.2.2"
44
44
  },
45
45
  "devDependencies": {
46
- "@types/node": "26.4.0",
47
- "@vitest/browser-playwright": "4.1.11",
48
- "@vitest/coverage-v8": "4.1.11",
49
- "@vitest/ui": "4.1.11",
50
- "cspell": "10.0.1",
46
+ "@types/node": "26.5.0",
47
+ "@vitest/browser-playwright": "5.0.0",
48
+ "@vitest/coverage-v8": "5.0.0",
49
+ "@vitest/ui": "5.0.0",
50
+ "cspell": "10.2.1",
51
51
  "dedent": "1.7.2",
52
52
  "esbuild": "0.28.2",
53
- "eslint": "10.8.1",
54
- "eslint-config-typed": "5.10.0",
55
- "eslint-plugin-ts-data-forge": "0.5.0",
56
- "eslint-plugin-ts-fortress": "1.0.4",
57
- "eslint-plugin-ts-type-forge": "0.7.0",
53
+ "eslint": "10.9.1",
54
+ "eslint-config-typed": "5.11.7",
55
+ "eslint-plugin-ts-data-forge": "0.7.4",
56
+ "eslint-plugin-ts-fortress": "1.2.2",
57
+ "eslint-plugin-ts-type-forge": "0.8.3",
58
58
  "jiti": "2.7.0",
59
59
  "npm-run-all2": "9.0.3",
60
60
  "playwright": "1.62.1",
61
61
  "prettier": "3.9.6",
62
62
  "prettier-plugin-organize-imports": "4.3.0",
63
63
  "prettier-plugin-packagejson": "3.0.2",
64
- "rollup": "4.62.4",
65
- "ts-repo-utils": "10.3.2",
66
- "tsx": "4.23.12",
64
+ "ts-repo-utils": "10.6.2",
65
+ "tsx": "4.23.13",
67
66
  "typedoc": "0.28.20",
68
67
  "typedoc-github-theme": "0.4.0",
69
68
  "typescript": "6.0.3",
70
69
  "typescript-native": "npm:typescript@7.0.2",
71
- "vite": "8.2.1",
72
- "vitest": "4.1.11"
70
+ "vite": "8.2.2",
71
+ "vitest": "5.0.0"
73
72
  },
74
73
  "engines": {
75
74
  "node": ">=22.22.2",
76
75
  "pnpm": ">=9.0.0"
77
76
  },
78
77
  "volta": {
79
- "node": "26.3.1"
78
+ "node": "26.8.1"
80
79
  },
81
80
  "publishConfig": {
82
81
  "access": "public",
@@ -95,6 +94,7 @@
95
94
  "cspell": "cspell \"**\" --gitignore --gitignore-root ../../ --no-progress",
96
95
  "doc": "tsx --tsconfig ../../tools/configs/tsconfig.tsx.json ./scripts/cmd/gen-docs.mts",
97
96
  "doc:embed": "tsx --tsconfig ../../tools/configs/tsconfig.tsx.json ./scripts/cmd/embed-examples.mts",
97
+ "doc:embed:jsdoc": "tsx --tsconfig ../../tools/configs/tsconfig.tsx.json ./scripts/cmd/embed-examples-in-jsdoc.mts",
98
98
  "doc:preview": "vite preview --config ./configs/vite.doc.config.mts",
99
99
  "doc:watch": "typedoc --options ./configs/typedoc.config.mjs --watch",
100
100
  "fmt": "tsx --tsconfig ../../tools/configs/tsconfig.tsx.json ../../libs/ts-repo-utils/src/cmd/format-uncommitted.mts --cwd .",
@@ -15,6 +15,7 @@ import {
15
15
  type StrictExtract,
16
16
  } from 'ts-type-forge';
17
17
  import {
18
+ hasCallOrConstructSignature,
18
19
  hasDisableNextLineComment,
19
20
  isAtomicTypeNode,
20
21
  isConditionalTypeDistributionGuard,
@@ -1032,10 +1033,24 @@ const transformTypeLiteralNode = (
1032
1033
  return;
1033
1034
  }
1034
1035
 
1036
+ // Whether this position is one where the literal is wrapped at all — a
1037
+ // context that already carries `Readonly` or `DeepReadonly` does it, and
1038
+ // inside an indexed access the modifier makes no difference to the result.
1039
+ const wrappingApplies =
1040
+ readonlyContext.type === 'none' && readonlyContext.indexedAccessDepth === 0;
1041
+
1042
+ // A call or construct signature does not survive `Readonly<T>`, so a literal
1043
+ // carrying one is left unwrapped and marked readonly member by member
1044
+ // instead. See `hasCallOrConstructSignature`.
1045
+ const skipWrapping = wrappingApplies && hasCallOrConstructSignature(node);
1046
+
1035
1047
  // Recursive processing
1036
1048
  transformMembers(
1037
1049
  node.getMembers(),
1038
- 'remove',
1050
+ // A wrapper — the one added below, or the one the context already carries —
1051
+ // is what makes the members readonly, so an inner modifier would be
1052
+ // redundant. Without one, each member has to say so itself.
1053
+ skipWrapping ? 'add' : 'remove',
1039
1054
  nextReadonlyContext({
1040
1055
  currentReadonlyContext: readonlyContext,
1041
1056
  nextReadonlyContextType: 'none',
@@ -1044,18 +1059,9 @@ const transformTypeLiteralNode = (
1044
1059
  options,
1045
1060
  );
1046
1061
 
1047
- switch (readonlyContext.type) {
1048
- case 'DeepReadonly':
1049
- case 'Readonly':
1050
- // Don't wrap with Readonly if already readonly
1051
- return;
1052
-
1053
- case 'none': {
1054
- if (readonlyContext.indexedAccessDepth === 0) {
1055
- // `{ readonly x: X, readonly y: Y } |-> Readonly<{ x: X, y: Y }>`
1056
- options.replaceNode(node, `Readonly<${node.getFullText()}>`);
1057
- }
1058
- }
1062
+ if (wrappingApplies && !skipWrapping) {
1063
+ // `{ readonly x: X, readonly y: Y } |-> Readonly<{ x: X, y: Y }>`
1064
+ options.replaceNode(node, `Readonly<${node.getFullText()}>`);
1059
1065
  }
1060
1066
  };
1061
1067
 
@@ -6,6 +6,7 @@ import dedent from 'dedent';
6
6
  import * as prettierPluginEstree from 'prettier/plugins/estree';
7
7
  import * as prettierPluginTypeScript from 'prettier/plugins/typescript';
8
8
  import * as prettier from 'prettier/standalone';
9
+ import { isString } from 'ts-data-forge';
9
10
  import {
10
11
  convertToReadonlyTransformer,
11
12
  type ReadonlyTransformerOptions,
@@ -63,7 +64,15 @@ const normalizeWhitespaceForComparison = (code: string): string => {
63
64
  // 1. Protect newlines immediately following line comments
64
65
  const protectedCode = code.replaceAll(
65
66
  /(\/\/.*?)\r?\n/gu,
66
- (_match, comment: string) => `${comment}${placeholder}`,
67
+ // The strict standard library types a capture group as `unknown`: an
68
+ // optional group is `undefined` when it does not participate, so `string`
69
+ // is not something the signature can promise. This group is not optional,
70
+ // so the narrowing never actually fails — it just has to be written.
71
+ (match: string, ...args: readonly unknown[]): string => {
72
+ const comment = args[0];
73
+
74
+ return isString(comment) ? `${comment}${placeholder}` : match;
75
+ },
67
76
  );
68
77
 
69
78
  // 2. Collapse multiple whitespace characters (including unprotected newlines) into a single space
@@ -3246,6 +3255,125 @@ describe(convertToReadonlyTransformer, () => {
3246
3255
  ])('$name', testFn);
3247
3256
  });
3248
3257
 
3258
+ describe('Call and construct signatures', () => {
3259
+ // `Readonly<T>` is a mapped type over `keyof T`, and a call or construct
3260
+ // signature is not a property, so wrapping a type literal that carries one
3261
+ // silently drops it — the value stops being callable. See #1881.
3262
+ test.each([
3263
+ {
3264
+ name: 'Overloaded call signatures are not wrapped',
3265
+ source: dedent`
3266
+ const panic: {
3267
+ (message: string, options?: { cause?: unknown }): never;
3268
+ (error: Error): never;
3269
+ } = impl;
3270
+ `,
3271
+ expected: dedent`
3272
+ const panic: {
3273
+ (message: string, options?: Readonly<{ cause?: unknown }>): never;
3274
+ (error: Error): never;
3275
+ } = impl;
3276
+ `,
3277
+ },
3278
+ {
3279
+ name: 'A single call signature is not wrapped',
3280
+ source: dedent`
3281
+ type F = { (arg: number[]): string[] };
3282
+ `,
3283
+ expected: dedent`
3284
+ type F = { (arg: readonly number[]): readonly string[] };
3285
+ `,
3286
+ },
3287
+ {
3288
+ name: 'A construct signature is not wrapped',
3289
+ source: dedent`
3290
+ type C = { new (arg: string[]): { prop: number[] } };
3291
+ `,
3292
+ expected: dedent`
3293
+ type C = { new (arg: readonly string[]): Readonly<{ prop: readonly number[] }> };
3294
+ `,
3295
+ },
3296
+ {
3297
+ name: 'Members alongside a call signature are marked readonly in place',
3298
+ source: dedent`
3299
+ type F = {
3300
+ (arg: number[]): void;
3301
+ prop: string[];
3302
+ [key: string]: unknown;
3303
+ };
3304
+ `,
3305
+ expected: dedent`
3306
+ type F = {
3307
+ (arg: readonly number[]): void;
3308
+ readonly prop: readonly string[];
3309
+ readonly [key: string]: unknown;
3310
+ };
3311
+ `,
3312
+ },
3313
+ {
3314
+ name: 'Members alongside a construct signature are marked readonly in place',
3315
+ source: dedent`
3316
+ type C = {
3317
+ new (arg: string[]): unknown;
3318
+ readonly prop: number[];
3319
+ };
3320
+ `,
3321
+ expected: dedent`
3322
+ type C = {
3323
+ new (arg: readonly string[]): unknown;
3324
+ readonly prop: readonly number[];
3325
+ };
3326
+ `,
3327
+ },
3328
+ {
3329
+ name: 'A callable type literal in a union is left alone',
3330
+ source: dedent`
3331
+ type T = { (): void } | { a: number[] };
3332
+ `,
3333
+ expected: dedent`
3334
+ type T = { (): void } | Readonly<{ a: readonly number[] }>;
3335
+ `,
3336
+ },
3337
+ {
3338
+ name: 'A callable type literal in an intersection is left alone',
3339
+ source: dedent`
3340
+ type T = { (): void } & { a: number[] };
3341
+ `,
3342
+ expected: dedent`
3343
+ type T = { (): void } & Readonly<{ a: readonly number[] }>;
3344
+ `,
3345
+ },
3346
+ {
3347
+ name: 'A nested callable type literal is left alone',
3348
+ source: dedent`
3349
+ type T = { fn: { (): void }; a: number[] };
3350
+ `,
3351
+ expected: dedent`
3352
+ type T = Readonly<{ fn: { (): void }; a: readonly number[] }>;
3353
+ `,
3354
+ },
3355
+ ])('$name', testFn);
3356
+
3357
+ test('a callable type literal reaches a fixed point', () => {
3358
+ const code = dedent`
3359
+ const panic: {
3360
+ (message: string, options?: { cause?: unknown }): never;
3361
+ (error: Error): never;
3362
+ } = impl;
3363
+ `;
3364
+
3365
+ const once = transformSourceCode(code, false, [
3366
+ convertToReadonlyTransformer(),
3367
+ ]);
3368
+
3369
+ const twice = transformSourceCode(once, false, [
3370
+ convertToReadonlyTransformer(),
3371
+ ]);
3372
+
3373
+ expect(twice).toBe(once);
3374
+ });
3375
+ });
3376
+
3249
3377
  describe('Generic Types', () => {
3250
3378
  test.each([
3251
3379
  {
@@ -37,17 +37,40 @@ const TRANSFORMER_NAME = 'enable-no-unchecked-indexed-access';
37
37
  * as the `if` of `const v = xs[0]!; if (v === undefined) …` now is.
38
38
  *
39
39
  * @example
40
+ *
40
41
  * ```ts
41
- * declare const xs: readonly number[];
42
- * declare const pair: readonly [number, number];
43
- * declare const rec: Record<string, number>;
44
- * declare const known: { a: number };
42
+ * const before = dedent`
43
+ * declare const xs: readonly number[];
44
+ * declare const pair: readonly [number, number];
45
+ * declare const rec: Record<string, number>;
46
+ * declare const known: { a: number };
47
+ *
48
+ * xs[0].toFixed();
49
+ * rec['a'].toFixed();
50
+ * rec.a.toFixed();
51
+ * pair[1].toFixed();
52
+ * known.a.toFixed();
53
+ * `;
54
+ *
55
+ * const after = transformSourceCode(before, false, [
56
+ * enableNoUncheckedIndexedAccessTransformer(),
57
+ * ]);
58
+ *
59
+ * assert.deepStrictEqual(
60
+ * after.trim(),
61
+ * dedent`
62
+ * declare const xs: readonly number[];
63
+ * declare const pair: readonly [number, number];
64
+ * declare const rec: Record<string, number>;
65
+ * declare const known: { a: number };
45
66
  *
46
- * xs[0].toFixed(); // -> xs[0]!.toFixed();
47
- * rec['a'].toFixed(); // -> rec['a']!.toFixed();
48
- * rec.a.toFixed(); // -> rec.a!.toFixed();
49
- * pair[1].toFixed(); // -> pair[1].toFixed(); (guaranteed to exist)
50
- * known.a.toFixed(); // -> known.a.toFixed(); (guaranteed to exist)
67
+ * xs[0]!.toFixed();
68
+ * rec['a']!.toFixed();
69
+ * rec.a!.toFixed();
70
+ * pair[1].toFixed();
71
+ * known.a.toFixed();
72
+ * `,
73
+ * );
51
74
  * ```
52
75
  */
53
76
  export const enableNoUncheckedIndexedAccessTransformer = (