entkapp 5.5.0 → 5.6.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 +1 -1
- package/bin/cli.js +2 -2
- package/bin/cli.mjs +175 -0
- package/index.cjs +18 -0
- package/index.mjs +51 -0
- package/package.json +7 -6
- package/src/EngineContext.mjs +428 -0
- package/src/Initializer.mjs +82 -0
- package/src/analyzers/CodeSmellAnalyzer.mjs +106 -0
- package/src/analyzers/OxcAnalyzer.mjs +11 -0
- package/src/api/HeadlessAPI.js +31 -16
- package/src/api/HeadlessAPI.mjs +369 -0
- package/src/api/PluginSDK.mjs +135 -0
- package/src/ast/ASTAnalyzer.js +17 -3
- package/src/ast/ASTAnalyzer.mjs +742 -0
- package/src/ast/AdvancedAnalysis.mjs +586 -0
- package/src/ast/BarrelParser.mjs +230 -0
- package/src/ast/DeadCodeDetector.js +7 -5
- package/src/ast/DeadCodeDetector.mjs +92 -0
- package/src/ast/MagicDetector.mjs +203 -0
- package/src/ast/OxcAnalyzer.mjs +188 -0
- package/src/ast/SecretScanner.mjs +374 -0
- package/src/healing/GitSandbox.mjs +82 -0
- package/src/healing/SelfHealer.mjs +48 -0
- package/src/index.js +37 -1
- package/src/index.mjs +1180 -0
- package/src/performance/GraphCache.mjs +108 -0
- package/src/performance/SupplyChainGuard.mjs +92 -0
- package/src/performance/WorkerPool.mjs +132 -0
- package/src/performance/WorkerTaskRunner.mjs +144 -0
- package/src/plugins/BasePlugin.mjs +240 -0
- package/src/plugins/PluginRegistry.mjs +203 -0
- package/src/plugins/ecosystems/BackendServices.mjs +197 -0
- package/src/plugins/ecosystems/GenericPlugins.mjs +142 -0
- package/src/plugins/ecosystems/ModernFrameworks.mjs +162 -0
- package/src/plugins/ecosystems/MorePlugins.mjs +562 -0
- package/src/plugins/ecosystems/NewPlugins.mjs +526 -0
- package/src/plugins/ecosystems/NextJsPlugin.mjs +45 -0
- package/src/plugins/ecosystems/PluginLoader.mjs +193 -0
- package/src/plugins/ecosystems/TypeScriptPlugin.mjs +56 -0
- package/src/plugins/ecosystems/UltimateBundle.mjs +1182 -0
- package/src/refractor/ImpactAnalyzer.mjs +92 -0
- package/src/refractor/SourceRewriter.mjs +86 -0
- package/src/refractor/TransactionManager.mjs +5 -0
- package/src/refractor/TypeIntegrity.mjs +4 -0
- package/src/resolution/BuildOrchestrator.mjs +46 -0
- package/src/resolution/CircularDetector.mjs +91 -0
- package/src/resolution/ConfigGenerator.mjs +83 -0
- package/src/resolution/ConfigLoader.mjs +94 -0
- package/src/resolution/DepencyResolver.mjs +66 -0
- package/src/resolution/DependencyFixer.mjs +88 -0
- package/src/resolution/DependencyProfiler.mjs +286 -0
- package/src/resolution/EntryPointDetector.mjs +134 -0
- package/src/resolution/FrameworkConfigParser.mjs +119 -0
- package/src/resolution/GraphAnalyzer.mjs +80 -0
- package/src/resolution/MigrationAnalyzer.mjs +60 -0
- package/src/resolution/PathMapper.mjs +82 -0
- package/src/resolution/TSConfigLoader.mjs +162 -0
- package/src/resolution/WorkSpaceGraph.mjs +183 -0
- package/src/resolution/WorkspaceDiagnostic.mjs +139 -0
- package/test.js +76 -0
- package/wrangler.toml +6 -0
|
@@ -0,0 +1,742 @@
|
|
|
1
|
+
import ts from 'typescript';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import fs from 'fs';
|
|
4
|
+
|
|
5
|
+
export class ASTAnalyzer {
|
|
6
|
+
constructor(context) {
|
|
7
|
+
this.context = context;
|
|
8
|
+
this.scopeStack = [];
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
getScriptKind(filePath) {
|
|
12
|
+
if (filePath.endsWith('.tsx') || filePath.endsWith('.jsx')) return ts.ScriptKind.TSX;
|
|
13
|
+
if (filePath.endsWith('.mjs') || filePath.endsWith('.mjs') || filePath.endsWith('.cjs')) return ts.ScriptKind.JS;
|
|
14
|
+
return ts.ScriptKind.TS;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
parseFile(filePath, content, fileNode) {
|
|
18
|
+
if (this.context.verbose) {
|
|
19
|
+
console.log(`[AST] Parsing: ${filePath}`);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const sourceFile = ts.createSourceFile(
|
|
23
|
+
filePath,
|
|
24
|
+
content,
|
|
25
|
+
ts.ScriptTarget.Latest,
|
|
26
|
+
true,
|
|
27
|
+
this.getScriptKind(filePath)
|
|
28
|
+
);
|
|
29
|
+
|
|
30
|
+
fileNode.ast = sourceFile;
|
|
31
|
+
this.extractTopLevelJSDocSuppreessions(sourceFile, fileNode);
|
|
32
|
+
|
|
33
|
+
// --- TWO-PASS ANALYSIS STRATEGY ---
|
|
34
|
+
|
|
35
|
+
// Pass 1: Declaration Indexing
|
|
36
|
+
this.currentScope = { symbols: new Map(), parent: null, children: [] };
|
|
37
|
+
this.scopeStack = [this.currentScope];
|
|
38
|
+
this.scopeCounter = 0;
|
|
39
|
+
this.pass = 1;
|
|
40
|
+
try {
|
|
41
|
+
this.walkAST(sourceFile, fileNode, sourceFile);
|
|
42
|
+
} catch (e) {
|
|
43
|
+
if (this.context.verbose) console.error(`[AST] Pass 1 failed for ${filePath}: ${e.message}`);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Pass 2: Reference Tracking
|
|
47
|
+
if (this.scopeStack.length > 0) {
|
|
48
|
+
this.currentScope = this.scopeStack[0];
|
|
49
|
+
this.scopeCounter = 0;
|
|
50
|
+
this.pass = 2;
|
|
51
|
+
try {
|
|
52
|
+
this.walkAST(sourceFile, fileNode, sourceFile);
|
|
53
|
+
} catch (e) {
|
|
54
|
+
if (this.context.verbose) console.error(`[AST] Pass 2 failed for ${filePath}: ${e.message}`);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Pass 3: Side-Effect Detection (Diamond Edition)
|
|
59
|
+
this.detectSideEffects(sourceFile, fileNode);
|
|
60
|
+
|
|
61
|
+
// Pass 4: Barrel Detection
|
|
62
|
+
this.detectBarrelStatus(sourceFile, fileNode);
|
|
63
|
+
|
|
64
|
+
// UPGRADE: Final check for "joke" imports or unused barrel re-exports
|
|
65
|
+
if (fileNode.isBarrel && fileNode.instantiatedIdentifiers.size === 0) {
|
|
66
|
+
fileNode.isPureBarrel = true;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// ADVANCED: Deep reachability analysis for dynamic imports
|
|
70
|
+
if (fileNode.dynamicImports.size > 0) {
|
|
71
|
+
fileNode.dynamicImports.forEach(specifier => {
|
|
72
|
+
const resolved = this.resolveAbsoluteTargetFile(specifier, filePath);
|
|
73
|
+
if (resolved) {
|
|
74
|
+
fileNode.outgoingEdges.add(resolved);
|
|
75
|
+
this.context.getOrCreateNode(resolved).isDynamicEntry = true;
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
this.scopeStack = [];
|
|
81
|
+
this.currentScope = null;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
detectBarrelStatus(sourceFile, fileNode) {
|
|
85
|
+
let totalExportDeclarations = 0;
|
|
86
|
+
let totalTopLevelStatements = sourceFile.statements.length;
|
|
87
|
+
let usesInternalImport = false;
|
|
88
|
+
|
|
89
|
+
if (totalTopLevelStatements === 0) return;
|
|
90
|
+
|
|
91
|
+
// UPGRADE: Barrel Files that re-exports everything may get caught as "OH YOU ENTRY POINT"
|
|
92
|
+
// but it never used the functions it imported.
|
|
93
|
+
// In the entry point, it has to use at least 1 import that it imported,
|
|
94
|
+
// or else its barrel or files that arent importing it as a joke.
|
|
95
|
+
|
|
96
|
+
sourceFile.statements.forEach(node => {
|
|
97
|
+
if (ts.isExportDeclaration(node) && node.moduleSpecifier) {
|
|
98
|
+
totalExportDeclarations++;
|
|
99
|
+
} else if (ts.isImportDeclaration(node)) {
|
|
100
|
+
// Imports are common in barrels, we don't count them against the ratio
|
|
101
|
+
totalTopLevelStatements--;
|
|
102
|
+
} else if (ts.isExportAssignment(node)) {
|
|
103
|
+
totalExportDeclarations++;
|
|
104
|
+
}
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
// Check if any imported symbol is actually used in the file (Pass 2 already does this via instantiatedIdentifiers)
|
|
108
|
+
// However, for Barrel detection, we want to know if it's ONLY re-exporting.
|
|
109
|
+
|
|
110
|
+
const barrelRatio = totalExportDeclarations / Math.max(1, totalTopLevelStatements);
|
|
111
|
+
|
|
112
|
+
// If it's mostly exports and imports, it's likely a barrel.
|
|
113
|
+
if (barrelRatio > 0.8) {
|
|
114
|
+
fileNode.isBarrel = true;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
detectSideEffects(sourceFile, fileNode) {
|
|
119
|
+
let hasSideEffects = false;
|
|
120
|
+
|
|
121
|
+
const checkNode = (node) => {
|
|
122
|
+
if (hasSideEffects) return;
|
|
123
|
+
|
|
124
|
+
// 1. Top-level Call Expressions (excluding declarations)
|
|
125
|
+
if (ts.isExpressionStatement(node)) {
|
|
126
|
+
const expr = node.expression;
|
|
127
|
+
if (ts.isCallExpression(expr) || ts.isAwaitExpression(expr)) {
|
|
128
|
+
// Check for specific bootstrap patterns
|
|
129
|
+
const callText = expr.getText(sourceFile);
|
|
130
|
+
const bootstrapTriggers = [
|
|
131
|
+
'app.listen', 'http.createServer', 'ReactDOM.render',
|
|
132
|
+
'process.on', 'process.exit', 'console.log',
|
|
133
|
+
'setInterval', 'setTimeout', 'new Vue', 'new App'
|
|
134
|
+
];
|
|
135
|
+
|
|
136
|
+
if (bootstrapTriggers.some(trigger => callText.includes(trigger))) {
|
|
137
|
+
hasSideEffects = true;
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// Any top-level call that isn't just a simple utility might be an entry
|
|
142
|
+
// We exclude common non-executing calls if necessary, but generally,
|
|
143
|
+
// a top-level call in a non-export-only file is a strong signal.
|
|
144
|
+
hasSideEffects = true;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// 2. Node.js direct execution check: if (require.main === module)
|
|
149
|
+
if (ts.isIfStatement(node)) {
|
|
150
|
+
const cond = node.expression.getText(sourceFile);
|
|
151
|
+
if (cond.includes('require.main === module')) {
|
|
152
|
+
hasSideEffects = true;
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// 3. IIFE (Immediately Invoked Function Expression)
|
|
158
|
+
if (ts.isExpressionStatement(node) && ts.isCallExpression(node.expression)) {
|
|
159
|
+
const func = node.expression.expression;
|
|
160
|
+
if (ts.isParenthesizedExpression(func) || ts.isFunctionExpression(func) || ts.isArrowFunction(func)) {
|
|
161
|
+
hasSideEffects = true;
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
ts.forEachChild(sourceFile, checkNode);
|
|
168
|
+
fileNode.hasSideEffects = hasSideEffects;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
pushScope() {
|
|
172
|
+
if (this.pass === 1) {
|
|
173
|
+
const newScope = { symbols: new Map(), parent: this.currentScope, children: [] };
|
|
174
|
+
if (this.currentScope) {
|
|
175
|
+
this.currentScope.children.push(newScope);
|
|
176
|
+
}
|
|
177
|
+
this.scopeStack.push(newScope);
|
|
178
|
+
this.currentScope = newScope;
|
|
179
|
+
} else {
|
|
180
|
+
// In Pass 2, we follow the pre-built children
|
|
181
|
+
if (this.currentScope && this.currentScope.children) {
|
|
182
|
+
const nextScope = this.currentScope.children[this.scopeCounter++];
|
|
183
|
+
if (nextScope) {
|
|
184
|
+
this.scopeStack.push(nextScope);
|
|
185
|
+
this.currentScope = nextScope;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
popScope() {
|
|
192
|
+
this.scopeStack.pop();
|
|
193
|
+
this.currentScope = this.scopeStack[this.scopeStack.length - 1];
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
addDeclaredSymbol(name, node, sourceFile) {
|
|
197
|
+
if (this.currentScope) {
|
|
198
|
+
const loc = sourceFile.getLineAndCharacterOfPosition(node.getStart(sourceFile));
|
|
199
|
+
this.currentScope.symbols.set(name, { node, line: loc.line + 1, column: loc.character + 1 });
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
walkAST(node, fileNode, sourceFile) {
|
|
204
|
+
const isScopeNode = ts.isBlock(node) || ts.isFunctionDeclaration(node) || ts.isClassDeclaration(node) || ts.isModuleDeclaration(node) || ts.isArrowFunction(node);
|
|
205
|
+
|
|
206
|
+
let previousCounter = 0;
|
|
207
|
+
if (isScopeNode) {
|
|
208
|
+
if (this.pass === 2) previousCounter = this.scopeCounter;
|
|
209
|
+
this.scopeCounter = 0;
|
|
210
|
+
this.pushScope();
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
if (this.pass === 1) {
|
|
214
|
+
// --- PASS 1: DECLARATION INDEXING ---
|
|
215
|
+
switch (node.kind) {
|
|
216
|
+
case ts.SyntaxKind.ImportDeclaration:
|
|
217
|
+
this.handleImportDeclaration(node, fileNode, sourceFile);
|
|
218
|
+
break;
|
|
219
|
+
case ts.SyntaxKind.ExportAssignment:
|
|
220
|
+
fileNode.internalExports.set('default', { type: 'default', start: node.getStart(sourceFile), end: node.getEnd() });
|
|
221
|
+
break;
|
|
222
|
+
case ts.SyntaxKind.VariableStatement:
|
|
223
|
+
this.handleVariableStatement(node, fileNode, sourceFile);
|
|
224
|
+
break;
|
|
225
|
+
case ts.SyntaxKind.FunctionDeclaration:
|
|
226
|
+
case ts.SyntaxKind.ClassDeclaration:
|
|
227
|
+
case ts.SyntaxKind.InterfaceDeclaration:
|
|
228
|
+
case ts.SyntaxKind.TypeAliasDeclaration:
|
|
229
|
+
case ts.SyntaxKind.EnumDeclaration:
|
|
230
|
+
case ts.SyntaxKind.ModuleDeclaration:
|
|
231
|
+
this.handleNamedDeclaration(node, fileNode, sourceFile);
|
|
232
|
+
break;
|
|
233
|
+
case ts.SyntaxKind.ExpressionStatement:
|
|
234
|
+
this.handleAssignmentExpressionPass1(node, fileNode, sourceFile);
|
|
235
|
+
break;
|
|
236
|
+
case ts.SyntaxKind.ExportDeclaration:
|
|
237
|
+
this.handleExportDeclaration(node, fileNode, sourceFile);
|
|
238
|
+
break;
|
|
239
|
+
}
|
|
240
|
+
} else {
|
|
241
|
+
// --- PASS 2: REFERENCE TRACKING ---
|
|
242
|
+
switch (node.kind) {
|
|
243
|
+
case ts.SyntaxKind.Identifier:
|
|
244
|
+
if (!this.isDeclarationName(node)) {
|
|
245
|
+
// Scope-Aware Check: Only track if NOT shadowed by a local variable
|
|
246
|
+
if (!this.isLocalShadowing(node.text)) {
|
|
247
|
+
fileNode.instantiatedIdentifiers.add(node.text);
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
break;
|
|
251
|
+
|
|
252
|
+
case ts.SyntaxKind.CallExpression:
|
|
253
|
+
// FIX: Führe zuerst die generische Aufrufanalyse aus, die vorher blockiert war
|
|
254
|
+
this.handleCallExpression(node, fileNode, sourceFile);
|
|
255
|
+
|
|
256
|
+
if (node.expression.kind === ts.SyntaxKind.ImportKeyword) {
|
|
257
|
+
const arg = node.arguments[0];
|
|
258
|
+
if (arg) {
|
|
259
|
+
if (ts.isStringLiteral(arg)) {
|
|
260
|
+
const specifier = arg.text;
|
|
261
|
+
fileNode.dynamicImports.add(specifier);
|
|
262
|
+
fileNode.explicitImports.add(specifier);
|
|
263
|
+
} else {
|
|
264
|
+
// UPGRADE: Handle Dynamic Imports with variables/expressions
|
|
265
|
+
fileNode.hasCalculatedDynamicImports = true;
|
|
266
|
+
const exprText = arg.getText(sourceFile);
|
|
267
|
+
if (!fileNode.calculatedDynamicImports || Array.isArray(fileNode.calculatedDynamicImports)) {
|
|
268
|
+
fileNode.calculatedDynamicImports = new Set();
|
|
269
|
+
}
|
|
270
|
+
fileNode.calculatedDynamicImports.add(exprText);
|
|
271
|
+
fileNode.dynamicImports.add('__DYNAMIC_PATTERN__');
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
} else if (ts.isIdentifier(node.expression) && node.expression.text === 'require') {
|
|
275
|
+
// UPGRADE: CommonJS require() detection
|
|
276
|
+
const arg = node.arguments[0];
|
|
277
|
+
if (arg && ts.isStringLiteral(arg)) {
|
|
278
|
+
const specifier = arg.text;
|
|
279
|
+
fileNode.explicitImports.add(specifier);
|
|
280
|
+
fileNode.importedSymbols.add(`${specifier}:*`);
|
|
281
|
+
if (!specifier.startsWith('.') && !specifier.startsWith('/')) {
|
|
282
|
+
fileNode.externalPackageUsage.add(this._extractPackageName(specifier));
|
|
283
|
+
}
|
|
284
|
+
} else if (arg && ts.isTemplateExpression(arg)) {
|
|
285
|
+
// Dynamic require with template literal
|
|
286
|
+
fileNode.hasCalculatedDynamicImports = true;
|
|
287
|
+
const exprText = arg.getText(sourceFile);
|
|
288
|
+
|
|
289
|
+
// FIX: Typ-Integrität als Set erzwingen (kein Array [])
|
|
290
|
+
if (!fileNode.calculatedDynamicImports || Array.isArray(fileNode.calculatedDynamicImports)) {
|
|
291
|
+
fileNode.calculatedDynamicImports = new Set();
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
// FIX: .add() statt .push() verwenden, um Absturz in Pass 2 zu verhindern
|
|
295
|
+
fileNode.calculatedDynamicImports.add({
|
|
296
|
+
kind: 'DynamicRequire',
|
|
297
|
+
expression: exprText,
|
|
298
|
+
start: arg.getStart(sourceFile)
|
|
299
|
+
});
|
|
300
|
+
fileNode.dynamicImports.add('__DYNAMIC_PATTERN__');
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
break;
|
|
304
|
+
|
|
305
|
+
case ts.SyntaxKind.PropertyAccessExpression:
|
|
306
|
+
{
|
|
307
|
+
const chain = node.getText(sourceFile);
|
|
308
|
+
fileNode.propertyAccessChains.add(chain);
|
|
309
|
+
if (node.name && ts.isIdentifier(node.name)) {
|
|
310
|
+
fileNode.instantiatedIdentifiers.add(node.name.text);
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
break;
|
|
314
|
+
|
|
315
|
+
case ts.SyntaxKind.JsxElement:
|
|
316
|
+
case ts.SyntaxKind.JsxSelfClosingElement:
|
|
317
|
+
this.handleJsxElement(node, fileNode, sourceFile);
|
|
318
|
+
break;
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
ts.forEachChild(node, child => this.walkAST(child, fileNode, sourceFile));
|
|
323
|
+
|
|
324
|
+
if (isScopeNode) {
|
|
325
|
+
this.popScope();
|
|
326
|
+
if (this.pass === 2) this.scopeCounter = previousCounter + 1;
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
isLocalShadowing(name) {
|
|
331
|
+
let scope = this.currentScope;
|
|
332
|
+
// We check all scopes up to the root.
|
|
333
|
+
// If the name is found in a scope that is NOT the root scope, it's definitely shadowing.
|
|
334
|
+
// If it's found in the root scope, it's only shadowing if it's NOT an export.
|
|
335
|
+
while (scope) {
|
|
336
|
+
if (scope.symbols.has(name)) {
|
|
337
|
+
// If we are in the root scope and found the symbol, it's the global declaration.
|
|
338
|
+
// We only consider it shadowing if we are currently in a deeper scope.
|
|
339
|
+
if (scope.parent === null) {
|
|
340
|
+
return this.currentScope !== scope;
|
|
341
|
+
}
|
|
342
|
+
return true;
|
|
343
|
+
}
|
|
344
|
+
scope = scope.parent;
|
|
345
|
+
}
|
|
346
|
+
return false;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
isDeclarationName(node) {
|
|
350
|
+
const parent = node.parent;
|
|
351
|
+
if (!parent) return false;
|
|
352
|
+
if (ts.isVariableDeclaration(parent) && parent.name === node) return true;
|
|
353
|
+
if (ts.isFunctionDeclaration(parent) && parent.name === node) return true;
|
|
354
|
+
if (ts.isClassDeclaration(parent) && parent.name === node) return true;
|
|
355
|
+
if (ts.isInterfaceDeclaration(parent) && parent.name === node) return true;
|
|
356
|
+
if (ts.isEnumDeclaration(parent) && parent.name === node) return true;
|
|
357
|
+
if (ts.isTypeAliasDeclaration(parent) && parent.name === node) return true;
|
|
358
|
+
if (ts.isImportSpecifier(parent) && parent.name === node) return true;
|
|
359
|
+
if (ts.isExportSpecifier(parent) && parent.name === node) return true;
|
|
360
|
+
return false;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
handleImportDeclaration(node, fileNode, sourceFile) {
|
|
364
|
+
if (node.moduleSpecifier && ts.isStringLiteral(node.moduleSpecifier)) {
|
|
365
|
+
const specifier = node.moduleSpecifier.text;
|
|
366
|
+
fileNode.explicitImports.add(specifier);
|
|
367
|
+
|
|
368
|
+
if (!specifier.startsWith('.') && !specifier.startsWith('/')) {
|
|
369
|
+
fileNode.externalPackageUsage.add(this._extractPackageName(specifier));
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
if (this.context.workspaceGraph && typeof this.context.workspaceGraph.auditImportSpecifier === 'function') {
|
|
373
|
+
this.context.workspaceGraph.auditImportSpecifier(specifier, sourceFile.fileName);
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
const targetFile = this.resolveAbsoluteTargetFile(specifier, sourceFile.fileName);
|
|
377
|
+
|
|
378
|
+
if (node.importClause) {
|
|
379
|
+
if (!this.context.importUsageRegistry) this.context.importUsageRegistry = new Set();
|
|
380
|
+
|
|
381
|
+
if (node.importClause.name) {
|
|
382
|
+
fileNode.importedSymbols.add(`${specifier}:default`);
|
|
383
|
+
if (targetFile) this.context.importUsageRegistry.add(`${targetFile}:default`);
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
if (node.importClause.namedBindings) {
|
|
387
|
+
if (ts.isNamespaceImport(node.importClause.namedBindings)) {
|
|
388
|
+
fileNode.importedSymbols.add(`${specifier}:*`);
|
|
389
|
+
if (targetFile) this.context.importUsageRegistry.add(`${targetFile}:*`);
|
|
390
|
+
} else if (ts.isNamedImports(node.importClause.namedBindings)) {
|
|
391
|
+
node.importClause.namedBindings.elements.forEach(element => {
|
|
392
|
+
const importedName = element.propertyName ? element.propertyName.text : element.name.text;
|
|
393
|
+
fileNode.importedSymbols.add(`${specifier}:${importedName}`);
|
|
394
|
+
if (targetFile) this.context.importUsageRegistry.add(`${targetFile}:${importedName}`);
|
|
395
|
+
});
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
} else {
|
|
399
|
+
// Side-Effect Import (import '...')
|
|
400
|
+
// FIX: Mark the target file as used even if no symbols are imported
|
|
401
|
+
if (targetFile) {
|
|
402
|
+
if (!this.context.importUsageRegistry) this.context.importUsageRegistry = new Set();
|
|
403
|
+
this.context.importUsageRegistry.add(`${targetFile}:*`);
|
|
404
|
+
fileNode.importedSymbols.add(`${specifier}:*`);
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
resolveAbsoluteTargetFile(spec, sourceFilePath) {
|
|
411
|
+
if (this.context.workspaceGraph && typeof this.context.workspaceGraph.isLocalWorkspaceSpecifier === 'function') {
|
|
412
|
+
if (this.context.workspaceGraph.isLocalWorkspaceSpecifier(spec)) {
|
|
413
|
+
const match = this.context.workspaceGraph.getWorkspacePackageMatch(spec);
|
|
414
|
+
if (match && match.entryPoints && match.entryPoints.length > 0) return match.entryPoints[0];
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
if (spec.startsWith('.')) {
|
|
418
|
+
let resolved = path.resolve(path.dirname(sourceFilePath), spec);
|
|
419
|
+
const extensions = ['.ts', '.tsx', '.mjs', '.jsx', '.mjs', '.cjs'];
|
|
420
|
+
if (!path.extname(resolved)) {
|
|
421
|
+
for (const ext of extensions) {
|
|
422
|
+
if (fs.existsSync(resolved + ext)) return resolved + ext;
|
|
423
|
+
}
|
|
424
|
+
// Check index files
|
|
425
|
+
for (const ext of extensions) {
|
|
426
|
+
if (fs.existsSync(path.join(resolved, 'index' + ext))) return path.join(resolved, 'index' + ext);
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
return resolved;
|
|
430
|
+
}
|
|
431
|
+
return null;
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
handleExportDeclaration(node, fileNode, sourceFile) {
|
|
435
|
+
const specifier = node.moduleSpecifier && ts.isStringLiteral(node.moduleSpecifier) ? node.moduleSpecifier.text : null;
|
|
436
|
+
|
|
437
|
+
if (specifier) {
|
|
438
|
+
fileNode.explicitImports.add(specifier);
|
|
439
|
+
if (!specifier.startsWith('.') && !specifier.startsWith('/')) {
|
|
440
|
+
fileNode.externalPackageUsage.add(this._extractPackageName(specifier));
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
if (!node.exportClause) {
|
|
444
|
+
fileNode.internalExports.set('*', { type: 're-export-all', source: specifier });
|
|
445
|
+
fileNode.importedSymbols.add(`${specifier}:*`);
|
|
446
|
+
} else if (ts.isNamespaceExport(node.exportClause)) {
|
|
447
|
+
const name = node.exportClause.name.text;
|
|
448
|
+
fileNode.internalExports.set(name, { type: 're-export-namespace', source: specifier, originalName: '*', start: node.getStart(sourceFile), end: node.getEnd() });
|
|
449
|
+
fileNode.importedSymbols.add(`${specifier}:*`);
|
|
450
|
+
} else if (ts.isNamedExports(node.exportClause)) {
|
|
451
|
+
node.exportClause.elements.forEach(element => {
|
|
452
|
+
const originalName = element.propertyName ? element.propertyName.text : element.name.text;
|
|
453
|
+
const exportedName = element.name.text;
|
|
454
|
+
fileNode.internalExports.set(exportedName, { type: 're-export', source: specifier, originalName, start: element.getStart(sourceFile), end: element.getEnd() });
|
|
455
|
+
fileNode.importedSymbols.add(`${specifier}:${originalName}`);
|
|
456
|
+
});
|
|
457
|
+
}
|
|
458
|
+
} else if (node.exportClause && ts.isNamedExports(node.exportClause)) {
|
|
459
|
+
node.exportClause.elements.forEach(element => {
|
|
460
|
+
const localName = element.propertyName ? element.propertyName.text : element.name.text;
|
|
461
|
+
const exportedName = element.name.text;
|
|
462
|
+
fileNode.internalExports.set(exportedName, { type: 'export', originalName: localName, start: element.getStart(sourceFile), end: element.getEnd() });
|
|
463
|
+
});
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
/**
|
|
468
|
+
* UPGRADE 6: Constant Folding for Computed Keys.
|
|
469
|
+
* Resolves the value of constants used as export keys.
|
|
470
|
+
*/
|
|
471
|
+
resolveConstantValue(node, sourceFile) {
|
|
472
|
+
if (ts.isIdentifier(node)) {
|
|
473
|
+
// Look for the symbol in the current scope chain
|
|
474
|
+
let scope = this.currentScope;
|
|
475
|
+
while (scope) {
|
|
476
|
+
if (scope.symbols.has(node.text)) {
|
|
477
|
+
const sym = scope.symbols.get(node.text);
|
|
478
|
+
if (sym.node && ts.isVariableDeclaration(sym.node) && sym.node.initializer) {
|
|
479
|
+
if (ts.isStringLiteral(sym.node.initializer)) {
|
|
480
|
+
return sym.node.initializer.text;
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
scope = scope.parent;
|
|
485
|
+
}
|
|
486
|
+
} else if (ts.isStringLiteral(node)) {
|
|
487
|
+
return node.text;
|
|
488
|
+
}
|
|
489
|
+
return null;
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
handleNamedDeclaration(node, fileNode, sourceFile) {
|
|
493
|
+
if (this.hasExportModifier(node)) {
|
|
494
|
+
const isDefault = node.modifiers?.some(m => m.kind === ts.SyntaxKind.DefaultKeyword);
|
|
495
|
+
const name = isDefault ? 'default' : (node.name?.text || 'anonymous');
|
|
496
|
+
|
|
497
|
+
if (!this.context.exportRegistry) this.context.exportRegistry = new Map();
|
|
498
|
+
const currentFile = sourceFile.fileName;
|
|
499
|
+
if (!this.context.exportRegistry.has(currentFile)) this.context.exportRegistry.set(currentFile, new Set());
|
|
500
|
+
if (name !== 'anonymous') this.context.exportRegistry.get(currentFile).add(name);
|
|
501
|
+
|
|
502
|
+
const exportInfo = {
|
|
503
|
+
type: ts.SyntaxKind[node.kind].toLowerCase().replace('declaration', ''),
|
|
504
|
+
start: node.getStart(sourceFile),
|
|
505
|
+
end: node.getEnd()
|
|
506
|
+
};
|
|
507
|
+
|
|
508
|
+
fileNode.internalExports.set(name, exportInfo);
|
|
509
|
+
|
|
510
|
+
// Extract members from object literals in variable exports
|
|
511
|
+
if (ts.isVariableDeclaration(node) && node.initializer && ts.isObjectLiteralExpression(node.initializer)) {
|
|
512
|
+
exportInfo.members = node.initializer.properties
|
|
513
|
+
.map(p => {
|
|
514
|
+
let mName = null;
|
|
515
|
+
if (p.name && ts.isIdentifier(p.name)) {
|
|
516
|
+
mName = p.name.text;
|
|
517
|
+
} else if (p.name && ts.isComputedPropertyName(p.name)) {
|
|
518
|
+
// UPGRADE 6: Constant Folding for Computed Keys
|
|
519
|
+
mName = this.resolveConstantValue(p.name.expression, sourceFile);
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
if (!mName) return null;
|
|
523
|
+
|
|
524
|
+
const mLoc = sourceFile.getLineAndCharacterOfPosition(p.getStart(sourceFile));
|
|
525
|
+
fileNode.symbolSourceLocations.set(`${name}.${mName}`, { line: mLoc.line + 1, column: mLoc.character + 1 });
|
|
526
|
+
fileNode.instantiatedIdentifiers.add(mName);
|
|
527
|
+
return {
|
|
528
|
+
name: mName,
|
|
529
|
+
type: 'property',
|
|
530
|
+
isPublic: true, // Object properties are usually public
|
|
531
|
+
start: p.getStart(sourceFile),
|
|
532
|
+
end: p.getEnd()
|
|
533
|
+
};
|
|
534
|
+
})
|
|
535
|
+
.filter(Boolean);
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
if (ts.isEnumDeclaration(node)) {
|
|
539
|
+
exportInfo.members = node.members.map(m => ({
|
|
540
|
+
name: m.name.getText(sourceFile),
|
|
541
|
+
type: 'enumMember',
|
|
542
|
+
start: m.getStart(sourceFile),
|
|
543
|
+
end: m.getEnd()
|
|
544
|
+
}));
|
|
545
|
+
} else if (ts.isClassDeclaration(node) || ts.isInterfaceDeclaration(node)) {
|
|
546
|
+
exportInfo.members = node.members
|
|
547
|
+
.filter(m => m.name)
|
|
548
|
+
.map(m => {
|
|
549
|
+
const mName = m.name.getText(sourceFile);
|
|
550
|
+
const mLoc = sourceFile.getLineAndCharacterOfPosition(m.getStart(sourceFile));
|
|
551
|
+
fileNode.symbolSourceLocations.set(`${name}.${mName}`, { line: mLoc.line + 1, column: mLoc.character + 1 });
|
|
552
|
+
fileNode.instantiatedIdentifiers.add(mName);
|
|
553
|
+
const isPrivate = m.modifiers?.some(mod => mod.kind === ts.SyntaxKind.PrivateKeyword);
|
|
554
|
+
return {
|
|
555
|
+
name: mName,
|
|
556
|
+
type: ts.SyntaxKind[m.kind].toLowerCase(),
|
|
557
|
+
isPublic: !isPrivate,
|
|
558
|
+
start: m.getStart(sourceFile),
|
|
559
|
+
end: m.getEnd()
|
|
560
|
+
};
|
|
561
|
+
});
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
const loc = sourceFile.getLineAndCharacterOfPosition(node.getStart(sourceFile));
|
|
565
|
+
fileNode.symbolSourceLocations.set(name, { line: loc.line + 1, column: loc.character + 1 });
|
|
566
|
+
this.addDeclaredSymbol(name, node, sourceFile);
|
|
567
|
+
} else if (node.name && ts.isIdentifier(node.name)) {
|
|
568
|
+
this.addDeclaredSymbol(node.name.text, node, sourceFile);
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
handleVariableStatement(node, fileNode, sourceFile) {
|
|
573
|
+
if (this.hasExportModifier(node)) {
|
|
574
|
+
node.declarationList.declarations.forEach(decl => {
|
|
575
|
+
if (decl.name && ts.isIdentifier(decl.name)) {
|
|
576
|
+
this.handleNamedDeclaration(decl, fileNode, sourceFile);
|
|
577
|
+
} else if (decl.name && ts.isObjectBindingPattern(decl.name)) {
|
|
578
|
+
decl.name.elements.forEach(element => {
|
|
579
|
+
if(element.name && ts.isIdentifier(element.name)) {
|
|
580
|
+
const name = element.name.text;
|
|
581
|
+
fileNode.internalExports.set(name, { type: 'variable', start: node.getStart(sourceFile), end: node.getEnd() });
|
|
582
|
+
const loc = sourceFile.getLineAndCharacterOfPosition(element.getStart(sourceFile));
|
|
583
|
+
fileNode.symbolSourceLocations.set(name, { line: loc.line + 1, column: loc.character + 1 });
|
|
584
|
+
this.addDeclaredSymbol(name, element, sourceFile);
|
|
585
|
+
}
|
|
586
|
+
});
|
|
587
|
+
}
|
|
588
|
+
});
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
handleCallExpression(node, fileNode, sourceFile) {
|
|
593
|
+
if (this.context.verbose) console.log(`[AST-DEBUG] Checking CallExpression in ${fileNode.filePath}`);
|
|
594
|
+
// Dynamic import(): import('./module')
|
|
595
|
+
if (node.expression.kind === ts.SyntaxKind.ImportKeyword) {
|
|
596
|
+
const arg = node.arguments[0];
|
|
597
|
+
if (arg) {
|
|
598
|
+
if (ts.isStringLiteral(arg)) {
|
|
599
|
+
const specifier = arg.text;
|
|
600
|
+
fileNode.explicitImports.add(specifier);
|
|
601
|
+
fileNode.dynamicImports.add(specifier);
|
|
602
|
+
fileNode.importedSymbols.add(`${specifier}:*`);
|
|
603
|
+
if (!specifier.startsWith('.') && !specifier.startsWith('/')) {
|
|
604
|
+
fileNode.externalPackageUsage.add(this._extractPackageName(specifier));
|
|
605
|
+
}
|
|
606
|
+
} else {
|
|
607
|
+
// Non-literal dynamic import
|
|
608
|
+
if (!fileNode.calculatedDynamicImports || Array.isArray(fileNode.calculatedDynamicImports)) {
|
|
609
|
+
fileNode.calculatedDynamicImports = new Set();
|
|
610
|
+
}
|
|
611
|
+
fileNode.calculatedDynamicImports.add({ kind: ts.SyntaxKind[arg.kind], start: arg.getStart(sourceFile) });
|
|
612
|
+
|
|
613
|
+
// UPGRADE 2: Try to perform Glob-Analysis on Template Strings
|
|
614
|
+
this.handlePartialEvaluation(arg, fileNode, sourceFile);
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
} else if (ts.isIdentifier(node.expression) && node.expression.text === 'require') {
|
|
618
|
+
if (this.context.verbose) console.log(`[AST-DEBUG] Found require() call in ${fileNode.filePath}`);
|
|
619
|
+
const arg = node.arguments[0];
|
|
620
|
+
if (arg && ts.isStringLiteral(arg)) {
|
|
621
|
+
const specifier = arg.text;
|
|
622
|
+
if (this.context.verbose) console.log(`[AST-DEBUG] Added import: ${specifier}`);
|
|
623
|
+
fileNode.explicitImports.add(specifier);
|
|
624
|
+
|
|
625
|
+
// UPGRADE: Also track package usage for unlisted dependency detection
|
|
626
|
+
if (!specifier.startsWith('.') && !specifier.startsWith('/')) {
|
|
627
|
+
fileNode.externalPackageUsage.add(this._extractPackageName(specifier));
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
/**
|
|
634
|
+
* UPGRADE 2: Partial Evaluation for dynamic imports.
|
|
635
|
+
* Detects pattern like import(`./dynamic/${name}.js`) and marks the directory as potentially used.
|
|
636
|
+
*/
|
|
637
|
+
handlePartialEvaluation(node, fileNode, sourceFile) {
|
|
638
|
+
if (ts.isTemplateExpression(node)) {
|
|
639
|
+
const head = node.head.text;
|
|
640
|
+
if (head.startsWith('./') || head.startsWith('../')) {
|
|
641
|
+
// FIX: Typ-Integrität als Set erzwingen, kein Array!
|
|
642
|
+
if (!fileNode.globImports || Array.isArray(fileNode.globImports)) {
|
|
643
|
+
fileNode.globImports = new Set();
|
|
644
|
+
}
|
|
645
|
+
const dirPath = path.dirname(head);
|
|
646
|
+
if (dirPath && dirPath !== '.') {
|
|
647
|
+
fileNode.globImports.add(dirPath); // Jetzt klappt .add() garantiert!
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
handleJsxElement(node, fileNode, sourceFile) {
|
|
654
|
+
const tagName = ts.isJsxElement(node) ? node.openingElement.tagName : node.tagName;
|
|
655
|
+
if (ts.isIdentifier(tagName)) {
|
|
656
|
+
fileNode.jsxComponents.add(tagName.text);
|
|
657
|
+
fileNode.instantiatedIdentifiers.add(tagName.text);
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
handleDecorator(node, fileNode, sourceFile) {
|
|
662
|
+
const getDecoratorName = (expr) => {
|
|
663
|
+
if (ts.isIdentifier(expr)) return expr.text;
|
|
664
|
+
if (ts.isCallExpression(expr) && ts.isIdentifier(expr.expression)) return expr.expression.text;
|
|
665
|
+
if (ts.isCallExpression(expr) && ts.isPropertyAccessExpression(expr.expression)) return expr.expression.name.text;
|
|
666
|
+
return 'unknown';
|
|
667
|
+
};
|
|
668
|
+
const decoratorName = getDecoratorName(node.expression);
|
|
669
|
+
fileNode.decorators.add(decoratorName);
|
|
670
|
+
fileNode.instantiatedIdentifiers.add(decoratorName);
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
hasExportModifier(node) {
|
|
674
|
+
if (ts.isVariableDeclaration(node)) {
|
|
675
|
+
return this.hasExportModifier(node.parent.parent);
|
|
676
|
+
}
|
|
677
|
+
return node.modifiers?.some(m => m.kind === ts.SyntaxKind.ExportKeyword) ?? false;
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
extractTopLevelJSDocSuppreessions(sourceFile, fileNode) {
|
|
681
|
+
const fullText = sourceFile.text;
|
|
682
|
+
const commentRegex = /\/\*\*?[\s\S]*?\*\/|\/\/.*/g;
|
|
683
|
+
let match;
|
|
684
|
+
while ((match = commentRegex.exec(fullText)) !== null) {
|
|
685
|
+
const suppressMatches = match[0].match(/@scaffold-suppress\s+([a-zA-Z0-9_\-*:]+)/g);
|
|
686
|
+
if (suppressMatches) {
|
|
687
|
+
suppressMatches.forEach(m => fileNode.localSuppressedRules.add(m.replace('@scaffold-suppress', '').trim()));
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
_extractPackageName(specifier) {
|
|
693
|
+
if (specifier.startsWith('@')) {
|
|
694
|
+
const parts = specifier.split('/');
|
|
695
|
+
return parts.length >= 2 ? `${parts[0]}/${parts[1]}` : specifier;
|
|
696
|
+
}
|
|
697
|
+
return specifier.split('/')[0];
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
// UPGRADE: CommonJS module.exports / exports detection
|
|
701
|
+
handleAssignmentExpressionPass1(node, fileNode, sourceFile) {
|
|
702
|
+
if (ts.isExpressionStatement(node)) {
|
|
703
|
+
const expr = node.expression;
|
|
704
|
+
if (ts.isBinaryExpression(expr) && expr.operatorToken.kind === ts.SyntaxKind.EqualsToken) {
|
|
705
|
+
const left = expr.left;
|
|
706
|
+
const right = expr.right;
|
|
707
|
+
|
|
708
|
+
if (ts.isPropertyAccessExpression(left)) {
|
|
709
|
+
const obj = left.expression;
|
|
710
|
+
const prop = left.name;
|
|
711
|
+
|
|
712
|
+
// module.exports = ...
|
|
713
|
+
if (ts.isIdentifier(obj) && obj.text === 'module' &&
|
|
714
|
+
ts.isIdentifier(prop) && prop.text === 'exports') {
|
|
715
|
+
fileNode.internalExports.set('default', { type: 'default', start: left.getStart(sourceFile), end: left.getEnd() });
|
|
716
|
+
|
|
717
|
+
// Handle module.exports = { a, b }
|
|
718
|
+
if (ts.isObjectLiteralExpression(right)) {
|
|
719
|
+
right.properties.forEach(p => {
|
|
720
|
+
if (ts.isPropertyAssignment(p) && ts.isIdentifier(p.name)) {
|
|
721
|
+
fileNode.internalExports.set(p.name.text, { type: 'variable', start: p.getStart(sourceFile), end: p.getEnd() });
|
|
722
|
+
} else if (ts.isShorthandPropertyAssignment(p)) {
|
|
723
|
+
fileNode.internalExports.set(p.name.text, { type: 'variable', start: p.getStart(sourceFile), end: p.getEnd() });
|
|
724
|
+
}
|
|
725
|
+
});
|
|
726
|
+
}
|
|
727
|
+
}
|
|
728
|
+
// exports.name = ...
|
|
729
|
+
else if (ts.isIdentifier(obj) && obj.text === 'exports') {
|
|
730
|
+
fileNode.internalExports.set(prop.text, { type: 'variable', start: left.getStart(sourceFile), end: left.getEnd() });
|
|
731
|
+
}
|
|
732
|
+
// module.exports.name = ...
|
|
733
|
+
else if (ts.isPropertyAccessExpression(obj) &&
|
|
734
|
+
ts.isIdentifier(obj.expression) && obj.expression.text === 'module' &&
|
|
735
|
+
ts.isIdentifier(obj.name) && obj.name.text === 'exports') {
|
|
736
|
+
fileNode.internalExports.set(prop.text, { type: 'variable', start: left.getStart(sourceFile), end: left.getEnd() });
|
|
737
|
+
}
|
|
738
|
+
}
|
|
739
|
+
}
|
|
740
|
+
}
|
|
741
|
+
}
|
|
742
|
+
}
|