ts-codemod-lib 2.0.4 → 2.0.5
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/dist/cmd/append-as-const.mjs +1 -1
- package/dist/cmd/convert-interface-to-type.mjs +1 -1
- package/dist/cmd/convert-to-readonly.mjs +1 -1
- package/dist/cmd/replace-any-with-unknown.mjs +1 -1
- package/dist/cmd/replace-record-with-unknown-record.mjs +1 -1
- package/dist/cmd/run-transformer-cli.mjs +3 -3
- package/dist/cmd/run-transformer-cli.mjs.map +1 -1
- package/dist/entry-point.mjs +1 -0
- package/dist/entry-point.mjs.map +1 -1
- package/dist/functions/ast-transformers/convert-interface-to-type.d.mts.map +1 -1
- package/dist/functions/ast-transformers/convert-interface-to-type.mjs +4 -4
- package/dist/functions/ast-transformers/convert-interface-to-type.mjs.map +1 -1
- package/dist/functions/ast-transformers/convert-to-readonly.d.mts.map +1 -1
- package/dist/functions/ast-transformers/convert-to-readonly.mjs +31 -12
- package/dist/functions/ast-transformers/convert-to-readonly.mjs.map +1 -1
- package/dist/functions/ast-transformers/readonly-transformer-helpers/compare-union-types.mjs +1 -0
- package/dist/functions/ast-transformers/readonly-transformer-helpers/compare-union-types.mjs.map +1 -1
- package/dist/functions/ast-transformers/readonly-transformer-helpers/group-union-types.mjs +1 -0
- package/dist/functions/ast-transformers/readonly-transformer-helpers/group-union-types.mjs.map +1 -1
- package/dist/functions/ast-transformers/replace-any-with-unknown.mjs +1 -1
- package/dist/functions/ast-transformers/replace-record-with-unknown-record.mjs +13 -12
- 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 +3 -2
- package/dist/functions/ast-transformers/transform-source-code.mjs.map +1 -1
- package/dist/functions/functions/index.d.mts +1 -0
- package/dist/functions/functions/index.d.mts.map +1 -1
- package/dist/functions/functions/index.mjs +1 -0
- package/dist/functions/functions/index.mjs.map +1 -1
- package/dist/functions/functions/is-as-const-node.d.mts.map +1 -1
- package/dist/functions/functions/is-as-const-node.mjs +2 -1
- package/dist/functions/functions/is-as-const-node.mjs.map +1 -1
- package/dist/functions/functions/should-avoid-parentheses-for-readonly.d.mts +16 -0
- package/dist/functions/functions/should-avoid-parentheses-for-readonly.d.mts.map +1 -0
- package/dist/functions/functions/should-avoid-parentheses-for-readonly.mjs +32 -0
- package/dist/functions/functions/should-avoid-parentheses-for-readonly.mjs.map +1 -0
- package/dist/functions/functions/wrap-with-parentheses.d.mts +13 -1
- package/dist/functions/functions/wrap-with-parentheses.d.mts.map +1 -1
- package/dist/functions/functions/wrap-with-parentheses.mjs +58 -1
- package/dist/functions/functions/wrap-with-parentheses.mjs.map +1 -1
- package/dist/functions/index.mjs +1 -0
- package/dist/functions/index.mjs.map +1 -1
- package/dist/index.mjs +1 -0
- package/dist/index.mjs.map +1 -1
- package/package.json +8 -8
- package/src/cmd/append-as-const.mts +1 -1
- package/src/cmd/convert-interface-to-type.mts +1 -1
- package/src/cmd/convert-to-readonly.mts +1 -1
- package/src/cmd/replace-any-with-unknown.mts +1 -1
- package/src/cmd/replace-record-with-unknown-record.mts +1 -1
- package/src/cmd/run-transformer-cli.mts +3 -3
- package/src/functions/ast-transformers/convert-interface-to-type.mts +6 -7
- package/src/functions/ast-transformers/convert-to-readonly.mts +39 -13
- package/src/functions/ast-transformers/convert-to-readonly.test.mts +79 -0
- package/src/functions/ast-transformers/replace-record-with-unknown-record.mts +15 -12
- package/src/functions/ast-transformers/transform-source-code.mts +3 -2
- package/src/functions/functions/index.mts +1 -0
- package/src/functions/functions/is-as-const-node.mts +2 -1
- package/src/functions/functions/should-avoid-parentheses-for-readonly.mts +32 -0
- package/src/functions/functions/wrap-with-parentheses.mts +75 -2
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.mts","sourceRoot":"","sources":["../../../src/functions/functions/index.mts"],"names":[],"mappings":"AAAA,cAAc,qCAAqC,CAAC;AACpD,cAAc,wBAAwB,CAAC;AACvC,cAAc,2BAA2B,CAAC;AAC1C,cAAc,wBAAwB,CAAC;AACvC,cAAc,gCAAgC,CAAC;AAC/C,cAAc,0BAA0B,CAAC;AACzC,cAAc,uBAAuB,CAAC;AACtC,cAAc,6BAA6B,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.mts","sourceRoot":"","sources":["../../../src/functions/functions/index.mts"],"names":[],"mappings":"AAAA,cAAc,qCAAqC,CAAC;AACpD,cAAc,wBAAwB,CAAC;AACvC,cAAc,2BAA2B,CAAC;AAC1C,cAAc,wBAAwB,CAAC;AACvC,cAAc,gCAAgC,CAAC;AAC/C,cAAc,0BAA0B,CAAC;AACzC,cAAc,6CAA6C,CAAC;AAC5D,cAAc,uBAAuB,CAAC;AACtC,cAAc,6BAA6B,CAAC"}
|
|
@@ -4,6 +4,7 @@ export { isAtomicTypeNode } from './is-atomic-type-node.mjs';
|
|
|
4
4
|
export { isReadonlyArrayTypeNode, isReadonlyTupleOrArrayTypeNode, isReadonlyTupleTypeNode, isReadonlyTypeReferenceNode, isShallowReadonlyTypeNode } from './is-readonly-node.mjs';
|
|
5
5
|
export { isSpreadNamedTupleMemberNode, isSpreadParameterNode } from './is-spread-parameter-node.mjs';
|
|
6
6
|
export { removeParentheses } from './remove-parentheses.mjs';
|
|
7
|
+
export { shouldAvoidParenthesesForReadonly } from './should-avoid-parentheses-for-readonly.mjs';
|
|
7
8
|
export { unwrapReadonlyTypeArgText } from './unwrap-readonly.mjs';
|
|
8
9
|
export { wrapWithParentheses } from './wrap-with-parentheses.mjs';
|
|
9
10
|
//# sourceMappingURL=index.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","sources":[],"sourcesContent":[],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.mjs","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"is-as-const-node.d.mts","sourceRoot":"","sources":["../../../src/functions/functions/is-as-const-node.mts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"is-as-const-node.d.mts","sourceRoot":"","sources":["../../../src/functions/functions/is-as-const-node.mts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,MAAM,UAAU,CAAC;AAE/B,eAAO,MAAM,aAAa,GACxB,MAAM,EAAE,CAAC,IAAI,KACZ,IAAI,IAAI,EAAE,CAAC,YAAY,GACxB,QAAQ,CAAC;IACP,IAAI,EAAE,EAAE,CAAC,iBAAiB,GACxB,QAAQ,CAAC;QACP,QAAQ,EAAE,EAAE,CAAC,UAAU,GACrB,QAAQ,CAAC;YACP,IAAI,EAAE,OAAO,CAAC;SACf,CAAC,CAAC;QACL,aAAa,EAAE,SAAS,CAAC;KAC1B,CAAC,CAAC;CACN,CAgCF,CAAC"}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { Arr } from 'ts-data-forge';
|
|
1
2
|
import * as tsm from 'ts-morph';
|
|
2
3
|
|
|
3
4
|
const isAsConstNode = (node) => {
|
|
@@ -23,7 +24,7 @@ const isAsConstNode = (node) => {
|
|
|
23
24
|
// 6. Check if the Identifier's text is 'const'
|
|
24
25
|
// and that there are no type arguments (as const doesn't have them)
|
|
25
26
|
return (typeNameNode.getText() === 'const' &&
|
|
26
|
-
typeNode.getTypeArguments()
|
|
27
|
+
Arr.isArrayOfLength(typeNode.getTypeArguments(), 0));
|
|
27
28
|
};
|
|
28
29
|
|
|
29
30
|
export { isAsConstNode };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"is-as-const-node.mjs","sources":["../../../src/functions/functions/is-as-const-node.mts"],"sourcesContent":[null],"names":["ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"is-as-const-node.mjs","sources":["../../../src/functions/functions/is-as-const-node.mts"],"sourcesContent":[null],"names":["ts"],"mappings":";;;AAGO,MAAM,aAAa,GAAG,CAC3B,IAAa,KAWR;AACL,IAAA,IAAI,CAAC,IAAI,CAAC,MAAM,CAACA,GAAE,CAAC,UAAU,CAAC,YAAY,CAAC,EAAE;AAC5C,QAAA,OAAO,KAAK;IACd;;AAGA,IAAA,MAAM,QAAQ,GAAG,IAAI,CAAC,WAAW,EAAE;AAEnC,IAAA,IAAI,QAAQ,KAAK,SAAS,EAAE;QAC1B,OAAO,KAAK,CAAC;IACf;;AAGA,IAAA,IAAI,CAAC,QAAQ,CAAC,MAAM,CAACA,GAAE,CAAC,UAAU,CAAC,aAAa,CAAC,EAAE;AACjD,QAAA,OAAO,KAAK;IACd;;AAGA,IAAA,MAAM,YAAY,GAAG,QAAQ,CAAC,WAAW,EAAE;;AAG3C,IAAA,IAAI,CAAC,YAAY,CAAC,MAAM,CAACA,GAAE,CAAC,UAAU,CAAC,UAAU,CAAC,EAAE;;AAElD,QAAA,OAAO,KAAK;IACd;;;AAIA,IAAA,QACE,YAAY,CAAC,OAAO,EAAE,KAAK,OAAO;QAClC,GAAG,CAAC,eAAe,CAAC,QAAQ,CAAC,gBAAgB,EAAE,EAAE,CAAC,CAAC;AAEvD;;;;"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import * as tsm from 'ts-morph';
|
|
2
|
+
/**
|
|
3
|
+
* Determines if parentheses should be avoided when adding 'readonly' prefix to a type.
|
|
4
|
+
*
|
|
5
|
+
* Parentheses should be AVOIDED in these specific contexts:
|
|
6
|
+
* - Type predicates: `x is [T, U]` -> `x is readonly [T, U]` (not `x is (readonly [T, U])`)
|
|
7
|
+
* because parentheses around the type in a type predicate cause syntax errors
|
|
8
|
+
*
|
|
9
|
+
* For all other contexts, we keep parentheses for safety to avoid precedence issues,
|
|
10
|
+
* relying on prettier to remove unnecessary ones.
|
|
11
|
+
*
|
|
12
|
+
* @param node - The type node to check
|
|
13
|
+
* @returns true if parentheses should be avoided, false otherwise (keep parentheses)
|
|
14
|
+
*/
|
|
15
|
+
export declare const shouldAvoidParenthesesForReadonly: (node: tsm.Node) => boolean;
|
|
16
|
+
//# sourceMappingURL=should-avoid-parentheses-for-readonly.d.mts.map
|
|
@@ -0,0 +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,GAAI,MAAM,GAAG,CAAC,IAAI,KAAG,OAgBlE,CAAC"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import * as tsm from 'ts-morph';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Determines if parentheses should be avoided when adding 'readonly' prefix to a type.
|
|
5
|
+
*
|
|
6
|
+
* Parentheses should be AVOIDED in these specific contexts:
|
|
7
|
+
* - Type predicates: `x is [T, U]` -> `x is readonly [T, U]` (not `x is (readonly [T, U])`)
|
|
8
|
+
* because parentheses around the type in a type predicate cause syntax errors
|
|
9
|
+
*
|
|
10
|
+
* For all other contexts, we keep parentheses for safety to avoid precedence issues,
|
|
11
|
+
* relying on prettier to remove unnecessary ones.
|
|
12
|
+
*
|
|
13
|
+
* @param node - The type node to check
|
|
14
|
+
* @returns true if parentheses should be avoided, false otherwise (keep parentheses)
|
|
15
|
+
*/
|
|
16
|
+
const shouldAvoidParenthesesForReadonly = (node) => {
|
|
17
|
+
const parent = node.getParent();
|
|
18
|
+
if (parent === undefined) {
|
|
19
|
+
return false;
|
|
20
|
+
}
|
|
21
|
+
// Check if parent is TypePredicate: x is T
|
|
22
|
+
// In this case, we MUST avoid parentheses: x is readonly T (not x is (readonly T))
|
|
23
|
+
// because `x is (readonly T)` is a syntax error
|
|
24
|
+
if (parent.isKind(tsm.SyntaxKind.TypePredicate)) {
|
|
25
|
+
return true;
|
|
26
|
+
}
|
|
27
|
+
// For all other cases, keep parentheses for safety
|
|
28
|
+
return false;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
export { shouldAvoidParenthesesForReadonly };
|
|
32
|
+
//# sourceMappingURL=should-avoid-parentheses-for-readonly.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"should-avoid-parentheses-for-readonly.mjs","sources":["../../../src/functions/functions/should-avoid-parentheses-for-readonly.mts"],"sourcesContent":[null],"names":[],"mappings":";;AAEA;;;;;;;;;;;;AAYG;AACI,MAAM,iCAAiC,GAAG,CAAC,IAAc,KAAa;AAC3E,IAAA,MAAM,MAAM,GAAG,IAAI,CAAC,SAAS,EAAE;AAE/B,IAAA,IAAI,MAAM,KAAK,SAAS,EAAE;AACxB,QAAA,OAAO,KAAK;IACd;;;;IAKA,IAAI,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,UAAU,CAAC,aAAa,CAAC,EAAE;AAC/C,QAAA,OAAO,IAAI;IACb;;AAGA,IAAA,OAAO,KAAK;AACd;;;;"}
|
|
@@ -1,2 +1,14 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Wraps a string with parentheses if not already wrapped.
|
|
3
|
+
* Avoids adding redundant parentheses when the expression is already
|
|
4
|
+
* fully wrapped with balanced parentheses.
|
|
5
|
+
*
|
|
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)
|
|
12
|
+
*/
|
|
13
|
+
export declare const wrapWithParentheses: (nodeStr: string) => string;
|
|
2
14
|
//# 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":"
|
|
1
|
+
{"version":3,"file":"wrap-with-parentheses.d.mts","sourceRoot":"","sources":["../../../src/functions/functions/wrap-with-parentheses.mts"],"names":[],"mappings":"AAsDA;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,mBAAmB,GAAI,SAAS,MAAM,KAAG,MAQrD,CAAC"}
|
|
@@ -1,4 +1,61 @@
|
|
|
1
|
-
|
|
1
|
+
import { range, asUint32 } from 'ts-data-forge';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Checks if a string is already wrapped with a single pair of balanced parentheses
|
|
5
|
+
* that encompasses the entire expression.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* isWrappedWithParentheses('(A)') // true
|
|
9
|
+
* isWrappedWithParentheses('((A))') // true (outer pair wraps everything)
|
|
10
|
+
* isWrappedWithParentheses('(A) | (B)') // false (outer parens don't wrap everything)
|
|
11
|
+
* isWrappedWithParentheses('A') // false
|
|
12
|
+
*/
|
|
13
|
+
const isWrappedWithParentheses = (str) => {
|
|
14
|
+
const trimmed = str.trim();
|
|
15
|
+
if (!trimmed.startsWith('(') || !trimmed.endsWith(')')) {
|
|
16
|
+
return false;
|
|
17
|
+
}
|
|
18
|
+
// Check if the opening and closing parentheses are balanced
|
|
19
|
+
// and the opening parenthesis corresponds to the closing one
|
|
20
|
+
let mut_depth = 0;
|
|
21
|
+
for (const mut_i of range(0, asUint32(trimmed.length))) {
|
|
22
|
+
const char = trimmed.charAt(mut_i);
|
|
23
|
+
switch (char) {
|
|
24
|
+
case '(': {
|
|
25
|
+
mut_depth += 1;
|
|
26
|
+
break;
|
|
27
|
+
}
|
|
28
|
+
case ')': {
|
|
29
|
+
mut_depth -= 1;
|
|
30
|
+
// If we reach depth 0 before the end, the outer parentheses don't wrap everything
|
|
31
|
+
if (mut_depth === 0 && mut_i < trimmed.length - 1) {
|
|
32
|
+
return false;
|
|
33
|
+
}
|
|
34
|
+
break;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return true;
|
|
39
|
+
};
|
|
40
|
+
/**
|
|
41
|
+
* Wraps a string with parentheses if not already wrapped.
|
|
42
|
+
* Avoids adding redundant parentheses when the expression is already
|
|
43
|
+
* fully wrapped with balanced parentheses.
|
|
44
|
+
*
|
|
45
|
+
* @example
|
|
46
|
+
* wrapWithParentheses('A') // '(A)'
|
|
47
|
+
* wrapWithParentheses('(A)') // '(A)' (not '((A))')
|
|
48
|
+
* wrapWithParentheses('A | B') // '(A | B)'
|
|
49
|
+
* wrapWithParentheses('(A | B)') // '(A | B)' (not '((A | B))')
|
|
50
|
+
* wrapWithParentheses('(A) | (B)') // '((A) | (B))' (needs outer parens)
|
|
51
|
+
*/
|
|
52
|
+
const wrapWithParentheses = (nodeStr) => {
|
|
53
|
+
const trimmed = nodeStr.trim();
|
|
54
|
+
if (isWrappedWithParentheses(trimmed)) {
|
|
55
|
+
return trimmed;
|
|
56
|
+
}
|
|
57
|
+
return `(${trimmed})`;
|
|
58
|
+
};
|
|
2
59
|
|
|
3
60
|
export { wrapWithParentheses };
|
|
4
61
|
//# 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":[null],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"wrap-with-parentheses.mjs","sources":["../../../src/functions/functions/wrap-with-parentheses.mts"],"sourcesContent":[null],"names":[],"mappings":";;AAAA;;;;;;;;;AASG;AAGH,MAAM,wBAAwB,GAAG,CAAC,GAAW,KAAa;AACxD,IAAA,MAAM,OAAO,GAAG,GAAG,CAAC,IAAI,EAAE;AAE1B,IAAA,IAAI,CAAC,OAAO,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE;AACtD,QAAA,OAAO,KAAK;IACd;;;IAIA,IAAI,SAAS,GAAG,CAAC;AAEjB,IAAA,KAAK,MAAM,KAAK,IAAI,KAAK,CAAC,CAAC,EAAE,QAAQ,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,EAAE;QACtD,MAAM,IAAI,GAAW,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC;QAE1C,QAAQ,IAAI;YACV,KAAK,GAAG,EAAE;gBACR,SAAS,IAAI,CAAC;gBAEd;YACF;YAEA,KAAK,GAAG,EAAE;gBACR,SAAS,IAAI,CAAC;;AAGd,gBAAA,IAAI,SAAS,KAAK,CAAC,IAAI,KAAK,GAAG,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE;AACjD,oBAAA,OAAO,KAAK;gBACd;gBAEA;YACF;;IAOJ;AAEA,IAAA,OAAO,IAAI;AACb,CAAC;AAED;;;;;;;;;;;AAWG;AACI,MAAM,mBAAmB,GAAG,CAAC,OAAe,KAAY;AAC7D,IAAA,MAAM,OAAO,GAAG,OAAO,CAAC,IAAI,EAAE;AAE9B,IAAA,IAAI,wBAAwB,CAAC,OAAO,CAAC,EAAE;AACrC,QAAA,OAAO,OAAO;IAChB;IAEA,OAAO,CAAA,CAAA,EAAI,OAAO,CAAA,CAAA,CAAY;AAChC;;;;"}
|
package/dist/functions/index.mjs
CHANGED
|
@@ -15,6 +15,7 @@ export { isAtomicTypeNode } from './functions/is-atomic-type-node.mjs';
|
|
|
15
15
|
export { isReadonlyArrayTypeNode, isReadonlyTupleOrArrayTypeNode, isReadonlyTupleTypeNode, isReadonlyTypeReferenceNode, isShallowReadonlyTypeNode } from './functions/is-readonly-node.mjs';
|
|
16
16
|
export { isSpreadNamedTupleMemberNode, isSpreadParameterNode } from './functions/is-spread-parameter-node.mjs';
|
|
17
17
|
export { removeParentheses } from './functions/remove-parentheses.mjs';
|
|
18
|
+
export { shouldAvoidParenthesesForReadonly } from './functions/should-avoid-parentheses-for-readonly.mjs';
|
|
18
19
|
export { unwrapReadonlyTypeArgText } from './functions/unwrap-readonly.mjs';
|
|
19
20
|
export { wrapWithParentheses } from './functions/wrap-with-parentheses.mjs';
|
|
20
21
|
export { replaceNodeWithDebugPrint } from './utils/replace-with-debug.mjs';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","sources":[],"sourcesContent":[],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.mjs","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;"}
|
package/dist/index.mjs
CHANGED
|
@@ -15,6 +15,7 @@ export { isAtomicTypeNode } from './functions/functions/is-atomic-type-node.mjs'
|
|
|
15
15
|
export { isReadonlyArrayTypeNode, isReadonlyTupleOrArrayTypeNode, isReadonlyTupleTypeNode, isReadonlyTypeReferenceNode, isShallowReadonlyTypeNode } from './functions/functions/is-readonly-node.mjs';
|
|
16
16
|
export { isSpreadNamedTupleMemberNode, isSpreadParameterNode } from './functions/functions/is-spread-parameter-node.mjs';
|
|
17
17
|
export { removeParentheses } from './functions/functions/remove-parentheses.mjs';
|
|
18
|
+
export { shouldAvoidParenthesesForReadonly } from './functions/functions/should-avoid-parentheses-for-readonly.mjs';
|
|
18
19
|
export { unwrapReadonlyTypeArgText } from './functions/functions/unwrap-readonly.mjs';
|
|
19
20
|
export { wrapWithParentheses } from './functions/functions/wrap-with-parentheses.mjs';
|
|
20
21
|
export { replaceNodeWithDebugPrint } from './functions/utils/replace-with-debug.mjs';
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","sources":[],"sourcesContent":[],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.mjs","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ts-codemod-lib",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.5",
|
|
4
4
|
"private": false,
|
|
5
5
|
"keywords": [
|
|
6
6
|
"typescript",
|
|
@@ -99,7 +99,7 @@
|
|
|
99
99
|
"z:vitest:node": "pnpm run z:vitest --project='Node.js'"
|
|
100
100
|
},
|
|
101
101
|
"dependencies": {
|
|
102
|
-
"ts-data-forge": "6.
|
|
102
|
+
"ts-data-forge": "6.5.0",
|
|
103
103
|
"ts-morph": "27.0.2"
|
|
104
104
|
},
|
|
105
105
|
"devDependencies": {
|
|
@@ -113,17 +113,17 @@
|
|
|
113
113
|
"@semantic-release/github": "12.0.3",
|
|
114
114
|
"@semantic-release/npm": "13.1.3",
|
|
115
115
|
"@semantic-release/release-notes-generator": "14.1.0",
|
|
116
|
-
"@types/node": "25.2.
|
|
116
|
+
"@types/node": "25.2.3",
|
|
117
117
|
"@vitest/browser-playwright": "4.0.18",
|
|
118
118
|
"@vitest/coverage-v8": "4.0.18",
|
|
119
119
|
"@vitest/ui": "4.0.18",
|
|
120
120
|
"cmd-ts": "0.14.3",
|
|
121
121
|
"conventional-changelog-conventionalcommits": "9.1.0",
|
|
122
|
-
"cspell": "9.6.
|
|
122
|
+
"cspell": "9.6.4",
|
|
123
123
|
"dedent": "1.7.1",
|
|
124
124
|
"eslint": "9.39.2",
|
|
125
|
-
"eslint-config-typed": "4.6.
|
|
126
|
-
"github-settings-as-code": "1.1.
|
|
125
|
+
"eslint-config-typed": "4.6.2",
|
|
126
|
+
"github-settings-as-code": "1.1.4",
|
|
127
127
|
"jiti": "2.6.1",
|
|
128
128
|
"markdownlint": "0.40.0",
|
|
129
129
|
"markdownlint-cli2": "0.20.0",
|
|
@@ -134,7 +134,7 @@
|
|
|
134
134
|
"prettier-plugin-packagejson": "3.0.0",
|
|
135
135
|
"rollup": "4.57.1",
|
|
136
136
|
"semantic-release": "25.0.3",
|
|
137
|
-
"ts-repo-utils": "8.
|
|
137
|
+
"ts-repo-utils": "8.2.0",
|
|
138
138
|
"ts-type-forge": "2.3.1",
|
|
139
139
|
"tslib": "2.8.1",
|
|
140
140
|
"tsx": "4.21.0",
|
|
@@ -160,7 +160,7 @@
|
|
|
160
160
|
"optional": true
|
|
161
161
|
}
|
|
162
162
|
},
|
|
163
|
-
"packageManager": "pnpm@10.29.
|
|
163
|
+
"packageManager": "pnpm@10.29.3",
|
|
164
164
|
"engines": {
|
|
165
165
|
"node": ">=20.11.0",
|
|
166
166
|
"pnpm": ">=8.0.0"
|
|
@@ -44,7 +44,7 @@ export const runTransformerCLI = async (
|
|
|
44
44
|
|
|
45
45
|
const files = filesResult.value;
|
|
46
46
|
|
|
47
|
-
if (files
|
|
47
|
+
if (Arr.isArrayOfLength(files, 0)) {
|
|
48
48
|
echoIfNotSilent(
|
|
49
49
|
options.uncommitted
|
|
50
50
|
? 'No uncommitted files found'
|
|
@@ -71,7 +71,7 @@ export const runTransformerCLI = async (
|
|
|
71
71
|
📊 Total: ${files.length}
|
|
72
72
|
`);
|
|
73
73
|
|
|
74
|
-
if (errorFiles
|
|
74
|
+
if (Arr.isNonEmpty(errorFiles)) {
|
|
75
75
|
echoIfNotSilent('\nFiles with errors:');
|
|
76
76
|
|
|
77
77
|
for (const fileName of errorFiles) {
|
|
@@ -81,7 +81,7 @@ export const runTransformerCLI = async (
|
|
|
81
81
|
|
|
82
82
|
echoIfNotSilent(hr);
|
|
83
83
|
|
|
84
|
-
if (errorFiles
|
|
84
|
+
if (Arr.isNonEmpty(errorFiles)) {
|
|
85
85
|
return Result.err(undefined);
|
|
86
86
|
}
|
|
87
87
|
|
|
@@ -41,19 +41,18 @@ export const convertInterfaceToTypeTransformer = (): TsMorphTransformer =>
|
|
|
41
41
|
const members = interfaceDecl.getMembers();
|
|
42
42
|
|
|
43
43
|
// Build type parameters string
|
|
44
|
-
const typeParamsStr =
|
|
45
|
-
typeParameters.
|
|
46
|
-
|
|
47
|
-
: '';
|
|
44
|
+
const typeParamsStr = Arr.isNonEmpty(typeParameters)
|
|
45
|
+
? `<${typeParameters.map((tp) => tp.getText()).join(', ')}>`
|
|
46
|
+
: '';
|
|
48
47
|
|
|
49
48
|
// Build type literal from members
|
|
50
49
|
const mut_typeBody: string = (() => {
|
|
51
|
-
if (extendsExpressions
|
|
50
|
+
if (Arr.isArrayOfLength(extendsExpressions, 0)) {
|
|
52
51
|
// No extends: simple type literal
|
|
53
52
|
return buildTypeLiteral(members);
|
|
54
53
|
}
|
|
55
54
|
|
|
56
|
-
if (members
|
|
55
|
+
if (Arr.isArrayOfLength(members, 0)) {
|
|
57
56
|
// Only extends, no own members: union of extended types
|
|
58
57
|
const extendedTypes = extendsExpressions.map((ext) =>
|
|
59
58
|
ext.getText(),
|
|
@@ -98,7 +97,7 @@ export const convertInterfaceToTypeTransformer = (): TsMorphTransformer =>
|
|
|
98
97
|
const buildTypeLiteral = (
|
|
99
98
|
members: readonly tsm.TypeElementTypes[],
|
|
100
99
|
): string => {
|
|
101
|
-
if (members
|
|
100
|
+
if (Arr.isArrayOfLength(members, 0)) {
|
|
102
101
|
return 'Record<string, never>';
|
|
103
102
|
}
|
|
104
103
|
|
|
@@ -15,10 +15,11 @@ import {
|
|
|
15
15
|
isReadonlyTupleOrArrayTypeNode,
|
|
16
16
|
isReadonlyTupleTypeNode,
|
|
17
17
|
isReadonlyTypeReferenceNode,
|
|
18
|
-
type ReadonlyTypeReferenceNode,
|
|
19
18
|
removeParentheses,
|
|
19
|
+
shouldAvoidParenthesesForReadonly,
|
|
20
20
|
unwrapReadonlyTypeArgText,
|
|
21
21
|
wrapWithParentheses,
|
|
22
|
+
type ReadonlyTypeReferenceNode,
|
|
22
23
|
} from '../functions/index.mjs';
|
|
23
24
|
import { replaceNodeWithDebugPrint } from '../utils/index.mjs';
|
|
24
25
|
import {
|
|
@@ -552,9 +553,13 @@ const transformTypeReferenceNode = (
|
|
|
552
553
|
T.isKind(tsm.SyntaxKind.ArrayType) ||
|
|
553
554
|
T.isKind(tsm.SyntaxKind.TupleType)
|
|
554
555
|
) {
|
|
556
|
+
const readonlyText = `readonly ${T.getFullText()}`;
|
|
557
|
+
|
|
555
558
|
options.replaceNode(
|
|
556
559
|
node,
|
|
557
|
-
|
|
560
|
+
shouldAvoidParenthesesForReadonly(node)
|
|
561
|
+
? readonlyText
|
|
562
|
+
: wrapWithParentheses(readonlyText),
|
|
558
563
|
);
|
|
559
564
|
|
|
560
565
|
return;
|
|
@@ -626,9 +631,13 @@ const transformTypeReferenceNode = (
|
|
|
626
631
|
T.isKind(tsm.SyntaxKind.ArrayType) &&
|
|
627
632
|
isAtomicTypeNode(T.getElementTypeNode())
|
|
628
633
|
) {
|
|
634
|
+
const readonlyText = `readonly ${T.getFullText()}`;
|
|
635
|
+
|
|
629
636
|
options.replaceNode(
|
|
630
637
|
node,
|
|
631
|
-
|
|
638
|
+
shouldAvoidParenthesesForReadonly(node)
|
|
639
|
+
? readonlyText
|
|
640
|
+
: wrapWithParentheses(readonlyText),
|
|
632
641
|
);
|
|
633
642
|
|
|
634
643
|
return;
|
|
@@ -640,9 +649,13 @@ const transformTypeReferenceNode = (
|
|
|
640
649
|
T.isKind(tsm.SyntaxKind.TupleType) &&
|
|
641
650
|
T.getElements().every(isAtomicTypeNode)
|
|
642
651
|
) {
|
|
652
|
+
const readonlyText = `readonly ${T.getFullText()}`;
|
|
653
|
+
|
|
643
654
|
options.replaceNode(
|
|
644
655
|
node,
|
|
645
|
-
|
|
656
|
+
shouldAvoidParenthesesForReadonly(node)
|
|
657
|
+
? readonlyText
|
|
658
|
+
: wrapWithParentheses(readonlyText),
|
|
646
659
|
);
|
|
647
660
|
|
|
648
661
|
return;
|
|
@@ -692,9 +705,13 @@ const transformArrayTypeNode = (
|
|
|
692
705
|
case 'Readonly':
|
|
693
706
|
case 'none':
|
|
694
707
|
if (readonlyContext.indexedAccessDepth === 0) {
|
|
708
|
+
const readonlyText = `readonly ${node.getFullText()}`;
|
|
709
|
+
|
|
695
710
|
options.replaceNode(
|
|
696
711
|
node,
|
|
697
|
-
|
|
712
|
+
shouldAvoidParenthesesForReadonly(node)
|
|
713
|
+
? readonlyText
|
|
714
|
+
: wrapWithParentheses(readonlyText),
|
|
698
715
|
);
|
|
699
716
|
}
|
|
700
717
|
}
|
|
@@ -735,9 +752,13 @@ const transformTupleTypeNode = (
|
|
|
735
752
|
case 'Readonly':
|
|
736
753
|
case 'none':
|
|
737
754
|
if (readonlyContext.indexedAccessDepth === 0) {
|
|
755
|
+
const readonlyText = `readonly ${node.getFullText()}`;
|
|
756
|
+
|
|
738
757
|
options.replaceNode(
|
|
739
758
|
node,
|
|
740
|
-
|
|
759
|
+
shouldAvoidParenthesesForReadonly(node)
|
|
760
|
+
? readonlyText
|
|
761
|
+
: wrapWithParentheses(readonlyText),
|
|
741
762
|
);
|
|
742
763
|
}
|
|
743
764
|
}
|
|
@@ -825,7 +846,10 @@ const transformTypeLiteralNode = (
|
|
|
825
846
|
>,
|
|
826
847
|
options: ReadonlyTransformerOptionsInternal,
|
|
827
848
|
): void => {
|
|
828
|
-
if (
|
|
849
|
+
if (
|
|
850
|
+
options.ignoreEmptyObjectTypes &&
|
|
851
|
+
Arr.isArrayOfLength(node.getMembers(), 0)
|
|
852
|
+
) {
|
|
829
853
|
return;
|
|
830
854
|
}
|
|
831
855
|
|
|
@@ -1130,7 +1154,8 @@ const transformUnionOrIntersectionTypeNodeImpl = (
|
|
|
1130
1154
|
// eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
|
|
1131
1155
|
n: tsm.TypeLiteralNode | ReadonlyTypeReferenceNode,
|
|
1132
1156
|
): boolean =>
|
|
1133
|
-
n.isKind(tsm.SyntaxKind.TypeLiteral) &&
|
|
1157
|
+
n.isKind(tsm.SyntaxKind.TypeLiteral) &&
|
|
1158
|
+
Arr.isArrayOfLength(n.getMembers(), 0);
|
|
1134
1159
|
|
|
1135
1160
|
const nonEmptyTypeLiterals =
|
|
1136
1161
|
typeLiterals === undefined
|
|
@@ -1145,7 +1170,8 @@ const transformUnionOrIntersectionTypeNodeImpl = (
|
|
|
1145
1170
|
: typeLiterals.nodes.filter((n) => isEmptyTypeLiteral(n));
|
|
1146
1171
|
|
|
1147
1172
|
const typeLiteralsWrappedWithReadonly: readonly [] | readonly [string] =
|
|
1148
|
-
nonEmptyTypeLiterals === undefined ||
|
|
1173
|
+
nonEmptyTypeLiterals === undefined ||
|
|
1174
|
+
Arr.isArrayOfLength(nonEmptyTypeLiterals, 0)
|
|
1149
1175
|
? ([] as const)
|
|
1150
1176
|
: ([
|
|
1151
1177
|
unionToString({
|
|
@@ -1172,13 +1198,13 @@ const transformUnionOrIntersectionTypeNodeImpl = (
|
|
|
1172
1198
|
arraysAndTuples,
|
|
1173
1199
|
(a) => [a.nodes.map((n) => n.getFullText()), a.firstPosition] as const,
|
|
1174
1200
|
),
|
|
1175
|
-
nonEmptyTypeLiterals !== undefined && nonEmptyTypeLiterals
|
|
1201
|
+
nonEmptyTypeLiterals !== undefined && Arr.isNonEmpty(nonEmptyTypeLiterals)
|
|
1176
1202
|
? mapNullable(
|
|
1177
1203
|
typeLiterals,
|
|
1178
1204
|
(a) => [typeLiteralsWrappedWithReadonly, a.firstPosition] as const,
|
|
1179
1205
|
)
|
|
1180
1206
|
: undefined,
|
|
1181
|
-
emptyTypeLiterals !== undefined && emptyTypeLiterals
|
|
1207
|
+
emptyTypeLiterals !== undefined && Arr.isNonEmpty(emptyTypeLiterals)
|
|
1182
1208
|
? mapNullable(
|
|
1183
1209
|
typeLiterals,
|
|
1184
1210
|
(a) =>
|
|
@@ -1187,7 +1213,7 @@ const transformUnionOrIntersectionTypeNodeImpl = (
|
|
|
1187
1213
|
// Use a position after non-empty type literals if they exist
|
|
1188
1214
|
a.firstPosition +
|
|
1189
1215
|
(nonEmptyTypeLiterals !== undefined &&
|
|
1190
|
-
nonEmptyTypeLiterals
|
|
1216
|
+
Arr.isNonEmpty(nonEmptyTypeLiterals)
|
|
1191
1217
|
? 0.5
|
|
1192
1218
|
: 0),
|
|
1193
1219
|
] as const,
|
|
@@ -1226,7 +1252,7 @@ const unionToString = ({
|
|
|
1226
1252
|
op: '&' | '|';
|
|
1227
1253
|
wrapWithReadonly: boolean | string;
|
|
1228
1254
|
}>): string =>
|
|
1229
|
-
types
|
|
1255
|
+
Arr.isArrayOfLength(types, 0)
|
|
1230
1256
|
? 'never'
|
|
1231
1257
|
: Arr.isArrayOfLength(types, 1)
|
|
1232
1258
|
? wrapWithReadonly === false
|
|
@@ -3816,6 +3816,85 @@ describe(convertToReadonlyTransformer, () => {
|
|
|
3816
3816
|
])('$name', testFn);
|
|
3817
3817
|
});
|
|
3818
3818
|
|
|
3819
|
+
describe('shouldAvoidParenthesesForReadonly', () => {
|
|
3820
|
+
test.each([
|
|
3821
|
+
{
|
|
3822
|
+
name: 'Type predicate with array - avoid parentheses',
|
|
3823
|
+
source: dedent`
|
|
3824
|
+
function isStringArray(x: unknown): x is string[] {
|
|
3825
|
+
return Array.isArray(x);
|
|
3826
|
+
}
|
|
3827
|
+
`,
|
|
3828
|
+
expected: dedent`
|
|
3829
|
+
function isStringArray(x: unknown): x is readonly string[] {
|
|
3830
|
+
return Array.isArray(x);
|
|
3831
|
+
}
|
|
3832
|
+
`,
|
|
3833
|
+
},
|
|
3834
|
+
{
|
|
3835
|
+
name: 'Type predicate with tuple - avoid parentheses',
|
|
3836
|
+
source: dedent`
|
|
3837
|
+
function isTuple(x: unknown): x is [string, number] {
|
|
3838
|
+
return Array.isArray(x) && x.length === 2;
|
|
3839
|
+
}
|
|
3840
|
+
`,
|
|
3841
|
+
expected: dedent`
|
|
3842
|
+
function isTuple(x: unknown): x is readonly [string, number] {
|
|
3843
|
+
return Array.isArray(x) && x.length === 2;
|
|
3844
|
+
}
|
|
3845
|
+
`,
|
|
3846
|
+
},
|
|
3847
|
+
{
|
|
3848
|
+
name: 'Type predicate with Readonly<T[]> - avoid parentheses',
|
|
3849
|
+
source: dedent`
|
|
3850
|
+
function isArray(x: unknown): x is Readonly<string[]> {
|
|
3851
|
+
return Array.isArray(x);
|
|
3852
|
+
}
|
|
3853
|
+
`,
|
|
3854
|
+
expected: dedent`
|
|
3855
|
+
function isArray(x: unknown): x is readonly string[] {
|
|
3856
|
+
return Array.isArray(x);
|
|
3857
|
+
}
|
|
3858
|
+
`,
|
|
3859
|
+
},
|
|
3860
|
+
{
|
|
3861
|
+
name: 'Type predicate with Readonly<[T, U]> - avoid parentheses',
|
|
3862
|
+
source: dedent`
|
|
3863
|
+
function isTuple(x: unknown): x is Readonly<[string, number]> {
|
|
3864
|
+
return Array.isArray(x);
|
|
3865
|
+
}
|
|
3866
|
+
`,
|
|
3867
|
+
expected: dedent`
|
|
3868
|
+
function isTuple(x: unknown): x is readonly [string, number] {
|
|
3869
|
+
return Array.isArray(x);
|
|
3870
|
+
}
|
|
3871
|
+
`,
|
|
3872
|
+
},
|
|
3873
|
+
{
|
|
3874
|
+
name: 'Non-type predicate context - keep parentheses (handled by formatter)',
|
|
3875
|
+
source: dedent`
|
|
3876
|
+
type T = Readonly<string[]>;
|
|
3877
|
+
`,
|
|
3878
|
+
expected: dedent`
|
|
3879
|
+
type T = readonly string[];
|
|
3880
|
+
`,
|
|
3881
|
+
},
|
|
3882
|
+
{
|
|
3883
|
+
name: 'Function return type - keep parentheses (handled by formatter)',
|
|
3884
|
+
source: dedent`
|
|
3885
|
+
function foo(): Readonly<number[]> {
|
|
3886
|
+
return [];
|
|
3887
|
+
}
|
|
3888
|
+
`,
|
|
3889
|
+
expected: dedent`
|
|
3890
|
+
function foo(): readonly number[] {
|
|
3891
|
+
return [];
|
|
3892
|
+
}
|
|
3893
|
+
`,
|
|
3894
|
+
},
|
|
3895
|
+
])('$name', testFn);
|
|
3896
|
+
});
|
|
3897
|
+
|
|
3819
3898
|
describe('Error Cases', () => {
|
|
3820
3899
|
test('Invalid DeepReadonlyTypeName', () => {
|
|
3821
3900
|
expect(() => {
|