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.
Files changed (56) hide show
  1. package/dist/cmd/append-as-const.mjs +1 -1
  2. package/dist/cmd/convert-interface-to-type.mjs +1 -1
  3. package/dist/cmd/convert-to-readonly.mjs +1 -1
  4. package/dist/cmd/replace-any-with-unknown.mjs +1 -1
  5. package/dist/cmd/replace-record-with-unknown-record.mjs +1 -1
  6. package/dist/entry-point.mjs +1 -1
  7. package/dist/functions/ast-transformers/append-as-const.d.mts.map +1 -1
  8. package/dist/functions/ast-transformers/append-as-const.mjs +11 -5
  9. package/dist/functions/ast-transformers/append-as-const.mjs.map +1 -1
  10. package/dist/functions/ast-transformers/convert-interface-to-type.d.mts.map +1 -1
  11. package/dist/functions/ast-transformers/convert-interface-to-type.mjs +71 -66
  12. package/dist/functions/ast-transformers/convert-interface-to-type.mjs.map +1 -1
  13. package/dist/functions/ast-transformers/convert-to-readonly-type.d.mts.map +1 -1
  14. package/dist/functions/ast-transformers/convert-to-readonly-type.mjs +12 -6
  15. package/dist/functions/ast-transformers/convert-to-readonly-type.mjs.map +1 -1
  16. package/dist/functions/ast-transformers/replace-any-with-unknown.d.mts.map +1 -1
  17. package/dist/functions/ast-transformers/replace-any-with-unknown.mjs +35 -26
  18. package/dist/functions/ast-transformers/replace-any-with-unknown.mjs.map +1 -1
  19. package/dist/functions/ast-transformers/replace-record-with-unknown-record.d.mts.map +1 -1
  20. package/dist/functions/ast-transformers/replace-record-with-unknown-record.mjs +141 -135
  21. package/dist/functions/ast-transformers/replace-record-with-unknown-record.mjs.map +1 -1
  22. package/dist/functions/ast-transformers/transform-source-code.d.mts.map +1 -1
  23. package/dist/functions/ast-transformers/transform-source-code.mjs +40 -7
  24. package/dist/functions/ast-transformers/transform-source-code.mjs.map +1 -1
  25. package/dist/functions/ast-transformers/types.d.mts +9 -1
  26. package/dist/functions/ast-transformers/types.d.mts.map +1 -1
  27. package/dist/functions/constants/ignore-comment-text.d.mts +2 -2
  28. package/dist/functions/constants/ignore-comment-text.d.mts.map +1 -1
  29. package/dist/functions/constants/ignore-comment-text.mjs +3 -3
  30. package/dist/functions/constants/ignore-comment-text.mjs.map +1 -1
  31. package/dist/functions/constants/index.mjs +1 -1
  32. package/dist/functions/functions/has-disable-next-line-comment.d.mts +8 -2
  33. package/dist/functions/functions/has-disable-next-line-comment.d.mts.map +1 -1
  34. package/dist/functions/functions/has-disable-next-line-comment.mjs +31 -6
  35. package/dist/functions/functions/has-disable-next-line-comment.mjs.map +1 -1
  36. package/dist/functions/index.mjs +1 -1
  37. package/dist/index.mjs +1 -1
  38. package/package.json +9 -9
  39. package/src/cmd/append-as-const.mts +1 -1
  40. package/src/cmd/convert-interface-to-type.mts +1 -1
  41. package/src/cmd/convert-to-readonly.mts +1 -1
  42. package/src/cmd/replace-any-with-unknown.mts +1 -1
  43. package/src/cmd/replace-record-with-unknown-record.mts +1 -1
  44. package/src/functions/ast-transformers/append-as-const.mts +18 -9
  45. package/src/functions/ast-transformers/append-as-const.test.mts +58 -0
  46. package/src/functions/ast-transformers/convert-interface-to-type.mts +8 -2
  47. package/src/functions/ast-transformers/convert-to-readonly-type.mts +40 -31
  48. package/src/functions/ast-transformers/convert-to-readonly-type.test.mts +4 -0
  49. package/src/functions/ast-transformers/replace-any-with-unknown.mts +41 -28
  50. package/src/functions/ast-transformers/replace-any-with-unknown.test.mts +58 -0
  51. package/src/functions/ast-transformers/replace-record-with-unknown-record.mts +175 -167
  52. package/src/functions/ast-transformers/transform-source-code.mts +59 -9
  53. package/src/functions/ast-transformers/transformer-specific-ignore.test.mts +190 -0
  54. package/src/functions/ast-transformers/types.mts +10 -3
  55. package/src/functions/constants/ignore-comment-text.mts +2 -2
  56. 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 => (sourceAst) => {
13
- const processDeclarations = (
14
- container: tsm.SourceFile | tsm.ModuleDeclaration,
15
- ): void => {
16
- const typeAliases = container.getTypeAliases();
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
- for (const typeAlias of typeAliases) {
19
- const typeNode = typeAlias.getTypeNode();
19
+ for (const typeAlias of typeAliases) {
20
+ const typeNode = typeAlias.getTypeNode();
20
21
 
21
- if (typeNode === undefined) continue;
22
+ if (typeNode === undefined) continue;
22
23
 
23
- visitTypeNode(typeNode);
24
- }
24
+ visitTypeNode(typeNode);
25
+ }
25
26
 
26
- const interfaces = container.getInterfaces();
27
+ const interfaces = container.getInterfaces();
27
28
 
28
- for (const interfaceDecl of interfaces) {
29
- // Check if interface has index signature [k: string]: unknown
30
- const indexSignatures = interfaceDecl.getIndexSignatures();
29
+ for (const interfaceDecl of interfaces) {
30
+ // Check if interface has index signature [k: string]: unknown
31
+ const indexSignatures = interfaceDecl.getIndexSignatures();
31
32
 
32
- const hasStringUnknownSignature = indexSignatures.some((sig) => {
33
- const keyType = sig.getKeyType();
33
+ const hasStringUnknownSignature = indexSignatures.some((sig) => {
34
+ const keyType = sig.getKeyType();
34
35
 
35
- const returnType = sig.getReturnType();
36
+ const returnType = sig.getReturnType();
36
37
 
37
- return (
38
- keyType.getText() === 'string' && returnType.getText() === 'unknown'
39
- );
40
- });
38
+ return (
39
+ keyType.getText() === 'string' &&
40
+ returnType.getText() === 'unknown'
41
+ );
42
+ });
41
43
 
42
- // If it has the signature and no other members, replace entire interface with type alias
43
- if (hasStringUnknownSignature) {
44
- const properties = interfaceDecl.getProperties();
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
- if (properties.length === 0 && indexSignatures.length === 1) {
47
- // Replace interface with type alias
48
- const interfaceName = interfaceDecl.getName();
48
+ if (properties.length === 0 && indexSignatures.length === 1) {
49
+ // Replace interface with type alias
50
+ const interfaceName = interfaceDecl.getName();
49
51
 
50
- interfaceDecl.replaceWithText(
51
- `export type ${interfaceName} = UnknownRecord;`,
52
- );
52
+ interfaceDecl.replaceWithText(
53
+ `export type ${interfaceName} = UnknownRecord;`,
54
+ );
53
55
 
54
- continue;
56
+ continue;
57
+ }
55
58
  }
56
- }
57
59
 
58
- // Otherwise, check properties for Record types
59
- for (const property of interfaceDecl.getProperties()) {
60
- const typeNode = property.getTypeNode();
60
+ // Otherwise, check properties for Record types
61
+ for (const property of interfaceDecl.getProperties()) {
62
+ const typeNode = property.getTypeNode();
61
63
 
62
- if (typeNode === undefined) continue;
64
+ if (typeNode === undefined) continue;
63
65
 
64
- visitTypeNode(typeNode);
66
+ visitTypeNode(typeNode);
67
+ }
65
68
  }
66
- }
67
- };
69
+ };
68
70
 
69
- const visitTypeNode = (node: tsm.TypeNode): void => {
70
- if (tsm.Node.isTypeReference(node)) {
71
- // Check if it's Readonly<{ [k: string]: unknown }>
72
- if (node.getTypeName().getText() === 'Readonly') {
73
- const typeArgs = node.getTypeArguments();
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
- if (typeArgs.length === 1) {
76
- const typeArg = typeArgs[0];
77
+ if (typeArgs.length === 1) {
78
+ const typeArg = typeArgs[0];
77
79
 
78
- if (tsm.Node.isTypeLiteral(typeArg)) {
79
- const members = typeArg.getMembers();
80
+ if (tsm.Node.isTypeLiteral(typeArg)) {
81
+ const members = typeArg.getMembers();
80
82
 
81
- const indexSigs = members.filter((m) =>
82
- tsm.Node.isIndexSignatureDeclaration(m),
83
- );
83
+ const indexSigs = members.filter((m) =>
84
+ tsm.Node.isIndexSignatureDeclaration(m),
85
+ );
84
86
 
85
- // Check if it has only one index signature [k: string]: unknown
86
- if (
87
- members.length === 1 &&
88
- Arr.isArrayOfLength(indexSigs, 1) &&
89
- isStringUnknownIndexSignature(indexSigs[0])
90
- ) {
91
- node.replaceWithText('UnknownRecord');
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
- return;
94
- }
95
+ return;
96
+ }
95
97
 
96
- // Otherwise, recurse into the type literal to visit its properties
97
- visitTypeNode(typeArg);
98
+ // Otherwise, recurse into the type literal to visit its properties
99
+ visitTypeNode(typeArg);
98
100
 
99
- return;
101
+ return;
102
+ }
100
103
  }
101
104
  }
102
- }
103
105
 
104
- replaceIfRecordUnknown(node);
105
- }
106
+ replaceIfRecordUnknown(node);
107
+ }
106
108
 
107
- // Check for type literal { [k: string]: unknown }
108
- if (tsm.Node.isTypeLiteral(node)) {
109
- const members = node.getMembers();
109
+ // Check for type literal { [k: string]: unknown }
110
+ if (tsm.Node.isTypeLiteral(node)) {
111
+ const members = node.getMembers();
110
112
 
111
- const indexSigs = members.filter((m) =>
112
- tsm.Node.isIndexSignatureDeclaration(m),
113
- );
113
+ const indexSigs = members.filter((m) =>
114
+ tsm.Node.isIndexSignatureDeclaration(m),
115
+ );
114
116
 
115
- // Check if it has only one index signature [k: string]: unknown
116
- if (
117
- members.length === 1 &&
118
- Arr.isArrayOfLength(indexSigs, 1) &&
119
- isStringUnknownIndexSignature(indexSigs[0])
120
- ) {
121
- node.replaceWithText('UnknownRecord');
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
- return;
125
+ return;
126
+ }
124
127
  }
125
- }
126
128
 
127
- // Recursively visit child type nodes
128
- if (tsm.Node.isUnionTypeNode(node)) {
129
- for (const typeNode of node.getTypeNodes()) {
130
- visitTypeNode(typeNode);
131
- }
132
- } else if (tsm.Node.isIntersectionTypeNode(node)) {
133
- for (const typeNode of node.getTypeNodes()) {
134
- visitTypeNode(typeNode);
135
- }
136
- } else if (tsm.Node.isArrayTypeNode(node)) {
137
- visitTypeNode(node.getElementTypeNode());
138
- } else if (tsm.Node.isTupleTypeNode(node)) {
139
- for (const element of node.getElements()) {
140
- visitTypeNode(element);
141
- }
142
- } else if (tsm.Node.isParenthesizedTypeNode(node)) {
143
- visitTypeNode(node.getTypeNode());
144
- } else if (tsm.Node.isTypeLiteral(node)) {
145
- for (const member of node.getMembers()) {
146
- if (tsm.Node.isPropertySignature(member)) {
147
- const typeNode = member.getTypeNode();
148
-
149
- if (typeNode !== undefined) {
150
- visitTypeNode(typeNode);
151
- }
152
- } else if (tsm.Node.isIndexSignatureDeclaration(member)) {
153
- const typeNode = member.getReturnTypeNode();
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
- if (typeNode !== undefined) {
156
- visitTypeNode(typeNode);
157
+ if (typeNode !== undefined) {
158
+ visitTypeNode(typeNode);
159
+ }
157
160
  }
158
161
  }
159
162
  }
160
- }
161
- };
163
+ };
162
164
 
163
- const isStringUnknownIndexSignature = (
164
- sig: tsm.IndexSignatureDeclaration,
165
- ): boolean => {
166
- const keyType = sig.getKeyTypeNode();
165
+ const isStringUnknownIndexSignature = (
166
+ sig: tsm.IndexSignatureDeclaration,
167
+ ): boolean => {
168
+ const keyType = sig.getKeyTypeNode();
167
169
 
168
- const returnType = sig.getReturnTypeNode();
169
-
170
- return (
171
- keyType.getText() === 'string' && returnType?.getText() === 'unknown'
172
- );
173
- };
170
+ const returnType = sig.getReturnTypeNode();
174
171
 
175
- const replaceIfRecordUnknown = (node: tsm.TypeReferenceNode): void => {
176
- const typeName = node.getTypeName().getText();
177
-
178
- switch (typeName) {
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
- break;
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
- if (typeArgs.length === 1) {
198
- const innerType = typeArgs[0];
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
- innerType !== undefined &&
202
- tsm.Node.isTypeReference(innerType)
186
+ typeArgs.length === 2 &&
187
+ typeArgs[0]?.getText() === 'string' &&
188
+ typeArgs[1]?.getText() === 'unknown'
203
189
  ) {
204
- const innerTypeName = innerType.getTypeName().getText();
190
+ node.replaceWithText('UnknownRecord');
191
+ }
205
192
 
206
- if (innerTypeName === 'Record') {
207
- const innerTypeArgs = innerType.getTypeArguments();
193
+ break;
194
+ }
195
+ case 'Readonly': {
196
+ // Check if it's Readonly<Record<string, unknown>>
197
+ const typeArgs = node.getTypeArguments();
208
198
 
209
- if (
210
- innerTypeArgs.length === 2 &&
211
- innerTypeArgs[0]?.getText() === 'string' &&
212
- innerTypeArgs[1]?.getText() === 'unknown'
213
- ) {
214
- node.replaceWithText('UnknownRecord');
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
- break;
225
+ default: {
226
+ break;
227
+ }
221
228
  }
229
+ };
222
230
 
223
- default: {
224
- break;
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
- // Process top-level declarations
230
- processDeclarations(sourceAst);
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
- for (const namespace of namespaces) {
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
+ });