@eliasku/ts-transformers 0.0.8 → 0.0.9
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 +3 -23
- package/package.json +1 -2
- package/src/index.ts +7 -95
- package/src/types.ts +0 -6
- package/src/const-enum/evaluator.ts +0 -158
- package/src/const-enum/registry.ts +0 -152
- package/src/const-enum/utils.ts +0 -13
package/README.md
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
|
|
4
4
|
# @eliasku/ts-transformers
|
|
5
5
|
|
|
6
|
-
TypeScript transformer for aggressive code minification through type-aware property renaming
|
|
6
|
+
TypeScript transformer for aggressive code minification through type-aware property renaming.
|
|
7
7
|
|
|
8
8
|
## Important Requirement
|
|
9
9
|
|
|
@@ -24,7 +24,7 @@ Two-phase optimization pipeline:
|
|
|
24
24
|
|
|
25
25
|
Based on type analysis, properties are categorized as:
|
|
26
26
|
|
|
27
|
-
- **Public
|
|
27
|
+
- **Public**: Exported from entry points → **no prefix** (preserved)
|
|
28
28
|
- **Private**: Everything else → prefixed with `$_` (mangled by minifier)
|
|
29
29
|
|
|
30
30
|
**Example:**
|
|
@@ -52,23 +52,7 @@ class A{publicApi(){},a(){},b=1}
|
|
|
52
52
|
|
|
53
53
|
### Const Enum Inlining
|
|
54
54
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
```typescript
|
|
58
|
-
// Before
|
|
59
|
-
const enum Status {
|
|
60
|
-
Active = 1,
|
|
61
|
-
Inactive = 0,
|
|
62
|
-
}
|
|
63
|
-
export const status = Status.Active;
|
|
64
|
-
|
|
65
|
-
// After transformer
|
|
66
|
-
export let Status: {};
|
|
67
|
-
export const status = 1;
|
|
68
|
-
|
|
69
|
-
// After minifier
|
|
70
|
-
export const status=1;
|
|
71
|
-
```
|
|
55
|
+
TypeScript currently inlines const enums, if you use traditional compiler for production build. Check if you have option `isolatedModules: false` disabled in `tsconfig.json`.
|
|
72
56
|
|
|
73
57
|
## Usage
|
|
74
58
|
|
|
@@ -165,10 +149,6 @@ class AppComponent {
|
|
|
165
149
|
}
|
|
166
150
|
```
|
|
167
151
|
|
|
168
|
-
### inlineConstEnums (optional, default: true)
|
|
169
|
-
|
|
170
|
-
Inline const enum values and transform declarations to preserve export/import flow.
|
|
171
|
-
|
|
172
152
|
## Complete Example
|
|
173
153
|
|
|
174
154
|
```typescript
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@eliasku/ts-transformers",
|
|
3
3
|
"description": "TypeScript transformer for code optimization",
|
|
4
|
-
"version": "0.0.
|
|
4
|
+
"version": "0.0.9",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"scripts": {
|
|
@@ -40,7 +40,6 @@
|
|
|
40
40
|
"plugin",
|
|
41
41
|
"transformer",
|
|
42
42
|
"minify",
|
|
43
|
-
"const-enum",
|
|
44
43
|
"treeshake",
|
|
45
44
|
"mangle"
|
|
46
45
|
],
|
package/src/index.ts
CHANGED
|
@@ -1,6 +1,4 @@
|
|
|
1
1
|
import ts from "typescript";
|
|
2
|
-
export type { OptimizerOptions } from "./types";
|
|
3
|
-
|
|
4
2
|
import { ExportsSymbolTree } from "./exports/tracker";
|
|
5
3
|
import {
|
|
6
4
|
getDeclarationsForSymbol,
|
|
@@ -16,25 +14,17 @@ import {
|
|
|
16
14
|
} from "./utils/symbol-utils";
|
|
17
15
|
import { getNodeJSDocComment } from "./utils/ast-utils";
|
|
18
16
|
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
17
|
import { LOGS } from "./config";
|
|
23
18
|
|
|
24
|
-
export
|
|
25
|
-
program: ts.Program,
|
|
26
|
-
config?: Partial<OptimizerOptions>,
|
|
27
|
-
): ts.TransformerFactory<ts.SourceFile> => createTransformerFactory(program, config);
|
|
19
|
+
export type { OptimizerOptions } from "./types";
|
|
28
20
|
|
|
29
|
-
|
|
21
|
+
export const optimizer = (
|
|
30
22
|
program: ts.Program,
|
|
31
23
|
options?: Partial<OptimizerOptions>,
|
|
32
|
-
): ts.TransformerFactory<ts.SourceFile> {
|
|
24
|
+
): ts.TransformerFactory<ts.SourceFile> => {
|
|
33
25
|
const fullOptions: OptimizerOptions = { ...defaultOptions, ...options };
|
|
34
26
|
const typeChecker = program.getTypeChecker();
|
|
35
27
|
const exportsSymbolTree = new ExportsSymbolTree(program, fullOptions.entrySourceFiles);
|
|
36
|
-
const constEnumRegistry = new ConstEnumRegistry(program);
|
|
37
|
-
const enumEvaluator = new EnumEvaluator();
|
|
38
28
|
|
|
39
29
|
const cache = new Map<ts.Symbol, VisibilityType>();
|
|
40
30
|
|
|
@@ -45,16 +35,6 @@ function createTransformerFactory(
|
|
|
45
35
|
|
|
46
36
|
return (context: ts.TransformationContext) => {
|
|
47
37
|
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) {
|
|
52
|
-
ts.setOriginalNode(inlined, node);
|
|
53
|
-
return inlined;
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
|
|
58
38
|
// const a = { node }
|
|
59
39
|
if (ts.isShorthandPropertyAssignment(node)) {
|
|
60
40
|
return handleShorthandPropertyAssignment(node);
|
|
@@ -636,77 +616,9 @@ function createTransformerFactory(
|
|
|
636
616
|
return isSymbolClassMember(typeChecker.getSymbolAtLocation(node));
|
|
637
617
|
}
|
|
638
618
|
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
if (!isConstEnumType(expressionType)) return null;
|
|
642
|
-
|
|
643
|
-
const enumSymbol = expressionType.symbol || expressionType.aliasSymbol;
|
|
644
|
-
if (!enumSymbol) return null;
|
|
645
|
-
|
|
646
|
-
const enumInfo = constEnumRegistry.getEnumInfo(enumSymbol);
|
|
647
|
-
if (!enumInfo) return null;
|
|
648
|
-
|
|
649
|
-
const memberValue = enumInfo.members.get(node.name.text)?.value;
|
|
650
|
-
if (memberValue === undefined || memberValue === null) return null;
|
|
651
|
-
const newNode = enumEvaluator.createLiteral(memberValue);
|
|
652
|
-
|
|
653
|
-
const r = ts.setOriginalNode(newNode, node);
|
|
654
|
-
ts.setSourceMapRange(newNode, ts.getSourceMapRange(node));
|
|
655
|
-
(newNode as any).symbol = (node as any).symbol; // eslint-disable-line @typescript-eslint/no-explicit-any
|
|
656
|
-
return r;
|
|
657
|
-
}
|
|
658
|
-
|
|
659
|
-
return (sourceFile: ts.SourceFile) => {
|
|
660
|
-
function handleEnumDeclaration(node: ts.EnumDeclaration): ts.EnumDeclaration | undefined {
|
|
661
|
-
if (fullOptions.inlineConstEnums === false) {
|
|
662
|
-
return node;
|
|
663
|
-
}
|
|
664
|
-
if (!hasModifier(node, ts.SyntaxKind.ConstKeyword)) {
|
|
665
|
-
return node;
|
|
666
|
-
}
|
|
667
|
-
|
|
668
|
-
if (sourceFile.isDeclarationFile) {
|
|
669
|
-
return ts.factory.updateEnumDeclaration(
|
|
670
|
-
node,
|
|
671
|
-
node.modifiers?.filter((m) => m.kind !== ts.SyntaxKind.ConstKeyword),
|
|
672
|
-
node.name,
|
|
673
|
-
node.members,
|
|
674
|
-
);
|
|
675
|
-
}
|
|
676
|
-
|
|
677
|
-
return ts.factory.createVariableStatement(
|
|
678
|
-
[ts.factory.createModifier(ts.SyntaxKind.ExportKeyword)],
|
|
679
|
-
ts.factory.createVariableDeclarationList(
|
|
680
|
-
[
|
|
681
|
-
ts.factory.createVariableDeclaration(
|
|
682
|
-
node.name, // Reuse enum name
|
|
683
|
-
undefined,
|
|
684
|
-
ts.factory.createTypeLiteralNode([]), // Empty object type: {}
|
|
685
|
-
undefined, // No initializer (empty declaration)
|
|
686
|
-
),
|
|
687
|
-
],
|
|
688
|
-
ts.NodeFlags.Let, // Sets the 'const' modifier
|
|
689
|
-
),
|
|
690
|
-
) as unknown as ts.EnumDeclaration;
|
|
691
|
-
}
|
|
692
|
-
|
|
693
|
-
function wrapTransformNode(node: ts.Node): ts.Node | undefined {
|
|
694
|
-
if (ts.isEnumDeclaration(node)) {
|
|
695
|
-
const result = handleEnumDeclaration(node);
|
|
696
|
-
if (result !== node) {
|
|
697
|
-
if (result) {
|
|
698
|
-
return transformNode(result);
|
|
699
|
-
}
|
|
700
|
-
return result;
|
|
701
|
-
}
|
|
702
|
-
}
|
|
703
|
-
return transformNode(node);
|
|
704
|
-
}
|
|
705
|
-
|
|
706
|
-
const wrappedTransformNodeAndChildren = (node: ts.Node): ts.Node | undefined =>
|
|
707
|
-
ts.visitEachChild(wrapTransformNode(node), wrappedTransformNodeAndChildren, context);
|
|
619
|
+
const transformNodeAndChildren = (node: ts.Node): ts.Node | undefined =>
|
|
620
|
+
ts.visitEachChild(transformNode(node), transformNodeAndChildren, context);
|
|
708
621
|
|
|
709
|
-
|
|
710
|
-
};
|
|
622
|
+
return (sourceFile: ts.SourceFile) => transformNodeAndChildren(sourceFile) as ts.SourceFile;
|
|
711
623
|
};
|
|
712
|
-
}
|
|
624
|
+
};
|
package/src/types.ts
CHANGED
|
@@ -27,11 +27,6 @@ export interface OptimizerOptions {
|
|
|
27
27
|
* A field is treated as "decorated" if itself or any its parent (on type level) has a decorator.
|
|
28
28
|
*/
|
|
29
29
|
ignoreDecorated: boolean;
|
|
30
|
-
|
|
31
|
-
/**
|
|
32
|
-
* Whether to inline const enum values.
|
|
33
|
-
*/
|
|
34
|
-
inlineConstEnums?: boolean;
|
|
35
30
|
}
|
|
36
31
|
|
|
37
32
|
export const enum VisibilityType {
|
|
@@ -44,5 +39,4 @@ export const defaultOptions: OptimizerOptions = {
|
|
|
44
39
|
privatePrefix: "$_",
|
|
45
40
|
publicJSDocTag: "public",
|
|
46
41
|
ignoreDecorated: false,
|
|
47
|
-
inlineConstEnums: true,
|
|
48
42
|
};
|
|
@@ -1,158 +0,0 @@
|
|
|
1
|
-
import ts from "typescript";
|
|
2
|
-
|
|
3
|
-
export type EnumValue = string | number;
|
|
4
|
-
|
|
5
|
-
export interface EvaluationContext {
|
|
6
|
-
localMembers: Map<string, EnumValue>;
|
|
7
|
-
allowEnumReferences: boolean;
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
export class EnumEvaluator {
|
|
11
|
-
private lastImplicitValue = -1;
|
|
12
|
-
|
|
13
|
-
reset(): void {
|
|
14
|
-
this.lastImplicitValue = -1;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
evaluate(expr: ts.Expression, context: EvaluationContext): EnumValue {
|
|
18
|
-
if (ts.isPrefixUnaryExpression(expr)) {
|
|
19
|
-
return this.evaluateUnary(expr, context);
|
|
20
|
-
} else if (ts.isBinaryExpression(expr)) {
|
|
21
|
-
return this.evaluateBinary(expr, context);
|
|
22
|
-
} else if (ts.isStringLiteralLike(expr)) {
|
|
23
|
-
return expr.text;
|
|
24
|
-
} else if (ts.isNumericLiteral(expr)) {
|
|
25
|
-
return +expr.text;
|
|
26
|
-
} else if (ts.isParenthesizedExpression(expr)) {
|
|
27
|
-
return this.evaluate(expr.expression, context);
|
|
28
|
-
} else if (ts.isIdentifier(expr)) {
|
|
29
|
-
return this.evaluateIdentifier(expr, context);
|
|
30
|
-
} else if (
|
|
31
|
-
expr.kind === ts.SyntaxKind.TrueKeyword ||
|
|
32
|
-
expr.kind === ts.SyntaxKind.FalseKeyword ||
|
|
33
|
-
expr.kind === ts.SyntaxKind.NullKeyword
|
|
34
|
-
) {
|
|
35
|
-
throw this.createError(expr, `Unsupported literal: ${ts.SyntaxKind[expr.kind]}`);
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
throw this.createError(expr, `Cannot evaluate expression: ${expr.getText()}`);
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
evaluateEnumMember(member: ts.EnumMember, context: EvaluationContext): EnumValue {
|
|
42
|
-
if (!member.initializer) {
|
|
43
|
-
return this.evaluateImplicitMember(member);
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
const value = this.evaluate(member.initializer, context);
|
|
47
|
-
|
|
48
|
-
if (typeof value === "number") {
|
|
49
|
-
this.lastImplicitValue = value;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
return value;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
private evaluateImplicitMember(member: ts.EnumMember): EnumValue {
|
|
56
|
-
const name = ts.isIdentifier(member.name) ? member.name.text : `<computed>`;
|
|
57
|
-
void name;
|
|
58
|
-
|
|
59
|
-
if (this.lastImplicitValue === -1) {
|
|
60
|
-
this.lastImplicitValue = 0;
|
|
61
|
-
return 0;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
const nextValue = this.lastImplicitValue + 1;
|
|
65
|
-
this.lastImplicitValue = nextValue;
|
|
66
|
-
return nextValue;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
createLiteral(value: EnumValue): ts.Expression {
|
|
70
|
-
if (typeof value === "string") {
|
|
71
|
-
return ts.factory.createStringLiteral(value);
|
|
72
|
-
} else {
|
|
73
|
-
if (value < 0) {
|
|
74
|
-
return ts.factory.createPrefixMinus(ts.factory.createNumericLiteral(Math.abs(value).toString()));
|
|
75
|
-
}
|
|
76
|
-
return ts.factory.createNumericLiteral(value.toString());
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
private evaluateUnary(expr: ts.PrefixUnaryExpression, context: EvaluationContext): number {
|
|
81
|
-
const value = this.evaluate(expr.operand, context);
|
|
82
|
-
if (typeof value !== "number") {
|
|
83
|
-
throw this.createError(expr, `Unary operator requires numeric value, got ${typeof value}`);
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
switch (expr.operator) {
|
|
87
|
-
case ts.SyntaxKind.PlusToken:
|
|
88
|
-
return value;
|
|
89
|
-
case ts.SyntaxKind.MinusToken:
|
|
90
|
-
return -value;
|
|
91
|
-
case ts.SyntaxKind.TildeToken:
|
|
92
|
-
return ~value;
|
|
93
|
-
default:
|
|
94
|
-
throw this.createError(expr, `Unsupported unary operator: ${ts.SyntaxKind[expr.operator]}`);
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
private evaluateBinary(expr: ts.BinaryExpression, context: EvaluationContext): EnumValue {
|
|
99
|
-
const left = this.evaluate(expr.left, context);
|
|
100
|
-
const right = this.evaluate(expr.right, context);
|
|
101
|
-
|
|
102
|
-
if (typeof left === "string" && typeof right === "string" && expr.operatorToken.kind === ts.SyntaxKind.PlusToken) {
|
|
103
|
-
return left + right;
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
if (typeof left === "number" && typeof right === "number") {
|
|
107
|
-
switch (expr.operatorToken.kind) {
|
|
108
|
-
case ts.SyntaxKind.BarToken:
|
|
109
|
-
return left | right;
|
|
110
|
-
case ts.SyntaxKind.AmpersandToken:
|
|
111
|
-
return left & right;
|
|
112
|
-
case ts.SyntaxKind.GreaterThanGreaterThanToken:
|
|
113
|
-
return left >> right;
|
|
114
|
-
case ts.SyntaxKind.GreaterThanGreaterThanGreaterThanToken:
|
|
115
|
-
return left >>> right;
|
|
116
|
-
case ts.SyntaxKind.LessThanLessThanToken:
|
|
117
|
-
return left << right;
|
|
118
|
-
case ts.SyntaxKind.CaretToken:
|
|
119
|
-
return left ^ right;
|
|
120
|
-
case ts.SyntaxKind.AsteriskToken:
|
|
121
|
-
return left * right;
|
|
122
|
-
case ts.SyntaxKind.SlashToken:
|
|
123
|
-
return left / right;
|
|
124
|
-
case ts.SyntaxKind.PlusToken:
|
|
125
|
-
return left + right;
|
|
126
|
-
case ts.SyntaxKind.MinusToken:
|
|
127
|
-
return left - right;
|
|
128
|
-
case ts.SyntaxKind.PercentToken:
|
|
129
|
-
return left % right;
|
|
130
|
-
case ts.SyntaxKind.AsteriskAsteriskToken:
|
|
131
|
-
return left ** right;
|
|
132
|
-
default:
|
|
133
|
-
throw this.createError(expr, `Unsupported binary operator: ${ts.SyntaxKind[expr.operatorToken.kind]}`);
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
throw this.createError(expr, `Cannot evaluate binary expression with types ${typeof left} and ${typeof right}`);
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
private evaluateIdentifier(expr: ts.Identifier, context: EvaluationContext): EnumValue {
|
|
141
|
-
if (!context.allowEnumReferences) {
|
|
142
|
-
throw this.createError(expr, `Cannot reference enum member here`);
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
const value = context.localMembers.get(expr.text);
|
|
146
|
-
if (value === undefined) {
|
|
147
|
-
throw this.createError(expr, `Undefined enum member: ${expr.text}`);
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
return value;
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
private createError(node: ts.Node, message: string): Error {
|
|
154
|
-
const sourceFile = node.getSourceFile();
|
|
155
|
-
const pos = sourceFile.getLineAndCharacterOfPosition(node.getStart());
|
|
156
|
-
return new Error(`${sourceFile.fileName}:${pos.line + 1}:${pos.character}: ${message}`);
|
|
157
|
-
}
|
|
158
|
-
}
|
|
@@ -1,152 +0,0 @@
|
|
|
1
|
-
import ts from "typescript";
|
|
2
|
-
import { EnumValue, EvaluationContext } from "./evaluator";
|
|
3
|
-
import { EnumEvaluator } from "./evaluator";
|
|
4
|
-
import { isConstEnumSymbol } from "./utils";
|
|
5
|
-
import { hasModifier } from "../utils/symbol-utils";
|
|
6
|
-
|
|
7
|
-
export interface ConstEnumInfo {
|
|
8
|
-
declaration: ts.EnumDeclaration;
|
|
9
|
-
members: Map<string, ConstEnumMemberInfo>;
|
|
10
|
-
isExported: boolean;
|
|
11
|
-
sourceFile: ts.SourceFile;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
export interface ConstEnumMemberInfo {
|
|
15
|
-
declaration: ts.EnumMember;
|
|
16
|
-
name: string;
|
|
17
|
-
value: EnumValue | null;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
export class ConstEnumRegistry {
|
|
21
|
-
private readonly program: ts.Program;
|
|
22
|
-
private readonly typeChecker: ts.TypeChecker;
|
|
23
|
-
private readonly enumDeclarations: Map<string, ConstEnumInfo>;
|
|
24
|
-
|
|
25
|
-
constructor(program: ts.Program) {
|
|
26
|
-
this.program = program;
|
|
27
|
-
this.typeChecker = program.getTypeChecker();
|
|
28
|
-
this.enumDeclarations = new Map();
|
|
29
|
-
this.collectConstEnumsFromEntryPoints();
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
getEnum(enumName: string): ConstEnumInfo | undefined {
|
|
33
|
-
return this.enumDeclarations.get(enumName);
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
getEnumInfo(symbol: ts.Symbol): ConstEnumInfo | undefined {
|
|
37
|
-
const name = this.getEnumSymbolName(symbol);
|
|
38
|
-
return this.enumDeclarations.get(name);
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
getMemberValue(enumName: string, memberName: string): EnumValue | undefined {
|
|
42
|
-
const enumInfo = this.enumDeclarations.get(enumName);
|
|
43
|
-
if (!enumInfo) return undefined;
|
|
44
|
-
const memberInfo = enumInfo.members.get(memberName);
|
|
45
|
-
return memberInfo?.value ?? undefined;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
getAllEnums(): ConstEnumInfo[] {
|
|
49
|
-
return Array.from(this.enumDeclarations.values());
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
getEnumCount(): number {
|
|
53
|
-
return this.enumDeclarations.size;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
private collectConstEnumsFromEntryPoints(): void {
|
|
57
|
-
const sourceFiles = this.program.getSourceFiles();
|
|
58
|
-
|
|
59
|
-
for (const sourceFile of sourceFiles) {
|
|
60
|
-
if (sourceFile.isDeclarationFile) {
|
|
61
|
-
continue;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
this.registerConstEnumFromSource(sourceFile);
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
private registerConstEnumFromSource(sourceFile: ts.SourceFile): void {
|
|
69
|
-
ts.forEachChild(sourceFile, (node) => {
|
|
70
|
-
if (ts.isEnumDeclaration(node) && hasModifier(node, ts.SyntaxKind.ConstKeyword)) {
|
|
71
|
-
const symbol = this.typeChecker.getSymbolAtLocation(node.name);
|
|
72
|
-
if (symbol && isConstEnumSymbol(symbol)) {
|
|
73
|
-
this.registerEnum(symbol, node, sourceFile);
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
});
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
private registerEnum(symbol: ts.Symbol, declaration: ts.EnumDeclaration, sourceFile: ts.SourceFile): void {
|
|
80
|
-
const name = this.getEnumSymbolName(symbol);
|
|
81
|
-
|
|
82
|
-
if (this.enumDeclarations.has(name)) {
|
|
83
|
-
return;
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
const isExported = this.hasExportModifier(declaration);
|
|
87
|
-
|
|
88
|
-
const enumInfo: ConstEnumInfo = {
|
|
89
|
-
declaration,
|
|
90
|
-
members: new Map(),
|
|
91
|
-
isExported,
|
|
92
|
-
sourceFile,
|
|
93
|
-
};
|
|
94
|
-
|
|
95
|
-
this.evaluateEnumMembers(enumInfo);
|
|
96
|
-
|
|
97
|
-
this.enumDeclarations.set(name, enumInfo);
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
private hasExportModifier(node: ts.Node): boolean {
|
|
101
|
-
if (!ts.canHaveModifiers(node)) {
|
|
102
|
-
return false;
|
|
103
|
-
}
|
|
104
|
-
const modifiers = ts.getModifiers(node);
|
|
105
|
-
if (!modifiers) {
|
|
106
|
-
return false;
|
|
107
|
-
}
|
|
108
|
-
return modifiers.some((m) => m.kind === ts.SyntaxKind.ExportKeyword);
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
private evaluateEnumMembers(enumInfo: ConstEnumInfo): void {
|
|
112
|
-
const evaluator = new EnumEvaluator();
|
|
113
|
-
evaluator.reset();
|
|
114
|
-
const context: EvaluationContext = {
|
|
115
|
-
localMembers: new Map(),
|
|
116
|
-
allowEnumReferences: true,
|
|
117
|
-
};
|
|
118
|
-
|
|
119
|
-
for (const member of enumInfo.declaration.members) {
|
|
120
|
-
if (!ts.isIdentifier(member.name)) continue;
|
|
121
|
-
|
|
122
|
-
try {
|
|
123
|
-
const value = evaluator.evaluateEnumMember(member, context);
|
|
124
|
-
context.localMembers.set(member.name.text, value);
|
|
125
|
-
enumInfo.members.set(member.name.text, {
|
|
126
|
-
declaration: member,
|
|
127
|
-
name: member.name.text,
|
|
128
|
-
value,
|
|
129
|
-
});
|
|
130
|
-
} catch (error) {
|
|
131
|
-
throw new Error(
|
|
132
|
-
`Failed to evaluate const enum member ${enumInfo.declaration.name.text}.${member.name.text}: ${error}`,
|
|
133
|
-
);
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
private getEnumDeclaration(symbol: ts.Symbol): ts.EnumDeclaration | null {
|
|
139
|
-
const decl = symbol.declarations?.[0];
|
|
140
|
-
if (decl && ts.isEnumDeclaration(decl)) {
|
|
141
|
-
return decl;
|
|
142
|
-
}
|
|
143
|
-
return null;
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
private getEnumSymbolName(symbol: ts.Symbol): string {
|
|
147
|
-
if (symbol.flags & ts.SymbolFlags.Alias) {
|
|
148
|
-
return this.typeChecker.getAliasedSymbol(symbol).name;
|
|
149
|
-
}
|
|
150
|
-
return symbol.name;
|
|
151
|
-
}
|
|
152
|
-
}
|
package/src/const-enum/utils.ts
DELETED
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
import ts from "typescript";
|
|
2
|
-
|
|
3
|
-
export const isConstEnumSymbol = (symbol: ts.Symbol): boolean => (symbol.flags & ts.SymbolFlags.ConstEnum) !== 0;
|
|
4
|
-
|
|
5
|
-
export const isConstEnumType = (type: ts.Type | undefined): boolean => {
|
|
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
|
-
};
|