ts-codemod-lib 3.3.2 → 3.4.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 (26) hide show
  1. package/README.md +16 -16
  2. package/dist/functions/ast-transformers/append-as-const.mjs +11 -10
  3. package/dist/functions/ast-transformers/append-as-const.mjs.map +1 -1
  4. package/dist/functions/ast-transformers/convert-to-readonly.mjs +74 -1
  5. package/dist/functions/ast-transformers/convert-to-readonly.mjs.map +1 -1
  6. package/dist/functions/ast-transformers/replace-record-with-unknown-record.mjs +10 -19
  7. package/dist/functions/ast-transformers/replace-record-with-unknown-record.mjs.map +1 -1
  8. package/dist/functions/ast-transformers/transform-source-code.d.mts.map +1 -1
  9. package/dist/functions/ast-transformers/transform-source-code.mjs +101 -49
  10. package/dist/functions/ast-transformers/transform-source-code.mjs.map +1 -1
  11. package/dist/functions/functions/should-avoid-parentheses-for-readonly.d.mts.map +1 -1
  12. package/dist/functions/functions/should-avoid-parentheses-for-readonly.mjs +1 -4
  13. package/dist/functions/functions/should-avoid-parentheses-for-readonly.mjs.map +1 -1
  14. package/dist/functions/functions/wrap-with-parentheses.d.mts.map +1 -1
  15. package/dist/functions/functions/wrap-with-parentheses.mjs +3 -4
  16. package/dist/functions/functions/wrap-with-parentheses.mjs.map +1 -1
  17. package/package.json +42 -41
  18. package/src/functions/ast-transformers/append-as-const.mts +22 -20
  19. package/src/functions/ast-transformers/convert-to-readonly.mts +96 -1
  20. package/src/functions/ast-transformers/convert-to-readonly.test.mts +205 -10
  21. package/src/functions/ast-transformers/enable-no-unchecked-indexed-access.test.mts +1 -1
  22. package/src/functions/ast-transformers/replace-record-with-unknown-record.mts +13 -22
  23. package/src/functions/ast-transformers/transform-source-code.mts +158 -57
  24. package/src/functions/ast-transformers/transform-source-code.test.mts +123 -0
  25. package/src/functions/functions/should-avoid-parentheses-for-readonly.mts +1 -5
  26. package/src/functions/functions/wrap-with-parentheses.mts +3 -5
@@ -1,63 +1,114 @@
1
1
  import { Arr } from 'ts-data-forge';
2
2
  import * as tsm from 'ts-morph';
3
- const extractFileIgnoreTransformers = (code) => {
3
+ const fileIgnorePrefixes = [
4
+ 'transformer-ignore',
5
+ 'ts-codemod-ignore',
6
+ 'codemod-ignore',
7
+ 'transform-ignore',
8
+ ];
9
+
10
+
11
+
12
+
13
+
4
14
 
5
- const patterns = [
6
- {
7
- prefix: 'transformer-ignore',
8
- regex: /\/\*\s*transformer-ignore\s*(.*?)\s*\*\//u,
9
- },
10
- {
11
- prefix: 'ts-codemod-ignore',
12
- regex: /\/\*\s*ts-codemod-ignore\s*(.*?)\s*\*\//u,
13
- },
14
- {
15
- prefix: 'codemod-ignore',
16
- regex: /\/\*\s*codemod-ignore\s*(.*?)\s*\*\//u,
17
- },
18
- {
19
- prefix: 'transform-ignore',
20
- regex: /\/\*\s*transform-ignore\s*(.*?)\s*\*\//u,
21
- },
22
- ];
23
- for (const { regex } of patterns) {
24
- const match = regex.exec(code);
25
- if (match !== null) {
26
- const targetTransformers = match[1]?.trim() ?? '';
27
-
28
- if (targetTransformers === '') {
29
- return [];
30
- }
31
15
 
32
- return targetTransformers.split(',').map((name) => name.trim());
16
+
17
+
18
+ const findFileIgnoreTransformers = (code) => {
19
+ for (const prefix of fileIgnorePrefixes) {
20
+ const names = findFileIgnoreComment(code, prefix);
21
+ if (names !== undefined) {
22
+ return names;
33
23
  }
34
24
  }
35
- return [];
25
+ return undefined;
36
26
  };
37
- const shouldSkipFile = (code, transformerName) => {
38
- const ignoredTransformers = extractFileIgnoreTransformers(code);
39
-
40
- const patterns = [
41
- /\/\*\s*transformer-ignore\s*.*?\s*\*\//u,
42
- /\/\*\s*ts-codemod-ignore\s*.*?\s*\*\//u,
43
- /\/\*\s*codemod-ignore\s*.*?\s*\*\//u,
44
- /\/\*\s*transform-ignore\s*.*?\s*\*\//u,
45
- ];
46
- const hasFileIgnoreComment = patterns.some((regex) => regex.test(code));
47
- if (!hasFileIgnoreComment && Arr.isEmpty(ignoredTransformers)) {
48
- return false;
49
- }
27
+ const findFileIgnoreComment = (code, prefix) => {
28
+
29
+
30
+ const findCommentEnd = createForwardSearch((from) => code.indexOf('*/', from));
31
+ const findLineTerminator = createForwardSearch((from) => indexOfLineTerminator(code, from));
32
+ const trimEndBefore = createLastValueCache((commentEnd) => skipWhitespaceBackward(code, commentEnd));
33
+ for (let mut_open = code.indexOf('/*'); mut_open !== -1; mut_open = code.indexOf('/*', mut_open + 2)) {
34
+ const prefixStart = skipWhitespace(code, mut_open + 2);
35
+ if (!code.startsWith(prefix, prefixStart)) {
36
+ continue;
37
+ }
38
+ const namesStart = skipWhitespace(code, prefixStart + prefix.length);
39
+ const commentEnd = findCommentEnd(namesStart);
40
+
41
+ if (commentEnd === -1) {
42
+ return undefined;
43
+ }
44
+ const namesEnd = Math.max(namesStart, trimEndBefore(commentEnd));
45
+ const lineTerminator = findLineTerminator(namesStart);
46
+ if (lineTerminator === -1 || lineTerminator >= namesEnd) {
47
+ const names = code.slice(namesStart, namesEnd);
50
48
 
51
- if (Arr.isEmpty(ignoredTransformers)) {
52
- return true;
53
- }
54
49
 
55
- if (transformerName === undefined) {
56
- return false;
50
+ return names === '' ? [] : names.split(',').map((name) => name.trim());
51
+ }
57
52
  }
53
+ return undefined;
54
+ };
55
+ const shouldSkipFile = (ignoredTransformers, transformerName) => ignoredTransformers !== undefined &&
56
+
57
+
58
+
59
+ (Arr.isEmpty(ignoredTransformers) ||
60
+ ignoredTransformers.includes(transformerName));
58
61
 
59
- return ignoredTransformers.includes(transformerName);
62
+
63
+
64
+
65
+ const createForwardSearch = (search) => {
66
+ let mut_from = Number.POSITIVE_INFINITY;
67
+ let mut_found = -1;
68
+ return (from) => {
69
+ if (from < mut_from || (mut_found !== -1 && from > mut_found)) {
70
+ mut_from = from;
71
+ mut_found = search(from);
72
+ }
73
+ return mut_found;
74
+ };
60
75
  };
76
+ const createLastValueCache = (fn) => {
77
+ let mut_arg = undefined;
78
+ let mut_value = 0;
79
+ return (arg) => {
80
+ if (arg !== mut_arg) {
81
+ mut_arg = arg;
82
+ mut_value = fn(arg);
83
+ }
84
+ return mut_value;
85
+ };
86
+ };
87
+ const skipWhitespace = (code, from) => {
88
+ let mut_index = from;
89
+ while (mut_index < code.length && isWhitespace(code[mut_index])) {
90
+ mut_index += 1;
91
+ }
92
+ return mut_index;
93
+ };
94
+ const skipWhitespaceBackward = (code, end) => {
95
+ let mut_index = end;
96
+ while (mut_index > 0 && isWhitespace(code[mut_index - 1])) {
97
+ mut_index -= 1;
98
+ }
99
+ return mut_index;
100
+ };
101
+ const indexOfLineTerminator = (code, from) => {
102
+ let mut_index = from;
103
+ while (mut_index < code.length && !isLineTerminator(code[mut_index])) {
104
+ mut_index += 1;
105
+ }
106
+ return mut_index < code.length ? mut_index : -1;
107
+ };
108
+
109
+ const isWhitespace = (char) => char !== undefined && /^\s$/u.test(char);
110
+
111
+ const isLineTerminator = (char) => char !== undefined && /^[\n\r\u{2028}\u{2029}]$/u.test(char);
61
112
  export const transformSourceCode = (code, isTsx, transformers, debug = false) => {
62
113
  const project = new tsm.Project({
63
114
  useInMemoryFileSystem: true,
@@ -68,9 +119,10 @@ export const transformSourceCode = (code, isTsx, transformers, debug = false) =>
68
119
  },
69
120
  });
70
121
  const sourceAst = project.createSourceFile(`source.${isTsx ? 'tsx' : 'ts'}`, code);
122
+ const ignoredTransformers = findFileIgnoreTransformers(code);
71
123
  for (const transformer of transformers) {
72
124
  const transformerName = transformer.name;
73
- if (shouldSkipFile(code, transformerName)) {
125
+ if (shouldSkipFile(ignoredTransformers, transformerName)) {
74
126
  if (debug) {
75
127
  console.debug(`skipped by ignore-file comment for transformer: ${transformerName}`);
76
128
  }
@@ -1 +1 @@
1
- {"version":3,"file":"transform-source-code.mjs","sourceRoot":"","sources":["../../../src/functions/ast-transformers/transform-source-code.mts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"transform-source-code.mjs","sourceRoot":"","sources":["../../../src/functions/ast-transformers/transform-source-code.mts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"should-avoid-parentheses-for-readonly.d.mts","sourceRoot":"","sources":["../../../src/functions/functions/should-avoid-parentheses-for-readonly.mts"],"names":[],"mappings":"AAAA,OAAO,KAAK,GAAG,MAAM,UAAU,CAAC;AAEhC;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,iCAAiC,SAAU,GAAG,CAAC,IAAI,KAAG,OAYlE,CAAC"}
1
+ {"version":3,"file":"should-avoid-parentheses-for-readonly.d.mts","sourceRoot":"","sources":["../../../src/functions/functions/should-avoid-parentheses-for-readonly.mts"],"names":[],"mappings":"AAAA,OAAO,KAAK,GAAG,MAAM,UAAU,CAAC;AAEhC;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,iCAAiC,SAAU,GAAG,CAAC,IAAI,KAAG,OAQlE,CAAC"}
@@ -14,13 +14,10 @@ import * as tsm from 'ts-morph';
14
14
 
15
15
  export const shouldAvoidParenthesesForReadonly = (node) => {
16
16
  const parent = node.getParent();
17
- if (parent === undefined) {
18
- return false;
19
- }
20
17
 
21
18
 
22
19
 
23
20
 
24
- return parent.isKind(tsm.SyntaxKind.TypePredicate);
21
+ return parent?.isKind(tsm.SyntaxKind.TypePredicate) ?? false;
25
22
  };
26
23
  //# sourceMappingURL=should-avoid-parentheses-for-readonly.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"should-avoid-parentheses-for-readonly.mjs","sourceRoot":"","sources":["../../../src/functions/functions/should-avoid-parentheses-for-readonly.mts"],"names":[],"mappings":"AAAA,OAAO,KAAK,GAAG,MAAM,UAAU,CAAC;AAEhC;;;;;;;;;;;;GAYG;AACH,MAAM,CAAC,MAAM,iCAAiC,GAAG,CAAC,IAAc,EAAW,EAAE;IAC3E,MAAM,MAAM,GAAG,IAAI,CAAC,SAAS,EAAE,CAAC;IAEhC,IAAI,MAAM,KAAK,SAAS,EAAE,CAAC;QACzB,OAAO,KAAK,CAAC;IACf,CAAC;IAED,2CAA2C;IAC3C,mFAAmF;IACnF,gDAAgD;IAChD,mDAAmD;IACnD,OAAO,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,UAAU,CAAC,aAAa,CAAC,CAAC;AACrD,CAAC,CAAC"}
1
+ {"version":3,"file":"should-avoid-parentheses-for-readonly.mjs","sourceRoot":"","sources":["../../../src/functions/functions/should-avoid-parentheses-for-readonly.mts"],"names":[],"mappings":"AAAA,OAAO,KAAK,GAAG,MAAM,UAAU,CAAC;AAEhC;;;;;;;;;;;;GAYG;AACH,MAAM,CAAC,MAAM,iCAAiC,GAAG,CAAC,IAAc,EAAW,EAAE;IAC3E,MAAM,MAAM,GAAG,IAAI,CAAC,SAAS,EAAE,CAAC;IAEhC,2CAA2C;IAC3C,mFAAmF;IACnF,gDAAgD;IAChD,mDAAmD;IACnD,OAAO,MAAM,EAAE,MAAM,CAAC,GAAG,CAAC,UAAU,CAAC,aAAa,CAAC,IAAI,KAAK,CAAC;AAC/D,CAAC,CAAC"}
@@ -1 +1 @@
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
+ {"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,MAMrD,CAAC"}
@@ -65,9 +65,8 @@ const isWrappedWithParentheses = (str) => {
65
65
 
66
66
  export const wrapWithParentheses = (nodeStr) => {
67
67
  const trimmed = nodeStr.trim();
68
- if (isWrappedWithParentheses(trimmed)) {
69
- return trimmed;
70
- }
71
- return `(${trimmed})`;
68
+ return isWrappedWithParentheses(trimmed)
69
+ ? trimmed
70
+ : `(${trimmed})`;
72
71
  };
73
72
  //# sourceMappingURL=wrap-with-parentheses.mjs.map
@@ -1 +1 @@
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
+ {"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,OAAO,wBAAwB,CAAC,OAAO,CAAC;QACtC,CAAC,CAAC,OAAO;QACT,CAAC,CAAE,IAAI,OAAO,GAAa,CAAC;AAChC,CAAC,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ts-codemod-lib",
3
- "version": "3.3.2",
3
+ "version": "3.4.0",
4
4
  "private": false,
5
5
  "keywords": [
6
6
  "typescript",
@@ -43,39 +43,39 @@
43
43
  "ts-type-forge": "^9.2.2"
44
44
  },
45
45
  "devDependencies": {
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",
46
+ "@types/node": "26.6.3",
47
+ "@vitest/browser-playwright": "5.0.2",
48
+ "@vitest/coverage-v8": "5.0.2",
49
+ "@vitest/ui": "5.0.2",
50
+ "cspell": "10.3.5",
51
51
  "dedent": "1.7.2",
52
52
  "esbuild": "0.28.2",
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",
53
+ "eslint": "10.11.0",
54
+ "eslint-config-typed": "5.16.0",
55
+ "eslint-plugin-ts-data-forge": "0.8.2",
56
+ "eslint-plugin-ts-fortress": "1.2.4",
57
+ "eslint-plugin-ts-type-forge": "0.8.5",
58
58
  "jiti": "2.7.0",
59
59
  "npm-run-all2": "9.0.3",
60
- "playwright": "1.62.1",
61
- "prettier": "3.9.6",
60
+ "playwright": "1.63.0",
61
+ "prettier": "3.9.9",
62
62
  "prettier-plugin-organize-imports": "4.3.0",
63
63
  "prettier-plugin-packagejson": "3.0.2",
64
- "ts-repo-utils": "10.6.2",
65
- "tsx": "4.23.13",
64
+ "ts-repo-utils": "10.7.1",
65
+ "tsx": "4.23.15",
66
66
  "typedoc": "0.28.20",
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.2.2",
71
- "vitest": "5.0.0"
70
+ "vite": "8.3.1",
71
+ "vitest": "5.0.2"
72
72
  },
73
73
  "engines": {
74
74
  "node": ">=22.22.2",
75
75
  "pnpm": ">=9.0.0"
76
76
  },
77
77
  "volta": {
78
- "node": "26.8.1"
78
+ "node": "26.10.0"
79
79
  },
80
80
  "publishConfig": {
81
81
  "access": "public",
@@ -83,38 +83,39 @@
83
83
  },
84
84
  "scripts": {
85
85
  "build": "tsx --tsconfig ../../tools/configs/tsconfig.tsx.json ./scripts/cmd/build.mts",
86
- "build:min": "tsx --tsconfig ../../tools/configs/tsconfig.tsx.json ./scripts/cmd/build.mts --skip-check",
86
+ "check:cspell": "cspell \"**\" --gitignore --gitignore-root ../../ --no-progress",
87
+ "check:dist": "tsx --tsconfig ../../tools/configs/tsconfig.tsx.json ./scripts/cmd/check-dist.mts",
87
88
  "check:ext": "tsx --tsconfig ../../tools/configs/tsconfig.tsx.json ./scripts/cmd/check-ext.mts",
89
+ "check:lint": "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",
88
94
  "check-all": "tsx --tsconfig ../../tools/configs/tsconfig.tsx.json ./scripts/cmd/check-all.mts",
89
95
  "clean": "pnpm run /clean:.*/",
90
96
  "clean:logs": "npx rimraf ./*.log",
91
- "codemod": "tsx --tsconfig ../../tools/configs/tsconfig.tsx.json ../ts-codemod-cli/src/cmd/ts-codemod.mts --uncommitted --transformer append-as-const --transformer convert-interface-to-type --transformer convert-to-readonly --transformer replace-any-with-unknown --transformer replace-record-with-unknown-record '{src,scripts,samples,test}/**/*.{mts,tsx}' && pnpm run fmt",
92
- "codemod:diff": "tsx --tsconfig ../../tools/configs/tsconfig.tsx.json ../ts-codemod-cli/src/cmd/ts-codemod.mts --diff-from origin/main --transformer append-as-const --transformer convert-interface-to-type --transformer convert-to-readonly --transformer replace-any-with-unknown --transformer replace-record-with-unknown-record '{src,scripts,samples,test}/**/*.{mts,tsx}' && pnpm run fmt",
93
- "codemod:full": "tsx --tsconfig ../../tools/configs/tsconfig.tsx.json ../ts-codemod-cli/src/cmd/ts-codemod.mts --transformer append-as-const --transformer convert-interface-to-type --transformer convert-to-readonly --transformer replace-any-with-unknown --transformer replace-record-with-unknown-record '{src,scripts,samples,test}/**/*.{mts,tsx}' && pnpm run fmt",
94
- "cspell": "cspell \"**\" --gitignore --gitignore-root ../../ --no-progress",
95
97
  "doc": "tsx --tsconfig ../../tools/configs/tsconfig.tsx.json ./scripts/cmd/gen-docs.mts",
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
- "doc:watch": "typedoc --options ./configs/typedoc.config.mjs --watch",
100
- "fmt": "tsx --tsconfig ../../tools/configs/tsconfig.tsx.json ../../libs/ts-repo-utils/src/cmd/format-uncommitted.mts --cwd .",
101
- "fmt:diff": "tsx --tsconfig ../../tools/configs/tsconfig.tsx.json ../../libs/ts-repo-utils/src/cmd/format-diff-from.mts origin/main --cwd .",
102
- "fmt:full": "prettier --write .",
103
- "gi": "run-s gi:src fmt",
104
- "gi:src": "tsx --tsconfig ../../tools/configs/tsconfig.tsx.json ../../libs/ts-repo-utils/src/cmd/gen-index-ts.mts ./src --index-ext .mts --export-ext .mjs --target-ext .mts --target-ext .tsx --exclude entry-point.mts",
105
- "lint": "eslint .",
106
- "lint:fix": "eslint . --fix",
107
- "prepare-release": "run-s fmt build doc",
99
+ "fix:codemod": "tsx --tsconfig ../../tools/configs/tsconfig.tsx.json ../ts-codemod-cli/src/cmd/ts-codemod.mts --uncommitted --transformer append-as-const --transformer convert-interface-to-type --transformer convert-to-readonly --transformer replace-any-with-unknown --transformer replace-record-with-unknown-record '{src,scripts,samples,test}/**/*.{mts,tsx}' && pnpm run fix:fmt",
100
+ "fix:codemod:diff": "tsx --tsconfig ../../tools/configs/tsconfig.tsx.json ../ts-codemod-cli/src/cmd/ts-codemod.mts --diff-from origin/main --transformer append-as-const --transformer convert-interface-to-type --transformer convert-to-readonly --transformer replace-any-with-unknown --transformer replace-record-with-unknown-record '{src,scripts,samples,test}/**/*.{mts,tsx}' && pnpm run fix:fmt",
101
+ "fix:codemod:full": "tsx --tsconfig ../../tools/configs/tsconfig.tsx.json ../ts-codemod-cli/src/cmd/ts-codemod.mts --transformer append-as-const --transformer convert-interface-to-type --transformer convert-to-readonly --transformer replace-any-with-unknown --transformer replace-record-with-unknown-record '{src,scripts,samples,test}/**/*.{mts,tsx}' && pnpm run fix:fmt",
102
+ "fix:fmt": "tsx --tsconfig ../../tools/configs/tsconfig.tsx.json ../../libs/ts-repo-utils/src/cmd/format-uncommitted.mts --cwd .",
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
+ "fix:fmt:full": "prettier --write .",
105
+ "fix:lint": "eslint . --fix",
106
+ "fmt": "pnpm run fix:fmt",
107
+ "gen": "run-s gen:index gen:readme gen:jsdoc",
108
+ "gen:index": "run-s gen:index:src fix:fmt",
109
+ "gen:index:src": "tsx --tsconfig ../../tools/configs/tsconfig.tsx.json ../../libs/ts-repo-utils/src/cmd/gen-index-ts.mts ./src --index-ext .mts --export-ext .mjs --target-ext .mts --target-ext .tsx --exclude entry-point.mts",
110
+ "gen:jsdoc": "tsx --tsconfig ../../tools/configs/tsconfig.tsx.json ./scripts/cmd/embed-examples-in-jsdoc.mts",
111
+ "gen:readme": "tsx --tsconfig ../../tools/configs/tsconfig.tsx.json ./scripts/cmd/embed-examples.mts",
108
112
  "run:test": "tsx --tsconfig ../../tools/configs/tsconfig.tsx.json ./scripts/test-code/transform-test-code.mts",
109
- "test": "pnpm run z:vitest:node run",
110
- "test:browser": "pnpm run z:vitest --project='Browser' run",
111
- "test:cov": "pnpm run z:vitest:node run --coverage",
113
+ "test": "pnpm run check:test",
112
114
  "test:cov:ui": "vite preview --outDir ./coverage",
113
115
  "test:ui": "pnpm run z:vitest:node --ui",
114
- "testw": "pnpm run z:vitest:node watch",
115
- "tsc": "node ../../node_modules/typescript-native/bin/tsc --noEmit",
116
- "tscw": "node ../../node_modules/typescript-native/bin/tsc --noEmit --watch -p ./tsconfig.json",
117
- "type-check": "node ../../node_modules/typescript-native/bin/tsc --noEmit",
116
+ "watch:doc": "typedoc --options ./configs/typedoc.config.mjs --watch",
117
+ "watch:test": "pnpm run z:vitest:node watch",
118
+ "watch:types": "node ../../node_modules/typescript-native/bin/tsc --noEmit --watch -p ./tsconfig.json",
118
119
  "z:vitest": "vitest --config ./configs/vitest.config.mts",
119
120
  "z:vitest:node": "pnpm run z:vitest --project='Node.js'"
120
121
  }
@@ -362,28 +362,30 @@ const transformNode = (
362
362
  return;
363
363
  }
364
364
 
365
- if (tsm.Node.isConditionalExpression(node)) {
366
- // For conditional expressions, traverse both branches in a non-const context
367
- transformNode(
368
- node.getWhenTrue(),
369
- {
370
- isDirectUnderConstInitializer: context.isDirectUnderConstInitializer,
371
- isUnderConstContext: false,
372
- },
373
- options,
374
- );
365
+ if (!tsm.Node.isConditionalExpression(node)) {
366
+ return;
367
+ }
375
368
 
376
- transformNode(
377
- node.getWhenFalse(),
378
- {
379
- isDirectUnderConstInitializer: context.isDirectUnderConstInitializer,
380
- isUnderConstContext: false,
381
- },
382
- options,
383
- );
369
+ // For conditional expressions, traverse both branches in a non-const context
370
+ transformNode(
371
+ node.getWhenTrue(),
372
+ {
373
+ isDirectUnderConstInitializer: context.isDirectUnderConstInitializer,
374
+ isUnderConstContext: false,
375
+ },
376
+ options,
377
+ );
384
378
 
385
- // return;
386
- }
379
+ transformNode(
380
+ node.getWhenFalse(),
381
+ {
382
+ isDirectUnderConstInitializer: context.isDirectUnderConstInitializer,
383
+ isUnderConstContext: false,
384
+ },
385
+ options,
386
+ );
387
+
388
+ // return;
387
389
  };
388
390
 
389
391
  /**
@@ -1303,6 +1303,7 @@ const transformIntersectionTypeNode = (
1303
1303
  /**
1304
1304
  * - `tr(A | B) |-> tr(A) | tr(B)`
1305
1305
  * - `tr(Readonly<A> | Readonly<B>) |-> Readonly<tr(A) | tr(B)>`
1306
+ * - `tr(DeepReadonly<A> | DeepReadonly<B>) |-> DeepReadonly<tr(A) | tr(B)>`
1306
1307
  */
1307
1308
  const transformUnionTypeNode = (
1308
1309
  // eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
@@ -1416,7 +1417,14 @@ const transformUnionOrIntersectionTypeNodeImpl = (
1416
1417
  others,
1417
1418
  (a) =>
1418
1419
  [
1419
- a.nodes.map((n) => wrapWithParentheses(n.getFullText())),
1420
+ // `&` is deliberately not merged, unlike `Readonly<A> & Readonly<B>`
1421
+ // above. See `mergeDeepReadonlyUnionMembers` for why.
1422
+ operator === '|'
1423
+ ? mergeDeepReadonlyUnionMembers(
1424
+ a.nodes,
1425
+ options.DeepReadonly.typeName,
1426
+ )
1427
+ : a.nodes.map((n) => wrapWithParentheses(n.getFullText())),
1420
1428
  a.firstPosition,
1421
1429
  ] as const,
1422
1430
  ),
@@ -1450,6 +1458,93 @@ const transformUnionOrIntersectionTypeNodeImpl = (
1450
1458
  );
1451
1459
  };
1452
1460
 
1461
+ /**
1462
+ * - `tr(DeepReadonly<A> | B | DeepReadonly<C>) |-> DeepReadonly<A | C> | B`
1463
+ *
1464
+ * The merged member takes the place of the first `DeepReadonly` member.
1465
+ *
1466
+ * Only a union is merged, although `Readonly<A> & Readonly<B>` is merged into
1467
+ * `Readonly<A & B>`. The two differ in kind:
1468
+ *
1469
+ * - `Readonly` is a homomorphic mapped type, and over type literals
1470
+ * `Readonly<A & B>` and `Readonly<A> & Readonly<B>` have the same properties
1471
+ * with the same modifiers. (That merge also takes `Readonly<T>` with any
1472
+ * `T`, where this does not hold for an array `T`; it is not relied on here.)
1473
+ * - `DeepReadonly` is a conditional type that picks a branch by what `T` is
1474
+ * (primitive, function, Map, Set, array, object). It distributes over a union
1475
+ * because each member is tested on its own, but an intersection is tested as
1476
+ * a whole and matches every branch any one member matches: with
1477
+ * `F = () => void`, `F & { x: number[] }` extends a function type because `F`
1478
+ * does, so `DeepReadonly<F & { x: number[] }>` hits that branch and returns
1479
+ * the intersection unchanged, leaving `x` mutable, while `DeepReadonly<F> &
1480
+ * DeepReadonly<{ x: number[] }>` is `F & { readonly x: readonly number[] }`.
1481
+ * The codemod cannot tell whether a type argument is such a member, so it
1482
+ * merges no intersection.
1483
+ *
1484
+ * The test "premises of merging DeepReadonly members" pins both facts against
1485
+ * ts-type-forge's `DeepReadonly`.
1486
+ */
1487
+ const mergeDeepReadonlyUnionMembers = (
1488
+ // eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
1489
+ nodes: readonly tsm.TypeNode[],
1490
+ deepReadonlyTypeName: string,
1491
+ ): readonly string[] => {
1492
+ const typeArgsOfDeepReadonly = nodes.map((n) =>
1493
+ getDeepReadonlyTypeArg(n, deepReadonlyTypeName),
1494
+ );
1495
+
1496
+ const typeArgs = typeArgsOfDeepReadonly.filter((t) => t !== undefined);
1497
+
1498
+ if (typeArgs.length < 2) {
1499
+ return nodes.map((n) => wrapWithParentheses(n.getFullText()));
1500
+ }
1501
+
1502
+ const firstIndex = typeArgsOfDeepReadonly.findIndex((t) => t !== undefined);
1503
+
1504
+ const merged = unionToString({
1505
+ types: typeArgs.map((t_) => {
1506
+ const t = removeParentheses(t_);
1507
+
1508
+ // NOTE: DeepReadonly<A | B> | DeepReadonly<C> -> DeepReadonly<A | B | C>
1509
+ // NOTE: DeepReadonly<() => void> | DeepReadonly<B> -> DeepReadonly<(() => void) | B>
1510
+ return t.isKind(tsm.SyntaxKind.UnionType)
1511
+ ? t.getFullText()
1512
+ : wrapWithParentheses(t.getFullText());
1513
+ }),
1514
+ op: '|',
1515
+ wrapWithReadonly: deepReadonlyTypeName,
1516
+ });
1517
+
1518
+ return nodes.flatMap((n, i) =>
1519
+ typeArgsOfDeepReadonly[i] === undefined
1520
+ ? [wrapWithParentheses(n.getFullText())]
1521
+ : i === firstIndex
1522
+ ? [merged]
1523
+ : [],
1524
+ );
1525
+ };
1526
+
1527
+ /** `DeepReadonly<T>` |-> `T`, anything else |-> `undefined` */
1528
+ const getDeepReadonlyTypeArg = (
1529
+ // eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
1530
+ node: tsm.TypeNode,
1531
+ deepReadonlyTypeName: string,
1532
+ ): tsm.TypeNode | undefined => {
1533
+ if (!node.isKind(tsm.SyntaxKind.TypeReference)) {
1534
+ return undefined;
1535
+ }
1536
+
1537
+ const typeName = node.getTypeName();
1538
+
1539
+ const typeArguments = node.getTypeArguments();
1540
+
1541
+ return typeName.isKind(tsm.SyntaxKind.Identifier) &&
1542
+ typeName.getText() === deepReadonlyTypeName &&
1543
+ Arr.isFixedLengthArray(1, typeArguments)
1544
+ ? typeArguments[0]
1545
+ : undefined;
1546
+ };
1547
+
1453
1548
  const unionToString = ({
1454
1549
  types: typesRaw,
1455
1550
  op,