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.
- package/LICENSE +201 -0
- package/README.md +33 -0
- package/dist/cmd/convert-to-readonly.d.mts +3 -0
- package/dist/cmd/convert-to-readonly.d.mts.map +1 -0
- package/dist/cmd/convert-to-readonly.mjs +137 -0
- package/dist/cmd/convert-to-readonly.mjs.map +1 -0
- package/dist/entry-point.d.mts +2 -0
- package/dist/entry-point.d.mts.map +1 -0
- package/dist/entry-point.mjs +19 -0
- package/dist/entry-point.mjs.map +1 -0
- package/dist/functions/ast-transformers/convert-interface-to-type.d.mts +7 -0
- package/dist/functions/ast-transformers/convert-interface-to-type.d.mts.map +1 -0
- package/dist/functions/ast-transformers/convert-interface-to-type.mjs +83 -0
- package/dist/functions/ast-transformers/convert-interface-to-type.mjs.map +1 -0
- package/dist/functions/ast-transformers/convert-to-readonly-type.d.mts +29 -0
- package/dist/functions/ast-transformers/convert-to-readonly-type.d.mts.map +1 -0
- package/dist/functions/ast-transformers/convert-to-readonly-type.mjs +811 -0
- package/dist/functions/ast-transformers/convert-to-readonly-type.mjs.map +1 -0
- package/dist/functions/ast-transformers/index.d.mts +7 -0
- package/dist/functions/ast-transformers/index.d.mts.map +1 -0
- package/dist/functions/ast-transformers/index.mjs +9 -0
- package/dist/functions/ast-transformers/index.mjs.map +1 -0
- package/dist/functions/ast-transformers/readonly-transformer-helpers/compare-union-types.d.mts +3 -0
- package/dist/functions/ast-transformers/readonly-transformer-helpers/compare-union-types.d.mts.map +1 -0
- package/dist/functions/ast-transformers/readonly-transformer-helpers/compare-union-types.mjs +22 -0
- package/dist/functions/ast-transformers/readonly-transformer-helpers/compare-union-types.mjs.map +1 -0
- package/dist/functions/ast-transformers/readonly-transformer-helpers/constants.d.mts +2 -0
- package/dist/functions/ast-transformers/readonly-transformer-helpers/constants.d.mts.map +1 -0
- package/dist/functions/ast-transformers/readonly-transformer-helpers/constants.mjs +15 -0
- package/dist/functions/ast-transformers/readonly-transformer-helpers/constants.mjs.map +1 -0
- package/dist/functions/ast-transformers/readonly-transformer-helpers/group-union-types.d.mts +21 -0
- package/dist/functions/ast-transformers/readonly-transformer-helpers/group-union-types.d.mts.map +1 -0
- package/dist/functions/ast-transformers/readonly-transformer-helpers/group-union-types.mjs +72 -0
- package/dist/functions/ast-transformers/readonly-transformer-helpers/group-union-types.mjs.map +1 -0
- package/dist/functions/ast-transformers/readonly-transformer-helpers/index.d.mts +5 -0
- package/dist/functions/ast-transformers/readonly-transformer-helpers/index.d.mts.map +1 -0
- package/dist/functions/ast-transformers/readonly-transformer-helpers/index.mjs +5 -0
- package/dist/functions/ast-transformers/readonly-transformer-helpers/index.mjs.map +1 -0
- package/dist/functions/ast-transformers/readonly-transformer-helpers/readonly-context.d.mts +37 -0
- package/dist/functions/ast-transformers/readonly-transformer-helpers/readonly-context.d.mts.map +1 -0
- package/dist/functions/ast-transformers/readonly-transformer-helpers/readonly-context.mjs +19 -0
- package/dist/functions/ast-transformers/readonly-transformer-helpers/readonly-context.mjs.map +1 -0
- package/dist/functions/ast-transformers/replace-record-with-unknown-record.d.mts +7 -0
- package/dist/functions/ast-transformers/replace-record-with-unknown-record.d.mts.map +1 -0
- package/dist/functions/ast-transformers/replace-record-with-unknown-record.mjs +173 -0
- package/dist/functions/ast-transformers/replace-record-with-unknown-record.mjs.map +1 -0
- package/dist/functions/ast-transformers/transform-source-code.d.mts +3 -0
- package/dist/functions/ast-transformers/transform-source-code.d.mts.map +1 -0
- package/dist/functions/ast-transformers/transform-source-code.mjs +27 -0
- package/dist/functions/ast-transformers/transform-source-code.mjs.map +1 -0
- package/dist/functions/ast-transformers/types.d.mts +3 -0
- package/dist/functions/ast-transformers/types.d.mts.map +1 -0
- package/dist/functions/ast-transformers/types.mjs +2 -0
- package/dist/functions/ast-transformers/types.mjs.map +1 -0
- package/dist/functions/constants/ignore-comment-text.d.mts +3 -0
- package/dist/functions/constants/ignore-comment-text.d.mts.map +1 -0
- package/dist/functions/constants/ignore-comment-text.mjs +5 -0
- package/dist/functions/constants/ignore-comment-text.mjs.map +1 -0
- package/dist/functions/constants/index.d.mts +2 -0
- package/dist/functions/constants/index.d.mts.map +1 -0
- package/dist/functions/constants/index.mjs +2 -0
- package/dist/functions/constants/index.mjs.map +1 -0
- package/dist/functions/functions/has-disable-next-line-comment.d.mts +10 -0
- package/dist/functions/functions/has-disable-next-line-comment.d.mts.map +1 -0
- package/dist/functions/functions/has-disable-next-line-comment.mjs +47 -0
- package/dist/functions/functions/has-disable-next-line-comment.mjs.map +1 -0
- package/dist/functions/functions/index.d.mts +9 -0
- package/dist/functions/functions/index.d.mts.map +1 -0
- package/dist/functions/functions/index.mjs +9 -0
- package/dist/functions/functions/index.mjs.map +1 -0
- package/dist/functions/functions/is-as-const-node.d.mts +10 -0
- package/dist/functions/functions/is-as-const-node.d.mts.map +1 -0
- package/dist/functions/functions/is-as-const-node.mjs +30 -0
- package/dist/functions/functions/is-as-const-node.mjs.map +1 -0
- package/dist/functions/functions/is-primitive-type-node.d.mts +15 -0
- package/dist/functions/functions/is-primitive-type-node.d.mts.map +1 -0
- package/dist/functions/functions/is-primitive-type-node.mjs +46 -0
- package/dist/functions/functions/is-primitive-type-node.mjs.map +1 -0
- package/dist/functions/functions/is-readonly-node.d.mts +21 -0
- package/dist/functions/functions/is-readonly-node.d.mts.map +1 -0
- package/dist/functions/functions/is-readonly-node.mjs +30 -0
- package/dist/functions/functions/is-readonly-node.mjs.map +1 -0
- package/dist/functions/functions/is-spread-parameter-node.d.mts +4 -0
- package/dist/functions/functions/is-spread-parameter-node.d.mts.map +1 -0
- package/dist/functions/functions/is-spread-parameter-node.mjs +9 -0
- package/dist/functions/functions/is-spread-parameter-node.mjs.map +1 -0
- package/dist/functions/functions/remove-parentheses.d.mts +3 -0
- package/dist/functions/functions/remove-parentheses.d.mts.map +1 -0
- package/dist/functions/functions/remove-parentheses.mjs +9 -0
- package/dist/functions/functions/remove-parentheses.mjs.map +1 -0
- package/dist/functions/functions/unwrap-readonly.d.mts +3 -0
- package/dist/functions/functions/unwrap-readonly.d.mts.map +1 -0
- package/dist/functions/functions/unwrap-readonly.mjs +8 -0
- package/dist/functions/functions/unwrap-readonly.mjs.map +1 -0
- package/dist/functions/functions/wrap-with-parentheses.d.mts +2 -0
- package/dist/functions/functions/wrap-with-parentheses.d.mts.map +1 -0
- package/dist/functions/functions/wrap-with-parentheses.mjs +4 -0
- package/dist/functions/functions/wrap-with-parentheses.mjs.map +1 -0
- package/dist/functions/index.d.mts +5 -0
- package/dist/functions/index.d.mts.map +1 -0
- package/dist/functions/index.mjs +19 -0
- package/dist/functions/index.mjs.map +1 -0
- package/dist/functions/utils/index.d.mts +2 -0
- package/dist/functions/utils/index.d.mts.map +1 -0
- package/dist/functions/utils/index.mjs +2 -0
- package/dist/functions/utils/index.mjs.map +1 -0
- package/dist/functions/utils/replace-with-debug.d.mts +3 -0
- package/dist/functions/utils/replace-with-debug.d.mts.map +1 -0
- package/dist/functions/utils/replace-with-debug.mjs +7 -0
- package/dist/functions/utils/replace-with-debug.mjs.map +1 -0
- package/dist/globals.d.mts +1 -0
- package/dist/index.d.mts +2 -0
- package/dist/index.d.mts.map +1 -0
- package/dist/index.mjs +19 -0
- package/dist/index.mjs.map +1 -0
- package/dist/tsconfig.json +1 -0
- package/dist/types.d.mts +2 -0
- package/package.json +134 -0
- package/src/cmd/convert-to-readonly.mts +195 -0
- package/src/entry-point.mts +1 -0
- package/src/functions/ast-transformers/convert-interface-to-type.mts +119 -0
- package/src/functions/ast-transformers/convert-interface-to-type.test.mts +295 -0
- package/src/functions/ast-transformers/convert-to-readonly-type.mts +1391 -0
- package/src/functions/ast-transformers/convert-to-readonly-type.test.mts +3653 -0
- package/src/functions/ast-transformers/index.mts +6 -0
- package/src/functions/ast-transformers/readonly-transformer-helpers/compare-union-types.mts +24 -0
- package/src/functions/ast-transformers/readonly-transformer-helpers/constants.mts +12 -0
- package/src/functions/ast-transformers/readonly-transformer-helpers/group-union-types.mts +152 -0
- package/src/functions/ast-transformers/readonly-transformer-helpers/index.mts +4 -0
- package/src/functions/ast-transformers/readonly-transformer-helpers/readonly-context.mts +65 -0
- package/src/functions/ast-transformers/replace-record-with-unknown-record.mts +238 -0
- package/src/functions/ast-transformers/transform-source-code.mts +38 -0
- package/src/functions/ast-transformers/types.mts +6 -0
- package/src/functions/constants/ignore-comment-text.mts +3 -0
- package/src/functions/constants/index.mts +1 -0
- package/src/functions/functions/has-disable-next-line-comment.mts +56 -0
- package/src/functions/functions/index.mts +8 -0
- package/src/functions/functions/is-as-const-node.mts +47 -0
- package/src/functions/functions/is-primitive-type-node.mts +301 -0
- package/src/functions/functions/is-readonly-node.mts +247 -0
- package/src/functions/functions/is-spread-parameter-node.mts +13 -0
- package/src/functions/functions/remove-parentheses.mts +7 -0
- package/src/functions/functions/unwrap-readonly.mts +7 -0
- package/src/functions/functions/wrap-with-parentheses.mts +2 -0
- package/src/functions/index.mts +4 -0
- package/src/functions/utils/index.mts +1 -0
- package/src/functions/utils/replace-with-debug.mts +10 -0
- package/src/globals.d.mts +1 -0
- 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
|
+
};
|