ts-codemod-lib 3.4.3 → 3.5.0

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 (50) hide show
  1. package/dist/functions/ast-transformers/append-as-const.d.mts.map +1 -1
  2. package/dist/functions/ast-transformers/append-as-const.mjs +38 -6
  3. package/dist/functions/ast-transformers/append-as-const.mjs.map +1 -1
  4. package/dist/functions/ast-transformers/convert-interface-to-type.d.mts.map +1 -1
  5. package/dist/functions/ast-transformers/convert-interface-to-type.mjs +2 -1
  6. package/dist/functions/ast-transformers/convert-interface-to-type.mjs.map +1 -1
  7. package/dist/functions/ast-transformers/convert-to-readonly.d.mts.map +1 -1
  8. package/dist/functions/ast-transformers/convert-to-readonly.mjs +2 -2
  9. package/dist/functions/ast-transformers/convert-to-readonly.mjs.map +1 -1
  10. package/dist/functions/ast-transformers/enable-no-unchecked-indexed-access.d.mts +1 -1
  11. package/dist/functions/ast-transformers/enable-no-unchecked-indexed-access.mjs +4 -0
  12. package/dist/functions/ast-transformers/enable-no-unchecked-indexed-access.mjs.map +1 -1
  13. package/dist/functions/ast-transformers/replace-any-with-unknown.d.mts.map +1 -1
  14. package/dist/functions/ast-transformers/replace-any-with-unknown.mjs +3 -2
  15. package/dist/functions/ast-transformers/replace-any-with-unknown.mjs.map +1 -1
  16. package/dist/functions/ast-transformers/replace-record-with-unknown-record.d.mts.map +1 -1
  17. package/dist/functions/ast-transformers/replace-record-with-unknown-record.mjs +6 -5
  18. package/dist/functions/ast-transformers/replace-record-with-unknown-record.mjs.map +1 -1
  19. package/dist/functions/functions/has-disable-next-line-comment.d.mts.map +1 -1
  20. package/dist/functions/functions/has-disable-next-line-comment.mjs +56 -4
  21. package/dist/functions/functions/has-disable-next-line-comment.mjs.map +1 -1
  22. package/dist/functions/functions/wrap-with-parentheses.d.mts +5 -5
  23. package/dist/functions/utils/index.d.mts +1 -0
  24. package/dist/functions/utils/index.d.mts.map +1 -1
  25. package/dist/functions/utils/index.mjs +1 -0
  26. package/dist/functions/utils/index.mjs.map +1 -1
  27. package/dist/functions/utils/replace-node-with-text.d.mts +21 -0
  28. package/dist/functions/utils/replace-node-with-text.d.mts.map +1 -0
  29. package/dist/functions/utils/replace-node-with-text.mjs +22 -0
  30. package/dist/functions/utils/replace-node-with-text.mjs.map +1 -0
  31. package/dist/functions/utils/replace-with-debug.d.mts.map +1 -1
  32. package/dist/functions/utils/replace-with-debug.mjs +2 -1
  33. package/dist/functions/utils/replace-with-debug.mjs.map +1 -1
  34. package/package.json +15 -15
  35. package/src/functions/ast-transformers/append-as-const.mts +57 -6
  36. package/src/functions/ast-transformers/append-as-const.test.mts +119 -0
  37. package/src/functions/ast-transformers/convert-interface-to-type.mts +2 -1
  38. package/src/functions/ast-transformers/convert-to-readonly.mts +5 -2
  39. package/src/functions/ast-transformers/convert-to-readonly.test.mts +9 -20
  40. package/src/functions/ast-transformers/enable-no-unchecked-indexed-access.mts +5 -1
  41. package/src/functions/ast-transformers/enable-no-unchecked-indexed-access.test.mts +36 -0
  42. package/src/functions/ast-transformers/replace-any-with-unknown.mts +3 -2
  43. package/src/functions/ast-transformers/replace-record-with-unknown-record.mts +7 -5
  44. package/src/functions/functions/has-disable-next-line-comment.mts +79 -6
  45. package/src/functions/functions/has-disable-next-line-comment.test.mts +96 -0
  46. package/src/functions/functions/wrap-with-parentheses.mts +5 -5
  47. package/src/functions/utils/index.mts +1 -0
  48. package/src/functions/utils/replace-node-with-text.mts +37 -0
  49. package/src/functions/utils/replace-node-with-text.test.mts +89 -0
  50. package/src/functions/utils/replace-with-debug.mts +2 -1
@@ -1 +1 @@
1
- {"version":3,"file":"has-disable-next-line-comment.d.mts","sourceRoot":"","sources":["../../../src/functions/functions/has-disable-next-line-comment.mts"],"names":[],"mappings":"AAAA,OAAO,KAAK,GAAG,MAAM,UAAU,CAAC;AAGhC;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,yBAAyB,SAC9B,GAAG,CAAC,IAAI,oBACI,MAAM,KACvB,OAwEF,CAAC"}
1
+ {"version":3,"file":"has-disable-next-line-comment.d.mts","sourceRoot":"","sources":["../../../src/functions/functions/has-disable-next-line-comment.mts"],"names":[],"mappings":"AACA,OAAO,KAAK,GAAG,MAAM,UAAU,CAAC;AAGhC;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,yBAAyB,SAC9B,GAAG,CAAC,IAAI,oBACI,MAAM,KACvB,OA8EF,CAAC"}
@@ -1,3 +1,4 @@
1
+ import { Arr } from 'ts-data-forge';
1
2
  import * as tsm from 'ts-morph';
2
3
  import { IGNORE_LINE_COMMENT_PREFIXES } from '../constants/index.mjs';
3
4
 
@@ -15,20 +16,25 @@ import { IGNORE_LINE_COMMENT_PREFIXES } from '../constants/index.mjs';
15
16
 
16
17
 
17
18
  export const hasDisableNextLineComment = (node, transformerName) => {
18
- const nodeStartLine = node.getStartLineNumber();
19
+
20
+
21
+ const leadingCommentRanges = node.getLeadingCommentRanges();
22
+ if (Arr.isEmpty(leadingCommentRanges)) {
23
+ return false;
24
+ }
25
+ const lineNumberAt = lineNumberLookup(node.getSourceFile().compilerNode);
26
+ const nodeStartLine = lineNumberAt(node.getStart());
19
27
 
20
28
  if (nodeStartLine <= 1) {
21
29
  return false;
22
30
  }
23
- const sourceFile = node.getSourceFile();
24
- const leadingCommentRanges = node.getLeadingCommentRanges();
25
31
 
26
32
  for (let mut_i = leadingCommentRanges.length - 1; mut_i >= 0; mut_i--) {
27
33
 
28
34
  const commentRange = leadingCommentRanges[mut_i];
29
35
 
30
36
  const commentEndPos = commentRange.getEnd();
31
- const commentEndLine = sourceFile.getLineAndColumnAtPos(commentEndPos).line;
37
+ const commentEndLine = lineNumberAt(commentEndPos);
32
38
 
33
39
  if (nodeStartLine === commentEndLine + 1) {
34
40
 
@@ -69,4 +75,50 @@ export const hasDisableNextLineComment = (node, transformerName) => {
69
75
  }
70
76
  return false;
71
77
  };
78
+
79
+
80
+
81
+
82
+
83
+
84
+
85
+
86
+
87
+
88
+
89
+
90
+ const lineNumberLookup = (
91
+
92
+ sourceFile) => {
93
+ const cached = newlinePositionsCache.get(sourceFile);
94
+ const newlinePositions = cached ?? newlinePositionsOf(sourceFile.text);
95
+ if (cached === undefined) {
96
+ newlinePositionsCache.set(sourceFile, newlinePositions);
97
+ }
98
+ return (pos) => countLessThan(newlinePositions, pos) + 1;
99
+ };
100
+ const newlinePositionsCache = new WeakMap();
101
+ const newlinePositionsOf = (text) => {
102
+ const mut_positions = [];
103
+ for (let mut_index = text.indexOf('\n'); mut_index !== -1; mut_index = text.indexOf('\n', mut_index + 1)) {
104
+ mut_positions.push(mut_index);
105
+ }
106
+ return mut_positions;
107
+ };
108
+
109
+ const countLessThan = (sorted, value) => {
110
+ let mut_low = 0;
111
+ let mut_high = sorted.length;
112
+ while (mut_low < mut_high) {
113
+ const mid = Math.floor((mut_low + mut_high) / 2);
114
+
115
+ if (sorted[mid] < value) {
116
+ mut_low = mid + 1;
117
+ }
118
+ else {
119
+ mut_high = mid;
120
+ }
121
+ }
122
+ return mut_low;
123
+ };
72
124
  //# sourceMappingURL=has-disable-next-line-comment.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"has-disable-next-line-comment.mjs","sourceRoot":"","sources":["../../../src/functions/functions/has-disable-next-line-comment.mts"],"names":[],"mappings":"AAAA,OAAO,KAAK,GAAG,MAAM,UAAU,CAAC;AAChC,OAAO,EAAE,4BAA4B,EAAE,MAAM,wBAAwB,CAAC;AAEtE;;;;;;;;;;;;;GAaG;AACH,MAAM,CAAC,MAAM,yBAAyB,GAAG,CACvC,IAAc,EACd,eAAwB,EACf,EAAE;IACX,MAAM,aAAa,GAAG,IAAI,CAAC,kBAAkB,EAAE,CAAC;IAEhD,8CAA8C;IAC9C,IAAI,aAAa,IAAI,CAAC,EAAE,CAAC;QACvB,OAAO,KAAK,CAAC;IACf,CAAC;IAED,MAAM,UAAU,GAAG,IAAI,CAAC,aAAa,EAAE,CAAC,CAAC,qBAAqB;IAE9D,MAAM,oBAAoB,GAAG,IAAI,CAAC,uBAAuB,EAAE,CAAC;IAE5D,yEAAyE;IACzE,KAAK,IAAI,KAAK,GAAG,oBAAoB,CAAC,MAAM,GAAG,CAAC,EAAE,KAAK,IAAI,CAAC,EAAE,KAAK,EAAE,EAAE,CAAC;QACtE,oEAAoE;QACpE,MAAM,YAAY,GAAG,oBAAoB,CAAC,KAAK,CAAE,CAAC;QAElD,qDAAqD;QACrD,MAAM,aAAa,GAAG,YAAY,CAAC,MAAM,EAAE,CAAC;QAE5C,MAAM,cAAc,GAAG,UAAU,CAAC,qBAAqB,CAAC,aAAa,CAAC,CAAC,IAAI,CAAC;QAE5E,4DAA4D;QAC5D,IAAI,aAAa,KAAK,cAAc,GAAG,CAAC,EAAE,CAAC;YACzC,0EAA0E;YAC1E,IAAI,YAAY,CAAC,OAAO,EAAE,KAAK,GAAG,CAAC,UAAU,CAAC,uBAAuB,EAAE,CAAC;gBACtE,MAAM,WAAW,GAAG,YAAY,CAAC,OAAO,EAAE,CAAC,IAAI,EAAE,CAAC;gBAElD,qEAAqE;gBACrE,MAAM,aAAa,GAAG,4BAA4B,CAAC,IAAI,CAAC,CAAC,MAAM,EAAE,EAAE,CACjE,WAAW,CAAC,QAAQ,CAAC,MAAM,CAAC,CAC7B,CAAC;gBAEF,IAAI,aAAa,KAAK,SAAS,EAAE,CAAC;oBAChC,iFAAiF;oBACjF,IAAI,eAAe,KAAK,SAAS,EAAE,CAAC;wBAClC,OAAO,IAAI,CAAC;oBACd,CAAC;oBAED,oCAAoC;oBACpC,MAAM,WAAW,GAAG,WAAW;yBAC5B,KAAK,CAAC,WAAW,CAAC,OAAO,CAAC,aAAa,CAAC,CAAC;yBACzC,OAAO,CAAC,aAAa,EAAE,EAAE,CAAC;yBAC1B,IAAI,EAAE,CAAC;oBAEV,mEAAmE;oBACnE,IAAI,WAAW,KAAK,EAAE,EAAE,CAAC;wBACvB,OAAO,IAAI,CAAC;oBACd,CAAC;oBAED,+DAA+D;oBAC/D,MAAM,kBAAkB,GAAG,WAAW;yBACnC,KAAK,CAAC,GAAG,CAAC;yBACV,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC;oBAE9B,OAAO,kBAAkB,CAAC,QAAQ,CAAC,eAAe,CAAC,CAAC;gBACtD,CAAC;YACH,CAAC;YAED,iEAAiE;YACjE,8DAA8D;YAC9D,6EAA6E;YAC7E,OAAO,KAAK,CAAC;QACf,CAAC;QAED,6EAA6E;QAC7E,IAAI,cAAc,GAAG,aAAa,GAAG,CAAC,EAAE,CAAC;YACvC,MAAM;QACR,CAAC;IACH,CAAC;IAED,OAAO,KAAK,CAAC;AACf,CAAC,CAAC"}
1
+ {"version":3,"file":"has-disable-next-line-comment.mjs","sourceRoot":"","sources":["../../../src/functions/functions/has-disable-next-line-comment.mts"],"names":[],"mappings":"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"}
@@ -6,17 +6,17 @@
6
6
  * @example
7
7
  *
8
8
  * ```ts
9
- * assert.deepStrictEqual(wrapWithParentheses('A'), '(A)');
9
+ * assert.strictEqual(wrapWithParentheses('A'), '(A)');
10
10
  *
11
11
  * // already wrapped, so not '((A))'
12
- * assert.deepStrictEqual(wrapWithParentheses('(A)'), '(A)');
12
+ * assert.strictEqual(wrapWithParentheses('(A)'), '(A)');
13
13
  *
14
- * assert.deepStrictEqual(wrapWithParentheses('A | B'), '(A | B)');
14
+ * assert.strictEqual(wrapWithParentheses('A | B'), '(A | B)');
15
15
  *
16
- * assert.deepStrictEqual(wrapWithParentheses('(A | B)'), '(A | B)');
16
+ * assert.strictEqual(wrapWithParentheses('(A | B)'), '(A | B)');
17
17
  *
18
18
  * // the outer parentheses do not span everything, so a pair is added
19
- * assert.deepStrictEqual(wrapWithParentheses('(A) | (B)'), '((A) | (B))');
19
+ * assert.strictEqual(wrapWithParentheses('(A) | (B)'), '((A) | (B))');
20
20
  * ```
21
21
  */
22
22
  export declare const wrapWithParentheses: (nodeStr: string) => string;
@@ -1,2 +1,3 @@
1
+ export * from './replace-node-with-text.mjs';
1
2
  export * from './replace-with-debug.mjs';
2
3
  //# sourceMappingURL=index.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.mts","sourceRoot":"","sources":["../../../src/functions/utils/index.mts"],"names":[],"mappings":"AAAA,cAAc,0BAA0B,CAAC"}
1
+ {"version":3,"file":"index.d.mts","sourceRoot":"","sources":["../../../src/functions/utils/index.mts"],"names":[],"mappings":"AAAA,cAAc,8BAA8B,CAAC;AAC7C,cAAc,0BAA0B,CAAC"}
@@ -1,2 +1,3 @@
1
+ export * from './replace-node-with-text.mjs';
1
2
  export * from './replace-with-debug.mjs';
2
3
  //# sourceMappingURL=index.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","sourceRoot":"","sources":["../../../src/functions/utils/index.mts"],"names":[],"mappings":"AAAA,cAAc,0BAA0B,CAAC"}
1
+ {"version":3,"file":"index.mjs","sourceRoot":"","sources":["../../../src/functions/utils/index.mts"],"names":[],"mappings":"AAAA,cAAc,8BAA8B,CAAC;AAC7C,cAAc,0BAA0B,CAAC"}
@@ -0,0 +1,21 @@
1
+ import type * as tsm from 'ts-morph';
2
+ /**
3
+ * `node.replaceWithText(newNodeText)`, sparing a one-line text the indentation
4
+ * lookup.
5
+ *
6
+ * `replaceWithText` writes the text through a writer whose indentation it
7
+ * looks up first, for the lines after the first one, and the lookup reparses
8
+ * the whole file in the language service: on top of the reparse every
9
+ * replacement makes anyway, once per replacement, so it is quadratic in the
10
+ * size of the file. A text with no `\n` has no line after the first, the
11
+ * indentation is never applied, and a plain writer from the same project
12
+ * writes the same text — so the lookup is skipped. A multi-line text takes
13
+ * the original path.
14
+ *
15
+ * The writer is passed as `replaceWithText`'s second argument, which its
16
+ * declaration leaves out and ts-morph passes itself when it sets an
17
+ * expression. Were it ever dropped, the method would look the indentation up
18
+ * as before: slower, never different.
19
+ */
20
+ export declare const replaceNodeWithText: (node: tsm.Node, newNodeText: string) => tsm.Node;
21
+ //# sourceMappingURL=replace-node-with-text.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"replace-node-with-text.d.mts","sourceRoot":"","sources":["../../../src/functions/utils/replace-node-with-text.mts"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,GAAG,MAAM,UAAU,CAAC;AAErC;;;;;;;;;;;;;;;;;GAiBG;AACH,eAAO,MAAM,mBAAmB,SACxB,GAAG,CAAC,IAAI,eACD,MAAM,KAClB,GAAG,CAAC,IAOA,CAAC"}
@@ -0,0 +1,22 @@
1
+
2
+
3
+
4
+
5
+
6
+
7
+
8
+
9
+
10
+
11
+
12
+
13
+
14
+
15
+
16
+
17
+
18
+
19
+ export const replaceNodeWithText = (node, newNodeText) => newNodeText.includes('\n')
20
+ ? node.replaceWithText(newNodeText)
21
+ : node.replaceWithText.call(node, newNodeText, node.getProject().createWriter());
22
+ //# sourceMappingURL=replace-node-with-text.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"replace-node-with-text.mjs","sourceRoot":"","sources":["../../../src/functions/utils/replace-node-with-text.mts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,CAAC,MAAM,mBAAmB,GAAG,CACjC,IAAc,EACd,WAAmB,EACT,EAAE,CACZ,WAAW,CAAC,QAAQ,CAAC,IAAI,CAAC;IACxB,CAAC,CAAC,IAAI,CAAC,eAAe,CAAC,WAAW,CAAC;IACnC,CAAC,CAAE,IAAI,CAAC,eAA6C,CAAC,IAAI,CACtD,IAAI,EACJ,WAAW,EACX,IAAI,CAAC,UAAU,EAAE,CAAC,YAAY,EAAE,CACjC,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"replace-with-debug.d.mts","sourceRoot":"","sources":["../../../src/functions/utils/replace-with-debug.mts"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,GAAG,MAAM,UAAU,CAAC;AAErC,eAAO,MAAM,yBAAyB,SAC9B,GAAG,CAAC,IAAI,eACD,MAAM,KAClB,IAIF,CAAC"}
1
+ {"version":3,"file":"replace-with-debug.d.mts","sourceRoot":"","sources":["../../../src/functions/utils/replace-with-debug.mts"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,GAAG,MAAM,UAAU,CAAC;AAGrC,eAAO,MAAM,yBAAyB,SAC9B,GAAG,CAAC,IAAI,eACD,MAAM,KAClB,IAIF,CAAC"}
@@ -1,5 +1,6 @@
1
+ import { replaceNodeWithText } from './replace-node-with-text.mjs';
1
2
  export const replaceNodeWithDebugPrint = (node, newNodeText) => {
2
3
  console.debug(`${node.getText()} -> ${newNodeText}`);
3
- node.replaceWithText(newNodeText);
4
+ replaceNodeWithText(node, newNodeText);
4
5
  };
5
6
  //# sourceMappingURL=replace-with-debug.mjs.map
@@ -1 +1 @@
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"}
1
+ {"version":3,"file":"replace-with-debug.mjs","sourceRoot":"","sources":["../../../src/functions/utils/replace-with-debug.mts"],"names":[],"mappings":"AACA,OAAO,EAAE,mBAAmB,EAAE,MAAM,8BAA8B,CAAC;AAEnE,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,mBAAmB,CAAC,IAAI,EAAE,WAAW,CAAC,CAAC;AACzC,CAAC,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ts-codemod-lib",
3
- "version": "3.4.3",
3
+ "version": "3.5.0",
4
4
  "private": false,
5
5
  "keywords": [
6
6
  "typescript",
@@ -38,7 +38,7 @@
38
38
  "LICENSE"
39
39
  ],
40
40
  "dependencies": {
41
- "ts-data-forge": "^14.7.1",
41
+ "ts-data-forge": "^14.8.0",
42
42
  "ts-morph": "^28.0.0",
43
43
  "ts-type-forge": "^9.2.2"
44
44
  },
@@ -51,13 +51,13 @@
51
51
  "dedent": "1.7.2",
52
52
  "esbuild": "0.28.2",
53
53
  "eslint": "10.12.0",
54
- "eslint-config-typed": "5.17.2",
55
- "eslint-plugin-ts-data-forge": "0.8.4",
56
- "eslint-plugin-ts-fortress": "1.3.1",
57
- "eslint-plugin-ts-type-forge": "0.8.6",
54
+ "eslint-config-typed": "5.19.0",
55
+ "eslint-plugin-ts-data-forge": "0.9.0",
56
+ "eslint-plugin-ts-fortress": "1.3.4",
57
+ "eslint-plugin-ts-type-forge": "0.8.9",
58
58
  "jiti": "2.7.0",
59
59
  "npm-run-all2": "9.0.3",
60
- "playwright": "1.63.0",
60
+ "playwright": "1.64.0",
61
61
  "prettier": "3.9.9",
62
62
  "prettier-plugin-organize-imports": "4.3.0",
63
63
  "prettier-plugin-packagejson": "3.0.2",
@@ -67,7 +67,7 @@
67
67
  "typedoc-github-theme": "0.4.0",
68
68
  "typescript": "6.0.3",
69
69
  "typescript-native": "npm:typescript@7.0.2",
70
- "vite": "8.3.2",
70
+ "vite": "8.3.3",
71
71
  "vitest": "5.0.3"
72
72
  },
73
73
  "engines": {
@@ -75,7 +75,7 @@
75
75
  "pnpm": ">=9.0.0"
76
76
  },
77
77
  "volta": {
78
- "node": "26.11.1"
78
+ "node": "26.12.0"
79
79
  },
80
80
  "publishConfig": {
81
81
  "access": "public",
@@ -86,11 +86,11 @@
86
86
  "check:cspell": "cspell \"**\" --gitignore --gitignore-root ../../ --no-progress",
87
87
  "check:dist": "tsx --tsconfig ../../tools/configs/tsconfig.tsx.json ./scripts/cmd/check-dist.mts",
88
88
  "check:ext": "tsx --tsconfig ../../tools/configs/tsconfig.tsx.json ./scripts/cmd/check-ext.mts",
89
- "check:lint": "oxlint . && eslint .",
90
- "check:test": "pnpm run z:vitest:node run",
91
- "check:test:browser": "pnpm run z:vitest --project='Browser' run",
92
- "check:test:cov": "pnpm run z:vitest:node run --coverage",
93
- "check:types": "node ../../node_modules/typescript-native/bin/tsc --noEmit",
89
+ "check:lint": "node ../../tools/scripts/cmd/assert-deps-built.mts && oxlint . && eslint .",
90
+ "check:test": "node ../../tools/scripts/cmd/assert-deps-built.mts && pnpm run z:vitest:node run",
91
+ "check:test:browser": "node ../../tools/scripts/cmd/assert-deps-built.mts && pnpm run z:vitest --project='Browser' run",
92
+ "check:test:cov": "node ../../tools/scripts/cmd/assert-deps-built.mts && pnpm run z:vitest:node run --coverage",
93
+ "check:types": "node ../../tools/scripts/cmd/assert-deps-built.mts && node ../../node_modules/typescript-native/bin/tsc --noEmit",
94
94
  "check-all": "tsx --tsconfig ../../tools/configs/tsconfig.tsx.json ./scripts/cmd/check-all.mts",
95
95
  "clean": "pnpm run /clean:.*/",
96
96
  "clean:logs": "npx rimraf ./*.log",
@@ -102,7 +102,7 @@
102
102
  "fix:fmt": "tsx --tsconfig ../../tools/configs/tsconfig.tsx.json ../../libs/ts-repo-utils/src/cmd/format-uncommitted.mts --cwd .",
103
103
  "fix:fmt:diff": "tsx --tsconfig ../../tools/configs/tsconfig.tsx.json ../../libs/ts-repo-utils/src/cmd/format-diff-from.mts origin/main --cwd .",
104
104
  "fix:fmt:full": "prettier --write .",
105
- "fix:lint": "oxlint . --fix && eslint . --fix",
105
+ "fix:lint": "node ../../tools/scripts/cmd/assert-deps-built.mts && oxlint . --fix && eslint . --fix",
106
106
  "fmt": "pnpm run fix:fmt",
107
107
  "gen": "run-s gen:index gen:readme gen:jsdoc",
108
108
  "gen:index": "run-s gen:index:src fix:fmt",
@@ -5,7 +5,10 @@ import {
5
5
  hasDisableNextLineComment,
6
6
  isAsConstNode,
7
7
  } from '../functions/index.mjs';
8
- import { replaceNodeWithDebugPrint } from '../utils/index.mjs';
8
+ import {
9
+ replaceNodeWithDebugPrint,
10
+ replaceNodeWithText,
11
+ } from '../utils/index.mjs';
9
12
  import type { TsMorphTransformer } from './types.mjs';
10
13
 
11
14
  const TRANSFORMER_NAME = 'append-as-const';
@@ -27,7 +30,7 @@ export const appendAsConstTransformer = (
27
30
  replaceNode:
28
31
  options?.debug === true
29
32
  ? replaceNodeWithDebugPrint
30
- : (node, newNodeText) => node.replaceWithText(newNodeText),
33
+ : (node, newNodeText) => replaceNodeWithText(node, newNodeText),
31
34
  } as const;
32
35
 
33
36
  return {
@@ -199,7 +202,10 @@ const transformNode = (
199
202
  }
200
203
 
201
204
  // Skip already type asserted nodes
202
- if (tsm.Node.isAsExpression(node) && !isAsConstNode(node)) {
205
+ if (
206
+ (tsm.Node.isAsExpression(node) || tsm.Node.isTypeAssertion(node)) &&
207
+ !isConstAssertionNode(node)
208
+ ) {
203
209
  return;
204
210
  }
205
211
 
@@ -308,8 +314,21 @@ const transformNode = (
308
314
  return;
309
315
  }
310
316
 
317
+ // `<const>x` is handled as the `x as const` it means. Appending `as const`
318
+ // to `x` in place would read as `(<const>x) as const`, and every position
319
+ // that reaches here already takes an `as` expression.
320
+ if (tsm.Node.isTypeAssertion(node) && isConstAssertionNode(node)) {
321
+ const asConstText = `${node.getExpression().getText()} as const` as const;
322
+
323
+ options.debugPrint(`${node.getText()} -> ${asConstText}`);
324
+
325
+ transformNode(replaceNodeWithText(node, asConstText), context, options);
326
+
327
+ return;
328
+ }
329
+
311
330
  // `as const` node
312
- if (isAsConstNode(node)) {
331
+ if (isConstAssertionNode(node)) {
313
332
  if (context.isDirectUnderConstInitializer) {
314
333
  // In const variable declarations, remove `as const` first and then re-append it later if needed
315
334
 
@@ -424,7 +443,9 @@ const removeAsConstInConstTypeParameterArgs = (
424
443
 
425
444
  // Cheap syntactic pre-check before touching the type checker
426
445
  if (
427
- args.every((arg) => !isAsConstNode(skipParenthesizedExpressions(arg)))
446
+ args.every(
447
+ (arg) => !isConstAssertionNode(skipParenthesizedExpressions(arg)),
448
+ )
428
449
  ) {
429
450
  continue;
430
451
  }
@@ -487,7 +508,7 @@ const removeAsConstArgsOfCall = (
487
508
  // `f(([1] as const))` is the same argument as `f([1] as const)`.
488
509
  const asConst = skipParenthesizedExpressions(argument);
489
510
 
490
- if (!isAsConstNode(asConst)) {
511
+ if (!isConstAssertionNode(asConst)) {
491
512
  continue;
492
513
  }
493
514
 
@@ -517,6 +538,31 @@ const removeAsConstArgsOfCall = (
517
538
  }
518
539
  };
519
540
 
541
+ /**
542
+ * `x as const`, or `<const>x`, the angle-bracket spelling TypeScript treats
543
+ * identically (outside `.tsx`, where it does not parse).
544
+ */
545
+ const isConstAssertionNode = (
546
+ node: tsm.Node,
547
+ ): node is tsm.AsExpression | tsm.TypeAssertion => {
548
+ if (isAsConstNode(node)) {
549
+ return true;
550
+ }
551
+
552
+ if (!tsm.Node.isTypeAssertion(node)) {
553
+ return false;
554
+ }
555
+
556
+ const typeNode = node.getTypeNode();
557
+
558
+ return (
559
+ typeNode !== undefined &&
560
+ typeNode.isKind(tsm.SyntaxKind.TypeReference) &&
561
+ typeNode.getTypeName().getText() === 'const' &&
562
+ Arr.isEmpty(typeNode.getTypeArguments())
563
+ );
564
+ };
565
+
520
566
  /** `node` with the parentheses written around it taken off. */
521
567
  const skipParenthesizedExpressions = (node: tsm.Node): tsm.Node =>
522
568
  tsm.Node.isParenthesizedExpression(node)
@@ -541,6 +587,11 @@ const resolveSingleSignatureCallTarget = (
541
587
  // eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
542
588
  call: tsm.CallExpression,
543
589
  ): ResolvedCallTarget | undefined => {
590
+ // The callee's type is read as the call sees it, after any `as`,
591
+ // `satisfies` or `!` around it: that is the signature the argument is
592
+ // inferred against, and an `as const` has no run-time effect for an inner
593
+ // layer to contradict.
594
+ //
544
595
  // 0 signatures: the callee is unresolved (e.g. imported, since each file
545
596
  // is transformed in an isolated single-file project).
546
597
  // 2+ signatures: overloads, where removing `as const` could change which
@@ -894,6 +894,125 @@ describe(appendAsConstTransformer, () => {
894
894
  ])('$name', testFn);
895
895
  });
896
896
 
897
+ describe('Callee judged by its type at the call, through type wrappers', () => {
898
+ test.each([
899
+ {
900
+ name: 'widening as to a const type parameter signature should remove as const',
901
+ source: dedent`
902
+ const g = <T,>(x: T): T => x;
903
+ const a = (g as <const T>(x: T) => T)([1, 2] as const);
904
+ `,
905
+ expected: dedent`
906
+ const g = <T,>(x: T): T => x;
907
+ const a = (g as <const T>(x: T) => T)([1, 2]);
908
+ `,
909
+ },
910
+ {
911
+ name: 'as to a non-const type parameter signature should keep as const',
912
+ source: dedent`
913
+ const g = <const T,>(x: T): T => x;
914
+ const a = (g as <T>(x: T) => T)([1, 2] as const);
915
+ `,
916
+ expected: dedent`
917
+ const g = <const T,>(x: T): T => x;
918
+ const a = (g as <T>(x: T) => T)([1, 2] as const);
919
+ `,
920
+ },
921
+ {
922
+ name: 'satisfies keeps the callee type, so as const should be removed',
923
+ source: dedent`
924
+ const g = <const T,>(x: T): T => x;
925
+ const a = (g satisfies unknown)([1, 2] as const);
926
+ `,
927
+ expected: dedent`
928
+ const g = <const T,>(x: T): T => x;
929
+ const a = (g satisfies unknown)([1, 2]);
930
+ `,
931
+ },
932
+ {
933
+ name: 'non-null asserted callee should remove as const',
934
+ source: dedent`
935
+ declare const g: (<const T>(x: T) => T) | undefined;
936
+ const a = g!([1, 2] as const);
937
+ `,
938
+ expected: dedent`
939
+ declare const g: (<const T>(x: T) => T) | undefined;
940
+ const a = g!([1, 2]);
941
+ `,
942
+ },
943
+ {
944
+ name: 'parenthesized callee should remove as const',
945
+ source: dedent`
946
+ const g = <const T,>(x: T): T => x;
947
+ const a = ((g))([1, 2] as const);
948
+ `,
949
+ expected: dedent`
950
+ const g = <const T,>(x: T): T => x;
951
+ const a = ((g))([1, 2]);
952
+ `,
953
+ },
954
+ ])('$name', testFn);
955
+ });
956
+
957
+ describe('Angle-bracket const assertions (<const>x)', () => {
958
+ test.each([
959
+ {
960
+ name: 'should be removed inside an array literal that gets as const',
961
+ source: 'const c = [<const>[1], 2];',
962
+ expected: 'const c = [[1], 2] as const;',
963
+ },
964
+ {
965
+ name: 'should be removed inside an object literal that gets as const',
966
+ source: 'const o = { a: <const>[1] };',
967
+ expected: 'const o = { a: [1] } as const;',
968
+ },
969
+ {
970
+ name: 'should be spelled as const on a const initializer',
971
+ source: 'const b = <const>[1, 2];',
972
+ expected: 'const b = [1, 2] as const;',
973
+ },
974
+ {
975
+ name: 'should be removed from a primitive const initializer',
976
+ source: "const s = <const>'a';",
977
+ expected: "const s = 'a';",
978
+ },
979
+ {
980
+ name: 'should be removed inside an outer as const',
981
+ source: 'const n = [<const>[1], 2] as const;',
982
+ expected: 'const n = [[1], 2] as const;',
983
+ },
984
+ {
985
+ name: 'const type parameter should remove it from an argument',
986
+ source: dedent`
987
+ function f<const T>(x: T): T { return x; }
988
+ const a = f(<const>[1, 2]);
989
+ const b = f((<const>[1, 2]));
990
+ `,
991
+ expected: dedent`
992
+ function f<const T>(x: T): T { return x; }
993
+ const a = f([1, 2]);
994
+ const b = f([1, 2]);
995
+ `,
996
+ },
997
+ {
998
+ name: 'non-const type parameter should keep it in an argument',
999
+ source: dedent`
1000
+ function g<T>(x: T): T { return x; }
1001
+ const a = g(<const>[1, 2]);
1002
+ `,
1003
+ expected: dedent`
1004
+ function g<T>(x: T): T { return x; }
1005
+ const a = g(<const>[1, 2]);
1006
+ `,
1007
+ },
1008
+ {
1009
+ name: 'other angle-bracket assertions should be left as they are',
1010
+ source: 'const d = <readonly number[]>[1, 2];',
1011
+ expected: 'const d = <readonly number[]>[1, 2];',
1012
+ },
1013
+ ])('$name', testFn);
1014
+ });
1015
+
897
1016
  describe('ignorePrefixes option (mut_ prefix)', () => {
898
1017
  test.each([
899
1018
  {
@@ -3,6 +3,7 @@
3
3
  import { Arr } from 'ts-data-forge';
4
4
  import type * as tsm from 'ts-morph';
5
5
  import { hasDisableNextLineComment } from '../functions/index.mjs';
6
+ import { replaceNodeWithText } from '../utils/index.mjs';
6
7
  import type { TsMorphTransformer } from './types.mjs';
7
8
 
8
9
  const TRANSFORMER_NAME = 'convert-interface-to-type';
@@ -91,7 +92,7 @@ export const convertInterfaceToTypeTransformer = (): TsMorphTransformer =>
91
92
  const typeAliasText =
92
93
  `${jsDocText}${exportKeyword}type ${interfaceName}${typeParamsStr} = ${mut_typeBody};` as const;
93
94
 
94
- interfaceDecl.replaceWithText(typeAliasText);
95
+ replaceNodeWithText(interfaceDecl, typeAliasText);
95
96
  };
96
97
 
97
98
  const buildTypeLiteral = (
@@ -29,7 +29,10 @@ import {
29
29
  wrapWithParentheses,
30
30
  type ReadonlyTypeReferenceNode,
31
31
  } from '../functions/index.mjs';
32
- import { replaceNodeWithDebugPrint } from '../utils/index.mjs';
32
+ import {
33
+ replaceNodeWithDebugPrint,
34
+ replaceNodeWithText,
35
+ } from '../utils/index.mjs';
33
36
  import {
34
37
  groupUnionIntersectionTypes,
35
38
  invalidDeepReadonlyTypeName,
@@ -72,7 +75,7 @@ export const convertToReadonlyTransformer = (
72
75
  replaceNode:
73
76
  options?.debug === true
74
77
  ? replaceNodeWithDebugPrint
75
- : (node, newNodeText) => node.replaceWithText(newNodeText),
78
+ : (node, newNodeText) => replaceNodeWithText(node, newNodeText),
76
79
  } as const;
77
80
 
78
81
  return {
@@ -7,9 +7,7 @@ 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
9
  import { expectType } from 'ts-data-forge';
10
- // Aliased: an import named `DeepReadonly` makes `vitest/prefer-describe-function-title`
11
- // rewrite `describe('DeepReadonly', ...)` into a reference to this type.
12
- import type { DeepReadonly as TsTypeForgeDeepReadonly } from 'ts-type-forge';
10
+ import type { DeepReadonly } from 'ts-type-forge';
13
11
  import {
14
12
  convertToReadonlyTransformer,
15
13
  type ReadonlyTransformerOptions,
@@ -3090,34 +3088,25 @@ describe(convertToReadonlyTransformer, () => {
3090
3088
  type B = Readonly<{ b: string[] }>;
3091
3089
 
3092
3090
  // `|` commutes with DeepReadonly, so merging a union is safe.
3093
- expectType<
3094
- TsTypeForgeDeepReadonly<A | B>,
3095
- TsTypeForgeDeepReadonly<A> | TsTypeForgeDeepReadonly<B>
3096
- >('=');
3091
+ expectType<DeepReadonly<A | B>, DeepReadonly<A> | DeepReadonly<B>>('=');
3097
3092
 
3098
- expectType<
3099
- TsTypeForgeDeepReadonly<F | X>,
3100
- TsTypeForgeDeepReadonly<F> | TsTypeForgeDeepReadonly<X>
3101
- >('=');
3093
+ expectType<DeepReadonly<F | X>, DeepReadonly<F> | DeepReadonly<X>>('=');
3102
3094
 
3103
3095
  expectType<
3104
- TsTypeForgeDeepReadonly<readonly string[] | X>,
3105
- TsTypeForgeDeepReadonly<readonly string[]> | TsTypeForgeDeepReadonly<X>
3096
+ DeepReadonly<readonly string[] | X>,
3097
+ DeepReadonly<readonly string[]> | DeepReadonly<X>
3106
3098
  >('=');
3107
3099
 
3108
3100
  // `&` does not: an intersection is tested as a whole and matches every
3109
3101
  // branch any one member matches. `F & X` hits the function branch and
3110
3102
  // comes back unchanged, leaving `x` mutable.
3111
- expectType<TsTypeForgeDeepReadonly<F & X>, F & X>('=');
3103
+ expectType<DeepReadonly<F & X>, F & X>('=');
3112
3104
 
3113
- expectType<
3114
- TsTypeForgeDeepReadonly<F & X>,
3115
- TsTypeForgeDeepReadonly<F> & TsTypeForgeDeepReadonly<X>
3116
- >('!=');
3105
+ expectType<DeepReadonly<F & X>, DeepReadonly<F> & DeepReadonly<X>>('!=');
3117
3106
 
3118
3107
  expectType<
3119
- TsTypeForgeDeepReadonly<readonly string[] & X>,
3120
- TsTypeForgeDeepReadonly<readonly string[]> & TsTypeForgeDeepReadonly<X>
3108
+ DeepReadonly<readonly string[] & X>,
3109
+ DeepReadonly<readonly string[]> & DeepReadonly<X>
3121
3110
  >('!=');
3122
3111
 
3123
3112
  // `Readonly` over type literals does commute with `&`, which is what