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.
- package/README.md +16 -16
- package/dist/functions/ast-transformers/append-as-const.mjs +11 -10
- package/dist/functions/ast-transformers/append-as-const.mjs.map +1 -1
- package/dist/functions/ast-transformers/convert-to-readonly.mjs +74 -1
- package/dist/functions/ast-transformers/convert-to-readonly.mjs.map +1 -1
- package/dist/functions/ast-transformers/replace-record-with-unknown-record.mjs +10 -19
- package/dist/functions/ast-transformers/replace-record-with-unknown-record.mjs.map +1 -1
- package/dist/functions/ast-transformers/transform-source-code.d.mts.map +1 -1
- package/dist/functions/ast-transformers/transform-source-code.mjs +101 -49
- package/dist/functions/ast-transformers/transform-source-code.mjs.map +1 -1
- package/dist/functions/functions/should-avoid-parentheses-for-readonly.d.mts.map +1 -1
- package/dist/functions/functions/should-avoid-parentheses-for-readonly.mjs +1 -4
- package/dist/functions/functions/should-avoid-parentheses-for-readonly.mjs.map +1 -1
- package/dist/functions/functions/wrap-with-parentheses.d.mts.map +1 -1
- package/dist/functions/functions/wrap-with-parentheses.mjs +3 -4
- package/dist/functions/functions/wrap-with-parentheses.mjs.map +1 -1
- package/package.json +42 -41
- package/src/functions/ast-transformers/append-as-const.mts +22 -20
- package/src/functions/ast-transformers/convert-to-readonly.mts +96 -1
- package/src/functions/ast-transformers/convert-to-readonly.test.mts +205 -10
- package/src/functions/ast-transformers/enable-no-unchecked-indexed-access.test.mts +1 -1
- package/src/functions/ast-transformers/replace-record-with-unknown-record.mts +13 -22
- package/src/functions/ast-transformers/transform-source-code.mts +158 -57
- package/src/functions/ast-transformers/transform-source-code.test.mts +123 -0
- package/src/functions/functions/should-avoid-parentheses-for-readonly.mts +1 -5
- 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
|
|
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
|
-
|
|
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
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
const
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
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
|
-
|
|
56
|
-
|
|
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
|
-
|
|
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(
|
|
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":"AAAA,OAAO,EAAE,GAAG,EAAE,MAAM,eAAe,CAAC;AACpC,OAAO,KAAK,GAAG,MAAM,UAAU,CAAC;AAGhC,MAAM,
|
|
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,
|
|
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
|
|
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,
|
|
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,
|
|
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
|
-
|
|
69
|
-
|
|
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,
|
|
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
|
+
"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.
|
|
47
|
-
"@vitest/browser-playwright": "5.0.
|
|
48
|
-
"@vitest/coverage-v8": "5.0.
|
|
49
|
-
"@vitest/ui": "5.0.
|
|
50
|
-
"cspell": "10.
|
|
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.
|
|
54
|
-
"eslint-config-typed": "5.
|
|
55
|
-
"eslint-plugin-ts-data-forge": "0.
|
|
56
|
-
"eslint-plugin-ts-fortress": "1.2.
|
|
57
|
-
"eslint-plugin-ts-type-forge": "0.8.
|
|
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.
|
|
61
|
-
"prettier": "3.9.
|
|
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.
|
|
65
|
-
"tsx": "4.23.
|
|
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.
|
|
71
|
-
"vitest": "5.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.
|
|
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
|
-
"
|
|
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
|
-
"
|
|
100
|
-
"
|
|
101
|
-
"
|
|
102
|
-
"fmt
|
|
103
|
-
"
|
|
104
|
-
"
|
|
105
|
-
"lint": "eslint .",
|
|
106
|
-
"
|
|
107
|
-
"
|
|
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
|
|
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
|
-
"
|
|
115
|
-
"
|
|
116
|
-
"
|
|
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
|
-
|
|
367
|
-
|
|
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
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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,
|