ts-codemod-lib 2.1.5 → 2.1.6

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 CHANGED
@@ -314,6 +314,8 @@ const transformedCode = transformSourceCode(originalCode, isTsx, [
314
314
  ]);
315
315
 
316
316
  const expected = dedent`
317
+ import { type UnknownRecord } from "ts-type-forge";
318
+
317
319
  export type A = Readonly< {
318
320
  name?: string;
319
321
  point: (readonly [x: number, y: number, z?: number]);
@@ -341,7 +343,7 @@ const expected = dedent`
341
343
 
342
344
  if (import.meta.vitest !== undefined) {
343
345
  test('transformSourceCode', () => {
344
- assert.isTrue(transformedCode === expected);
346
+ assert.strictEqual(transformedCode, expected);
345
347
  });
346
348
  }
347
349
  ```
@@ -10,7 +10,7 @@ import { runTransformerCLI } from './run-transformer-cli.mjs';
10
10
  const transformer = appendAsConstTransformer();
11
11
  const cmdDef = cmd.command({
12
12
  name: transformer.name,
13
- version: '2.1.5',
13
+ version: '2.1.6',
14
14
  args: {
15
15
  baseDir: cmd.positional({
16
16
  type: cmd.string,
@@ -10,7 +10,7 @@ import { runTransformerCLI } from './run-transformer-cli.mjs';
10
10
  const transformer = convertInterfaceToTypeTransformer();
11
11
  const cmdDef = cmd.command({
12
12
  name: transformer.name,
13
- version: '2.1.5',
13
+ version: '2.1.6',
14
14
  args: {
15
15
  baseDir: cmd.positional({
16
16
  type: cmd.string,
@@ -10,7 +10,7 @@ import { runTransformerCLI } from './run-transformer-cli.mjs';
10
10
  const transformer = convertToReadonlyTransformer();
11
11
  const cmdDef = cmd.command({
12
12
  name: transformer.name,
13
- version: '2.1.5',
13
+ version: '2.1.6',
14
14
  args: {
15
15
  baseDir: cmd.positional({
16
16
  type: cmd.string,
@@ -10,7 +10,7 @@ import { runTransformerCLI } from './run-transformer-cli.mjs';
10
10
  const transformer = replaceAnyWithUnknownTransformer();
11
11
  const cmdDef = cmd.command({
12
12
  name: transformer.name,
13
- version: '2.1.5',
13
+ version: '2.1.6',
14
14
  args: {
15
15
  baseDir: cmd.positional({
16
16
  type: cmd.string,
@@ -10,7 +10,7 @@ import { runTransformerCLI } from './run-transformer-cli.mjs';
10
10
  const transformer = replaceRecordWithUnknownRecordTransformer();
11
11
  const cmdDef = cmd.command({
12
12
  name: transformer.name,
13
- version: '2.1.5',
13
+ version: '2.1.6',
14
14
  args: {
15
15
  baseDir: cmd.positional({
16
16
  type: cmd.string,
@@ -1,7 +1,11 @@
1
1
  import { type TsMorphTransformer } from './types.mjs';
2
2
  /**
3
3
  * Replaces `Readonly<Record<string, unknown>>`, `Record<string, unknown>`,
4
- * and index signatures `[k: string]: unknown` with `UnknownRecord`
4
+ * and index signatures `[k: string]: unknown` with `UnknownRecord`.
5
+ *
6
+ * When a replacement occurs, also inserts
7
+ * `import { type UnknownRecord } from 'ts-type-forge';` at the top of the
8
+ * source file (or merges into an existing import from `'ts-type-forge'`).
5
9
  */
6
10
  export declare const replaceRecordWithUnknownRecordTransformer: () => TsMorphTransformer;
7
11
  //# sourceMappingURL=replace-record-with-unknown-record.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"replace-record-with-unknown-record.d.mts","sourceRoot":"","sources":["../../../src/functions/ast-transformers/replace-record-with-unknown-record.mts"],"names":[],"mappings":"AAIA,OAAO,EAAE,KAAK,kBAAkB,EAAE,MAAM,aAAa,CAAC;AAItD;;;GAGG;AACH,eAAO,MAAM,yCAAyC,QAChD,kBAcS,CAAC"}
1
+ {"version":3,"file":"replace-record-with-unknown-record.d.mts","sourceRoot":"","sources":["../../../src/functions/ast-transformers/replace-record-with-unknown-record.mts"],"names":[],"mappings":"AAIA,OAAO,EAAE,KAAK,kBAAkB,EAAE,MAAM,aAAa,CAAC;AAQtD;;;;;;;GAOG;AACH,eAAO,MAAM,yCAAyC,QAChD,kBAeS,CAAC"}
@@ -5,64 +5,81 @@ import '../functions/is-atomic-type-node.mjs';
5
5
 
6
6
  /* eslint-disable @typescript-eslint/prefer-readonly-parameter-types -- ts-morph uses mutable types */
7
7
  const TRANSFORMER_NAME = 'replace-record-with-unknown-record';
8
+ const UNKNOWN_RECORD_MODULE = 'ts-type-forge';
9
+ const UNKNOWN_RECORD_NAME = 'UnknownRecord';
8
10
  /**
9
11
  * Replaces `Readonly<Record<string, unknown>>`, `Record<string, unknown>`,
10
- * and index signatures `[k: string]: unknown` with `UnknownRecord`
12
+ * and index signatures `[k: string]: unknown` with `UnknownRecord`.
13
+ *
14
+ * When a replacement occurs, also inserts
15
+ * `import { type UnknownRecord } from 'ts-type-forge';` at the top of the
16
+ * source file (or merges into an existing import from `'ts-type-forge'`).
11
17
  */
12
18
  const replaceRecordWithUnknownRecordTransformer = () => ({
13
19
  name: TRANSFORMER_NAME,
14
20
  transform: (sourceAst) => {
15
- // Process top-level declarations
16
- processDeclarations(sourceAst);
17
- // Process declarations inside namespaces/modules
18
- const namespaces = sourceAst.getModules();
19
- for (const namespace of namespaces) {
20
- processDeclarations(namespace);
21
+ const containerModifications = [
22
+ processDeclarations(sourceAst),
23
+ ...sourceAst
24
+ .getModules()
25
+ .map((namespace) => processDeclarations(namespace)),
26
+ ];
27
+ if (containerModifications.includes(true)) {
28
+ ensureUnknownRecordImport(sourceAst);
21
29
  }
22
30
  },
23
31
  });
24
32
  const processDeclarations = (container) => {
25
- const typeAliases = container.getTypeAliases();
26
- for (const typeAlias of typeAliases) {
33
+ const typeAliasModifications = container.getTypeAliases().map((typeAlias) => {
27
34
  if (hasDisableNextLineComment(typeAlias, TRANSFORMER_NAME)) {
28
- continue;
35
+ return false;
29
36
  }
30
37
  const typeNode = typeAlias.getTypeNode();
31
38
  if (typeNode === undefined)
32
- continue;
33
- visitTypeNode(typeNode);
39
+ return false;
40
+ return visitTypeNode(typeNode);
41
+ });
42
+ const interfaceModifications = container
43
+ .getInterfaces()
44
+ .map((interfaceDecl) => processInterfaceDeclaration(interfaceDecl));
45
+ return [...typeAliasModifications, ...interfaceModifications].includes(true);
46
+ };
47
+ const processInterfaceDeclaration = (interfaceDecl) => {
48
+ if (hasDisableNextLineComment(interfaceDecl, TRANSFORMER_NAME)) {
49
+ return false;
34
50
  }
35
- const interfaces = container.getInterfaces();
36
- for (const interfaceDecl of interfaces) {
37
- if (hasDisableNextLineComment(interfaceDecl, TRANSFORMER_NAME)) {
38
- continue;
39
- }
40
- // Check if interface has index signature [k: string]: unknown
41
- const indexSignatures = interfaceDecl.getIndexSignatures();
42
- const hasStringUnknownSignature = indexSignatures.some((sig) => {
43
- const keyType = sig.getKeyType();
44
- const returnType = sig.getReturnType();
45
- return (keyType.getText() === 'string' && returnType.getText() === 'unknown');
46
- });
47
- // If it has the signature and no other members, replace entire interface with type alias
48
- if (hasStringUnknownSignature) {
49
- const properties = interfaceDecl.getProperties();
50
- if (Arr.isArrayOfLength(properties, 0) &&
51
- Arr.isArrayOfLength(indexSignatures, 1)) {
52
- // Replace interface with type alias
53
- const interfaceName = interfaceDecl.getName();
54
- interfaceDecl.replaceWithText(`export type ${interfaceName} = UnknownRecord;`);
55
- continue;
56
- }
57
- }
58
- // Otherwise, check properties for Record types
59
- for (const property of interfaceDecl.getProperties()) {
60
- const typeNode = property.getTypeNode();
61
- if (typeNode === undefined)
62
- continue;
63
- visitTypeNode(typeNode);
64
- }
51
+ // Check if interface has index signature [k: string]: unknown
52
+ const indexSignatures = interfaceDecl.getIndexSignatures();
53
+ const hasStringUnknownSignature = indexSignatures.some((sig) => {
54
+ const keyType = sig.getKeyType();
55
+ const returnType = sig.getReturnType();
56
+ return keyType.getText() === 'string' && returnType.getText() === 'unknown';
57
+ });
58
+ // If it has the signature and no other members, replace entire interface with type alias.
59
+ // Skip when the interface carries semantics that a plain type alias would lose
60
+ // (type parameters, extends clauses, declare modifier, default export).
61
+ if (hasStringUnknownSignature &&
62
+ Arr.isArrayOfLength(interfaceDecl.getProperties(), 0) &&
63
+ Arr.isArrayOfLength(indexSignatures, 1) &&
64
+ Arr.isArrayOfLength(interfaceDecl.getTypeParameters(), 0) &&
65
+ Arr.isArrayOfLength(interfaceDecl.getExtends(), 0) &&
66
+ !interfaceDecl.hasDeclareKeyword() &&
67
+ !interfaceDecl.hasDefaultKeyword()) {
68
+ const exportPrefix = interfaceDecl.hasExportKeyword() ? 'export ' : '';
69
+ const interfaceName = interfaceDecl.getName();
70
+ interfaceDecl.replaceWithText(`${exportPrefix}type ${interfaceName} = UnknownRecord;`);
71
+ return true;
65
72
  }
73
+ // Otherwise, check properties for Record types
74
+ return interfaceDecl
75
+ .getProperties()
76
+ .map((property) => {
77
+ const typeNode = property.getTypeNode();
78
+ if (typeNode === undefined)
79
+ return false;
80
+ return visitTypeNode(typeNode);
81
+ })
82
+ .includes(true);
66
83
  };
67
84
  const visitTypeNode = (node) => {
68
85
  if (tsm.Node.isTypeReference(node)) {
@@ -79,15 +96,14 @@ const visitTypeNode = (node) => {
79
96
  Arr.isArrayOfLength(indexSigs, 1) &&
80
97
  isStringUnknownIndexSignature(indexSigs[0])) {
81
98
  node.replaceWithText('UnknownRecord');
82
- return;
99
+ return true;
83
100
  }
84
101
  // Otherwise, recurse into the type literal to visit its properties
85
- visitTypeNode(typeArg);
86
- return;
102
+ return visitTypeNode(typeArg);
87
103
  }
88
104
  }
89
105
  }
90
- replaceIfRecordUnknown(node);
106
+ return replaceIfRecordUnknown(node);
91
107
  }
92
108
  // Check for type literal { [k: string]: unknown }
93
109
  if (tsm.Node.isTypeLiteral(node)) {
@@ -98,47 +114,52 @@ const visitTypeNode = (node) => {
98
114
  Arr.isArrayOfLength(indexSigs, 1) &&
99
115
  isStringUnknownIndexSignature(indexSigs[0])) {
100
116
  node.replaceWithText('UnknownRecord');
101
- return;
117
+ return true;
102
118
  }
119
+ return members
120
+ .map((member) => {
121
+ if (tsm.Node.isPropertySignature(member)) {
122
+ const typeNode = member.getTypeNode();
123
+ return typeNode === undefined ? false : visitTypeNode(typeNode);
124
+ }
125
+ if (tsm.Node.isIndexSignatureDeclaration(member)) {
126
+ const typeNode = member.getReturnTypeNode();
127
+ return typeNode === undefined ? false : visitTypeNode(typeNode);
128
+ }
129
+ return false;
130
+ })
131
+ .includes(true);
103
132
  }
104
133
  // Recursively visit child type nodes
105
- if (tsm.Node.isUnionTypeNode(node)) {
106
- for (const typeNode of node.getTypeNodes()) {
107
- visitTypeNode(typeNode);
108
- }
134
+ if (tsm.Node.isUnionTypeNode(node) || tsm.Node.isIntersectionTypeNode(node)) {
135
+ return node
136
+ .getTypeNodes()
137
+ .map((typeNode) => visitTypeNode(typeNode))
138
+ .includes(true);
109
139
  }
110
- else if (tsm.Node.isIntersectionTypeNode(node)) {
111
- for (const typeNode of node.getTypeNodes()) {
112
- visitTypeNode(typeNode);
113
- }
140
+ if (tsm.Node.isArrayTypeNode(node)) {
141
+ return visitTypeNode(node.getElementTypeNode());
114
142
  }
115
- else if (tsm.Node.isArrayTypeNode(node)) {
116
- visitTypeNode(node.getElementTypeNode());
143
+ if (tsm.Node.isTupleTypeNode(node)) {
144
+ return node
145
+ .getElements()
146
+ .map((element) => visitTypeNode(element))
147
+ .includes(true);
117
148
  }
118
- else if (tsm.Node.isTupleTypeNode(node)) {
119
- for (const element of node.getElements()) {
120
- visitTypeNode(element);
121
- }
149
+ if (tsm.Node.isParenthesizedTypeNode(node)) {
150
+ return visitTypeNode(node.getTypeNode());
122
151
  }
123
- else if (tsm.Node.isParenthesizedTypeNode(node)) {
124
- visitTypeNode(node.getTypeNode());
152
+ // Handles `readonly T[]`, `readonly [...]`, `keyof T`
153
+ if (tsm.Node.isTypeOperatorTypeNode(node)) {
154
+ return visitTypeNode(node.getTypeNode());
125
155
  }
126
- else if (tsm.Node.isTypeLiteral(node)) {
127
- for (const member of node.getMembers()) {
128
- if (tsm.Node.isPropertySignature(member)) {
129
- const typeNode = member.getTypeNode();
130
- if (typeNode !== undefined) {
131
- visitTypeNode(typeNode);
132
- }
133
- }
134
- else if (tsm.Node.isIndexSignatureDeclaration(member)) {
135
- const typeNode = member.getReturnTypeNode();
136
- if (typeNode !== undefined) {
137
- visitTypeNode(typeNode);
138
- }
139
- }
140
- }
156
+ if (tsm.Node.isRestTypeNode(node)) {
157
+ return visitTypeNode(node.getTypeNode());
141
158
  }
159
+ if (tsm.Node.isNamedTupleMember(node)) {
160
+ return visitTypeNode(node.getTypeNode());
161
+ }
162
+ return false;
142
163
  };
143
164
  const isStringUnknownIndexSignature = (sig) => {
144
165
  const keyType = sig.getKeyTypeNode();
@@ -155,8 +176,10 @@ const replaceIfRecordUnknown = (node) => {
155
176
  typeArgs[0].getText() === 'string' &&
156
177
  typeArgs[1].getText() === 'unknown') {
157
178
  node.replaceWithText('UnknownRecord');
179
+ return true;
158
180
  }
159
- break;
181
+ // Recurse into type arguments (e.g. Record<string, Record<string, unknown>>)
182
+ return typeArgs.map((typeArg) => visitTypeNode(typeArg)).includes(true);
160
183
  }
161
184
  case 'Readonly': {
162
185
  // Check if it's Readonly<Record<string, unknown>>
@@ -171,14 +194,45 @@ const replaceIfRecordUnknown = (node) => {
171
194
  innerTypeArgs[0].getText() === 'string' &&
172
195
  innerTypeArgs[1].getText() === 'unknown') {
173
196
  node.replaceWithText('UnknownRecord');
197
+ return true;
174
198
  }
175
199
  }
176
200
  }
201
+ // Otherwise, recurse into the type argument
202
+ return visitTypeNode(innerType);
177
203
  }
178
- break;
204
+ return false;
205
+ }
206
+ default: {
207
+ // Recurse into type arguments for any other generic type
208
+ return node
209
+ .getTypeArguments()
210
+ .map((typeArg) => visitTypeNode(typeArg))
211
+ .includes(true);
179
212
  }
180
213
  }
181
214
  };
215
+ const ensureUnknownRecordImport = (sourceFile) => {
216
+ const existingImport = sourceFile
217
+ .getImportDeclarations()
218
+ .find((decl) => decl.getModuleSpecifierValue() === UNKNOWN_RECORD_MODULE);
219
+ if (existingImport === undefined) {
220
+ sourceFile.insertImportDeclaration(0, {
221
+ moduleSpecifier: UNKNOWN_RECORD_MODULE,
222
+ namedImports: [{ name: UNKNOWN_RECORD_NAME, isTypeOnly: true }],
223
+ });
224
+ return;
225
+ }
226
+ const alreadyImported = existingImport
227
+ .getNamedImports()
228
+ .some((ni) => ni.getName() === UNKNOWN_RECORD_NAME);
229
+ if (alreadyImported)
230
+ return;
231
+ existingImport.addNamedImport({
232
+ name: UNKNOWN_RECORD_NAME,
233
+ isTypeOnly: true,
234
+ });
235
+ };
182
236
 
183
237
  export { replaceRecordWithUnknownRecordTransformer };
184
238
  //# sourceMappingURL=replace-record-with-unknown-record.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"replace-record-with-unknown-record.mjs","sources":["../../../src/functions/ast-transformers/replace-record-with-unknown-record.mts"],"sourcesContent":[null],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"replace-record-with-unknown-record.mjs","sources":["../../../src/functions/ast-transformers/replace-record-with-unknown-record.mts"],"sourcesContent":[null],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ts-codemod-lib",
3
- "version": "2.1.5",
3
+ "version": "2.1.6",
4
4
  "private": false,
5
5
  "keywords": [
6
6
  "typescript",
@@ -9,7 +9,7 @@ const transformer = appendAsConstTransformer();
9
9
 
10
10
  const cmdDef = cmd.command({
11
11
  name: transformer.name,
12
- version: '2.1.5',
12
+ version: '2.1.6',
13
13
  args: {
14
14
  baseDir: cmd.positional({
15
15
  type: cmd.string,
@@ -9,7 +9,7 @@ const transformer = convertInterfaceToTypeTransformer();
9
9
 
10
10
  const cmdDef = cmd.command({
11
11
  name: transformer.name,
12
- version: '2.1.5',
12
+ version: '2.1.6',
13
13
  args: {
14
14
  baseDir: cmd.positional({
15
15
  type: cmd.string,
@@ -9,7 +9,7 @@ const transformer = convertToReadonlyTransformer();
9
9
 
10
10
  const cmdDef = cmd.command({
11
11
  name: transformer.name,
12
- version: '2.1.5',
12
+ version: '2.1.6',
13
13
  args: {
14
14
  baseDir: cmd.positional({
15
15
  type: cmd.string,
@@ -9,7 +9,7 @@ const transformer = replaceAnyWithUnknownTransformer();
9
9
 
10
10
  const cmdDef = cmd.command({
11
11
  name: transformer.name,
12
- version: '2.1.5',
12
+ version: '2.1.6',
13
13
  args: {
14
14
  baseDir: cmd.positional({
15
15
  type: cmd.string,
@@ -9,7 +9,7 @@ const transformer = replaceRecordWithUnknownRecordTransformer();
9
9
 
10
10
  const cmdDef = cmd.command({
11
11
  name: transformer.name,
12
- version: '2.1.5',
12
+ version: '2.1.6',
13
13
  args: {
14
14
  baseDir: cmd.positional({
15
15
  type: cmd.string,
@@ -6,95 +6,113 @@ import { type TsMorphTransformer } from './types.mjs';
6
6
 
7
7
  const TRANSFORMER_NAME = 'replace-record-with-unknown-record';
8
8
 
9
+ const UNKNOWN_RECORD_MODULE = 'ts-type-forge';
10
+
11
+ const UNKNOWN_RECORD_NAME = 'UnknownRecord';
12
+
9
13
  /**
10
14
  * Replaces `Readonly<Record<string, unknown>>`, `Record<string, unknown>`,
11
- * and index signatures `[k: string]: unknown` with `UnknownRecord`
15
+ * and index signatures `[k: string]: unknown` with `UnknownRecord`.
16
+ *
17
+ * When a replacement occurs, also inserts
18
+ * `import { type UnknownRecord } from 'ts-type-forge';` at the top of the
19
+ * source file (or merges into an existing import from `'ts-type-forge'`).
12
20
  */
13
21
  export const replaceRecordWithUnknownRecordTransformer =
14
22
  (): TsMorphTransformer =>
15
23
  ({
16
24
  name: TRANSFORMER_NAME,
17
25
  transform: (sourceAst) => {
18
- // Process top-level declarations
19
- processDeclarations(sourceAst);
20
-
21
- // Process declarations inside namespaces/modules
22
- const namespaces = sourceAst.getModules();
23
-
24
- for (const namespace of namespaces) {
25
- processDeclarations(namespace);
26
+ const containerModifications = [
27
+ processDeclarations(sourceAst),
28
+ ...sourceAst
29
+ .getModules()
30
+ .map((namespace) => processDeclarations(namespace)),
31
+ ];
32
+
33
+ if (containerModifications.includes(true)) {
34
+ ensureUnknownRecordImport(sourceAst);
26
35
  }
27
36
  },
28
37
  }) as const;
29
38
 
30
39
  const processDeclarations = (
31
40
  container: tsm.SourceFile | tsm.ModuleDeclaration,
32
- ): void => {
33
- const typeAliases = container.getTypeAliases();
34
-
35
- for (const typeAlias of typeAliases) {
41
+ ): boolean => {
42
+ const typeAliasModifications = container.getTypeAliases().map((typeAlias) => {
36
43
  if (hasDisableNextLineComment(typeAlias, TRANSFORMER_NAME)) {
37
- continue;
44
+ return false;
38
45
  }
39
46
 
40
47
  const typeNode = typeAlias.getTypeNode();
41
48
 
42
- if (typeNode === undefined) continue;
49
+ if (typeNode === undefined) return false;
43
50
 
44
- visitTypeNode(typeNode);
45
- }
51
+ return visitTypeNode(typeNode);
52
+ });
46
53
 
47
- const interfaces = container.getInterfaces();
54
+ const interfaceModifications = container
55
+ .getInterfaces()
56
+ .map((interfaceDecl) => processInterfaceDeclaration(interfaceDecl));
48
57
 
49
- for (const interfaceDecl of interfaces) {
50
- if (hasDisableNextLineComment(interfaceDecl, TRANSFORMER_NAME)) {
51
- continue;
52
- }
58
+ return [...typeAliasModifications, ...interfaceModifications].includes(true);
59
+ };
53
60
 
54
- // Check if interface has index signature [k: string]: unknown
55
- const indexSignatures = interfaceDecl.getIndexSignatures();
61
+ const processInterfaceDeclaration = (
62
+ interfaceDecl: tsm.InterfaceDeclaration,
63
+ ): boolean => {
64
+ if (hasDisableNextLineComment(interfaceDecl, TRANSFORMER_NAME)) {
65
+ return false;
66
+ }
56
67
 
57
- const hasStringUnknownSignature = indexSignatures.some((sig) => {
58
- const keyType = sig.getKeyType();
68
+ // Check if interface has index signature [k: string]: unknown
69
+ const indexSignatures = interfaceDecl.getIndexSignatures();
59
70
 
60
- const returnType = sig.getReturnType();
71
+ const hasStringUnknownSignature = indexSignatures.some((sig) => {
72
+ const keyType = sig.getKeyType();
61
73
 
62
- return (
63
- keyType.getText() === 'string' && returnType.getText() === 'unknown'
64
- );
65
- });
74
+ const returnType = sig.getReturnType();
66
75
 
67
- // If it has the signature and no other members, replace entire interface with type alias
68
- if (hasStringUnknownSignature) {
69
- const properties = interfaceDecl.getProperties();
76
+ return keyType.getText() === 'string' && returnType.getText() === 'unknown';
77
+ });
70
78
 
71
- if (
72
- Arr.isArrayOfLength(properties, 0) &&
73
- Arr.isArrayOfLength(indexSignatures, 1)
74
- ) {
75
- // Replace interface with type alias
76
- const interfaceName = interfaceDecl.getName();
79
+ // If it has the signature and no other members, replace entire interface with type alias.
80
+ // Skip when the interface carries semantics that a plain type alias would lose
81
+ // (type parameters, extends clauses, declare modifier, default export).
82
+ if (
83
+ hasStringUnknownSignature &&
84
+ Arr.isArrayOfLength(interfaceDecl.getProperties(), 0) &&
85
+ Arr.isArrayOfLength(indexSignatures, 1) &&
86
+ Arr.isArrayOfLength(interfaceDecl.getTypeParameters(), 0) &&
87
+ Arr.isArrayOfLength(interfaceDecl.getExtends(), 0) &&
88
+ !interfaceDecl.hasDeclareKeyword() &&
89
+ !interfaceDecl.hasDefaultKeyword()
90
+ ) {
91
+ const exportPrefix = interfaceDecl.hasExportKeyword() ? 'export ' : '';
77
92
 
78
- interfaceDecl.replaceWithText(
79
- `export type ${interfaceName} = UnknownRecord;`,
80
- );
93
+ const interfaceName = interfaceDecl.getName();
81
94
 
82
- continue;
83
- }
84
- }
95
+ interfaceDecl.replaceWithText(
96
+ `${exportPrefix}type ${interfaceName} = UnknownRecord;`,
97
+ );
98
+
99
+ return true;
100
+ }
85
101
 
86
- // Otherwise, check properties for Record types
87
- for (const property of interfaceDecl.getProperties()) {
102
+ // Otherwise, check properties for Record types
103
+ return interfaceDecl
104
+ .getProperties()
105
+ .map((property) => {
88
106
  const typeNode = property.getTypeNode();
89
107
 
90
- if (typeNode === undefined) continue;
108
+ if (typeNode === undefined) return false;
91
109
 
92
- visitTypeNode(typeNode);
93
- }
94
- }
110
+ return visitTypeNode(typeNode);
111
+ })
112
+ .includes(true);
95
113
  };
96
114
 
97
- const visitTypeNode = (node: tsm.TypeNode): void => {
115
+ const visitTypeNode = (node: tsm.TypeNode): boolean => {
98
116
  if (tsm.Node.isTypeReference(node)) {
99
117
  // Check if it's Readonly<{ [k: string]: unknown }>
100
118
  if (node.getTypeName().getText() === 'Readonly') {
@@ -118,18 +136,16 @@ const visitTypeNode = (node: tsm.TypeNode): void => {
118
136
  ) {
119
137
  node.replaceWithText('UnknownRecord');
120
138
 
121
- return;
139
+ return true;
122
140
  }
123
141
 
124
142
  // Otherwise, recurse into the type literal to visit its properties
125
- visitTypeNode(typeArg);
126
-
127
- return;
143
+ return visitTypeNode(typeArg);
128
144
  }
129
145
  }
130
146
  }
131
147
 
132
- replaceIfRecordUnknown(node);
148
+ return replaceIfRecordUnknown(node);
133
149
  }
134
150
 
135
151
  // Check for type literal { [k: string]: unknown }
@@ -148,44 +164,65 @@ const visitTypeNode = (node: tsm.TypeNode): void => {
148
164
  ) {
149
165
  node.replaceWithText('UnknownRecord');
150
166
 
151
- return;
167
+ return true;
152
168
  }
153
- }
154
169
 
155
- // Recursively visit child type nodes
156
- if (tsm.Node.isUnionTypeNode(node)) {
157
- for (const typeNode of node.getTypeNodes()) {
158
- visitTypeNode(typeNode);
159
- }
160
- } else if (tsm.Node.isIntersectionTypeNode(node)) {
161
- for (const typeNode of node.getTypeNodes()) {
162
- visitTypeNode(typeNode);
163
- }
164
- } else if (tsm.Node.isArrayTypeNode(node)) {
165
- visitTypeNode(node.getElementTypeNode());
166
- } else if (tsm.Node.isTupleTypeNode(node)) {
167
- for (const element of node.getElements()) {
168
- visitTypeNode(element);
169
- }
170
- } else if (tsm.Node.isParenthesizedTypeNode(node)) {
171
- visitTypeNode(node.getTypeNode());
172
- } else if (tsm.Node.isTypeLiteral(node)) {
173
- for (const member of node.getMembers()) {
174
- if (tsm.Node.isPropertySignature(member)) {
175
- const typeNode = member.getTypeNode();
176
-
177
- if (typeNode !== undefined) {
178
- visitTypeNode(typeNode);
170
+ return members
171
+ .map((member) => {
172
+ if (tsm.Node.isPropertySignature(member)) {
173
+ const typeNode = member.getTypeNode();
174
+
175
+ return typeNode === undefined ? false : visitTypeNode(typeNode);
179
176
  }
180
- } else if (tsm.Node.isIndexSignatureDeclaration(member)) {
181
- const typeNode = member.getReturnTypeNode();
182
177
 
183
- if (typeNode !== undefined) {
184
- visitTypeNode(typeNode);
178
+ if (tsm.Node.isIndexSignatureDeclaration(member)) {
179
+ const typeNode = member.getReturnTypeNode();
180
+
181
+ return typeNode === undefined ? false : visitTypeNode(typeNode);
185
182
  }
186
- }
187
- }
183
+
184
+ return false;
185
+ })
186
+ .includes(true);
187
+ }
188
+
189
+ // Recursively visit child type nodes
190
+ if (tsm.Node.isUnionTypeNode(node) || tsm.Node.isIntersectionTypeNode(node)) {
191
+ return node
192
+ .getTypeNodes()
193
+ .map((typeNode) => visitTypeNode(typeNode))
194
+ .includes(true);
195
+ }
196
+
197
+ if (tsm.Node.isArrayTypeNode(node)) {
198
+ return visitTypeNode(node.getElementTypeNode());
199
+ }
200
+
201
+ if (tsm.Node.isTupleTypeNode(node)) {
202
+ return node
203
+ .getElements()
204
+ .map((element) => visitTypeNode(element))
205
+ .includes(true);
206
+ }
207
+
208
+ if (tsm.Node.isParenthesizedTypeNode(node)) {
209
+ return visitTypeNode(node.getTypeNode());
210
+ }
211
+
212
+ // Handles `readonly T[]`, `readonly [...]`, `keyof T`
213
+ if (tsm.Node.isTypeOperatorTypeNode(node)) {
214
+ return visitTypeNode(node.getTypeNode());
215
+ }
216
+
217
+ if (tsm.Node.isRestTypeNode(node)) {
218
+ return visitTypeNode(node.getTypeNode());
188
219
  }
220
+
221
+ if (tsm.Node.isNamedTupleMember(node)) {
222
+ return visitTypeNode(node.getTypeNode());
223
+ }
224
+
225
+ return false;
189
226
  };
190
227
 
191
228
  const isStringUnknownIndexSignature = (
@@ -198,7 +235,7 @@ const isStringUnknownIndexSignature = (
198
235
  return keyType.getText() === 'string' && returnType?.getText() === 'unknown';
199
236
  };
200
237
 
201
- const replaceIfRecordUnknown = (node: tsm.TypeReferenceNode): void => {
238
+ const replaceIfRecordUnknown = (node: tsm.TypeReferenceNode): boolean => {
202
239
  const typeName = node.getTypeName().getText();
203
240
 
204
241
  switch (typeName) {
@@ -212,9 +249,12 @@ const replaceIfRecordUnknown = (node: tsm.TypeReferenceNode): void => {
212
249
  typeArgs[1].getText() === 'unknown'
213
250
  ) {
214
251
  node.replaceWithText('UnknownRecord');
252
+
253
+ return true;
215
254
  }
216
255
 
217
- break;
256
+ // Recurse into type arguments (e.g. Record<string, Record<string, unknown>>)
257
+ return typeArgs.map((typeArg) => visitTypeNode(typeArg)).includes(true);
218
258
  }
219
259
  case 'Readonly': {
220
260
  // Check if it's Readonly<Record<string, unknown>>
@@ -235,16 +275,51 @@ const replaceIfRecordUnknown = (node: tsm.TypeReferenceNode): void => {
235
275
  innerTypeArgs[1].getText() === 'unknown'
236
276
  ) {
237
277
  node.replaceWithText('UnknownRecord');
278
+
279
+ return true;
238
280
  }
239
281
  }
240
282
  }
283
+
284
+ // Otherwise, recurse into the type argument
285
+ return visitTypeNode(innerType);
241
286
  }
242
287
 
243
- break;
288
+ return false;
244
289
  }
245
290
 
246
291
  default: {
247
- break;
292
+ // Recurse into type arguments for any other generic type
293
+ return node
294
+ .getTypeArguments()
295
+ .map((typeArg) => visitTypeNode(typeArg))
296
+ .includes(true);
248
297
  }
249
298
  }
250
299
  };
300
+
301
+ const ensureUnknownRecordImport = (sourceFile: tsm.SourceFile): void => {
302
+ const existingImport = sourceFile
303
+ .getImportDeclarations()
304
+ .find((decl) => decl.getModuleSpecifierValue() === UNKNOWN_RECORD_MODULE);
305
+
306
+ if (existingImport === undefined) {
307
+ sourceFile.insertImportDeclaration(0, {
308
+ moduleSpecifier: UNKNOWN_RECORD_MODULE,
309
+ namedImports: [{ name: UNKNOWN_RECORD_NAME, isTypeOnly: true }],
310
+ });
311
+
312
+ return;
313
+ }
314
+
315
+ const alreadyImported = existingImport
316
+ .getNamedImports()
317
+ .some((ni) => ni.getName() === UNKNOWN_RECORD_NAME);
318
+
319
+ if (alreadyImported) return;
320
+
321
+ existingImport.addNamedImport({
322
+ name: UNKNOWN_RECORD_NAME,
323
+ isTypeOnly: true,
324
+ });
325
+ };
@@ -0,0 +1,312 @@
1
+ /* eslint-disable tree-shakable/import-star */
2
+ /* eslint-disable vitest/expect-expect */
3
+ import dedent from 'dedent';
4
+ import * as prettierPluginEstree from 'prettier/plugins/estree';
5
+ import * as prettierPluginTypeScript from 'prettier/plugins/typescript';
6
+ import * as prettier from 'prettier/standalone';
7
+ import { replaceRecordWithUnknownRecordTransformer } from './replace-record-with-unknown-record.mjs';
8
+ import { transformSourceCode } from './transform-source-code.mjs';
9
+
10
+ const testFn = async ({
11
+ source,
12
+ expected,
13
+ }: Readonly<{
14
+ source: string;
15
+ expected: string;
16
+ }>): Promise<void> => {
17
+ const transformed = await formatter(
18
+ transformSourceCode(source, false, [
19
+ replaceRecordWithUnknownRecordTransformer(),
20
+ ]),
21
+ );
22
+
23
+ const expectedFormatted = await formatter(expected);
24
+
25
+ expect(transformed).toBe(expectedFormatted);
26
+ };
27
+
28
+ const formatter = (code: string): Promise<string> =>
29
+ prettier.format(code, {
30
+ parser: 'typescript',
31
+ plugins: [prettierPluginTypeScript, prettierPluginEstree],
32
+ });
33
+
34
+ describe(replaceRecordWithUnknownRecordTransformer, () => {
35
+ test.each([
36
+ {
37
+ name: 'Simple Record<string, unknown> with import added',
38
+ source: 'type Foo = Record<string, unknown>;',
39
+ expected: dedent`
40
+ import { type UnknownRecord } from 'ts-type-forge';
41
+
42
+ type Foo = UnknownRecord;
43
+ `,
44
+ },
45
+ {
46
+ name: 'Readonly<Record<string, unknown>>',
47
+ source: 'type Foo = Readonly<Record<string, unknown>>;',
48
+ expected: dedent`
49
+ import { type UnknownRecord } from 'ts-type-forge';
50
+
51
+ type Foo = UnknownRecord;
52
+ `,
53
+ },
54
+ {
55
+ name: 'Type literal { [k: string]: unknown }',
56
+ source: 'type Foo = { [k: string]: unknown };',
57
+ expected: dedent`
58
+ import { type UnknownRecord } from 'ts-type-forge';
59
+
60
+ type Foo = UnknownRecord;
61
+ `,
62
+ },
63
+ {
64
+ name: 'Readonly type literal Readonly<{ [k: string]: unknown }>',
65
+ source: 'type Foo = Readonly<{ [k: string]: unknown }>;',
66
+ expected: dedent`
67
+ import { type UnknownRecord } from 'ts-type-forge';
68
+
69
+ type Foo = UnknownRecord;
70
+ `,
71
+ },
72
+ {
73
+ name: 'Exported interface with only [k: string]: unknown',
74
+ source: dedent`
75
+ export interface Foo {
76
+ [k: string]: unknown;
77
+ }
78
+ `,
79
+ expected: dedent`
80
+ import { type UnknownRecord } from 'ts-type-forge';
81
+
82
+ export type Foo = UnknownRecord;
83
+ `,
84
+ },
85
+ {
86
+ name: 'Non-exported interface with only [k: string]: unknown preserves missing export',
87
+ source: dedent`
88
+ interface Foo {
89
+ [k: string]: unknown;
90
+ }
91
+ `,
92
+ expected: dedent`
93
+ import { type UnknownRecord } from 'ts-type-forge';
94
+
95
+ type Foo = UnknownRecord;
96
+ `,
97
+ },
98
+ {
99
+ name: 'Generic interface with [k: string]: unknown is left untouched',
100
+ source: dedent`
101
+ export interface Foo<T> {
102
+ [k: string]: unknown;
103
+ }
104
+ `,
105
+ expected: dedent`
106
+ export interface Foo<T> {
107
+ [k: string]: unknown;
108
+ }
109
+ `,
110
+ },
111
+ {
112
+ name: 'Interface with extends + [k: string]: unknown is left untouched',
113
+ source: dedent`
114
+ interface Bar {
115
+ a: number;
116
+ }
117
+ export interface Foo extends Bar {
118
+ [k: string]: unknown;
119
+ }
120
+ `,
121
+ expected: dedent`
122
+ interface Bar {
123
+ a: number;
124
+ }
125
+ export interface Foo extends Bar {
126
+ [k: string]: unknown;
127
+ }
128
+ `,
129
+ },
130
+ {
131
+ name: 'declare interface with [k: string]: unknown is left untouched',
132
+ source: dedent`
133
+ declare interface Foo {
134
+ [k: string]: unknown;
135
+ }
136
+ `,
137
+ expected: dedent`
138
+ declare interface Foo {
139
+ [k: string]: unknown;
140
+ }
141
+ `,
142
+ },
143
+ {
144
+ name: 'Generic interface still has its property types transformed',
145
+ source: dedent`
146
+ export interface Foo<T> {
147
+ value: Record<string, unknown>;
148
+ tag: T;
149
+ }
150
+ `,
151
+ expected: dedent`
152
+ import { type UnknownRecord } from 'ts-type-forge';
153
+
154
+ export interface Foo<T> {
155
+ value: UnknownRecord;
156
+ tag: T;
157
+ }
158
+ `,
159
+ },
160
+ {
161
+ name: 'Record nested under readonly tuple (regression: TypeOperator)',
162
+ source: dedent`
163
+ type Foo =
164
+ | readonly [
165
+ 'always',
166
+ Readonly<{
167
+ patternOptions?: Readonly<Record<string, unknown>>;
168
+ }>,
169
+ ];
170
+ `,
171
+ expected: dedent`
172
+ import { type UnknownRecord } from 'ts-type-forge';
173
+
174
+ type Foo =
175
+ | readonly [
176
+ 'always',
177
+ Readonly<{
178
+ patternOptions?: UnknownRecord;
179
+ }>,
180
+ ];
181
+ `,
182
+ },
183
+ {
184
+ name: 'Record nested under readonly array (regression: TypeOperator)',
185
+ source: dedent`
186
+ type Foo = Readonly<{
187
+ forbid?: readonly (string | Record<string, unknown>)[];
188
+ }>;
189
+ `,
190
+ expected: dedent`
191
+ import { type UnknownRecord } from 'ts-type-forge';
192
+
193
+ type Foo = Readonly<{
194
+ forbid?: readonly (string | UnknownRecord)[];
195
+ }>;
196
+ `,
197
+ },
198
+ {
199
+ name: 'readonly Readonly<Record<...>>[] at type alias root (regression)',
200
+ source: 'type Foo = readonly Readonly<Record<string, unknown>>[];',
201
+ expected: dedent`
202
+ import { type UnknownRecord } from 'ts-type-forge';
203
+
204
+ type Foo = readonly UnknownRecord[];
205
+ `,
206
+ },
207
+ {
208
+ name: 'Record inside namespace',
209
+ source: dedent`
210
+ namespace Ns {
211
+ export type Foo = Record<string, unknown>;
212
+ }
213
+ `,
214
+ expected: dedent`
215
+ import { type UnknownRecord } from 'ts-type-forge';
216
+
217
+ namespace Ns {
218
+ export type Foo = UnknownRecord;
219
+ }
220
+ `,
221
+ },
222
+ {
223
+ name: 'Record nested in union inside namespace',
224
+ source: dedent`
225
+ namespace Ns {
226
+ export type Options = Readonly<{
227
+ targets:
228
+ | string
229
+ | readonly unknown[]
230
+ | Readonly<Record<string, unknown>>;
231
+ }>;
232
+ }
233
+ `,
234
+ expected: dedent`
235
+ import { type UnknownRecord } from 'ts-type-forge';
236
+
237
+ namespace Ns {
238
+ export type Options = Readonly<{
239
+ targets: string | readonly unknown[] | UnknownRecord;
240
+ }>;
241
+ }
242
+ `,
243
+ },
244
+ {
245
+ name: 'No transformation -> no import added',
246
+ source: 'type Foo = string;',
247
+ expected: 'type Foo = string;',
248
+ },
249
+ {
250
+ name: 'Existing UnknownRecord import is not duplicated',
251
+ source: dedent`
252
+ import { type UnknownRecord } from 'ts-type-forge';
253
+
254
+ type Foo = Record<string, unknown>;
255
+ `,
256
+ expected: dedent`
257
+ import { type UnknownRecord } from 'ts-type-forge';
258
+
259
+ type Foo = UnknownRecord;
260
+ `,
261
+ },
262
+ {
263
+ name: 'Merged into existing ts-type-forge import with other names',
264
+ source: dedent`
265
+ import { type DeepReadonly } from 'ts-type-forge';
266
+
267
+ type Foo = Record<string, unknown>;
268
+ `,
269
+ expected: dedent`
270
+ import { type DeepReadonly, type UnknownRecord } from 'ts-type-forge';
271
+
272
+ type Foo = UnknownRecord;
273
+ `,
274
+ },
275
+ {
276
+ name: 'Disable comment skips the type alias and does not add import',
277
+ source: dedent`
278
+ // transformer-ignore-next-line
279
+ type Foo = Record<string, unknown>;
280
+ `,
281
+ expected: dedent`
282
+ // transformer-ignore-next-line
283
+ type Foo = Record<string, unknown>;
284
+ `,
285
+ },
286
+ {
287
+ name: 'Transformer-specific disable comment is respected',
288
+ source: dedent`
289
+ // transformer-ignore-next-line replace-record-with-unknown-record
290
+ type A = Record<string, unknown>;
291
+ type B = Record<string, unknown>;
292
+ `,
293
+ expected: dedent`
294
+ import { type UnknownRecord } from 'ts-type-forge';
295
+
296
+ // transformer-ignore-next-line replace-record-with-unknown-record
297
+ type A = Record<string, unknown>;
298
+ type B = UnknownRecord;
299
+ `,
300
+ },
301
+ {
302
+ name: 'Record<string, string> (non-unknown value) is left alone',
303
+ source: 'type Foo = Record<string, string>;',
304
+ expected: 'type Foo = Record<string, string>;',
305
+ },
306
+ {
307
+ name: 'Record<number, unknown> (non-string key) is left alone',
308
+ source: 'type Foo = Record<number, unknown>;',
309
+ expected: 'type Foo = Record<number, unknown>;',
310
+ },
311
+ ])('$name', testFn);
312
+ });