ts-codemod-lib 1.1.3 → 1.2.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/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/entry-point.mjs +1 -1
- package/dist/functions/ast-transformers/append-as-const.d.mts.map +1 -1
- package/dist/functions/ast-transformers/append-as-const.mjs +11 -5
- package/dist/functions/ast-transformers/append-as-const.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 +71 -66
- package/dist/functions/ast-transformers/convert-interface-to-type.mjs.map +1 -1
- package/dist/functions/ast-transformers/convert-to-readonly-type.d.mts.map +1 -1
- package/dist/functions/ast-transformers/convert-to-readonly-type.mjs +12 -6
- package/dist/functions/ast-transformers/convert-to-readonly-type.mjs.map +1 -1
- package/dist/functions/ast-transformers/replace-any-with-unknown.d.mts.map +1 -1
- package/dist/functions/ast-transformers/replace-any-with-unknown.mjs +35 -26
- package/dist/functions/ast-transformers/replace-any-with-unknown.mjs.map +1 -1
- package/dist/functions/ast-transformers/replace-record-with-unknown-record.d.mts.map +1 -1
- package/dist/functions/ast-transformers/replace-record-with-unknown-record.mjs +141 -135
- 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 +40 -7
- package/dist/functions/ast-transformers/transform-source-code.mjs.map +1 -1
- package/dist/functions/ast-transformers/types.d.mts +9 -1
- package/dist/functions/ast-transformers/types.d.mts.map +1 -1
- package/dist/functions/constants/ignore-comment-text.d.mts +2 -2
- package/dist/functions/constants/ignore-comment-text.d.mts.map +1 -1
- package/dist/functions/constants/ignore-comment-text.mjs +3 -3
- package/dist/functions/constants/ignore-comment-text.mjs.map +1 -1
- package/dist/functions/constants/index.mjs +1 -1
- package/dist/functions/functions/has-disable-next-line-comment.d.mts +8 -2
- package/dist/functions/functions/has-disable-next-line-comment.d.mts.map +1 -1
- package/dist/functions/functions/has-disable-next-line-comment.mjs +31 -6
- package/dist/functions/functions/has-disable-next-line-comment.mjs.map +1 -1
- package/dist/functions/index.mjs +1 -1
- package/dist/index.mjs +1 -1
- package/package.json +9 -9
- 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/functions/ast-transformers/append-as-const.mts +18 -9
- package/src/functions/ast-transformers/append-as-const.test.mts +58 -0
- package/src/functions/ast-transformers/convert-interface-to-type.mts +8 -2
- package/src/functions/ast-transformers/convert-to-readonly-type.mts +40 -31
- package/src/functions/ast-transformers/convert-to-readonly-type.test.mts +4 -0
- package/src/functions/ast-transformers/replace-any-with-unknown.mts +41 -28
- package/src/functions/ast-transformers/replace-any-with-unknown.test.mts +58 -0
- package/src/functions/ast-transformers/replace-record-with-unknown-record.mts +175 -167
- package/src/functions/ast-transformers/transform-source-code.mts +59 -9
- package/src/functions/ast-transformers/transformer-specific-ignore.test.mts +190 -0
- package/src/functions/ast-transformers/types.mts +10 -3
- package/src/functions/constants/ignore-comment-text.mts +2 -2
- package/src/functions/functions/has-disable-next-line-comment.mts +39 -8
|
@@ -9,230 +9,238 @@ import { type TsMorphTransformer } from './types.mjs';
|
|
|
9
9
|
* and index signatures `[k: string]: unknown` with `UnknownRecord`
|
|
10
10
|
*/
|
|
11
11
|
export const replaceRecordWithUnknownRecordTransformer =
|
|
12
|
-
(): TsMorphTransformer =>
|
|
13
|
-
const
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
12
|
+
(): TsMorphTransformer => {
|
|
13
|
+
const transformer: TsMorphTransformer = (sourceAst) => {
|
|
14
|
+
const processDeclarations = (
|
|
15
|
+
container: tsm.SourceFile | tsm.ModuleDeclaration,
|
|
16
|
+
): void => {
|
|
17
|
+
const typeAliases = container.getTypeAliases();
|
|
17
18
|
|
|
18
|
-
|
|
19
|
-
|
|
19
|
+
for (const typeAlias of typeAliases) {
|
|
20
|
+
const typeNode = typeAlias.getTypeNode();
|
|
20
21
|
|
|
21
|
-
|
|
22
|
+
if (typeNode === undefined) continue;
|
|
22
23
|
|
|
23
|
-
|
|
24
|
-
|
|
24
|
+
visitTypeNode(typeNode);
|
|
25
|
+
}
|
|
25
26
|
|
|
26
|
-
|
|
27
|
+
const interfaces = container.getInterfaces();
|
|
27
28
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
29
|
+
for (const interfaceDecl of interfaces) {
|
|
30
|
+
// Check if interface has index signature [k: string]: unknown
|
|
31
|
+
const indexSignatures = interfaceDecl.getIndexSignatures();
|
|
31
32
|
|
|
32
|
-
|
|
33
|
-
|
|
33
|
+
const hasStringUnknownSignature = indexSignatures.some((sig) => {
|
|
34
|
+
const keyType = sig.getKeyType();
|
|
34
35
|
|
|
35
|
-
|
|
36
|
+
const returnType = sig.getReturnType();
|
|
36
37
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
38
|
+
return (
|
|
39
|
+
keyType.getText() === 'string' &&
|
|
40
|
+
returnType.getText() === 'unknown'
|
|
41
|
+
);
|
|
42
|
+
});
|
|
41
43
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
44
|
+
// If it has the signature and no other members, replace entire interface with type alias
|
|
45
|
+
if (hasStringUnknownSignature) {
|
|
46
|
+
const properties = interfaceDecl.getProperties();
|
|
45
47
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
48
|
+
if (properties.length === 0 && indexSignatures.length === 1) {
|
|
49
|
+
// Replace interface with type alias
|
|
50
|
+
const interfaceName = interfaceDecl.getName();
|
|
49
51
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
52
|
+
interfaceDecl.replaceWithText(
|
|
53
|
+
`export type ${interfaceName} = UnknownRecord;`,
|
|
54
|
+
);
|
|
53
55
|
|
|
54
|
-
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
55
58
|
}
|
|
56
|
-
}
|
|
57
59
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
60
|
+
// Otherwise, check properties for Record types
|
|
61
|
+
for (const property of interfaceDecl.getProperties()) {
|
|
62
|
+
const typeNode = property.getTypeNode();
|
|
61
63
|
|
|
62
|
-
|
|
64
|
+
if (typeNode === undefined) continue;
|
|
63
65
|
|
|
64
|
-
|
|
66
|
+
visitTypeNode(typeNode);
|
|
67
|
+
}
|
|
65
68
|
}
|
|
66
|
-
}
|
|
67
|
-
};
|
|
69
|
+
};
|
|
68
70
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
71
|
+
const visitTypeNode = (node: tsm.TypeNode): void => {
|
|
72
|
+
if (tsm.Node.isTypeReference(node)) {
|
|
73
|
+
// Check if it's Readonly<{ [k: string]: unknown }>
|
|
74
|
+
if (node.getTypeName().getText() === 'Readonly') {
|
|
75
|
+
const typeArgs = node.getTypeArguments();
|
|
74
76
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
+
if (typeArgs.length === 1) {
|
|
78
|
+
const typeArg = typeArgs[0];
|
|
77
79
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
+
if (tsm.Node.isTypeLiteral(typeArg)) {
|
|
81
|
+
const members = typeArg.getMembers();
|
|
80
82
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
83
|
+
const indexSigs = members.filter((m) =>
|
|
84
|
+
tsm.Node.isIndexSignatureDeclaration(m),
|
|
85
|
+
);
|
|
84
86
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
87
|
+
// Check if it has only one index signature [k: string]: unknown
|
|
88
|
+
if (
|
|
89
|
+
members.length === 1 &&
|
|
90
|
+
Arr.isArrayOfLength(indexSigs, 1) &&
|
|
91
|
+
isStringUnknownIndexSignature(indexSigs[0])
|
|
92
|
+
) {
|
|
93
|
+
node.replaceWithText('UnknownRecord');
|
|
92
94
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
95
97
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
+
// Otherwise, recurse into the type literal to visit its properties
|
|
99
|
+
visitTypeNode(typeArg);
|
|
98
100
|
|
|
99
|
-
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
100
103
|
}
|
|
101
104
|
}
|
|
102
|
-
}
|
|
103
105
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
+
replaceIfRecordUnknown(node);
|
|
107
|
+
}
|
|
106
108
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
109
|
+
// Check for type literal { [k: string]: unknown }
|
|
110
|
+
if (tsm.Node.isTypeLiteral(node)) {
|
|
111
|
+
const members = node.getMembers();
|
|
110
112
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
113
|
+
const indexSigs = members.filter((m) =>
|
|
114
|
+
tsm.Node.isIndexSignatureDeclaration(m),
|
|
115
|
+
);
|
|
114
116
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
117
|
+
// Check if it has only one index signature [k: string]: unknown
|
|
118
|
+
if (
|
|
119
|
+
members.length === 1 &&
|
|
120
|
+
Arr.isArrayOfLength(indexSigs, 1) &&
|
|
121
|
+
isStringUnknownIndexSignature(indexSigs[0])
|
|
122
|
+
) {
|
|
123
|
+
node.replaceWithText('UnknownRecord');
|
|
122
124
|
|
|
123
|
-
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
124
127
|
}
|
|
125
|
-
}
|
|
126
128
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
129
|
+
// Recursively visit child type nodes
|
|
130
|
+
if (tsm.Node.isUnionTypeNode(node)) {
|
|
131
|
+
for (const typeNode of node.getTypeNodes()) {
|
|
132
|
+
visitTypeNode(typeNode);
|
|
133
|
+
}
|
|
134
|
+
} else if (tsm.Node.isIntersectionTypeNode(node)) {
|
|
135
|
+
for (const typeNode of node.getTypeNodes()) {
|
|
136
|
+
visitTypeNode(typeNode);
|
|
137
|
+
}
|
|
138
|
+
} else if (tsm.Node.isArrayTypeNode(node)) {
|
|
139
|
+
visitTypeNode(node.getElementTypeNode());
|
|
140
|
+
} else if (tsm.Node.isTupleTypeNode(node)) {
|
|
141
|
+
for (const element of node.getElements()) {
|
|
142
|
+
visitTypeNode(element);
|
|
143
|
+
}
|
|
144
|
+
} else if (tsm.Node.isParenthesizedTypeNode(node)) {
|
|
145
|
+
visitTypeNode(node.getTypeNode());
|
|
146
|
+
} else if (tsm.Node.isTypeLiteral(node)) {
|
|
147
|
+
for (const member of node.getMembers()) {
|
|
148
|
+
if (tsm.Node.isPropertySignature(member)) {
|
|
149
|
+
const typeNode = member.getTypeNode();
|
|
150
|
+
|
|
151
|
+
if (typeNode !== undefined) {
|
|
152
|
+
visitTypeNode(typeNode);
|
|
153
|
+
}
|
|
154
|
+
} else if (tsm.Node.isIndexSignatureDeclaration(member)) {
|
|
155
|
+
const typeNode = member.getReturnTypeNode();
|
|
154
156
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
+
if (typeNode !== undefined) {
|
|
158
|
+
visitTypeNode(typeNode);
|
|
159
|
+
}
|
|
157
160
|
}
|
|
158
161
|
}
|
|
159
162
|
}
|
|
160
|
-
}
|
|
161
|
-
};
|
|
163
|
+
};
|
|
162
164
|
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
165
|
+
const isStringUnknownIndexSignature = (
|
|
166
|
+
sig: tsm.IndexSignatureDeclaration,
|
|
167
|
+
): boolean => {
|
|
168
|
+
const keyType = sig.getKeyTypeNode();
|
|
167
169
|
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
return (
|
|
171
|
-
keyType.getText() === 'string' && returnType?.getText() === 'unknown'
|
|
172
|
-
);
|
|
173
|
-
};
|
|
170
|
+
const returnType = sig.getReturnTypeNode();
|
|
174
171
|
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
case 'Record': {
|
|
180
|
-
// Check if it's Record<string, unknown>
|
|
181
|
-
const typeArgs = node.getTypeArguments();
|
|
182
|
-
|
|
183
|
-
if (
|
|
184
|
-
typeArgs.length === 2 &&
|
|
185
|
-
typeArgs[0]?.getText() === 'string' &&
|
|
186
|
-
typeArgs[1]?.getText() === 'unknown'
|
|
187
|
-
) {
|
|
188
|
-
node.replaceWithText('UnknownRecord');
|
|
189
|
-
}
|
|
172
|
+
return (
|
|
173
|
+
keyType.getText() === 'string' && returnType?.getText() === 'unknown'
|
|
174
|
+
);
|
|
175
|
+
};
|
|
190
176
|
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
case 'Readonly': {
|
|
194
|
-
// Check if it's Readonly<Record<string, unknown>>
|
|
195
|
-
const typeArgs = node.getTypeArguments();
|
|
177
|
+
const replaceIfRecordUnknown = (node: tsm.TypeReferenceNode): void => {
|
|
178
|
+
const typeName = node.getTypeName().getText();
|
|
196
179
|
|
|
197
|
-
|
|
198
|
-
|
|
180
|
+
switch (typeName) {
|
|
181
|
+
case 'Record': {
|
|
182
|
+
// Check if it's Record<string, unknown>
|
|
183
|
+
const typeArgs = node.getTypeArguments();
|
|
199
184
|
|
|
200
185
|
if (
|
|
201
|
-
|
|
202
|
-
|
|
186
|
+
typeArgs.length === 2 &&
|
|
187
|
+
typeArgs[0]?.getText() === 'string' &&
|
|
188
|
+
typeArgs[1]?.getText() === 'unknown'
|
|
203
189
|
) {
|
|
204
|
-
|
|
190
|
+
node.replaceWithText('UnknownRecord');
|
|
191
|
+
}
|
|
205
192
|
|
|
206
|
-
|
|
207
|
-
|
|
193
|
+
break;
|
|
194
|
+
}
|
|
195
|
+
case 'Readonly': {
|
|
196
|
+
// Check if it's Readonly<Record<string, unknown>>
|
|
197
|
+
const typeArgs = node.getTypeArguments();
|
|
208
198
|
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
199
|
+
if (typeArgs.length === 1) {
|
|
200
|
+
const innerType = typeArgs[0];
|
|
201
|
+
|
|
202
|
+
if (
|
|
203
|
+
innerType !== undefined &&
|
|
204
|
+
tsm.Node.isTypeReference(innerType)
|
|
205
|
+
) {
|
|
206
|
+
const innerTypeName = innerType.getTypeName().getText();
|
|
207
|
+
|
|
208
|
+
if (innerTypeName === 'Record') {
|
|
209
|
+
const innerTypeArgs = innerType.getTypeArguments();
|
|
210
|
+
|
|
211
|
+
if (
|
|
212
|
+
innerTypeArgs.length === 2 &&
|
|
213
|
+
innerTypeArgs[0]?.getText() === 'string' &&
|
|
214
|
+
innerTypeArgs[1]?.getText() === 'unknown'
|
|
215
|
+
) {
|
|
216
|
+
node.replaceWithText('UnknownRecord');
|
|
217
|
+
}
|
|
215
218
|
}
|
|
216
219
|
}
|
|
217
220
|
}
|
|
221
|
+
|
|
222
|
+
break;
|
|
218
223
|
}
|
|
219
224
|
|
|
220
|
-
|
|
225
|
+
default: {
|
|
226
|
+
break;
|
|
227
|
+
}
|
|
221
228
|
}
|
|
229
|
+
};
|
|
222
230
|
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
231
|
+
// Process top-level declarations
|
|
232
|
+
processDeclarations(sourceAst);
|
|
233
|
+
|
|
234
|
+
// Process declarations inside namespaces/modules
|
|
235
|
+
const namespaces = sourceAst.getModules();
|
|
236
|
+
|
|
237
|
+
for (const namespace of namespaces) {
|
|
238
|
+
processDeclarations(namespace);
|
|
226
239
|
}
|
|
227
240
|
};
|
|
228
241
|
|
|
229
|
-
//
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
// Process declarations inside namespaces/modules
|
|
233
|
-
const namespaces = sourceAst.getModules();
|
|
242
|
+
// eslint-disable-next-line functional/immutable-data
|
|
243
|
+
transformer.transformerName = 'replace-record-with-unknown-record';
|
|
234
244
|
|
|
235
|
-
|
|
236
|
-
processDeclarations(namespace);
|
|
237
|
-
}
|
|
245
|
+
return transformer;
|
|
238
246
|
};
|
|
@@ -1,21 +1,59 @@
|
|
|
1
1
|
import * as tsm from 'ts-morph';
|
|
2
|
-
import { IGNORE_FILE_COMMENT_TEXT } from '../constants/index.mjs';
|
|
3
2
|
import { type TsMorphTransformer } from './types.mjs';
|
|
4
3
|
|
|
4
|
+
const extractFileIgnoreTransformers = (code: string): readonly string[] => {
|
|
5
|
+
const match = /\/\*\s*transformer-ignore\s*(.*?)\s*\*\//u.exec(code);
|
|
6
|
+
|
|
7
|
+
if (match === null) {
|
|
8
|
+
return [];
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const targetTransformers = match[1]?.trim() ?? '';
|
|
12
|
+
|
|
13
|
+
// Empty means ignore all transformers
|
|
14
|
+
if (targetTransformers === '') {
|
|
15
|
+
return [];
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// Parse comma-separated transformer names
|
|
19
|
+
return targetTransformers.split(',').map((name) => name.trim());
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
const shouldSkipFile = (
|
|
23
|
+
code: string,
|
|
24
|
+
transformerName: string | undefined,
|
|
25
|
+
): boolean => {
|
|
26
|
+
const ignoredTransformers = extractFileIgnoreTransformers(code);
|
|
27
|
+
|
|
28
|
+
// If no file-level ignore comment found, don't skip
|
|
29
|
+
if (
|
|
30
|
+
ignoredTransformers.length === 0 &&
|
|
31
|
+
!/\/\*\s*transformer-ignore\s*.*?\s*\*\//u.test(code)
|
|
32
|
+
) {
|
|
33
|
+
return false;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Empty array means ignore all transformers (file-level ignore without specific transformers)
|
|
37
|
+
if (ignoredTransformers.length === 0) {
|
|
38
|
+
return true;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// If transformer name is not specified, don't skip
|
|
42
|
+
if (transformerName === undefined) {
|
|
43
|
+
return false;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Check if the transformer is in the ignore list
|
|
47
|
+
return ignoredTransformers.includes(transformerName);
|
|
48
|
+
};
|
|
49
|
+
|
|
5
50
|
export const transformSourceCode = (
|
|
6
51
|
code: string,
|
|
7
52
|
isTsx: boolean,
|
|
53
|
+
// eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
|
|
8
54
|
transformers: readonly TsMorphTransformer[],
|
|
9
55
|
debug: boolean = false,
|
|
10
56
|
): string => {
|
|
11
|
-
if (code.includes(IGNORE_FILE_COMMENT_TEXT)) {
|
|
12
|
-
if (debug) {
|
|
13
|
-
console.debug('skipped by ignore-file comment');
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
return code;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
57
|
const project = new tsm.Project({
|
|
20
58
|
useInMemoryFileSystem: true,
|
|
21
59
|
compilerOptions: {
|
|
@@ -31,6 +69,18 @@ export const transformSourceCode = (
|
|
|
31
69
|
);
|
|
32
70
|
|
|
33
71
|
for (const transformer of transformers) {
|
|
72
|
+
const transformerName = transformer.transformerName;
|
|
73
|
+
|
|
74
|
+
if (shouldSkipFile(code, transformerName)) {
|
|
75
|
+
if (debug) {
|
|
76
|
+
console.debug(
|
|
77
|
+
`skipped by ignore-file comment${transformerName !== undefined ? ` for transformer: ${transformerName}` : ''}`,
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
83
|
+
|
|
34
84
|
transformer(sourceAst);
|
|
35
85
|
}
|
|
36
86
|
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
/* eslint-disable tree-shakable/import-star */
|
|
2
|
+
/* eslint-disable import-x/namespace */
|
|
3
|
+
/* eslint-disable vitest/expect-expect */
|
|
4
|
+
import dedent from 'dedent';
|
|
5
|
+
import * as prettierPluginEstree from 'prettier/plugins/estree';
|
|
6
|
+
import * as prettierPluginTypeScript from 'prettier/plugins/typescript';
|
|
7
|
+
import * as prettier from 'prettier/standalone';
|
|
8
|
+
import { appendAsConstTransformer } from './append-as-const.mjs';
|
|
9
|
+
import { convertToReadonlyTypeTransformer } from './convert-to-readonly-type.mjs';
|
|
10
|
+
import { replaceAnyWithUnknownTransformer } from './replace-any-with-unknown.mjs';
|
|
11
|
+
import { transformSourceCode } from './transform-source-code.mjs';
|
|
12
|
+
|
|
13
|
+
const testFn = async ({
|
|
14
|
+
source,
|
|
15
|
+
expected,
|
|
16
|
+
debug,
|
|
17
|
+
}: Readonly<{
|
|
18
|
+
source: string;
|
|
19
|
+
expected: string;
|
|
20
|
+
debug?: boolean;
|
|
21
|
+
}>): Promise<void> => {
|
|
22
|
+
if (debug !== true) {
|
|
23
|
+
// eslint-disable-next-line vitest/no-restricted-vi-methods
|
|
24
|
+
vi.spyOn(console, 'debug').mockImplementation(() => {});
|
|
25
|
+
|
|
26
|
+
// eslint-disable-next-line vitest/no-restricted-vi-methods
|
|
27
|
+
vi.spyOn(console, 'log').mockImplementation(() => {});
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const transformed = await formatter(
|
|
31
|
+
transformSourceCode(source, false, [
|
|
32
|
+
replaceAnyWithUnknownTransformer(),
|
|
33
|
+
appendAsConstTransformer(),
|
|
34
|
+
convertToReadonlyTypeTransformer(),
|
|
35
|
+
]),
|
|
36
|
+
);
|
|
37
|
+
|
|
38
|
+
const expectedFormatted = await formatter(expected);
|
|
39
|
+
|
|
40
|
+
expect(transformed).toBe(expectedFormatted);
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
const formatter = (code: string): Promise<string> =>
|
|
44
|
+
prettier.format(code, {
|
|
45
|
+
parser: 'typescript',
|
|
46
|
+
plugins: [prettierPluginTypeScript, prettierPluginEstree],
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
describe('Transformer-specific ignore comments (Integration)', () => {
|
|
50
|
+
test.each([
|
|
51
|
+
{
|
|
52
|
+
name: 'Ignore specific transformer on next line',
|
|
53
|
+
source: dedent`
|
|
54
|
+
// transformer-ignore-next-line replace-any-with-unknown
|
|
55
|
+
type A = any; // Should remain any
|
|
56
|
+
const b = [1, 2, 3]; // Should have as const
|
|
57
|
+
type C = number[]; // Should be readonly
|
|
58
|
+
`,
|
|
59
|
+
expected: dedent`
|
|
60
|
+
// transformer-ignore-next-line replace-any-with-unknown
|
|
61
|
+
type A = any; // Should remain any
|
|
62
|
+
const b = [1, 2, 3] as const; // Should have as const
|
|
63
|
+
type C = readonly number[]; // Should be readonly
|
|
64
|
+
`,
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
name: 'Ignore specific transformer for entire file',
|
|
68
|
+
source: dedent`
|
|
69
|
+
/* transformer-ignore replace-any-with-unknown */
|
|
70
|
+
type A = any; // Should remain any
|
|
71
|
+
const b = [1, 2, 3]; // Should have as const
|
|
72
|
+
type C = number[]; // Should be readonly
|
|
73
|
+
type D = any; // Should remain any
|
|
74
|
+
`,
|
|
75
|
+
expected: dedent`
|
|
76
|
+
/* transformer-ignore replace-any-with-unknown */
|
|
77
|
+
type A = any; // Should remain any
|
|
78
|
+
const b = [1, 2, 3] as const; // Should have as const
|
|
79
|
+
type C = readonly number[]; // Should be readonly
|
|
80
|
+
type D = any; // Should remain any
|
|
81
|
+
`,
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
name: 'Ignore multiple specific transformers on next line',
|
|
85
|
+
source: dedent`
|
|
86
|
+
type A = any;
|
|
87
|
+
// transformer-ignore-next-line replace-any-with-unknown, append-as-const
|
|
88
|
+
type B = any;
|
|
89
|
+
const c = [1, 2, 3];
|
|
90
|
+
type D = number[];
|
|
91
|
+
`,
|
|
92
|
+
expected: dedent`
|
|
93
|
+
type A = unknown;
|
|
94
|
+
// transformer-ignore-next-line replace-any-with-unknown, append-as-const
|
|
95
|
+
type B = any;
|
|
96
|
+
const c = [1, 2, 3] as const;
|
|
97
|
+
type D = readonly number[];
|
|
98
|
+
`,
|
|
99
|
+
},
|
|
100
|
+
{
|
|
101
|
+
name: 'Ignore multiple specific transformers for entire file',
|
|
102
|
+
source: dedent`
|
|
103
|
+
/* transformer-ignore replace-any-with-unknown, convert-to-readonly-type */
|
|
104
|
+
type A = any; // Should remain any
|
|
105
|
+
const b = [1, 2, 3]; // Should have as const
|
|
106
|
+
type C = number[]; // Should remain mutable
|
|
107
|
+
type D = any; // Should remain any
|
|
108
|
+
`,
|
|
109
|
+
expected: dedent`
|
|
110
|
+
/* transformer-ignore replace-any-with-unknown, convert-to-readonly-type */
|
|
111
|
+
type A = any; // Should remain any
|
|
112
|
+
const b = [1, 2, 3] as const; // Should have as const
|
|
113
|
+
type C = number[]; // Should remain mutable
|
|
114
|
+
type D = any; // Should remain any
|
|
115
|
+
`,
|
|
116
|
+
},
|
|
117
|
+
{
|
|
118
|
+
name: 'Different ignore comments for different lines',
|
|
119
|
+
source: dedent`
|
|
120
|
+
type A = any;
|
|
121
|
+
// transformer-ignore-next-line replace-any-with-unknown
|
|
122
|
+
type B = any;
|
|
123
|
+
const c = [1, 2, 3];
|
|
124
|
+
// transformer-ignore-next-line append-as-const
|
|
125
|
+
const d = [4, 5, 6];
|
|
126
|
+
type E = number[];
|
|
127
|
+
// transformer-ignore-next-line convert-to-readonly-type
|
|
128
|
+
type F = string[];
|
|
129
|
+
`,
|
|
130
|
+
expected: dedent`
|
|
131
|
+
type A = unknown;
|
|
132
|
+
// transformer-ignore-next-line replace-any-with-unknown
|
|
133
|
+
type B = any;
|
|
134
|
+
const c = [1, 2, 3] as const;
|
|
135
|
+
// transformer-ignore-next-line append-as-const
|
|
136
|
+
const d = [4, 5, 6];
|
|
137
|
+
type E = readonly number[];
|
|
138
|
+
// transformer-ignore-next-line convert-to-readonly-type
|
|
139
|
+
type F = string[];
|
|
140
|
+
`,
|
|
141
|
+
},
|
|
142
|
+
{
|
|
143
|
+
name: 'Wrong transformer name should not affect any transformer',
|
|
144
|
+
source: dedent`
|
|
145
|
+
// transformer-ignore-next-line non-existent-transformer
|
|
146
|
+
type A = any; // Should be unknown
|
|
147
|
+
const b = [1, 2, 3]; // Should have as const
|
|
148
|
+
type C = number[]; // Should be readonly
|
|
149
|
+
`,
|
|
150
|
+
expected: dedent`
|
|
151
|
+
// transformer-ignore-next-line non-existent-transformer
|
|
152
|
+
type A = unknown; // Should be unknown
|
|
153
|
+
const b = [1, 2, 3] as const; // Should have as const
|
|
154
|
+
type C = readonly number[]; // Should be readonly
|
|
155
|
+
`,
|
|
156
|
+
},
|
|
157
|
+
{
|
|
158
|
+
name: 'Ignore all transformers on next line (no transformer specified)',
|
|
159
|
+
source: dedent`
|
|
160
|
+
type A = any;
|
|
161
|
+
// transformer-ignore-next-line
|
|
162
|
+
type B = any;
|
|
163
|
+
const c = [1, 2, 3];
|
|
164
|
+
type D = number[];
|
|
165
|
+
`,
|
|
166
|
+
expected: dedent`
|
|
167
|
+
type A = unknown;
|
|
168
|
+
// transformer-ignore-next-line
|
|
169
|
+
type B = any;
|
|
170
|
+
const c = [1, 2, 3] as const;
|
|
171
|
+
type D = readonly number[];
|
|
172
|
+
`,
|
|
173
|
+
},
|
|
174
|
+
{
|
|
175
|
+
name: 'Ignore all transformers for entire file (no transformer specified)',
|
|
176
|
+
source: dedent`
|
|
177
|
+
/* transformer-ignore */
|
|
178
|
+
type A = any; // Should remain any
|
|
179
|
+
const b = [1, 2, 3]; // Should remain without as const
|
|
180
|
+
type C = number[]; // Should remain mutable
|
|
181
|
+
`,
|
|
182
|
+
expected: dedent`
|
|
183
|
+
/* transformer-ignore */
|
|
184
|
+
type A = any; // Should remain any
|
|
185
|
+
const b = [1, 2, 3]; // Should remain without as const
|
|
186
|
+
type C = number[]; // Should remain mutable
|
|
187
|
+
`,
|
|
188
|
+
},
|
|
189
|
+
])('$name', testFn);
|
|
190
|
+
});
|