ts-codemod-lib 1.0.1 → 1.1.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/README.md +428 -6
- package/dist/cmd/append-as-const.d.mts +3 -0
- package/dist/cmd/append-as-const.d.mts.map +1 -0
- package/dist/cmd/append-as-const.mjs +138 -0
- package/dist/cmd/append-as-const.mjs.map +1 -0
- package/dist/cmd/convert-interface-to-type.d.mts +3 -0
- package/dist/cmd/convert-interface-to-type.d.mts.map +1 -0
- package/dist/cmd/convert-interface-to-type.mjs +138 -0
- package/dist/cmd/convert-interface-to-type.mjs.map +1 -0
- package/dist/cmd/convert-to-readonly.mjs +3 -2
- package/dist/cmd/convert-to-readonly.mjs.map +1 -1
- package/dist/cmd/replace-any-with-unknown.d.mts +3 -0
- package/dist/cmd/replace-any-with-unknown.d.mts.map +1 -0
- package/dist/cmd/replace-any-with-unknown.mjs +138 -0
- package/dist/cmd/replace-any-with-unknown.mjs.map +1 -0
- package/dist/cmd/replace-record-with-unknown-record.d.mts +3 -0
- package/dist/cmd/replace-record-with-unknown-record.d.mts.map +1 -0
- package/dist/cmd/replace-record-with-unknown-record.mjs +138 -0
- package/dist/cmd/replace-record-with-unknown-record.mjs.map +1 -0
- package/dist/entry-point.mjs +2 -0
- package/dist/entry-point.mjs.map +1 -1
- package/dist/functions/ast-transformers/append-as-const.d.mts +13 -0
- package/dist/functions/ast-transformers/append-as-const.d.mts.map +1 -0
- package/dist/functions/ast-transformers/append-as-const.mjs +98 -0
- package/dist/functions/ast-transformers/append-as-const.mjs.map +1 -0
- package/dist/functions/ast-transformers/index.d.mts +2 -0
- package/dist/functions/ast-transformers/index.d.mts.map +1 -1
- package/dist/functions/ast-transformers/index.mjs +2 -0
- package/dist/functions/ast-transformers/index.mjs.map +1 -1
- package/dist/functions/ast-transformers/replace-any-with-unknown.d.mts +3 -0
- package/dist/functions/ast-transformers/replace-any-with-unknown.d.mts.map +1 -0
- package/dist/functions/ast-transformers/replace-any-with-unknown.mjs +38 -0
- package/dist/functions/ast-transformers/replace-any-with-unknown.mjs.map +1 -0
- package/dist/functions/index.mjs +2 -0
- package/dist/functions/index.mjs.map +1 -1
- package/dist/index.mjs +2 -0
- package/dist/index.mjs.map +1 -1
- package/package.json +61 -57
- package/src/cmd/append-as-const.mts +197 -0
- package/src/cmd/convert-interface-to-type.mts +197 -0
- package/src/cmd/convert-to-readonly.mts +3 -1
- package/src/cmd/replace-any-with-unknown.mts +197 -0
- package/src/cmd/replace-record-with-unknown-record.mts +197 -0
- package/src/functions/ast-transformers/append-as-const.mts +154 -0
- package/src/functions/ast-transformers/append-as-const.test.mts +339 -0
- package/src/functions/ast-transformers/convert-to-readonly-type.test.mts +3 -3
- package/src/functions/ast-transformers/index.mts +2 -0
- package/src/functions/ast-transformers/replace-any-with-unknown.mts +49 -0
- package/src/functions/ast-transformers/replace-any-with-unknown.test.mts +140 -0
|
@@ -0,0 +1,197 @@
|
|
|
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
|
+
replaceAnyWithUnknownTransformer,
|
|
10
|
+
transformSourceCode,
|
|
11
|
+
} from '../functions/index.mjs';
|
|
12
|
+
|
|
13
|
+
const cmdDef = cmd.command({
|
|
14
|
+
name: 'replace-any-with-unknown-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
|
+
replaceAnyWithUnknownCLI({
|
|
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 replaceAnyWithUnknownCLI = 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
|
+
const isTsx = fileName.endsWith('.tsx') || fileName.endsWith('.jsx');
|
|
166
|
+
|
|
167
|
+
try {
|
|
168
|
+
const originalCode = await fs.readFile(filePath, 'utf8');
|
|
169
|
+
|
|
170
|
+
// Transform the code with all transformers
|
|
171
|
+
const transformedCode = transformSourceCode(originalCode, isTsx, [
|
|
172
|
+
replaceAnyWithUnknownTransformer(),
|
|
173
|
+
]);
|
|
174
|
+
|
|
175
|
+
// Check if the code was actually changed
|
|
176
|
+
if (transformedCode === originalCode) {
|
|
177
|
+
echoIfNotSilent(`⏭️ ${fileName} - no changes needed`);
|
|
178
|
+
|
|
179
|
+
return Result.ok('unchanged');
|
|
180
|
+
} else {
|
|
181
|
+
// Write back the transformed code
|
|
182
|
+
await fs.writeFile(filePath, transformedCode, 'utf8');
|
|
183
|
+
|
|
184
|
+
echoIfNotSilent(`✅ ${fileName} - transformed`);
|
|
185
|
+
|
|
186
|
+
return Result.ok('transformed');
|
|
187
|
+
}
|
|
188
|
+
} catch (error) {
|
|
189
|
+
const errStr = unknownToString(error);
|
|
190
|
+
|
|
191
|
+
errorIfNotSilent(`❌ ${fileName} - error: ${errStr}`);
|
|
192
|
+
|
|
193
|
+
return Result.err(errStr);
|
|
194
|
+
}
|
|
195
|
+
};
|
|
196
|
+
|
|
197
|
+
await cmd.run(cmdDef, process.argv.slice(2));
|
|
@@ -0,0 +1,197 @@
|
|
|
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
|
+
replaceRecordWithUnknownRecordTransformer,
|
|
10
|
+
transformSourceCode,
|
|
11
|
+
} from '../functions/index.mjs';
|
|
12
|
+
|
|
13
|
+
const cmdDef = cmd.command({
|
|
14
|
+
name: 'replace-record-with-unknown-record-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
|
+
replaceRecordWithUnknownRecordCLI({
|
|
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 replaceRecordWithUnknownRecordCLI = 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
|
+
const isTsx = fileName.endsWith('.tsx') || fileName.endsWith('.jsx');
|
|
166
|
+
|
|
167
|
+
try {
|
|
168
|
+
const originalCode = await fs.readFile(filePath, 'utf8');
|
|
169
|
+
|
|
170
|
+
// Transform the code with all transformers
|
|
171
|
+
const transformedCode = transformSourceCode(originalCode, isTsx, [
|
|
172
|
+
replaceRecordWithUnknownRecordTransformer(),
|
|
173
|
+
]);
|
|
174
|
+
|
|
175
|
+
// Check if the code was actually changed
|
|
176
|
+
if (transformedCode === originalCode) {
|
|
177
|
+
echoIfNotSilent(`⏭️ ${fileName} - no changes needed`);
|
|
178
|
+
|
|
179
|
+
return Result.ok('unchanged');
|
|
180
|
+
} else {
|
|
181
|
+
// Write back the transformed code
|
|
182
|
+
await fs.writeFile(filePath, transformedCode, 'utf8');
|
|
183
|
+
|
|
184
|
+
echoIfNotSilent(`✅ ${fileName} - transformed`);
|
|
185
|
+
|
|
186
|
+
return Result.ok('transformed');
|
|
187
|
+
}
|
|
188
|
+
} catch (error) {
|
|
189
|
+
const errStr = unknownToString(error);
|
|
190
|
+
|
|
191
|
+
errorIfNotSilent(`❌ ${fileName} - error: ${errStr}`);
|
|
192
|
+
|
|
193
|
+
return Result.err(errStr);
|
|
194
|
+
}
|
|
195
|
+
};
|
|
196
|
+
|
|
197
|
+
await cmd.run(cmdDef, process.argv.slice(2));
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import { ISet } from 'ts-data-forge';
|
|
2
|
+
import * as tsm from 'ts-morph';
|
|
3
|
+
import {
|
|
4
|
+
hasDisableNextLineComment,
|
|
5
|
+
isAsConstNode,
|
|
6
|
+
} from '../functions/index.mjs';
|
|
7
|
+
import { type TsMorphTransformer } from './types.mjs';
|
|
8
|
+
|
|
9
|
+
export const appendAsConstTransformer =
|
|
10
|
+
(options?: AppendAsConstTransformerOptions): TsMorphTransformer =>
|
|
11
|
+
(sourceAst) => {
|
|
12
|
+
const ignorePrefixes = ISet.create(options?.ignorePrefixes ?? ['mut_']);
|
|
13
|
+
|
|
14
|
+
const optionsInternal: AppendAsConstTransformerOptionsInternal = {
|
|
15
|
+
applyLevel: options?.applyLevel ?? 'avoidInFunctionArgs',
|
|
16
|
+
ignoredPrefixes: ignorePrefixes,
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
for (const node of sourceAst.getChildren()) {
|
|
20
|
+
transformNode(node, optionsInternal);
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export type AppendAsConstTransformerOptions = DeepReadonly<{
|
|
25
|
+
applyLevel?: 'all' | 'avoidInFunctionArgs';
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* A mute keywords to ignore the readonly conversion.
|
|
29
|
+
*
|
|
30
|
+
* (e.g. `"mut_"`)
|
|
31
|
+
*/
|
|
32
|
+
ignorePrefixes?: string[];
|
|
33
|
+
|
|
34
|
+
ignoreConstTypeParameter?: boolean;
|
|
35
|
+
}>;
|
|
36
|
+
|
|
37
|
+
type AppendAsConstTransformerOptionsInternal = DeepReadonly<{
|
|
38
|
+
applyLevel: 'all' | 'avoidInFunctionArgs';
|
|
39
|
+
ignoredPrefixes: ISet<string>;
|
|
40
|
+
}>;
|
|
41
|
+
|
|
42
|
+
const transformNode = (
|
|
43
|
+
node: tsm.Node,
|
|
44
|
+
options: AppendAsConstTransformerOptionsInternal,
|
|
45
|
+
): void => {
|
|
46
|
+
if (hasDisableNextLineComment(node)) {
|
|
47
|
+
console.debug('skipped by disable-next-line comment');
|
|
48
|
+
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// check for ignorePrefix
|
|
53
|
+
if (node.isKind(tsm.SyntaxKind.VariableDeclaration)) {
|
|
54
|
+
const nodeName = node.getName();
|
|
55
|
+
|
|
56
|
+
if (options.ignoredPrefixes.some((p) => nodeName.startsWith(p))) {
|
|
57
|
+
// Skip conversion for variable declarations with ignored prefixes
|
|
58
|
+
// Example: const mut_foo: string[] = []; -> remains as is, without appending `as const`
|
|
59
|
+
console.debug('skipped variable declaration by ignorePrefixes');
|
|
60
|
+
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// TODO: Support ignoredPrefixes in ArrayBindingPattern
|
|
65
|
+
// if (ts.isArrayBindingPattern(nodeName)) {
|
|
66
|
+
// // for (const [i, el] of nodeName.elements.entries())
|
|
67
|
+
// }
|
|
68
|
+
|
|
69
|
+
// TODO: Support ignoredPrefixes in ObjectBindingPattern
|
|
70
|
+
// if (ts.isObjectBindingPattern(nodeName)) {
|
|
71
|
+
// // for (const [i, el] of nodeName.elements.entries())
|
|
72
|
+
// }
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (
|
|
76
|
+
options.applyLevel === 'avoidInFunctionArgs' &&
|
|
77
|
+
tsm.Node.isCallExpression(node)
|
|
78
|
+
) {
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// `as const` node
|
|
83
|
+
if (isAsConstNode(node)) {
|
|
84
|
+
const expression = removeParenthesis(node.getExpression());
|
|
85
|
+
|
|
86
|
+
if (
|
|
87
|
+
!tsm.Node.isArrayLiteralExpression(expression) &&
|
|
88
|
+
!tsm.Node.isObjectLiteralExpression(expression)
|
|
89
|
+
) {
|
|
90
|
+
// `as const` is not needed for primitive types
|
|
91
|
+
// Example: `0 as const` -> `0`
|
|
92
|
+
node.replaceWithText(expression.getText());
|
|
93
|
+
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Avoid appending `as const` twice
|
|
98
|
+
removeAsConstRecursively(node.getExpression());
|
|
99
|
+
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
if (tsm.Node.isArrayLiteralExpression(node)) {
|
|
104
|
+
for (const el of node.getElements()) {
|
|
105
|
+
removeAsConstRecursively(el);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
node.replaceWithText(`${node.getText()} as const`);
|
|
109
|
+
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
if (tsm.Node.isObjectLiteralExpression(node)) {
|
|
114
|
+
for (const el of node.getProperties()) {
|
|
115
|
+
removeAsConstRecursively(el);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
node.replaceWithText(`${node.getText()} as const`);
|
|
119
|
+
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
for (const child of node.getChildren()) {
|
|
124
|
+
transformNode(child, options);
|
|
125
|
+
}
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
const removeAsConstRecursively = (node: tsm.Node): void => {
|
|
129
|
+
if (hasDisableNextLineComment(node)) {
|
|
130
|
+
console.debug('skipped by disable-next-line comment');
|
|
131
|
+
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
if (isAsConstNode(node)) {
|
|
136
|
+
// Extract node.expression to remove `as const` and recursively call the function
|
|
137
|
+
// to remove `as const` from nested nodes
|
|
138
|
+
// Example: `[[1,2] as const, [3,4]] as const` -> `[[1,2], [3,4]]`
|
|
139
|
+
removeAsConstRecursively(node.getExpression());
|
|
140
|
+
|
|
141
|
+
node.replaceWithText(node.getExpression().getText());
|
|
142
|
+
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
for (const child of node.getChildren()) {
|
|
147
|
+
removeAsConstRecursively(child);
|
|
148
|
+
}
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
const removeParenthesis = (node: tsm.Node): tsm.Node =>
|
|
152
|
+
tsm.Node.isParenthesizedExpression(node)
|
|
153
|
+
? removeParenthesis(node.getExpression())
|
|
154
|
+
: node;
|