ts-codemod-lib 1.0.1

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 (149) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +33 -0
  3. package/dist/cmd/convert-to-readonly.d.mts +3 -0
  4. package/dist/cmd/convert-to-readonly.d.mts.map +1 -0
  5. package/dist/cmd/convert-to-readonly.mjs +137 -0
  6. package/dist/cmd/convert-to-readonly.mjs.map +1 -0
  7. package/dist/entry-point.d.mts +2 -0
  8. package/dist/entry-point.d.mts.map +1 -0
  9. package/dist/entry-point.mjs +19 -0
  10. package/dist/entry-point.mjs.map +1 -0
  11. package/dist/functions/ast-transformers/convert-interface-to-type.d.mts +7 -0
  12. package/dist/functions/ast-transformers/convert-interface-to-type.d.mts.map +1 -0
  13. package/dist/functions/ast-transformers/convert-interface-to-type.mjs +83 -0
  14. package/dist/functions/ast-transformers/convert-interface-to-type.mjs.map +1 -0
  15. package/dist/functions/ast-transformers/convert-to-readonly-type.d.mts +29 -0
  16. package/dist/functions/ast-transformers/convert-to-readonly-type.d.mts.map +1 -0
  17. package/dist/functions/ast-transformers/convert-to-readonly-type.mjs +811 -0
  18. package/dist/functions/ast-transformers/convert-to-readonly-type.mjs.map +1 -0
  19. package/dist/functions/ast-transformers/index.d.mts +7 -0
  20. package/dist/functions/ast-transformers/index.d.mts.map +1 -0
  21. package/dist/functions/ast-transformers/index.mjs +9 -0
  22. package/dist/functions/ast-transformers/index.mjs.map +1 -0
  23. package/dist/functions/ast-transformers/readonly-transformer-helpers/compare-union-types.d.mts +3 -0
  24. package/dist/functions/ast-transformers/readonly-transformer-helpers/compare-union-types.d.mts.map +1 -0
  25. package/dist/functions/ast-transformers/readonly-transformer-helpers/compare-union-types.mjs +22 -0
  26. package/dist/functions/ast-transformers/readonly-transformer-helpers/compare-union-types.mjs.map +1 -0
  27. package/dist/functions/ast-transformers/readonly-transformer-helpers/constants.d.mts +2 -0
  28. package/dist/functions/ast-transformers/readonly-transformer-helpers/constants.d.mts.map +1 -0
  29. package/dist/functions/ast-transformers/readonly-transformer-helpers/constants.mjs +15 -0
  30. package/dist/functions/ast-transformers/readonly-transformer-helpers/constants.mjs.map +1 -0
  31. package/dist/functions/ast-transformers/readonly-transformer-helpers/group-union-types.d.mts +21 -0
  32. package/dist/functions/ast-transformers/readonly-transformer-helpers/group-union-types.d.mts.map +1 -0
  33. package/dist/functions/ast-transformers/readonly-transformer-helpers/group-union-types.mjs +72 -0
  34. package/dist/functions/ast-transformers/readonly-transformer-helpers/group-union-types.mjs.map +1 -0
  35. package/dist/functions/ast-transformers/readonly-transformer-helpers/index.d.mts +5 -0
  36. package/dist/functions/ast-transformers/readonly-transformer-helpers/index.d.mts.map +1 -0
  37. package/dist/functions/ast-transformers/readonly-transformer-helpers/index.mjs +5 -0
  38. package/dist/functions/ast-transformers/readonly-transformer-helpers/index.mjs.map +1 -0
  39. package/dist/functions/ast-transformers/readonly-transformer-helpers/readonly-context.d.mts +37 -0
  40. package/dist/functions/ast-transformers/readonly-transformer-helpers/readonly-context.d.mts.map +1 -0
  41. package/dist/functions/ast-transformers/readonly-transformer-helpers/readonly-context.mjs +19 -0
  42. package/dist/functions/ast-transformers/readonly-transformer-helpers/readonly-context.mjs.map +1 -0
  43. package/dist/functions/ast-transformers/replace-record-with-unknown-record.d.mts +7 -0
  44. package/dist/functions/ast-transformers/replace-record-with-unknown-record.d.mts.map +1 -0
  45. package/dist/functions/ast-transformers/replace-record-with-unknown-record.mjs +173 -0
  46. package/dist/functions/ast-transformers/replace-record-with-unknown-record.mjs.map +1 -0
  47. package/dist/functions/ast-transformers/transform-source-code.d.mts +3 -0
  48. package/dist/functions/ast-transformers/transform-source-code.d.mts.map +1 -0
  49. package/dist/functions/ast-transformers/transform-source-code.mjs +27 -0
  50. package/dist/functions/ast-transformers/transform-source-code.mjs.map +1 -0
  51. package/dist/functions/ast-transformers/types.d.mts +3 -0
  52. package/dist/functions/ast-transformers/types.d.mts.map +1 -0
  53. package/dist/functions/ast-transformers/types.mjs +2 -0
  54. package/dist/functions/ast-transformers/types.mjs.map +1 -0
  55. package/dist/functions/constants/ignore-comment-text.d.mts +3 -0
  56. package/dist/functions/constants/ignore-comment-text.d.mts.map +1 -0
  57. package/dist/functions/constants/ignore-comment-text.mjs +5 -0
  58. package/dist/functions/constants/ignore-comment-text.mjs.map +1 -0
  59. package/dist/functions/constants/index.d.mts +2 -0
  60. package/dist/functions/constants/index.d.mts.map +1 -0
  61. package/dist/functions/constants/index.mjs +2 -0
  62. package/dist/functions/constants/index.mjs.map +1 -0
  63. package/dist/functions/functions/has-disable-next-line-comment.d.mts +10 -0
  64. package/dist/functions/functions/has-disable-next-line-comment.d.mts.map +1 -0
  65. package/dist/functions/functions/has-disable-next-line-comment.mjs +47 -0
  66. package/dist/functions/functions/has-disable-next-line-comment.mjs.map +1 -0
  67. package/dist/functions/functions/index.d.mts +9 -0
  68. package/dist/functions/functions/index.d.mts.map +1 -0
  69. package/dist/functions/functions/index.mjs +9 -0
  70. package/dist/functions/functions/index.mjs.map +1 -0
  71. package/dist/functions/functions/is-as-const-node.d.mts +10 -0
  72. package/dist/functions/functions/is-as-const-node.d.mts.map +1 -0
  73. package/dist/functions/functions/is-as-const-node.mjs +30 -0
  74. package/dist/functions/functions/is-as-const-node.mjs.map +1 -0
  75. package/dist/functions/functions/is-primitive-type-node.d.mts +15 -0
  76. package/dist/functions/functions/is-primitive-type-node.d.mts.map +1 -0
  77. package/dist/functions/functions/is-primitive-type-node.mjs +46 -0
  78. package/dist/functions/functions/is-primitive-type-node.mjs.map +1 -0
  79. package/dist/functions/functions/is-readonly-node.d.mts +21 -0
  80. package/dist/functions/functions/is-readonly-node.d.mts.map +1 -0
  81. package/dist/functions/functions/is-readonly-node.mjs +30 -0
  82. package/dist/functions/functions/is-readonly-node.mjs.map +1 -0
  83. package/dist/functions/functions/is-spread-parameter-node.d.mts +4 -0
  84. package/dist/functions/functions/is-spread-parameter-node.d.mts.map +1 -0
  85. package/dist/functions/functions/is-spread-parameter-node.mjs +9 -0
  86. package/dist/functions/functions/is-spread-parameter-node.mjs.map +1 -0
  87. package/dist/functions/functions/remove-parentheses.d.mts +3 -0
  88. package/dist/functions/functions/remove-parentheses.d.mts.map +1 -0
  89. package/dist/functions/functions/remove-parentheses.mjs +9 -0
  90. package/dist/functions/functions/remove-parentheses.mjs.map +1 -0
  91. package/dist/functions/functions/unwrap-readonly.d.mts +3 -0
  92. package/dist/functions/functions/unwrap-readonly.d.mts.map +1 -0
  93. package/dist/functions/functions/unwrap-readonly.mjs +8 -0
  94. package/dist/functions/functions/unwrap-readonly.mjs.map +1 -0
  95. package/dist/functions/functions/wrap-with-parentheses.d.mts +2 -0
  96. package/dist/functions/functions/wrap-with-parentheses.d.mts.map +1 -0
  97. package/dist/functions/functions/wrap-with-parentheses.mjs +4 -0
  98. package/dist/functions/functions/wrap-with-parentheses.mjs.map +1 -0
  99. package/dist/functions/index.d.mts +5 -0
  100. package/dist/functions/index.d.mts.map +1 -0
  101. package/dist/functions/index.mjs +19 -0
  102. package/dist/functions/index.mjs.map +1 -0
  103. package/dist/functions/utils/index.d.mts +2 -0
  104. package/dist/functions/utils/index.d.mts.map +1 -0
  105. package/dist/functions/utils/index.mjs +2 -0
  106. package/dist/functions/utils/index.mjs.map +1 -0
  107. package/dist/functions/utils/replace-with-debug.d.mts +3 -0
  108. package/dist/functions/utils/replace-with-debug.d.mts.map +1 -0
  109. package/dist/functions/utils/replace-with-debug.mjs +7 -0
  110. package/dist/functions/utils/replace-with-debug.mjs.map +1 -0
  111. package/dist/globals.d.mts +1 -0
  112. package/dist/index.d.mts +2 -0
  113. package/dist/index.d.mts.map +1 -0
  114. package/dist/index.mjs +19 -0
  115. package/dist/index.mjs.map +1 -0
  116. package/dist/tsconfig.json +1 -0
  117. package/dist/types.d.mts +2 -0
  118. package/package.json +134 -0
  119. package/src/cmd/convert-to-readonly.mts +195 -0
  120. package/src/entry-point.mts +1 -0
  121. package/src/functions/ast-transformers/convert-interface-to-type.mts +119 -0
  122. package/src/functions/ast-transformers/convert-interface-to-type.test.mts +295 -0
  123. package/src/functions/ast-transformers/convert-to-readonly-type.mts +1391 -0
  124. package/src/functions/ast-transformers/convert-to-readonly-type.test.mts +3653 -0
  125. package/src/functions/ast-transformers/index.mts +6 -0
  126. package/src/functions/ast-transformers/readonly-transformer-helpers/compare-union-types.mts +24 -0
  127. package/src/functions/ast-transformers/readonly-transformer-helpers/constants.mts +12 -0
  128. package/src/functions/ast-transformers/readonly-transformer-helpers/group-union-types.mts +152 -0
  129. package/src/functions/ast-transformers/readonly-transformer-helpers/index.mts +4 -0
  130. package/src/functions/ast-transformers/readonly-transformer-helpers/readonly-context.mts +65 -0
  131. package/src/functions/ast-transformers/replace-record-with-unknown-record.mts +238 -0
  132. package/src/functions/ast-transformers/transform-source-code.mts +38 -0
  133. package/src/functions/ast-transformers/types.mts +6 -0
  134. package/src/functions/constants/ignore-comment-text.mts +3 -0
  135. package/src/functions/constants/index.mts +1 -0
  136. package/src/functions/functions/has-disable-next-line-comment.mts +56 -0
  137. package/src/functions/functions/index.mts +8 -0
  138. package/src/functions/functions/is-as-const-node.mts +47 -0
  139. package/src/functions/functions/is-primitive-type-node.mts +301 -0
  140. package/src/functions/functions/is-readonly-node.mts +247 -0
  141. package/src/functions/functions/is-spread-parameter-node.mts +13 -0
  142. package/src/functions/functions/remove-parentheses.mts +7 -0
  143. package/src/functions/functions/unwrap-readonly.mts +7 -0
  144. package/src/functions/functions/wrap-with-parentheses.mts +2 -0
  145. package/src/functions/index.mts +4 -0
  146. package/src/functions/utils/index.mts +1 -0
  147. package/src/functions/utils/replace-with-debug.mts +10 -0
  148. package/src/globals.d.mts +1 -0
  149. package/src/index.mts +1 -0
package/package.json ADDED
@@ -0,0 +1,134 @@
1
+ {
2
+ "name": "ts-codemod-lib",
3
+ "version": "1.0.1",
4
+ "private": false,
5
+ "keywords": [
6
+ "typescript",
7
+ "codemod",
8
+ "ast",
9
+ "transformation"
10
+ ],
11
+ "repository": {
12
+ "type": "git",
13
+ "url": "git+https://github.com/noshiro-pf/ts-codemod-lib.git"
14
+ },
15
+ "license": "Apache-2.0",
16
+ "author": "noshiro-pf <noshiro.pf@gmail.com>",
17
+ "sideEffects": false,
18
+ "type": "module",
19
+ "exports": {
20
+ ".": {
21
+ "import": {
22
+ "types": "./dist/types.d.mts",
23
+ "default": "./dist/entry-point.mjs"
24
+ }
25
+ }
26
+ },
27
+ "module": "./dist/entry-point.mjs",
28
+ "types": "./dist/types.d.mts",
29
+ "bin": {
30
+ "convert-interface-to-type": "./dist/cmd/convert-interface-to-type.mjs",
31
+ "convert-to-readonly": "./dist/cmd/convert-to-readonly.mjs",
32
+ "replace-record-with-unknown-record": "./dist/cmd/replace-record-with-unknown-record.mjs"
33
+ },
34
+ "files": [
35
+ "src",
36
+ "dist",
37
+ "README.md",
38
+ "LICENSE"
39
+ ],
40
+ "dependencies": {
41
+ "cmd-ts": "0.14.3",
42
+ "dedent": "1.7.1",
43
+ "ts-morph": "27.0.2",
44
+ "ts-repo-utils": "8.0.2"
45
+ },
46
+ "devDependencies": {
47
+ "@rollup/plugin-replace": "6.0.3",
48
+ "@rollup/plugin-strip": "3.0.4",
49
+ "@rollup/plugin-typescript": "12.3.0",
50
+ "@semantic-release/changelog": "6.0.3",
51
+ "@semantic-release/commit-analyzer": "13.0.1",
52
+ "@semantic-release/exec": "7.1.0",
53
+ "@semantic-release/git": "10.0.1",
54
+ "@semantic-release/github": "12.0.2",
55
+ "@semantic-release/npm": "13.1.3",
56
+ "@semantic-release/release-notes-generator": "14.1.0",
57
+ "@types/node": "25.0.6",
58
+ "@vitest/browser-playwright": "4.0.16",
59
+ "@vitest/coverage-v8": "4.0.16",
60
+ "@vitest/ui": "4.0.16",
61
+ "conventional-changelog-conventionalcommits": "9.1.0",
62
+ "cspell": "9.4.0",
63
+ "eslint": "9.39.2",
64
+ "eslint-config-typed": "4.4.1",
65
+ "github-settings-as-code": "1.0.7",
66
+ "jiti": "2.6.1",
67
+ "markdownlint": "0.40.0",
68
+ "markdownlint-cli2": "0.20.0",
69
+ "npm-run-all2": "8.0.4",
70
+ "playwright": "1.57.0",
71
+ "prettier": "3.7.4",
72
+ "prettier-plugin-organize-imports": "4.3.0",
73
+ "prettier-plugin-packagejson": "2.5.20",
74
+ "rollup": "4.54.0",
75
+ "semantic-release": "25.0.2",
76
+ "ts-data-forge": "6.2.1",
77
+ "ts-type-forge": "2.3.0",
78
+ "tslib": "2.8.1",
79
+ "tsx": "4.21.0",
80
+ "typedoc": "0.28.15",
81
+ "typedoc-github-theme": "0.3.1",
82
+ "typescript": "5.9.3",
83
+ "vite": "7.3.0",
84
+ "vitest": "4.0.16"
85
+ },
86
+ "engines": {
87
+ "node": ">=22",
88
+ "pnpm": ">=8.0.0"
89
+ },
90
+ "volta": {
91
+ "node": "25.2.1"
92
+ },
93
+ "scripts": {
94
+ "build": "tsx ./scripts/cmd/build.mts",
95
+ "build:min": "tsx ./scripts/cmd/build.mts --skip-check",
96
+ "check-all": "tsx ./scripts/cmd/check-all.mts",
97
+ "check:ext": "tsx ./scripts/cmd/check-ext.mts",
98
+ "clean": "pnpm run /clean:.*/",
99
+ "clean:logs": "npx rimraf ./*.log",
100
+ "cspell": "cspell \"**\" --gitignore --gitignore-root ./ --no-progress",
101
+ "doc": "tsx ./scripts/cmd/gen-docs.mts",
102
+ "doc:embed": "tsx ./scripts/cmd/embed-samples.mts",
103
+ "doc:preview": "vite preview --config ./configs/vite.doc.config.mts",
104
+ "doc:watch": "typedoc --options ./configs/typedoc.config.mjs --watch",
105
+ "fmt": "format-uncommitted",
106
+ "fmt:diff": "format-diff-from origin/main",
107
+ "fmt:full": "prettier --write .",
108
+ "gh:apply-all": "gh-apply-all",
109
+ "gh:apply-repository-settings": "gh-apply-repository-settings",
110
+ "gh:apply-rulesets": "gh-apply-rulesets",
111
+ "gh:apply-variables": "gh-apply-variables",
112
+ "gh:backup-all": "gh-backup-all",
113
+ "gh:backup-repository-settings": "gh-backup-repository-settings",
114
+ "gh:backup-rulesets": "gh-backup-rulesets",
115
+ "gi": "run-s gi:src fmt",
116
+ "gi:src": "gen-index-ts ./src --index-ext .mts --export-ext .mjs --target-ext .mts --target-ext .tsx --exclude entry-point.mts --exclude cmd",
117
+ "lint": "eslint .",
118
+ "lint:fix": "eslint . --fix",
119
+ "md": "markdownlint-cli2",
120
+ "run:cmd": "tsx ./src/cmd/convert-to-readonly.mts ./test/convert-to-readonly.mts",
121
+ "test": "pnpm run z:vitest:node run",
122
+ "test:browser": "pnpm run z:vitest --project='Browser' run",
123
+ "test:cov": "pnpm run z:vitest:node run --coverage",
124
+ "test:cov:ui": "vite preview --outDir ./coverage",
125
+ "test:ui": "pnpm run z:vitest:node --ui",
126
+ "testw": "pnpm run z:vitest:node watch",
127
+ "tsc": "tsc --noEmit",
128
+ "tscw": "tsc --noEmit --watch -p ./tsconfig.json",
129
+ "type-check": "tsc --noEmit",
130
+ "update-packages": "pnpm update --latest",
131
+ "z:vitest": "vitest --config ./configs/vitest.config.mts",
132
+ "z:vitest:node": "vitest --config ./configs/vitest.config.mts --project='Node.js'"
133
+ }
134
+ }
@@ -0,0 +1,195 @@
1
+ #!/usr/bin/env node
2
+ /* eslint-disable no-await-in-loop */
3
+
4
+ import * as cmd from 'cmd-ts';
5
+ import dedent from 'dedent';
6
+ import { castMutable, Result, unknownToString } from 'ts-data-forge';
7
+ import 'ts-repo-utils';
8
+ import {
9
+ convertToReadonlyTypeTransformer,
10
+ transformSourceCode,
11
+ } from '../functions/index.mjs';
12
+
13
+ const cmdDef = cmd.command({
14
+ name: 'convert-to-readonly-cli',
15
+ version: '1.0.0',
16
+ args: {
17
+ baseDir: cmd.positional({
18
+ type: cmd.string,
19
+ displayName: 'baseDir',
20
+ description: 'The base directory in which to perform the conversion',
21
+ }),
22
+ exclude: cmd.multioption({
23
+ long: 'exclude',
24
+ type: cmd.optional(cmd.array(cmd.string)),
25
+ description:
26
+ 'Glob patterns of files to exclude from the base directory (e.g., "src/generated/**/*.mts")',
27
+ }),
28
+ silent: cmd.flag({
29
+ long: 'silent',
30
+ type: cmd.optional(cmd.boolean),
31
+ description: 'If true, suppresses output messages (default: false)',
32
+ }),
33
+ },
34
+ handler: (args) => {
35
+ convertToReadonlyCLI({
36
+ baseDir: args.baseDir,
37
+ exclude: args.exclude ?? [],
38
+ silent: args.silent ?? false,
39
+ }).catch((error: unknown) => {
40
+ console.error('An error occurred:', error);
41
+
42
+ process.exit(1);
43
+ });
44
+ },
45
+ });
46
+
47
+ type Args = Readonly<{
48
+ baseDir: string;
49
+ exclude: readonly string[];
50
+ silent: boolean;
51
+ }>;
52
+
53
+ const convertToReadonlyCLI = async (
54
+ args: Args,
55
+ ): Promise<Result<undefined, undefined>> => {
56
+ const echoIfNotSilent = args.silent ? () => {} : echo;
57
+
58
+ const errorIfNotSilent = args.silent ? () => {} : console.error;
59
+
60
+ // Find all files matching the glob
61
+ const globResult = await glob(args.baseDir, {
62
+ ignore: castMutable(args.exclude),
63
+ });
64
+
65
+ if (Result.isErr(globResult)) {
66
+ errorIfNotSilent('Error finding files matching pattern:', globResult.value);
67
+
68
+ return Result.err(undefined);
69
+ }
70
+
71
+ const files = globResult.value;
72
+
73
+ if (files.length === 0) {
74
+ echoIfNotSilent('No files found matching pattern:', args.baseDir);
75
+
76
+ return Result.ok(undefined);
77
+ }
78
+
79
+ const { errorFiles, transformedCount, unchangedCount } = await transformFiles(
80
+ files,
81
+ args.silent,
82
+ );
83
+
84
+ const hr = '='.repeat(50);
85
+
86
+ echoIfNotSilent(dedent`
87
+ ${hr}
88
+ Summary:
89
+ ✅ Transformed: ${transformedCount}
90
+ ⏭️ Unchanged: ${unchangedCount}
91
+ ❌ Errors: ${errorFiles.length}
92
+ 📊 Total: ${files.length}
93
+ `);
94
+
95
+ if (errorFiles.length > 0) {
96
+ echoIfNotSilent('\nFiles with errors:');
97
+
98
+ for (const fileName of errorFiles) {
99
+ echoIfNotSilent(` - ${fileName}`);
100
+ }
101
+ }
102
+
103
+ echoIfNotSilent(hr);
104
+
105
+ if (errorFiles.length > 0) {
106
+ return Result.err(undefined);
107
+ }
108
+
109
+ return Result.ok(undefined);
110
+ };
111
+
112
+ const transformFiles = async (
113
+ filePaths: readonly string[],
114
+ silent: boolean,
115
+ ): Promise<
116
+ Readonly<{
117
+ transformedCount: number;
118
+ unchangedCount: number;
119
+ errorFiles: readonly string[];
120
+ }>
121
+ > => {
122
+ let mut_transformedCount: number = 0;
123
+
124
+ let mut_unchangedCount: number = 0;
125
+
126
+ const mut_errorFiles: string[] = [];
127
+
128
+ for (const filePath of filePaths) {
129
+ const result = await transformOneFile(filePath, silent);
130
+
131
+ if (Result.isOk(result)) {
132
+ switch (result.value) {
133
+ case 'transformed':
134
+ mut_transformedCount += 1;
135
+
136
+ break;
137
+
138
+ case 'unchanged':
139
+ mut_unchangedCount += 1;
140
+
141
+ break;
142
+ }
143
+ } else {
144
+ mut_errorFiles.push(path.basename(filePath));
145
+ }
146
+ }
147
+
148
+ return {
149
+ transformedCount: mut_transformedCount,
150
+ unchangedCount: mut_unchangedCount,
151
+ errorFiles: mut_errorFiles,
152
+ };
153
+ };
154
+
155
+ const transformOneFile = async (
156
+ filePath: string,
157
+ silent: boolean,
158
+ ): Promise<Result<'unchanged' | 'transformed', string>> => {
159
+ const echoIfNotSilent = silent ? () => {} : echo;
160
+
161
+ const errorIfNotSilent = silent ? () => {} : console.error;
162
+
163
+ const fileName = path.basename(filePath);
164
+
165
+ try {
166
+ const originalCode = await fs.readFile(filePath, 'utf8');
167
+
168
+ // Transform the code with all transformers
169
+ const transformedCode = transformSourceCode(originalCode, false, [
170
+ convertToReadonlyTypeTransformer(),
171
+ ]);
172
+
173
+ // Check if the code was actually changed
174
+ if (transformedCode === originalCode) {
175
+ echoIfNotSilent(`⏭️ ${fileName} - no changes needed`);
176
+
177
+ return Result.ok('unchanged');
178
+ } else {
179
+ // Write back the transformed code
180
+ await fs.writeFile(filePath, transformedCode, 'utf8');
181
+
182
+ echoIfNotSilent(`✅ ${fileName} - transformed`);
183
+
184
+ return Result.ok('transformed');
185
+ }
186
+ } catch (error) {
187
+ const errStr = unknownToString(error);
188
+
189
+ errorIfNotSilent(`❌ ${fileName} - error: ${errStr}`);
190
+
191
+ return Result.err(errStr);
192
+ }
193
+ };
194
+
195
+ await cmd.run(cmdDef, process.argv.slice(2));
@@ -0,0 +1 @@
1
+ export * from './index.mjs';
@@ -0,0 +1,119 @@
1
+ /* eslint-disable unicorn/consistent-function-scoping -- helper functions are kept inside for clarity */
2
+ /* eslint-disable @typescript-eslint/prefer-readonly-parameter-types -- ts-morph uses mutable types */
3
+ import { Arr } from 'ts-data-forge';
4
+ import type * as tsm from 'ts-morph';
5
+ import { type TsMorphTransformer } from './types.mjs';
6
+
7
+ /**
8
+ * interface による型定義を type による型定義に変換する。
9
+ * @typescript-eslint/consistent-type-definitions: ["error", "type"] と同等の動作
10
+ */
11
+ export const convertInterfaceToTypeTransformer =
12
+ (): TsMorphTransformer => (sourceAst) => {
13
+ const processInterfaces = (
14
+ container: tsm.SourceFile | tsm.ModuleDeclaration,
15
+ ): void => {
16
+ const interfaces = container.getInterfaces();
17
+
18
+ for (const interfaceDecl of interfaces) {
19
+ convertInterfaceToType(interfaceDecl);
20
+ }
21
+ };
22
+
23
+ const convertInterfaceToType = (
24
+ interfaceDecl: tsm.InterfaceDeclaration,
25
+ ): void => {
26
+ const interfaceName = interfaceDecl.getName();
27
+
28
+ const typeParameters = interfaceDecl.getTypeParameters();
29
+
30
+ const extendsExpressions = interfaceDecl.getExtends();
31
+
32
+ const members = interfaceDecl.getMembers();
33
+
34
+ // Build type parameters string
35
+ const typeParamsStr =
36
+ typeParameters.length > 0
37
+ ? `<${typeParameters.map((tp) => tp.getText()).join(', ')}>`
38
+ : '';
39
+
40
+ // Build type literal from members
41
+ const mut_typeBody: string = (() => {
42
+ if (extendsExpressions.length === 0) {
43
+ // No extends: simple type literal
44
+ return buildTypeLiteral(members);
45
+ }
46
+
47
+ if (members.length === 0) {
48
+ // Only extends, no own members: union of extended types
49
+ const extendedTypes = extendsExpressions.map((ext) => ext.getText());
50
+
51
+ return Arr.isArrayOfLength(extendedTypes, 1)
52
+ ? extendedTypes[0]
53
+ : extendedTypes.join(' & ');
54
+ }
55
+
56
+ // Both extends and own members: intersection
57
+ const extendedTypesWithMembers = extendsExpressions.map((ext) =>
58
+ ext.getText(),
59
+ );
60
+
61
+ const ownType = buildTypeLiteral(members);
62
+
63
+ return [...extendedTypesWithMembers, ownType].join(' & ');
64
+ })();
65
+
66
+ // Get export keyword
67
+ const isExported = interfaceDecl.isExported();
68
+
69
+ const exportKeyword = isExported ? 'export ' : '';
70
+
71
+ // Get leading comments (including JSDoc)
72
+ const leadingComments = interfaceDecl.getLeadingCommentRanges();
73
+
74
+ const commentTexts = leadingComments
75
+ .map((range) => range.getText())
76
+ .join('\n');
77
+
78
+ const jsDocText = commentTexts.length > 0 ? `${commentTexts}\n` : '';
79
+
80
+ // Replace interface with type alias
81
+ const typeAliasText = `${jsDocText}${exportKeyword}type ${interfaceName}${typeParamsStr} = ${mut_typeBody};`;
82
+
83
+ interfaceDecl.replaceWithText(typeAliasText);
84
+ };
85
+
86
+ const buildTypeLiteral = (
87
+ members: readonly tsm.TypeElementTypes[],
88
+ ): string => {
89
+ if (members.length === 0) {
90
+ return 'Record<string, never>';
91
+ }
92
+
93
+ const memberTexts = members.map((member) => {
94
+ // Preserve comments
95
+ const leadingComments = member
96
+ .getLeadingCommentRanges()
97
+ .map((range) => range.getText())
98
+ .join('\n');
99
+
100
+ const memberText = member.getText();
101
+
102
+ return leadingComments.length > 0
103
+ ? `${leadingComments}\n${memberText}`
104
+ : memberText;
105
+ });
106
+
107
+ return `{\n ${memberTexts.join('\n ')}\n}`;
108
+ };
109
+
110
+ // Process top-level interface declarations
111
+ processInterfaces(sourceAst);
112
+
113
+ // Process interfaces inside namespaces/modules
114
+ const namespaces = sourceAst.getModules();
115
+
116
+ for (const namespace of namespaces) {
117
+ processInterfaces(namespace);
118
+ }
119
+ };