@eliasku/ts-transformers 0.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/README.md +0 -0
- package/package.json +50 -0
- package/src/config.ts +1 -0
- package/src/const-enum/evaluator.ts +165 -0
- package/src/const-enum/index.ts +181 -0
- package/src/const-enum/registry.ts +169 -0
- package/src/const-enum/utils.ts +13 -0
- package/src/mangler/exports/tracker.ts +292 -0
- package/src/mangler/index.ts +642 -0
- package/src/mangler/types.ts +51 -0
- package/src/mangler/typescript-helpers.ts +204 -0
- package/src/mangler/utils/ast-utils.ts +7 -0
- package/src/mangler/utils/symbol-utils.ts +213 -0
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
import ts from "typescript";
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
getActualSymbol,
|
|
5
|
+
splitTransientSymbol,
|
|
6
|
+
isClassMember,
|
|
7
|
+
hasPrivateKeyword,
|
|
8
|
+
getExportsForSourceFile,
|
|
9
|
+
getDeclarationsForSymbol,
|
|
10
|
+
} from "../utils/symbol-utils";
|
|
11
|
+
import { LOGS } from "../../config";
|
|
12
|
+
|
|
13
|
+
export class ExportsSymbolTree {
|
|
14
|
+
private readonly program: ts.Program;
|
|
15
|
+
private readonly exportsTree = new Map<ts.Symbol, Set<ts.Symbol>>();
|
|
16
|
+
|
|
17
|
+
public constructor(program: ts.Program, entrySourceFiles: readonly string[]) {
|
|
18
|
+
this.program = program;
|
|
19
|
+
this.computeTreeForExports(entrySourceFiles);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
public isSymbolAccessibleFromExports(symbol: ts.Symbol): boolean {
|
|
23
|
+
symbol = this.getActualSymbol(symbol);
|
|
24
|
+
|
|
25
|
+
let result = false;
|
|
26
|
+
this.exportsTree.forEach((set: Set<ts.Symbol>) => {
|
|
27
|
+
result = result || set.has(symbol);
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
return result;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
private computeTreeForExports(entrySourceFiles: readonly string[]): void {
|
|
34
|
+
this.exportsTree.clear();
|
|
35
|
+
|
|
36
|
+
const typeChecker = this.program.getTypeChecker();
|
|
37
|
+
for (const filePath of entrySourceFiles) {
|
|
38
|
+
const sourceFile = this.program.getSourceFile(filePath);
|
|
39
|
+
if (sourceFile === undefined) {
|
|
40
|
+
throw new Error(`Cannot find source file ${filePath}`);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const entrySourceFile = typeChecker.getSymbolAtLocation(sourceFile);
|
|
44
|
+
if (entrySourceFile === undefined) {
|
|
45
|
+
// if a source file doesn't have any export - then it doesn't have a symbol as well
|
|
46
|
+
// so just skip it here
|
|
47
|
+
continue;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (LOGS) {
|
|
51
|
+
console.log(`[ExportsSymbolTree] Processing entry file: ${filePath}`);
|
|
52
|
+
}
|
|
53
|
+
for (const entryExportSymbol of getExportsForSourceFile(typeChecker, entrySourceFile)) {
|
|
54
|
+
if (LOGS) {
|
|
55
|
+
console.log(`[ExportsSymbolTree] Export symbol: ${entryExportSymbol.escapedName}`);
|
|
56
|
+
}
|
|
57
|
+
const exportSymbolsSet = new Set<ts.Symbol>();
|
|
58
|
+
this.exportsTree.set(entryExportSymbol, exportSymbolsSet);
|
|
59
|
+
|
|
60
|
+
for (const exportDeclaration of getDeclarationsForSymbol(entryExportSymbol)) {
|
|
61
|
+
if (LOGS) {
|
|
62
|
+
console.log(
|
|
63
|
+
`[ExportsSymbolTree] Declaration: ${ts.SyntaxKind[exportDeclaration.kind]} - ${exportDeclaration.getText()}`,
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
this.computeTreeForChildren(exportSymbolsSet, exportDeclaration, new Set<ts.Symbol>());
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
private computeTreeForChildren(
|
|
73
|
+
targetSymbolsSet: Set<ts.Symbol>,
|
|
74
|
+
node: ts.Node,
|
|
75
|
+
visitedSymbols: Set<ts.Symbol>,
|
|
76
|
+
): void {
|
|
77
|
+
if (LOGS) {
|
|
78
|
+
console.log(`[computeTreeForChildren] Processing: ${ts.SyntaxKind[node.kind]} - ${node.getText()}`);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Handle namespace exports (export * as name from './module')
|
|
82
|
+
// These create an alias symbol that points to the actual module symbol
|
|
83
|
+
if (ts.isNamespaceExport(node)) {
|
|
84
|
+
if (LOGS) {
|
|
85
|
+
console.log(`[computeTreeForChildren] Found NamespaceExport!`);
|
|
86
|
+
}
|
|
87
|
+
const typeChecker = this.program.getTypeChecker();
|
|
88
|
+
|
|
89
|
+
// Get the symbol at the namespace export's name (e.g., "fun1" in "export * as fun1 from './module'")
|
|
90
|
+
const symbol = typeChecker.getSymbolAtLocation(node.name);
|
|
91
|
+
if (LOGS) {
|
|
92
|
+
console.log(
|
|
93
|
+
`[computeTreeForChildren] Symbol: ${symbol?.name}, isAlias: ${symbol && (symbol.flags & ts.SymbolFlags.Alias) !== 0}`,
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
if (symbol !== undefined && symbol.flags & ts.SymbolFlags.Alias) {
|
|
97
|
+
// Resolve the alias to get the actual module symbol
|
|
98
|
+
const aliasedSymbol = typeChecker.getAliasedSymbol(symbol);
|
|
99
|
+
if (LOGS) {
|
|
100
|
+
console.log(`[computeTreeForChildren] Aliased to: ${aliasedSymbol.escapedName}`);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Get all exports from the referenced module
|
|
104
|
+
const moduleExports = typeChecker.getExportsOfModule(aliasedSymbol);
|
|
105
|
+
if (moduleExports && moduleExports.length > 0) {
|
|
106
|
+
if (LOGS) {
|
|
107
|
+
console.log(`[computeTreeForChildren] Module exports: ${moduleExports.map((e) => e.name).join(", ")}`);
|
|
108
|
+
}
|
|
109
|
+
for (const exportSymbol of moduleExports) {
|
|
110
|
+
const actualExport = getActualSymbol(exportSymbol, typeChecker);
|
|
111
|
+
if (!visitedSymbols.has(actualExport)) {
|
|
112
|
+
visitedSymbols.add(actualExport);
|
|
113
|
+
targetSymbolsSet.add(actualExport);
|
|
114
|
+
if (LOGS) {
|
|
115
|
+
console.log(`[computeTreeForChildren] Adding: ${actualExport.escapedName}`);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
for (const exportDeclaration of getDeclarationsForSymbol(actualExport)) {
|
|
119
|
+
this.computeTreeForChildren(targetSymbolsSet, exportDeclaration, visitedSymbols);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
} else {
|
|
124
|
+
console.warn(`[computeTreeForChildren] Could not get exports for aliased symbol`);
|
|
125
|
+
}
|
|
126
|
+
if (LOGS) {
|
|
127
|
+
console.log(`[computeTreeForChildren] Module exports: ${moduleExports.map((e) => e.name).join(", ")}`);
|
|
128
|
+
}
|
|
129
|
+
for (const exportSymbol of moduleExports) {
|
|
130
|
+
const actualExport = getActualSymbol(exportSymbol, typeChecker);
|
|
131
|
+
if (!visitedSymbols.has(actualExport)) {
|
|
132
|
+
visitedSymbols.add(actualExport);
|
|
133
|
+
targetSymbolsSet.add(actualExport);
|
|
134
|
+
if (LOGS) {
|
|
135
|
+
console.log(`[computeTreeForChildren] Adding: ${actualExport.escapedName}`);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// Recursively process the export's declarations
|
|
139
|
+
for (const exportDeclaration of getDeclarationsForSymbol(actualExport)) {
|
|
140
|
+
this.computeTreeForChildren(targetSymbolsSet, exportDeclaration, visitedSymbols);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// Handle re-export of namespace imports (import * as x from './module'; export { x })
|
|
149
|
+
// This pattern creates an export that references a namespace import
|
|
150
|
+
if (ts.isExportDeclaration(node) && node.exportClause !== undefined && ts.isNamedExports(node.exportClause)) {
|
|
151
|
+
if (LOGS) {
|
|
152
|
+
console.log(`[computeTreeForChildren] Found ExportDeclaration with named exports`);
|
|
153
|
+
}
|
|
154
|
+
const typeChecker = this.program.getTypeChecker();
|
|
155
|
+
|
|
156
|
+
for (const exportSpecifier of node.exportClause.elements) {
|
|
157
|
+
if (LOGS) {
|
|
158
|
+
console.log(`[computeTreeForChildren] Export specifier: ${exportSpecifier.name.text}`);
|
|
159
|
+
}
|
|
160
|
+
// Get the symbol for this exported name
|
|
161
|
+
const exportedSymbol = typeChecker.getSymbolAtLocation(exportSpecifier.name);
|
|
162
|
+
if (exportedSymbol !== undefined) {
|
|
163
|
+
if (LOGS) {
|
|
164
|
+
console.log(
|
|
165
|
+
`[computeTreeForChildren] Symbol: ${exportedSymbol.name}, flags: ${exportedSymbol.flags}`,
|
|
166
|
+
);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// Check if this is an alias (e.g., from a namespace import)
|
|
170
|
+
if ((exportedSymbol.flags & ts.SymbolFlags.Alias) !== 0) {
|
|
171
|
+
const aliasedSymbol = typeChecker.getAliasedSymbol(exportedSymbol);
|
|
172
|
+
if (LOGS) {
|
|
173
|
+
console.log(
|
|
174
|
+
`[computeTreeForChildren] Aliased to: ${aliasedSymbol.name}, flags: ${aliasedSymbol.flags}`,
|
|
175
|
+
);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// Check if the aliased symbol is a namespace module
|
|
179
|
+
if ((aliasedSymbol.flags & ts.SymbolFlags.NamespaceModule) !== 0) {
|
|
180
|
+
if (LOGS) {
|
|
181
|
+
console.log(`[computeTreeForChildren] Aliased symbol is a namespace module!`);
|
|
182
|
+
}
|
|
183
|
+
// Get all exports from this namespace module
|
|
184
|
+
const moduleExports = typeChecker.getExportsOfModule(aliasedSymbol);
|
|
185
|
+
if (LOGS) {
|
|
186
|
+
console.log(
|
|
187
|
+
`[computeTreeForChildren] Module exports: ${moduleExports.map((e) => e.name).join(", ")}`,
|
|
188
|
+
);
|
|
189
|
+
}
|
|
190
|
+
for (const exportSymbol of moduleExports) {
|
|
191
|
+
const actualExport = getActualSymbol(exportSymbol, typeChecker);
|
|
192
|
+
if (!visitedSymbols.has(actualExport)) {
|
|
193
|
+
visitedSymbols.add(actualExport);
|
|
194
|
+
targetSymbolsSet.add(actualExport);
|
|
195
|
+
if (LOGS) {
|
|
196
|
+
console.log(`[computeTreeForChildren] Adding: ${actualExport.escapedName}`);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// Recursively process the export's declarations
|
|
200
|
+
for (const exportDeclaration of getDeclarationsForSymbol(actualExport)) {
|
|
201
|
+
this.computeTreeForChildren(targetSymbolsSet, exportDeclaration, visitedSymbols);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
// Don't return here - continue to process children
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// it's similar to handling ts.Block node - both Block and variable's initializer are part of _implementation_
|
|
213
|
+
// and we don't care about that implementation at all - we just only need to worry it's definition
|
|
214
|
+
// for functions it is arguments and return type
|
|
215
|
+
// for variables - the type of a variable
|
|
216
|
+
if (ts.isVariableDeclaration(node)) {
|
|
217
|
+
const typeChecker = this.program.getTypeChecker();
|
|
218
|
+
const variableType = typeChecker.getTypeAtLocation(node);
|
|
219
|
+
const variableTypeSymbol = variableType.getSymbol();
|
|
220
|
+
if (variableTypeSymbol !== undefined) {
|
|
221
|
+
targetSymbolsSet.add(variableTypeSymbol);
|
|
222
|
+
if (LOGS) {
|
|
223
|
+
console.log(`[computeTreeForChildren] Added variable type symbol: ${variableTypeSymbol.escapedName}`);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
return;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
ts.forEachChild(node, (childNode: ts.Node) => this.computeTreeForNode(targetSymbolsSet, childNode, visitedSymbols));
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
private computeTreeForNode(targetSymbolsSet: Set<ts.Symbol>, node: ts.Node, visitedSymbols: Set<ts.Symbol>): void {
|
|
234
|
+
if (ts.isVariableStatement(node)) {
|
|
235
|
+
for (const varDeclaration of node.declarationList.declarations) {
|
|
236
|
+
this.computeTreeForNode(targetSymbolsSet, varDeclaration, visitedSymbols);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
if (node.kind === ts.SyntaxKind.JSDoc || ts.isBlock(node)) {
|
|
243
|
+
return;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
if (isClassMember(node) && hasPrivateKeyword(node)) {
|
|
247
|
+
return;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
if (ts.isIdentifier(node)) {
|
|
251
|
+
const symbol = this.getSymbol(node);
|
|
252
|
+
if (symbol === null) {
|
|
253
|
+
return;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
if (visitedSymbols.has(symbol)) {
|
|
257
|
+
return;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
visitedSymbols.add(symbol);
|
|
261
|
+
if (LOGS) {
|
|
262
|
+
console.log(`[computeTreeForNode] Processing identifier: ${node.getText()} -> ${symbol.escapedName}`);
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
for (const childSymbol of splitTransientSymbol(symbol, this.program.getTypeChecker())) {
|
|
266
|
+
targetSymbolsSet.add(childSymbol);
|
|
267
|
+
if (LOGS) {
|
|
268
|
+
console.log(`[computeTreeForNode] Added to set: ${childSymbol.escapedName}`);
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
for (const exportDeclaration of getDeclarationsForSymbol(childSymbol)) {
|
|
272
|
+
this.computeTreeForChildren(targetSymbolsSet, exportDeclaration, visitedSymbols);
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
this.computeTreeForChildren(targetSymbolsSet, node, visitedSymbols);
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
private getSymbol(node: ts.Node): ts.Symbol | null {
|
|
281
|
+
const nodeSymbol = this.program.getTypeChecker().getSymbolAtLocation(node);
|
|
282
|
+
if (nodeSymbol === undefined) {
|
|
283
|
+
return null;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
return this.getActualSymbol(nodeSymbol);
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
private getActualSymbol(symbol: ts.Symbol): ts.Symbol {
|
|
290
|
+
return getActualSymbol(symbol, this.program.getTypeChecker());
|
|
291
|
+
}
|
|
292
|
+
}
|