@eliasku/ts-transformers 0.0.1 → 0.0.3
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 +357 -0
- package/package.json +9 -6
- package/src/const-enum/evaluator.ts +0 -7
- package/src/const-enum/registry.ts +4 -21
- package/src/const-enum/utils.ts +7 -7
- package/src/{mangler/exports → exports}/tracker.ts +7 -8
- package/src/{mangler/index.ts → index.ts} +102 -23
- package/src/{mangler/types.ts → types.ts} +8 -2
- package/src/{mangler/typescript-helpers.ts → typescript-helpers.ts} +2 -2
- package/src/const-enum/index.ts +0 -181
- /package/src/{mangler/utils → utils}/ast-utils.ts +0 -0
- /package/src/{mangler/utils → utils}/symbol-utils.ts +0 -0
package/README.md
CHANGED
|
@@ -0,0 +1,357 @@
|
|
|
1
|
+
# @eliasku/ts-transformers
|
|
2
|
+
|
|
3
|
+
TypeScript transformer for code optimization and preparation for aggressive minification.
|
|
4
|
+
|
|
5
|
+
## ⚠️ Important Requirement
|
|
6
|
+
|
|
7
|
+
**You must compile ALL your code from TypeScript files.**
|
|
8
|
+
|
|
9
|
+
- No pre-transpiled JavaScript files (`.js`) in your source code
|
|
10
|
+
- Transformer requires TypeScript type information to analyze visibility
|
|
11
|
+
- Any `.js` files will be included as-is without optimization
|
|
12
|
+
- Mix of `.ts` and `.js` sources → partial optimization, unexpected results
|
|
13
|
+
|
|
14
|
+
Applicable for only for application build, not libraries. For libraries it's better to use buildless approach, and provide `*.ts` files.
|
|
15
|
+
|
|
16
|
+
## Approach
|
|
17
|
+
|
|
18
|
+
This transformer prepares your code for aggressive minification by analyzing TypeScript types and applying two main optimizations:
|
|
19
|
+
|
|
20
|
+
### 1. Property Renaming with Detectable Prefixes
|
|
21
|
+
|
|
22
|
+
Based on type analysis, properties are categorized into three visibility levels:
|
|
23
|
+
|
|
24
|
+
- **External (Public)**: Exported from entry points → **no prefix** (preserved by minifiers)
|
|
25
|
+
- **Internal**: Used internally but not exported → prefixed with `$i$` (e.g., `$i$internalProperty`)
|
|
26
|
+
- **Private**: Private class members → prefixed with `$p$` (e.g., `$p$privateMethod`)
|
|
27
|
+
|
|
28
|
+
The special prefixes make these properties easily detectable by downstream minifiers (like **esbuild** or **terser**) for aggressive mangling, while preserving your public API surface.
|
|
29
|
+
|
|
30
|
+
**Example:**
|
|
31
|
+
```typescript
|
|
32
|
+
// Before
|
|
33
|
+
class MyClass {
|
|
34
|
+
/** @public <- annotation in JSDoc to keep symbol name: property, method, field */
|
|
35
|
+
publicApi() {} // `publicApi` is not renamed because it's marked by annotation
|
|
36
|
+
|
|
37
|
+
public method() {} // Internal → renamed to $i$method
|
|
38
|
+
internalHelper() {} // Internal → renamed to $i$internalHelper
|
|
39
|
+
private secret = 1; // Private → renamed to $p$secret
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// After transformer (before minifier)
|
|
43
|
+
class MyClass {
|
|
44
|
+
publicApi() {}
|
|
45
|
+
$i$apiMethod() {}
|
|
46
|
+
$i$internalHelper() {}
|
|
47
|
+
$p$secret = 1;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// After esbuild minifier (aggressive property mangling)
|
|
51
|
+
class A{publicApi(){},a(){},b(){},c=1}
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### 2. Const Enum Inlining
|
|
55
|
+
|
|
56
|
+
Const enums are compile-time constants that should never exist at runtime. This transformer:
|
|
57
|
+
|
|
58
|
+
- Replaces all const enum member accesses with their literal values
|
|
59
|
+
- Removes const enum declarations from output
|
|
60
|
+
- Strips `const` modifier from declarations in `.d.ts` files
|
|
61
|
+
- Removes unused const enum imports
|
|
62
|
+
|
|
63
|
+
**Example:**
|
|
64
|
+
```typescript
|
|
65
|
+
// Before
|
|
66
|
+
const enum Status {
|
|
67
|
+
Active = 1,
|
|
68
|
+
Inactive = 0
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const status = Status.Active; // Access
|
|
72
|
+
|
|
73
|
+
// After transformer (and minifier)
|
|
74
|
+
const status = 1;
|
|
75
|
+
// Status declaration removed, import removed
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
## Workflow
|
|
79
|
+
|
|
80
|
+
This transformer is **Phase 1** of a two-phase optimization pipeline:
|
|
81
|
+
|
|
82
|
+
### Phase 1: Type-Aware Preparation (This Transformer)
|
|
83
|
+
- **Input**: TypeScript source files
|
|
84
|
+
- **Process**: Analyze types, detect visibility, apply prefixes, inline const enums
|
|
85
|
+
- **Output**: ES modules with detectable prefixes
|
|
86
|
+
- **Tools**: `@eliasku/ts-transformers` + Rollup + TypeScript compiler
|
|
87
|
+
|
|
88
|
+
### Phase 2: Aggressive Minification (esbuild / terser)
|
|
89
|
+
- **Input**: ES modules from Phase 1
|
|
90
|
+
- **Process**: Detect prefixes, mangle aggressively, apply all minification techniques
|
|
91
|
+
- **Output**: Minified bundle with preserved public API
|
|
92
|
+
- **Tools**: esbuild with property mangling
|
|
93
|
+
|
|
94
|
+
**Why Two Phases?**
|
|
95
|
+
- TypeScript types are only available during compilation (Phase 1)
|
|
96
|
+
- Production minifiers (Phase 2) are faster and more sophisticated
|
|
97
|
+
- Prefixes bridge the gap: Phase 1 marks what's safe to mangle, Phase 2 performs the mangling
|
|
98
|
+
|
|
99
|
+
## Usage
|
|
100
|
+
|
|
101
|
+
[Example Code](./example/build.ts)
|
|
102
|
+
|
|
103
|
+
```typescript
|
|
104
|
+
import { optimizer } from "@eliasku/ts-transformers";
|
|
105
|
+
import typescript from "@rollup/plugin-typescript";
|
|
106
|
+
import { rollup } from "rollup";
|
|
107
|
+
import { build } from "esbuild";
|
|
108
|
+
|
|
109
|
+
// Phase 1: Type-aware optimization with Rollup
|
|
110
|
+
const bundle = await rollup({
|
|
111
|
+
/// ...
|
|
112
|
+
input: "./src/index.ts",
|
|
113
|
+
plugins: [
|
|
114
|
+
typescript({
|
|
115
|
+
transformers: (program) => ({
|
|
116
|
+
before: [
|
|
117
|
+
optimizer(program, {
|
|
118
|
+
entrySourceFiles: ["./src/index.ts"],
|
|
119
|
+
inlineConstEnums: true,
|
|
120
|
+
}),
|
|
121
|
+
],
|
|
122
|
+
}),
|
|
123
|
+
}),
|
|
124
|
+
],
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
await bundle.write({
|
|
128
|
+
/// ...
|
|
129
|
+
file: "./dist/bundle.js",
|
|
130
|
+
format: "es",
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
// Phase 2: Aggressive minification with esbuild
|
|
134
|
+
await build({
|
|
135
|
+
entryPoints: ["./dist/bundle.js"],
|
|
136
|
+
outfile: "./dist/bundle.min.js",
|
|
137
|
+
minify: true,
|
|
138
|
+
mangleProps: /^\$[ip]\$/, // <- Match your custom prefixes here
|
|
139
|
+
mangleQuoted: false,
|
|
140
|
+
keepNames: false,
|
|
141
|
+
/// ...
|
|
142
|
+
});
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
### Customizing esbuild to Match Your Prefixes
|
|
146
|
+
|
|
147
|
+
If you customize the prefix options, update esbuild config to match:
|
|
148
|
+
|
|
149
|
+
```typescript
|
|
150
|
+
optimizer(program, {
|
|
151
|
+
entrySourceFiles: ["./src/index.ts"],
|
|
152
|
+
internalPrefix: "_int_", // Custom internal prefix
|
|
153
|
+
privatePrefix: "_priv_", // Custom private prefix
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
// Then in esbuild:
|
|
157
|
+
mangleProps: /^(_int_|_priv_)/, // Match custom prefixes
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
## Options
|
|
161
|
+
|
|
162
|
+
### entrySourceFiles (required)
|
|
163
|
+
|
|
164
|
+
An array of entry source files used to detect exported and external fields. This determines your public API surface.
|
|
165
|
+
|
|
166
|
+
```typescript
|
|
167
|
+
entrySourceFiles: ["./src/index.ts"]
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
### internalPrefix (optional, default: "$i$")
|
|
171
|
+
|
|
172
|
+
Prefix for internal properties (not exported, but used across your codebase). These will be aggressively mangled by esbuild.
|
|
173
|
+
|
|
174
|
+
```typescript
|
|
175
|
+
internalPrefix: "$i$" // default: myFunction → $i$myFunction
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
### privatePrefix (optional, default: "$p$")
|
|
179
|
+
|
|
180
|
+
Prefix for private class members. These will be aggressively mangled by esbuild.
|
|
181
|
+
|
|
182
|
+
```typescript
|
|
183
|
+
privatePrefix: "$p$" // default: this.private → this.$p$private
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
### publicJSDocTag (optional, default: "public")
|
|
187
|
+
|
|
188
|
+
JSDoc tag that marks a class/interface/property and all its children as public/external. Set to empty string to disable.
|
|
189
|
+
|
|
190
|
+
```typescript
|
|
191
|
+
publicJSDocTag: "public" // default
|
|
192
|
+
|
|
193
|
+
class MyClass {
|
|
194
|
+
/** @public */
|
|
195
|
+
apiMethod() {} // Treated as external, no prefix applied
|
|
196
|
+
|
|
197
|
+
internalHelper() {} // Treated as internal, gets $i$ prefix
|
|
198
|
+
}
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
### ignoreDecorated (optional, default: false)
|
|
202
|
+
|
|
203
|
+
Whether decorated fields should be renamed. A field is "decorated" if itself or any parent (on type level) has a decorator.
|
|
204
|
+
|
|
205
|
+
```typescript
|
|
206
|
+
ignoreDecorated: true // Don't rename decorated fields
|
|
207
|
+
|
|
208
|
+
@Component({
|
|
209
|
+
selector: "app-root"
|
|
210
|
+
})
|
|
211
|
+
class AppComponent {
|
|
212
|
+
@Input() data: any; // Decorated → not renamed with ignoreDecorated: true
|
|
213
|
+
private internal = 1; // Still renamed to $p$internal
|
|
214
|
+
}
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
### inlineConstEnums (optional, default: true)
|
|
218
|
+
|
|
219
|
+
Whether to inline const enum values and remove const enum declarations.
|
|
220
|
+
|
|
221
|
+
## Examples
|
|
222
|
+
|
|
223
|
+
### Example 1: Simple Property Renaming
|
|
224
|
+
|
|
225
|
+
```typescript
|
|
226
|
+
// src/index.ts (before)
|
|
227
|
+
class Calculator {
|
|
228
|
+
add(a: number, b: number): number {
|
|
229
|
+
return a + b;
|
|
230
|
+
}
|
|
231
|
+
logResult(value: number): void {
|
|
232
|
+
console.log(value);
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
export const calc = new Calculator();
|
|
237
|
+
export { Calculator };
|
|
238
|
+
|
|
239
|
+
// After transformer (before minifier)
|
|
240
|
+
class Calculator {
|
|
241
|
+
add(a, b) { return a + b; } // Exported method → no prefix
|
|
242
|
+
$i$logResult(value) { // Internal method → $i$ prefix
|
|
243
|
+
console.log(value);
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
### Example 2: JSDoc Public Annotation
|
|
249
|
+
|
|
250
|
+
```typescript
|
|
251
|
+
// src/index.ts (before)
|
|
252
|
+
class API {
|
|
253
|
+
/** @public */
|
|
254
|
+
fetchData(url: string): Promise<Data> {
|
|
255
|
+
return fetch(url);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
private cache = new Map();
|
|
259
|
+
internalTransform(data: Data): Processed {
|
|
260
|
+
// transformation logic
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
export const api = new API();
|
|
265
|
+
|
|
266
|
+
// After transformer (before minifier)
|
|
267
|
+
class API {
|
|
268
|
+
fetchData(url) { return fetch(url); } // @public → no prefix
|
|
269
|
+
$p$cache = new Map(); // Private → $p$ prefix
|
|
270
|
+
$i$internalTransform(data) { // Internal → $i$ prefix
|
|
271
|
+
// transformation logic
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
### Example 3: Const Enum Inlining
|
|
277
|
+
|
|
278
|
+
```typescript
|
|
279
|
+
// src/index.ts (before)
|
|
280
|
+
const enum LogLevel {
|
|
281
|
+
Debug = 0,
|
|
282
|
+
Info = 1,
|
|
283
|
+
Error = 2
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
function log(level: LogLevel, message: string): void {
|
|
287
|
+
console.log(`[${LogLevel[level]}] ${message}`);
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
export { log, LogLevel };
|
|
291
|
+
|
|
292
|
+
// After transformer (before minifier)
|
|
293
|
+
function log(level, message) {
|
|
294
|
+
console.log(`[${level}] ${message}`);
|
|
295
|
+
}
|
|
296
|
+
export { log };
|
|
297
|
+
|
|
298
|
+
// After esbuild minifier
|
|
299
|
+
function log(n,e){console.log(`[${n}]${e}`)}export{log};
|
|
300
|
+
// LogLevel values inlined: LogLevel.Debug → 0, LogLevel.Info → 1, etc.
|
|
301
|
+
// LogLevel enum declaration removed entirely
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
### Example 4: Complete Transformation + Minification
|
|
305
|
+
|
|
306
|
+
```typescript
|
|
307
|
+
// src/index.ts (before)
|
|
308
|
+
const enum HttpStatus {
|
|
309
|
+
OK = 200,
|
|
310
|
+
NotFound = 404
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
class API {
|
|
314
|
+
private baseUrl = "https://api.example.com";
|
|
315
|
+
/** @public */
|
|
316
|
+
async get(path: string): Promise<Response> {
|
|
317
|
+
const url = `${this.baseUrl}${path}`;
|
|
318
|
+
const response = await fetch(url);
|
|
319
|
+
return this.handleResponse(response);
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
private async handleResponse(response: Response): Promise<Response> {
|
|
323
|
+
return response;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
logStatus(status: HttpStatus): void {
|
|
327
|
+
console.log(status);
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
export const api = new API();
|
|
332
|
+
|
|
333
|
+
// After transformer (before minifier)
|
|
334
|
+
class API {
|
|
335
|
+
$p$baseUrl = "https://api.example.com";
|
|
336
|
+
async get(path) {
|
|
337
|
+
const url = `${this.$p$baseUrl}${path}`;
|
|
338
|
+
const response = await fetch(url);
|
|
339
|
+
return this.$i$handleResponse(response);
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
$i$handleResponse(response) {
|
|
343
|
+
return response;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
$i$logStatus(status) {
|
|
347
|
+
console.log(status);
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
// After esbuild minifier
|
|
352
|
+
class t{a="https://api.example.com";async get(t){const n=`${this.a}${t}`;return await fetch(n)}b(t){return t}c(t){console.log(t)}}const s=new t;export{s};
|
|
353
|
+
```
|
|
354
|
+
|
|
355
|
+
## License
|
|
356
|
+
|
|
357
|
+
MIT
|
package/package.json
CHANGED
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@eliasku/ts-transformers",
|
|
3
|
-
"description": "",
|
|
4
|
-
"version": "0.0.
|
|
3
|
+
"description": "TypeScript transformer for code optimization",
|
|
4
|
+
"version": "0.0.3",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"scripts": {
|
|
8
8
|
"test": "bun test",
|
|
9
9
|
"format": "prettier --write **/*.{ts,json,md,yml}",
|
|
10
10
|
"check": "tsc -p .",
|
|
11
|
-
"lint": "eslint ."
|
|
11
|
+
"lint": "eslint .",
|
|
12
|
+
"build:example": "cd example && bun run build.ts"
|
|
12
13
|
},
|
|
13
14
|
"dependencies": {
|
|
14
15
|
"typescript": "^5"
|
|
@@ -20,11 +21,13 @@
|
|
|
20
21
|
"@types/bun": "latest",
|
|
21
22
|
"rollup": "latest",
|
|
22
23
|
"@rollup/plugin-typescript": "latest",
|
|
23
|
-
"tslib": "latest"
|
|
24
|
+
"tslib": "latest",
|
|
25
|
+
"esbuild": "latest"
|
|
24
26
|
},
|
|
27
|
+
"module": "./src/index.ts",
|
|
28
|
+
"types": "./src/index.ts",
|
|
25
29
|
"exports": {
|
|
26
|
-
"
|
|
27
|
-
"./const-enum": "./src/const-enum/index.ts"
|
|
30
|
+
".": "./src/index.ts"
|
|
28
31
|
},
|
|
29
32
|
"repository": {
|
|
30
33
|
"type": "git",
|
|
@@ -9,13 +9,9 @@ export interface EvaluationContext {
|
|
|
9
9
|
|
|
10
10
|
export class EnumEvaluator {
|
|
11
11
|
private lastImplicitValue = -1;
|
|
12
|
-
private enumType: "numeric" | "string" | "mixed" = "numeric";
|
|
13
|
-
|
|
14
|
-
constructor(private readonly typeChecker: ts.TypeChecker) {}
|
|
15
12
|
|
|
16
13
|
reset(): void {
|
|
17
14
|
this.lastImplicitValue = -1;
|
|
18
|
-
this.enumType = "numeric";
|
|
19
15
|
}
|
|
20
16
|
|
|
21
17
|
evaluate(expr: ts.Expression, context: EvaluationContext): EnumValue {
|
|
@@ -58,7 +54,6 @@ export class EnumEvaluator {
|
|
|
58
54
|
|
|
59
55
|
private evaluateImplicitMember(member: ts.EnumMember): EnumValue {
|
|
60
56
|
const name = ts.isIdentifier(member.name) ? member.name.text : `<computed>`;
|
|
61
|
-
// unused
|
|
62
57
|
void name;
|
|
63
58
|
|
|
64
59
|
if (this.lastImplicitValue === -1) {
|
|
@@ -104,12 +99,10 @@ export class EnumEvaluator {
|
|
|
104
99
|
const left = this.evaluate(expr.left, context);
|
|
105
100
|
const right = this.evaluate(expr.right, context);
|
|
106
101
|
|
|
107
|
-
// String concatenation
|
|
108
102
|
if (typeof left === "string" && typeof right === "string" && expr.operatorToken.kind === ts.SyntaxKind.PlusToken) {
|
|
109
103
|
return left + right;
|
|
110
104
|
}
|
|
111
105
|
|
|
112
|
-
// Numeric operations
|
|
113
106
|
if (typeof left === "number" && typeof right === "number") {
|
|
114
107
|
switch (expr.operatorToken.kind) {
|
|
115
108
|
case ts.SyntaxKind.BarToken:
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import ts from "typescript";
|
|
2
2
|
import { EnumValue, EvaluationContext } from "./evaluator";
|
|
3
3
|
import { EnumEvaluator } from "./evaluator";
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
4
|
+
import { isConstEnumSymbol } from "./utils";
|
|
5
|
+
import { hasModifier } from "../typescript-helpers";
|
|
6
6
|
|
|
7
7
|
export interface ConstEnumInfo {
|
|
8
8
|
declaration: ts.EnumDeclaration;
|
|
@@ -20,13 +20,11 @@ export interface ConstEnumMemberInfo {
|
|
|
20
20
|
export class ConstEnumRegistry {
|
|
21
21
|
private readonly program: ts.Program;
|
|
22
22
|
private readonly typeChecker: ts.TypeChecker;
|
|
23
|
-
private readonly entrySourceFiles: readonly string[];
|
|
24
23
|
private readonly enumDeclarations: Map<string, ConstEnumInfo>;
|
|
25
24
|
|
|
26
|
-
constructor(program: ts.Program
|
|
25
|
+
constructor(program: ts.Program) {
|
|
27
26
|
this.program = program;
|
|
28
27
|
this.typeChecker = program.getTypeChecker();
|
|
29
|
-
this.entrySourceFiles = entrySourceFiles || program.getRootFileNames();
|
|
30
28
|
this.enumDeclarations = new Map();
|
|
31
29
|
this.collectConstEnumsFromEntryPoints();
|
|
32
30
|
}
|
|
@@ -56,29 +54,15 @@ export class ConstEnumRegistry {
|
|
|
56
54
|
}
|
|
57
55
|
|
|
58
56
|
private collectConstEnumsFromEntryPoints(): void {
|
|
59
|
-
if (LOGS) {
|
|
60
|
-
console.log(`[const-enum registry] Starting collection from ${this.entrySourceFiles.length} entry point(s)`);
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
// Collect all const enums from the entire program
|
|
64
57
|
const sourceFiles = this.program.getSourceFiles();
|
|
65
|
-
if (LOGS) {
|
|
66
|
-
console.log(`[const-enum registry] Program has ${sourceFiles.length} source files`);
|
|
67
|
-
}
|
|
68
58
|
|
|
69
59
|
for (const sourceFile of sourceFiles) {
|
|
70
|
-
// We are using typescript files from node_modules as well, so don't skip them
|
|
71
|
-
// but skip declaration files
|
|
72
60
|
if (sourceFile.isDeclarationFile) {
|
|
73
61
|
continue;
|
|
74
62
|
}
|
|
75
63
|
|
|
76
64
|
this.registerConstEnumFromSource(sourceFile);
|
|
77
65
|
}
|
|
78
|
-
|
|
79
|
-
if (LOGS) {
|
|
80
|
-
console.log(`[const-enum registry] Found ${this.enumDeclarations.size} const enum declarations`);
|
|
81
|
-
}
|
|
82
66
|
}
|
|
83
67
|
|
|
84
68
|
private registerConstEnumFromSource(sourceFile: ts.SourceFile): void {
|
|
@@ -96,7 +80,6 @@ export class ConstEnumRegistry {
|
|
|
96
80
|
const name = this.getEnumSymbolName(symbol);
|
|
97
81
|
|
|
98
82
|
if (this.enumDeclarations.has(name)) {
|
|
99
|
-
// Already registered (might be from different import)
|
|
100
83
|
return;
|
|
101
84
|
}
|
|
102
85
|
|
|
@@ -126,7 +109,7 @@ export class ConstEnumRegistry {
|
|
|
126
109
|
}
|
|
127
110
|
|
|
128
111
|
private evaluateEnumMembers(enumInfo: ConstEnumInfo): void {
|
|
129
|
-
const evaluator = new EnumEvaluator(
|
|
112
|
+
const evaluator = new EnumEvaluator();
|
|
130
113
|
evaluator.reset();
|
|
131
114
|
const context: EvaluationContext = {
|
|
132
115
|
localMembers: new Map(),
|
package/src/const-enum/utils.ts
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import ts from "typescript";
|
|
2
2
|
|
|
3
|
-
export const hasModifier = (node: ts.Node, modifier: ts.SyntaxKind) =>
|
|
4
|
-
ts.canHaveModifiers(node) && ts.getModifiers(node)?.some((mod: ts.Modifier) => mod.kind === modifier);
|
|
5
|
-
|
|
6
3
|
export const isConstEnumSymbol = (symbol: ts.Symbol): boolean => (symbol.flags & ts.SymbolFlags.ConstEnum) !== 0;
|
|
7
4
|
|
|
8
5
|
export const isConstEnumType = (type: ts.Type | undefined): boolean => {
|
|
9
|
-
if (
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
6
|
+
if (type) {
|
|
7
|
+
const symbol = type.symbol || type.aliasSymbol;
|
|
8
|
+
if (symbol) {
|
|
9
|
+
return (symbol.flags & ts.SymbolFlags.ConstEnum) !== 0;
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
return false;
|
|
13
13
|
};
|
|
@@ -8,7 +8,7 @@ import {
|
|
|
8
8
|
getExportsForSourceFile,
|
|
9
9
|
getDeclarationsForSymbol,
|
|
10
10
|
} from "../utils/symbol-utils";
|
|
11
|
-
import { LOGS } from "
|
|
11
|
+
import { LOGS } from "../config";
|
|
12
12
|
|
|
13
13
|
export class ExportsSymbolTree {
|
|
14
14
|
private readonly program: ts.Program;
|
|
@@ -21,13 +21,12 @@ export class ExportsSymbolTree {
|
|
|
21
21
|
|
|
22
22
|
public isSymbolAccessibleFromExports(symbol: ts.Symbol): boolean {
|
|
23
23
|
symbol = this.getActualSymbol(symbol);
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
return result;
|
|
24
|
+
for (const [, set] of this.exportsTree) {
|
|
25
|
+
if (set.has(symbol)) {
|
|
26
|
+
return true;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
return false;
|
|
31
30
|
}
|
|
32
31
|
|
|
33
32
|
private computeTreeForExports(entrySourceFiles: readonly string[]): void {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import ts from "typescript";
|
|
2
|
+
export type { OptimizerOptions } from "./types";
|
|
2
3
|
|
|
3
4
|
import { ExportsSymbolTree } from "./exports/tracker";
|
|
4
5
|
import {
|
|
@@ -14,23 +15,26 @@ import {
|
|
|
14
15
|
isNodeNamedDeclaration,
|
|
15
16
|
} from "./utils/symbol-utils";
|
|
16
17
|
import { getNodeJSDocComment } from "./utils/ast-utils";
|
|
17
|
-
import {
|
|
18
|
-
import {
|
|
18
|
+
import { OptimizerOptions, defaultOptions, VisibilityType } from "./types";
|
|
19
|
+
import { ConstEnumRegistry } from "./const-enum/registry";
|
|
20
|
+
import { EnumEvaluator } from "./const-enum/evaluator";
|
|
21
|
+
import { isConstEnumType } from "./const-enum/utils";
|
|
22
|
+
import { LOGS } from "./config";
|
|
19
23
|
|
|
20
|
-
export
|
|
24
|
+
export const optimizer = (
|
|
21
25
|
program: ts.Program,
|
|
22
|
-
config?: Partial<
|
|
23
|
-
): ts.TransformerFactory<ts.SourceFile>
|
|
24
|
-
return createTransformerFactory(program, config);
|
|
25
|
-
}
|
|
26
|
+
config?: Partial<OptimizerOptions>,
|
|
27
|
+
): ts.TransformerFactory<ts.SourceFile> => createTransformerFactory(program, config);
|
|
26
28
|
|
|
27
29
|
function createTransformerFactory(
|
|
28
30
|
program: ts.Program,
|
|
29
|
-
options?: Partial<
|
|
31
|
+
options?: Partial<OptimizerOptions>,
|
|
30
32
|
): ts.TransformerFactory<ts.SourceFile> {
|
|
31
|
-
const fullOptions:
|
|
33
|
+
const fullOptions: OptimizerOptions = { ...defaultOptions, ...options };
|
|
32
34
|
const typeChecker = program.getTypeChecker();
|
|
33
35
|
const exportsSymbolTree = new ExportsSymbolTree(program, fullOptions.entrySourceFiles);
|
|
36
|
+
const constEnumRegistry = new ConstEnumRegistry(program);
|
|
37
|
+
const enumEvaluator = new EnumEvaluator();
|
|
34
38
|
|
|
35
39
|
const cache = new Map<ts.Symbol, VisibilityType>();
|
|
36
40
|
|
|
@@ -40,17 +44,28 @@ function createTransformerFactory(
|
|
|
40
44
|
}
|
|
41
45
|
|
|
42
46
|
return (context: ts.TransformationContext) => {
|
|
43
|
-
function
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
47
|
+
function transformNode(node: ts.Node): ts.Node | undefined {
|
|
48
|
+
if (fullOptions.inlineConstEnums !== false) {
|
|
49
|
+
if (ts.isPropertyAccessExpression(node)) {
|
|
50
|
+
const inlined = tryInlineConstEnum(node);
|
|
51
|
+
if (inlined) return inlined;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (ts.isImportSpecifier(node)) {
|
|
55
|
+
const removed = tryRemoveConstEnumImport(node);
|
|
56
|
+
if (removed === undefined) {
|
|
57
|
+
return undefined;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (ts.isImportClause(node)) {
|
|
62
|
+
const removed = tryRemoveConstEnumImportClause(node);
|
|
63
|
+
if (removed === undefined) {
|
|
64
|
+
return undefined;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
52
68
|
|
|
53
|
-
function transformNode(node: ts.Node): ts.Node {
|
|
54
69
|
// const a = { node }
|
|
55
70
|
if (ts.isShorthandPropertyAssignment(node)) {
|
|
56
71
|
return handleShorthandPropertyAssignment(node);
|
|
@@ -60,9 +75,8 @@ function createTransformerFactory(
|
|
|
60
75
|
if (ts.isBindingElement(node) && node.propertyName === undefined) {
|
|
61
76
|
if (node.parent && ts.isObjectBindingPattern(node.parent)) {
|
|
62
77
|
return handleShorthandObjectBindingElement(node);
|
|
63
|
-
} else {
|
|
64
|
-
console.warn("!!!", node);
|
|
65
78
|
}
|
|
79
|
+
return node;
|
|
66
80
|
}
|
|
67
81
|
|
|
68
82
|
// is not supported:
|
|
@@ -538,7 +552,6 @@ function createTransformerFactory(
|
|
|
538
552
|
}
|
|
539
553
|
|
|
540
554
|
if (nodeSymbol.escapedName === "prototype") {
|
|
541
|
-
// accessing to prototype
|
|
542
555
|
return putToCache(nodeSymbol, VisibilityType.External);
|
|
543
556
|
}
|
|
544
557
|
|
|
@@ -637,6 +650,72 @@ function createTransformerFactory(
|
|
|
637
650
|
return isSymbolClassMember(typeChecker.getSymbolAtLocation(node));
|
|
638
651
|
}
|
|
639
652
|
|
|
640
|
-
|
|
653
|
+
function tryInlineConstEnum(node: ts.PropertyAccessExpression): ts.Expression | null {
|
|
654
|
+
const expressionType = typeChecker.getTypeAtLocation(node.expression);
|
|
655
|
+
if (!isConstEnumType(expressionType)) return null;
|
|
656
|
+
|
|
657
|
+
const enumSymbol = expressionType.symbol || expressionType.aliasSymbol;
|
|
658
|
+
if (!enumSymbol) return null;
|
|
659
|
+
|
|
660
|
+
const enumInfo = constEnumRegistry.getEnumInfo(enumSymbol);
|
|
661
|
+
if (!enumInfo) return null;
|
|
662
|
+
|
|
663
|
+
const memberValue = enumInfo.members.get(node.name.text)?.value;
|
|
664
|
+
if (memberValue === undefined || memberValue === null) return null;
|
|
665
|
+
|
|
666
|
+
return enumEvaluator.createLiteral(memberValue);
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
function tryRemoveConstEnumImport(node: ts.ImportSpecifier): ts.ImportSpecifier | undefined {
|
|
670
|
+
const importedType = typeChecker.getTypeAtLocation(node);
|
|
671
|
+
if (isConstEnumType(importedType)) {
|
|
672
|
+
return undefined;
|
|
673
|
+
}
|
|
674
|
+
return node;
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
function tryRemoveConstEnumImportClause(node: ts.ImportClause): ts.ImportClause | undefined {
|
|
678
|
+
if (!node.name) return node;
|
|
679
|
+
const type = typeChecker.getTypeAtLocation(node.name);
|
|
680
|
+
if (isConstEnumType(type)) {
|
|
681
|
+
return undefined;
|
|
682
|
+
}
|
|
683
|
+
return node;
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
return (sourceFile: ts.SourceFile) => {
|
|
687
|
+
function handleEnumDeclaration(node: ts.EnumDeclaration): ts.EnumDeclaration | undefined {
|
|
688
|
+
if (fullOptions.inlineConstEnums === false) return node;
|
|
689
|
+
if (!hasModifier(node, ts.SyntaxKind.ConstKeyword)) return node;
|
|
690
|
+
|
|
691
|
+
if (sourceFile.isDeclarationFile) {
|
|
692
|
+
return ts.factory.updateEnumDeclaration(
|
|
693
|
+
node,
|
|
694
|
+
node.modifiers?.filter((m) => m.kind !== ts.SyntaxKind.ConstKeyword),
|
|
695
|
+
node.name,
|
|
696
|
+
node.members,
|
|
697
|
+
);
|
|
698
|
+
}
|
|
699
|
+
return undefined;
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
function wrapTransformNode(node: ts.Node): ts.Node | undefined {
|
|
703
|
+
if (ts.isEnumDeclaration(node)) {
|
|
704
|
+
const result = handleEnumDeclaration(node);
|
|
705
|
+
if (result === undefined) {
|
|
706
|
+
return undefined;
|
|
707
|
+
}
|
|
708
|
+
if (result !== node) {
|
|
709
|
+
return result;
|
|
710
|
+
}
|
|
711
|
+
}
|
|
712
|
+
return transformNode(node);
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
const wrappedTransformNodeAndChildren = (node: ts.Node): ts.Node | undefined =>
|
|
716
|
+
ts.visitEachChild(wrapTransformNode(node), wrappedTransformNodeAndChildren, context);
|
|
717
|
+
|
|
718
|
+
return wrappedTransformNodeAndChildren(sourceFile) as ts.SourceFile;
|
|
719
|
+
};
|
|
641
720
|
};
|
|
642
721
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export interface
|
|
1
|
+
export interface OptimizerOptions {
|
|
2
2
|
/**
|
|
3
3
|
* An array of entry source files which will used to detect exported and internal fields.
|
|
4
4
|
* Basically it should be entry point(s) of the library/project.
|
|
@@ -34,6 +34,11 @@ export interface RenameOptions {
|
|
|
34
34
|
* A field is treated as "decorated" if itself or any its parent (on type level) has a decorator.
|
|
35
35
|
*/
|
|
36
36
|
ignoreDecorated: boolean;
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Whether to inline const enum values.
|
|
40
|
+
*/
|
|
41
|
+
inlineConstEnums?: boolean;
|
|
37
42
|
}
|
|
38
43
|
|
|
39
44
|
export const enum VisibilityType {
|
|
@@ -42,10 +47,11 @@ export const enum VisibilityType {
|
|
|
42
47
|
External = 2,
|
|
43
48
|
}
|
|
44
49
|
|
|
45
|
-
export const defaultOptions:
|
|
50
|
+
export const defaultOptions: OptimizerOptions = {
|
|
46
51
|
entrySourceFiles: [],
|
|
47
52
|
privatePrefix: "$p$",
|
|
48
53
|
internalPrefix: "$i$",
|
|
49
54
|
publicJSDocTag: "public",
|
|
50
55
|
ignoreDecorated: false,
|
|
56
|
+
inlineConstEnums: true,
|
|
51
57
|
};
|
|
@@ -139,8 +139,8 @@ function getModifiers(node: ts.Node): readonly ts.Modifier[] {
|
|
|
139
139
|
return node.modifiers || [];
|
|
140
140
|
}
|
|
141
141
|
|
|
142
|
-
export const hasModifier = (node: ts.Node, modifier: ts.SyntaxKind) =>
|
|
143
|
-
getModifiers(node)
|
|
142
|
+
export const hasModifier = (node: ts.Node, modifier: ts.SyntaxKind): boolean =>
|
|
143
|
+
ts.canHaveModifiers(node) && getModifiers(node)?.some((mod) => mod.kind === modifier);
|
|
144
144
|
|
|
145
145
|
function getDecorators(node: ts.Node): readonly unknown[] {
|
|
146
146
|
if (isBreakingTypeScriptApi(ts)) {
|
package/src/const-enum/index.ts
DELETED
|
@@ -1,181 +0,0 @@
|
|
|
1
|
-
import ts from "typescript";
|
|
2
|
-
import { ConstEnumRegistry } from "./registry";
|
|
3
|
-
import { EnumEvaluator } from "./evaluator";
|
|
4
|
-
import { hasModifier, isConstEnumType } from "./utils";
|
|
5
|
-
import { LOGS } from "../config";
|
|
6
|
-
|
|
7
|
-
export const tsTransformConstEnums = (
|
|
8
|
-
program: ts.Program,
|
|
9
|
-
entrySourceFiles?: readonly string[],
|
|
10
|
-
): ts.TransformerFactory<ts.SourceFile> => {
|
|
11
|
-
if (LOGS) {
|
|
12
|
-
console.log("[const-enum] tsTransformConstEnums called!");
|
|
13
|
-
}
|
|
14
|
-
const startTime = performance.now();
|
|
15
|
-
const registry = new ConstEnumRegistry(program, entrySourceFiles);
|
|
16
|
-
const typeChecker = program.getTypeChecker();
|
|
17
|
-
const evaluator = new EnumEvaluator(typeChecker);
|
|
18
|
-
if (LOGS) {
|
|
19
|
-
console.log(
|
|
20
|
-
`[const-enum] Found ${registry.getEnumCount()} const enum declarations in ${performance.now() - startTime}ms`,
|
|
21
|
-
);
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
return (context: ts.TransformationContext) => {
|
|
25
|
-
function transformNodeAndChildren(
|
|
26
|
-
node: ts.Node,
|
|
27
|
-
ctx: ts.TransformationContext,
|
|
28
|
-
sourceFile: ts.SourceFile,
|
|
29
|
-
): ts.Node {
|
|
30
|
-
return ts.visitEachChild(
|
|
31
|
-
transformNode(node, sourceFile, ctx, registry, evaluator, typeChecker),
|
|
32
|
-
(childNode: ts.Node) => transformNodeAndChildren(childNode, ctx, sourceFile),
|
|
33
|
-
ctx,
|
|
34
|
-
);
|
|
35
|
-
}
|
|
36
|
-
return (sourceFile: ts.SourceFile) => transformNodeAndChildren(sourceFile, context, sourceFile) as ts.SourceFile;
|
|
37
|
-
};
|
|
38
|
-
};
|
|
39
|
-
|
|
40
|
-
function transformNode(
|
|
41
|
-
node: ts.Node,
|
|
42
|
-
sourceFile: ts.SourceFile,
|
|
43
|
-
ctx: ts.TransformationContext,
|
|
44
|
-
registry: ConstEnumRegistry,
|
|
45
|
-
evaluator: EnumEvaluator,
|
|
46
|
-
typeChecker: ts.TypeChecker,
|
|
47
|
-
): ts.Node {
|
|
48
|
-
if (ts.isPropertyAccessExpression(node)) {
|
|
49
|
-
return transformPropertyAccess(node, ctx, registry, evaluator, typeChecker);
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
if (ts.isEnumDeclaration(node)) {
|
|
53
|
-
return transformEnumDeclaration(node, sourceFile, ctx);
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
if (ts.isImportSpecifier(node)) {
|
|
57
|
-
return transformImportSpecifier(node, ctx, registry, typeChecker);
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
if (ts.isImportClause(node)) {
|
|
61
|
-
return transformImportClause(node, ctx, registry, typeChecker);
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
return ts.visitEachChild(
|
|
65
|
-
node,
|
|
66
|
-
(child) => transformNode(child, sourceFile, ctx, registry, evaluator, typeChecker),
|
|
67
|
-
ctx,
|
|
68
|
-
);
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
function transformPropertyAccess(
|
|
72
|
-
node: ts.PropertyAccessExpression,
|
|
73
|
-
ctx: ts.TransformationContext,
|
|
74
|
-
registry: ConstEnumRegistry,
|
|
75
|
-
evaluator: EnumEvaluator,
|
|
76
|
-
typeChecker: ts.TypeChecker,
|
|
77
|
-
): ts.Expression | ts.PropertyAccessExpression {
|
|
78
|
-
const expressionType = typeChecker.getTypeAtLocation(node.expression);
|
|
79
|
-
|
|
80
|
-
if (!isConstEnumType(expressionType)) {
|
|
81
|
-
return node;
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
const enumSymbol = expressionType.symbol || expressionType.aliasSymbol;
|
|
85
|
-
if (!enumSymbol) {
|
|
86
|
-
return node;
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
const enumInfo = registry.getEnumInfo(enumSymbol);
|
|
90
|
-
if (!enumInfo) {
|
|
91
|
-
if (LOGS) {
|
|
92
|
-
console.warn(`[const-enum] Could not find const enum ${enumSymbol.name}`);
|
|
93
|
-
}
|
|
94
|
-
return node;
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
const memberValue = enumInfo.members.get(node.name.text)?.value;
|
|
98
|
-
if (memberValue === undefined || memberValue === null) {
|
|
99
|
-
if (LOGS) {
|
|
100
|
-
console.warn(`[const-enum] Could not find member ${enumSymbol.name}.${node.name.text}`);
|
|
101
|
-
}
|
|
102
|
-
return node;
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
const literal = evaluator.createLiteral(memberValue);
|
|
106
|
-
if (LOGS) {
|
|
107
|
-
console.log(`[const-enum] Inline ${enumSymbol.name}.${node.name.text} → ${JSON.stringify(memberValue)}`);
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
return literal;
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
function transformEnumDeclaration(
|
|
114
|
-
node: ts.EnumDeclaration,
|
|
115
|
-
sourceFile: ts.SourceFile,
|
|
116
|
-
ctx: ts.TransformationContext,
|
|
117
|
-
): ts.EnumDeclaration | undefined {
|
|
118
|
-
// unused
|
|
119
|
-
void ctx;
|
|
120
|
-
|
|
121
|
-
if (!hasModifier(node, ts.SyntaxKind.ConstKeyword)) {
|
|
122
|
-
return node;
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
if (sourceFile.isDeclarationFile) {
|
|
126
|
-
if (LOGS) {
|
|
127
|
-
console.log(`[const-enum] Strip 'const' from ${node.name.text} in ${sourceFile.fileName}`);
|
|
128
|
-
}
|
|
129
|
-
return ts.factory.updateEnumDeclaration(
|
|
130
|
-
node,
|
|
131
|
-
node.modifiers?.filter((m) => m.kind !== ts.SyntaxKind.ConstKeyword),
|
|
132
|
-
node.name,
|
|
133
|
-
node.members,
|
|
134
|
-
);
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
if (LOGS) {
|
|
138
|
-
console.log(`[const-enum] Remove const enum declaration ${node.name.text} in ${sourceFile.fileName}`);
|
|
139
|
-
}
|
|
140
|
-
return undefined;
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
function transformImportSpecifier(
|
|
144
|
-
node: ts.ImportSpecifier,
|
|
145
|
-
ctx: ts.TransformationContext,
|
|
146
|
-
registry: ConstEnumRegistry,
|
|
147
|
-
typeChecker: ts.TypeChecker,
|
|
148
|
-
): ts.ImportSpecifier | undefined {
|
|
149
|
-
const importedType = typeChecker.getTypeAtLocation(node);
|
|
150
|
-
|
|
151
|
-
if (isConstEnumType(importedType)) {
|
|
152
|
-
if (LOGS) {
|
|
153
|
-
console.log(`[const-enum] Remove import of const enum ${importedType.symbol?.name}`);
|
|
154
|
-
}
|
|
155
|
-
return undefined;
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
return node;
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
function transformImportClause(
|
|
162
|
-
node: ts.ImportClause,
|
|
163
|
-
ctx: ts.TransformationContext,
|
|
164
|
-
registry: ConstEnumRegistry,
|
|
165
|
-
typeChecker: ts.TypeChecker,
|
|
166
|
-
): ts.ImportClause | undefined {
|
|
167
|
-
if (!node.name) {
|
|
168
|
-
return node;
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
const type = typeChecker.getTypeAtLocation(node.name);
|
|
172
|
-
|
|
173
|
-
if (isConstEnumType(type)) {
|
|
174
|
-
if (LOGS) {
|
|
175
|
-
console.log(`[const-enum] Remove import clause for const enum ${type.symbol?.name}`);
|
|
176
|
-
}
|
|
177
|
-
return undefined;
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
return node;
|
|
181
|
-
}
|
|
File without changes
|
|
File without changes
|