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.
- package/bin/cli.js +4 -4
- package/entkapp/config.json +0 -1
- package/package.json +5 -6
- package/src/EngineContext.js +276 -78
- package/src/analyzers/OxcAnalyzer.js +8 -380
- package/src/api/HeadlessAPI.js +1 -1
- package/src/api/PluginSDK.js +23 -187
- package/src/ast/ASTAnalyzer.js +481 -252
- package/src/ast/AdvancedAnalysis.js +6 -5
- package/src/ast/BarrelParser.js +39 -30
- package/src/ast/DeadCodeDetector.js +30 -18
- package/src/ast/MagicDetector.js +1 -1
- package/src/ast/OxcAnalyzer.js +335 -273
- package/src/index.js +279 -365
- package/src/performance/GraphCache.js +21 -2
- package/src/performance/WorkerPool.js +11 -1
- package/src/performance/WorkerTaskRunner.js +72 -25
- package/src/plugins/PluginRegistry.js +5 -16
- package/src/plugins/ecosystems/GenericPlugins.js +61 -0
- package/src/refractor/TransactionManager.js +3 -136
- package/src/refractor/TypeIntegrity.js +2 -73
- package/src/resolution/CircularDetector.js +44 -44
- package/src/resolution/ConfigLoader.js +2 -85
- package/src/resolution/DepencyResolver.js +28 -121
- package/src/resolution/EntryPointDetector.js +134 -0
- package/src/resolution/PathMapper.js +31 -107
- package/src/resolution/TSConfigLoader.js +76 -0
- package/src/resolution/WorkSpaceGraph.js +6 -472
- package/src/resolution/WorkspaceDiagnostic.js +3 -57
- package/src/plugins/KnipAdapter.js +0 -106
|
@@ -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
|
|
package/src/ast/BarrelParser.js
CHANGED
|
@@ -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
|
-
|
|
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
|
|
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
|
-
|
|
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) //
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
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
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
package/src/ast/MagicDetector.js
CHANGED
|
@@ -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.
|
|
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, '/');
|