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.
Files changed (62) hide show
  1. package/README.md +1 -1
  2. package/bin/cli.js +2 -2
  3. package/bin/cli.mjs +175 -0
  4. package/index.cjs +18 -0
  5. package/index.mjs +51 -0
  6. package/package.json +7 -6
  7. package/src/EngineContext.mjs +428 -0
  8. package/src/Initializer.mjs +82 -0
  9. package/src/analyzers/CodeSmellAnalyzer.mjs +106 -0
  10. package/src/analyzers/OxcAnalyzer.mjs +11 -0
  11. package/src/api/HeadlessAPI.js +31 -16
  12. package/src/api/HeadlessAPI.mjs +369 -0
  13. package/src/api/PluginSDK.mjs +135 -0
  14. package/src/ast/ASTAnalyzer.js +17 -3
  15. package/src/ast/ASTAnalyzer.mjs +742 -0
  16. package/src/ast/AdvancedAnalysis.mjs +586 -0
  17. package/src/ast/BarrelParser.mjs +230 -0
  18. package/src/ast/DeadCodeDetector.js +7 -5
  19. package/src/ast/DeadCodeDetector.mjs +92 -0
  20. package/src/ast/MagicDetector.mjs +203 -0
  21. package/src/ast/OxcAnalyzer.mjs +188 -0
  22. package/src/ast/SecretScanner.mjs +374 -0
  23. package/src/healing/GitSandbox.mjs +82 -0
  24. package/src/healing/SelfHealer.mjs +48 -0
  25. package/src/index.js +37 -1
  26. package/src/index.mjs +1180 -0
  27. package/src/performance/GraphCache.mjs +108 -0
  28. package/src/performance/SupplyChainGuard.mjs +92 -0
  29. package/src/performance/WorkerPool.mjs +132 -0
  30. package/src/performance/WorkerTaskRunner.mjs +144 -0
  31. package/src/plugins/BasePlugin.mjs +240 -0
  32. package/src/plugins/PluginRegistry.mjs +203 -0
  33. package/src/plugins/ecosystems/BackendServices.mjs +197 -0
  34. package/src/plugins/ecosystems/GenericPlugins.mjs +142 -0
  35. package/src/plugins/ecosystems/ModernFrameworks.mjs +162 -0
  36. package/src/plugins/ecosystems/MorePlugins.mjs +562 -0
  37. package/src/plugins/ecosystems/NewPlugins.mjs +526 -0
  38. package/src/plugins/ecosystems/NextJsPlugin.mjs +45 -0
  39. package/src/plugins/ecosystems/PluginLoader.mjs +193 -0
  40. package/src/plugins/ecosystems/TypeScriptPlugin.mjs +56 -0
  41. package/src/plugins/ecosystems/UltimateBundle.mjs +1182 -0
  42. package/src/refractor/ImpactAnalyzer.mjs +92 -0
  43. package/src/refractor/SourceRewriter.mjs +86 -0
  44. package/src/refractor/TransactionManager.mjs +5 -0
  45. package/src/refractor/TypeIntegrity.mjs +4 -0
  46. package/src/resolution/BuildOrchestrator.mjs +46 -0
  47. package/src/resolution/CircularDetector.mjs +91 -0
  48. package/src/resolution/ConfigGenerator.mjs +83 -0
  49. package/src/resolution/ConfigLoader.mjs +94 -0
  50. package/src/resolution/DepencyResolver.mjs +66 -0
  51. package/src/resolution/DependencyFixer.mjs +88 -0
  52. package/src/resolution/DependencyProfiler.mjs +286 -0
  53. package/src/resolution/EntryPointDetector.mjs +134 -0
  54. package/src/resolution/FrameworkConfigParser.mjs +119 -0
  55. package/src/resolution/GraphAnalyzer.mjs +80 -0
  56. package/src/resolution/MigrationAnalyzer.mjs +60 -0
  57. package/src/resolution/PathMapper.mjs +82 -0
  58. package/src/resolution/TSConfigLoader.mjs +162 -0
  59. package/src/resolution/WorkSpaceGraph.mjs +183 -0
  60. package/src/resolution/WorkspaceDiagnostic.mjs +139 -0
  61. package/test.js +76 -0
  62. 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
+ }