entkapp 4.5.1 → 5.1.0

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.
@@ -112,6 +112,7 @@ export class AdvancedAnalysis {
112
112
  * Package.json Export Reachability
113
113
  */
114
114
  analyzeExportReachability(projectGraph, packageManifests) {
115
+ if (!packageManifests) return;
115
116
  for (const [pkgName, metadata] of packageManifests.entries()) {
116
117
  const publicEntries = new Set(metadata.entryPoints);
117
118
 
@@ -557,13 +558,13 @@ export class AdvancedAnalysis {
557
558
  this.analyzeDataIntegrity(filePath, node.ast);
558
559
  this.analyzeEventLeaks(filePath, node.ast);
559
560
  this.analyzeBinaryShaking(filePath, node.ast);
561
+ this.analyzeDereferences(filePath, node.ast);
562
+ this.analyzeClones(filePath, node.ast);
563
+ this.analyzeEscapes(filePath, node.ast);
564
+ this.analyzeLoops(filePath, node.ast);
565
+ this.analyzeConfigSanity(filePath, node.ast);
560
566
  }
561
567
  this.analyzeSandboxing(filePath, projectGraph);
562
- this.analyzeDereferences(filePath, node.ast);
563
- this.analyzeClones(filePath, node.ast);
564
- this.analyzeEscapes(filePath, node.ast);
565
- this.analyzeLoops(filePath, node.ast);
566
- this.analyzeConfigSanity(filePath, node.ast);
567
568
  }
568
569
  this.analyzeExportReachability(projectGraph, packageManifests);
569
570
 
@@ -7,7 +7,7 @@ import fs from 'fs/promises';
7
7
  */
8
8
  export class BarrelParser {
9
9
  constructor(context, resolver) {
10
- this.context = context;
10
+ this.context = context; // Enthält globale Konfigurationen (z.B. context.entrypoints)
11
11
  this.resolver = resolver;
12
12
  this.cachedSpecifications = new Map();
13
13
  }
@@ -23,28 +23,43 @@ export class BarrelParser {
23
23
 
24
24
  const specification = {
25
25
  isBarrelInstance: false,
26
+ isApplicationEntrypoint: false, // FIX: Schutzschicht für Einstiegspunkte gegen Fehldetektion
26
27
  wildcardExports: new Set(), // export * from './module';
27
28
  namespacedWildcardExports: new Map(), // export * as Utils from './module';
28
29
  forwardedNamedExports: new Map(), // export { token as alias } from './module';
29
30
  declaredLocalExports: new Set() // export const a = 1;
30
31
  };
31
32
 
33
+ // FIX: Überprüfe, ob die aktuelle Datei als App-Einstiegspunkt deklariert ist
34
+ if (this.context && this.context.entrypoints) {
35
+ const isEntry = this.context.entrypoints.some(entry =>
36
+ this.resolver.resolveModulePath(filePath, entry) === filePath || entry === filePath
37
+ );
38
+ if (isEntry) {
39
+ specification.isApplicationEntrypoint = true;
40
+ }
41
+ }
42
+
32
43
  try {
33
44
  const code = await fs.readFile(filePath, 'utf8');
34
45
  const sourceFile = ts.createSourceFile(filePath, code, ts.ScriptTarget.Latest, true);
35
46
 
36
47
  this.harvestExportSignatures(sourceFile, specification);
37
48
 
38
- if (specification.wildcardExports.size > 0 ||
49
+ // FIX: Eine Datei ist NUR DANN ein rein kosmetisches Barrel, wenn sie Re-Exports besitzt
50
+ // UND NICHT der geschützte Haupteinstiegspunkt (Entrypoint) der Anwendung ist!
51
+ if (!specification.isApplicationEntrypoint && (
52
+ specification.wildcardExports.size > 0 ||
39
53
  specification.namespacedWildcardExports.size > 0 ||
40
- specification.forwardedNamedExports.size > 0) {
54
+ specification.forwardedNamedExports.size > 0
55
+ )) {
41
56
  specification.isBarrelInstance = true;
42
57
  }
43
58
 
44
59
  this.cachedSpecifications.set(filePath, specification);
45
60
  return specification;
46
61
  } catch {
47
- return specification; // Error state defaults to safe boundaries layout manifest
62
+ return specification; // Error-State fällt auf sicheres Standard-Layout zurück
48
63
  }
49
64
  }
50
65
 
@@ -117,14 +132,7 @@ export class BarrelParser {
117
132
  break;
118
133
  }
119
134
 
120
- // Track default exports declared directly within the file boundary:
121
- // Handles both `export default function foo()` and `export default expression`.
122
- // The canonical symbol name stored is 'default' so that forwardedNamedExports
123
- // entries whose sourceSymbol is 'default' can resolve correctly via Rule A.
124
135
  case ts.SyntaxKind.ExportAssignment: {
125
- // ExportAssignment covers `export default <expr>` (isExportEquals === false)
126
- // as well as `export = <expr>` (isExportEquals === true, CommonJS style).
127
- // We register 'default' for both to ensure the barrel tracer can settle here.
128
136
  spec.declaredLocalExports.add('default');
129
137
  break;
130
138
  }
@@ -156,7 +164,7 @@ export class BarrelParser {
156
164
  return { originFile: contextFilePath, originSymbol: targetSymbolName };
157
165
  }
158
166
 
159
- // Rule B: Evaluate explicit named re-export mappings
167
+ // Rule B: Evaluate explicit named re-export mappings (export { A as B } from 'module')
160
168
  if (spec.forwardedNamedExports.has(targetSymbolName)) {
161
169
  const routingRule = spec.forwardedNamedExports.get(targetSymbolName);
162
170
  const fullyResolvedPath = this.resolver.resolveModulePath(contextFilePath, routingRule.targetModule);
@@ -167,7 +175,10 @@ export class BarrelParser {
167
175
 
168
176
  // Rule C: Evaluate structural namespace alias groupings (export * as name from 'module')
169
177
  for (const [namespaceAlias, relativeModule] of spec.namespacedWildcardExports.entries()) {
170
- // If the targetSymbolName is prefixed with the namespaceAlias, then it's a member of this namespace re-export
178
+ if (targetSymbolName === namespaceAlias) {
179
+ const fullyResolvedPath = this.resolver.resolveModulePath(contextFilePath, relativeModule);
180
+ return { originFile: fullyResolvedPath, originSymbol: '*' };
181
+ }
171
182
  if (targetSymbolName.startsWith(`${namespaceAlias}.`)) {
172
183
  const originalSymbol = targetSymbolName.substring(namespaceAlias.length + 1);
173
184
  const fullyResolvedPath = this.resolver.resolveModulePath(contextFilePath, relativeModule);
@@ -178,38 +189,36 @@ export class BarrelParser {
178
189
  }
179
190
 
180
191
  // Rule D: Sweep through anonymous star re-exports vectors (export * from 'module')
181
- //
182
- // Algorithm:
183
- // 1. For each `export * from './child'`, recursively resolve the symbol in the child.
184
- // 2. A non-barrel child immediately returns `{ originFile: childPath }` regardless of
185
- // whether it actually declares the symbol. We therefore must verify that the
186
- // returned origin file actually contains the symbol in its declaredLocalExports
187
- // before accepting the result.
188
- // 3. If the child is itself a barrel, the recursive call already performs the full
189
- // chain walk, so we only need to verify the final origin.
190
192
  for (const relativePath of spec.wildcardExports) {
191
193
  const fullyResolvedPath = this.resolver.resolveModulePath(contextFilePath, relativePath);
192
194
 
193
195
  if (fullyResolvedPath) {
196
+ // Verbinde Graph-Kanten direkt während der Wildcard-Traversierung
197
+ const contextNode = activeProjectGraph.get(contextFilePath);
198
+ if (contextNode) {
199
+ contextNode.outgoingEdges.add(fullyResolvedPath);
200
+ const targetNode = activeProjectGraph.get(fullyResolvedPath);
201
+ if (targetNode) {
202
+ targetNode.incomingEdges.add(contextFilePath);
203
+ }
204
+ }
205
+
194
206
  const continuousResolutionTrace = await this.determineSymbolDeclarationOrigin(
195
207
  fullyResolvedPath,
196
208
  targetSymbolName,
197
209
  activeProjectGraph,
198
- new Set(protectionStack) // Use a copy so sibling branches don't block each other
210
+ new Set(protectionStack) // Kopie verhindert geschwisterliche Branch-Blockaden
199
211
  );
200
212
 
201
213
  if (!continuousResolutionTrace) continue;
202
214
  if (continuousResolutionTrace.originFile === contextFilePath) continue;
203
215
 
204
- // Verify that the resolved origin actually declares the symbol.
205
- // This prevents a non-barrel sibling (e.g. constants.ts) from being
206
- // incorrectly returned for a symbol it does not export (e.g. formatData).
207
216
  const originSpec = await this.parseBarrelSpecification(continuousResolutionTrace.originFile);
208
- if (originSpec.declaredLocalExports.has(continuousResolutionTrace.originSymbol)) {
217
+ if (originSpec.declaredLocalExports.has(continuousResolutionTrace.originSymbol) ||
218
+ continuousResolutionTrace.originSymbol === '*') {
209
219
  return continuousResolutionTrace;
210
220
  }
211
- // The origin spec is itself a barrel (isBarrelInstance = true) and the
212
- // recursive call already resolved through it – accept the result.
221
+
213
222
  if (originSpec.isBarrelInstance) {
214
223
  return continuousResolutionTrace;
215
224
  }
@@ -218,4 +227,4 @@ export class BarrelParser {
218
227
 
219
228
  return { originFile: contextFilePath, originSymbol: targetSymbolName };
220
229
  }
221
- }
230
+ }
@@ -10,26 +10,32 @@ export class DeadCodeDetector {
10
10
  // Find all entry points
11
11
  const entryPoints = new Set();
12
12
  for (const [filePath, node] of graph.entries()) {
13
- if (node.isEntry || node.isNextJsRoute || node.isSvelteComponent || node.isAstroPage) {
13
+ // NIGHTMARE FIX: node.isEntry must be respected, and also check for library entries
14
+ if (node.isEntry || node.isLibraryEntry || node.isNextJsRoute || node.isSvelteComponent || node.isAstroPage) {
14
15
  entryPoints.add(filePath);
15
16
  }
16
17
  }
17
18
 
18
- // Traverse from entry points to find used files
19
+ // UPGRADE 5: Cycle-tolerant Graph Traversal.
20
+ // We use a robust recursive traversal with a visited set to handle cycles.
19
21
  const usedFiles = new Set();
20
- const queue = Array.from(entryPoints);
21
-
22
- while (queue.length > 0) {
23
- const current = queue.shift();
24
- if (!usedFiles.has(current)) {
25
- usedFiles.add(current);
26
- const node = graph.get(current);
27
- if (node && node.outgoingEdges) {
28
- for (const edge of node.outgoingEdges) {
29
- queue.push(edge);
30
- }
22
+ const visited = new Set();
23
+
24
+ const traverse = (filePath) => {
25
+ if (visited.has(filePath)) return;
26
+ visited.add(filePath);
27
+ usedFiles.add(filePath);
28
+
29
+ const node = graph.get(filePath);
30
+ if (node && node.outgoingEdges) {
31
+ for (const edge of node.outgoingEdges) {
32
+ traverse(edge);
31
33
  }
32
34
  }
35
+ };
36
+
37
+ for (const entry of entryPoints) {
38
+ traverse(entry);
33
39
  }
34
40
 
35
41
  // Identify dead files
@@ -45,25 +51,31 @@ export class DeadCodeDetector {
45
51
  if (!node) continue;
46
52
 
47
53
  // If it's an entry point, we consider its exports used (unless strictly configured otherwise)
48
- if (node.isEntry) continue;
54
+ // NIGHTMARE FIX: Even in entry points, we might want to find unused exports if they are not re-exported
55
+ if (node.isEntry && !node.isLibraryEntry) {
56
+ // If it's a main entry but not a library, its exports are usually unused
57
+ // unless it's a barrel file. Let's proceed to check usage.
58
+ } else if (node.isLibraryEntry) {
59
+ continue;
60
+ }
49
61
 
50
62
  for (const [exportName, exportInfo] of node.internalExports.entries()) {
51
63
  if (exportName === '*' || exportName === 'default') continue; // Skip wildcards for now
52
64
 
53
65
  let isUsed = false;
54
- // Check incoming edges if they import this specific symbol
66
+ // Check ALL nodes in the graph for usage of this symbol, not just those with edges
67
+ // (Handles cases where edges might be missing or complex)
55
68
  for (const [otherPath, otherNode] of graph.entries()) {
56
- if (otherNode.outgoingEdges.has(filePath)) {
57
69
  if (otherNode.importedSymbols.has(`${filePath}:${exportName}`) ||
58
70
  otherNode.importedSymbols.has(`${filePath}:*`)) {
59
71
  isUsed = true;
60
72
  break;
61
73
  }
62
- }
63
74
  }
64
75
 
65
76
  // Additional check for library entries or index files
66
- if (!isUsed && (node.isLibraryEntry || filePath.toLowerCase().includes('index'))) {
77
+ // NIGHTMARE FIX: Don't blindly protect 'index' files if they are not real entries
78
+ if (!isUsed && (node.isLibraryEntry)) {
67
79
  isUsed = true;
68
80
  }
69
81
 
@@ -177,7 +177,7 @@ export class MagicDetector {
177
177
  if (!await this.isImplicitlyRequiredByEcosystem(filePath, activeFrameworks)) return;
178
178
 
179
179
  // Retain entry point elements within memory to keep verification safe
180
- fileNode.isLibraryEntry = true;
180
+ fileNode.isEntry = true;
181
181
 
182
182
  // Apply dynamic exports coverage metrics based on active platform contracts
183
183
  const normalizedPath = filePath.replace(/\\/g, '/');