@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.
@@ -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
+ }