entkapp 5.4.0 → 5.6.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/README.md +7 -1
- package/bin/cli.js +117 -58
- package/bin/cli.mjs +175 -0
- package/index.cjs +18 -0
- package/index.mjs +51 -0
- package/package.json +7 -6
- package/src/EngineContext.js +0 -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 +23 -97
- 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.js +43 -23
- package/src/ast/MagicDetector.mjs +203 -0
- package/src/ast/OxcAnalyzer.js +27 -190
- 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 +41 -88
- package/src/index.mjs +1176 -0
- package/src/performance/GraphCache.js +0 -27
- package/src/performance/GraphCache.mjs +108 -0
- package/src/performance/SupplyChainGuard.mjs +92 -0
- package/src/performance/WorkerPool.js +4 -22
- package/src/performance/WorkerPool.mjs +132 -0
- package/src/performance/WorkerTaskRunner.js +0 -26
- package/src/performance/WorkerTaskRunner.mjs +144 -0
- package/src/plugins/BasePlugin.js +27 -16
- package/src/plugins/BasePlugin.mjs +240 -0
- package/src/plugins/PluginRegistry.js +0 -44
- 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.js +0 -259
- 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.js +26 -190
- 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.js +1 -65
- package/src/resolution/GraphAnalyzer.mjs +80 -0
- package/src/resolution/MigrationAnalyzer.mjs +60 -0
- package/src/resolution/PathMapper.js +0 -81
- package/src/resolution/PathMapper.mjs +82 -0
- package/src/resolution/TSConfigLoader.js +1 -3
- package/src/resolution/TSConfigLoader.mjs +162 -0
- package/src/resolution/WorkSpaceGraph.js +89 -260
- 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,230 @@
|
|
|
1
|
+
import ts from 'typescript';
|
|
2
|
+
import fs from 'fs/promises';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Enterprise Wildcard Optimization & Flattening Layer for Barrel Operations
|
|
6
|
+
* Traces unreferenced paths through redistribution entries.
|
|
7
|
+
*/
|
|
8
|
+
export class BarrelParser {
|
|
9
|
+
constructor(context, resolver) {
|
|
10
|
+
this.context = context; // Enthält globale Konfigurationen (z.B. context.entrypoints)
|
|
11
|
+
this.resolver = resolver;
|
|
12
|
+
this.cachedSpecifications = new Map();
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Compiles the structural export layout for a specific index or barrel target.
|
|
17
|
+
* @param {string} filePath - Absolute path to on-disk target element
|
|
18
|
+
*/
|
|
19
|
+
async parseBarrelSpecification(filePath) {
|
|
20
|
+
if (this.cachedSpecifications.has(filePath)) {
|
|
21
|
+
return this.cachedSpecifications.get(filePath);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const specification = {
|
|
25
|
+
isBarrelInstance: false,
|
|
26
|
+
isApplicationEntrypoint: false, // FIX: Schutzschicht für Einstiegspunkte gegen Fehldetektion
|
|
27
|
+
wildcardExports: new Set(), // export * from './module';
|
|
28
|
+
namespacedWildcardExports: new Map(), // export * as Utils from './module';
|
|
29
|
+
forwardedNamedExports: new Map(), // export { token as alias } from './module';
|
|
30
|
+
declaredLocalExports: new Set() // export const a = 1;
|
|
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
|
+
|
|
43
|
+
try {
|
|
44
|
+
const code = await fs.readFile(filePath, 'utf8');
|
|
45
|
+
const sourceFile = ts.createSourceFile(filePath, code, ts.ScriptTarget.Latest, true);
|
|
46
|
+
|
|
47
|
+
this.harvestExportSignatures(sourceFile, specification);
|
|
48
|
+
|
|
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 ||
|
|
53
|
+
specification.namespacedWildcardExports.size > 0 ||
|
|
54
|
+
specification.forwardedNamedExports.size > 0
|
|
55
|
+
)) {
|
|
56
|
+
specification.isBarrelInstance = true;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
this.cachedSpecifications.set(filePath, specification);
|
|
60
|
+
return specification;
|
|
61
|
+
} catch {
|
|
62
|
+
return specification; // Error-State fällt auf sicheres Standard-Layout zurück
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
harvestExportSignatures(node, spec) {
|
|
67
|
+
if (!node) return;
|
|
68
|
+
|
|
69
|
+
switch (node.kind) {
|
|
70
|
+
case ts.SyntaxKind.ExportDeclaration: {
|
|
71
|
+
const targetSpecifier = node.moduleSpecifier && ts.isStringLiteral(node.moduleSpecifier)
|
|
72
|
+
? node.moduleSpecifier.text
|
|
73
|
+
: null;
|
|
74
|
+
|
|
75
|
+
if (targetSpecifier) {
|
|
76
|
+
if (!node.exportClause) {
|
|
77
|
+
// Standard Wildcard Transfer: export * from './module';
|
|
78
|
+
spec.wildcardExports.add(targetSpecifier);
|
|
79
|
+
} else if (ts.isNamespaceExport(node.exportClause)) {
|
|
80
|
+
// Namespaced Wildcard Transfer: export * as Core from './module';
|
|
81
|
+
const namespaceAlias = node.exportClause.name.text;
|
|
82
|
+
spec.namespacedWildcardExports.set(namespaceAlias, targetSpecifier);
|
|
83
|
+
} else if (ts.isNamedExports(node.exportClause)) {
|
|
84
|
+
// Selective Re-export Forwarding: export { x, y as z } from './module';
|
|
85
|
+
node.exportClause.elements.forEach(element => {
|
|
86
|
+
const originalSymbol = element.propertyName ? element.propertyName.text : element.name.text;
|
|
87
|
+
const exposedAlias = element.name.text;
|
|
88
|
+
spec.forwardedNamedExports.set(exposedAlias, {
|
|
89
|
+
targetModule: targetSpecifier,
|
|
90
|
+
sourceSymbol: originalSymbol
|
|
91
|
+
});
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
break;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Track items declared directly within the file boundary
|
|
99
|
+
case ts.SyntaxKind.FunctionDeclaration:
|
|
100
|
+
case ts.SyntaxKind.ClassDeclaration:
|
|
101
|
+
case ts.SyntaxKind.InterfaceDeclaration:
|
|
102
|
+
case ts.SyntaxKind.TypeAliasDeclaration:
|
|
103
|
+
case ts.SyntaxKind.EnumDeclaration: {
|
|
104
|
+
if (node.modifiers && node.modifiers.some(m => m.kind === ts.SyntaxKind.ExportKeyword)) {
|
|
105
|
+
if (node.name && ts.isIdentifier(node.name)) {
|
|
106
|
+
spec.declaredLocalExports.add(node.name.text);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
break;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
case ts.SyntaxKind.VariableStatement: {
|
|
113
|
+
if (node.modifiers && node.modifiers.some(m => m.kind === ts.SyntaxKind.ExportKeyword)) {
|
|
114
|
+
node.declarationList.declarations.forEach(decl => {
|
|
115
|
+
if (ts.isIdentifier(decl.name)) {
|
|
116
|
+
spec.declaredLocalExports.add(decl.name.text);
|
|
117
|
+
} else if (ts.isObjectBindingPattern(decl.name)) {
|
|
118
|
+
decl.name.elements.forEach(element => {
|
|
119
|
+
if (element.name && ts.isIdentifier(element.name)) {
|
|
120
|
+
spec.declaredLocalExports.add(element.name.text);
|
|
121
|
+
}
|
|
122
|
+
});
|
|
123
|
+
} else if (ts.isArrayBindingPattern(decl.name)) {
|
|
124
|
+
decl.name.elements.forEach(element => {
|
|
125
|
+
if (ts.isBindingElement(element) && element.name && ts.isIdentifier(element.name)) {
|
|
126
|
+
spec.declaredLocalExports.add(element.name.text);
|
|
127
|
+
}
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
break;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
case ts.SyntaxKind.ExportAssignment: {
|
|
136
|
+
spec.declaredLocalExports.add('default');
|
|
137
|
+
break;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
ts.forEachChild(node, child => this.harvestExportSignatures(child, spec));
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Challenge #3 Resolution Logic. Unwraps nested redistribution links to find origin source nodes.
|
|
146
|
+
* @param {string} contextFilePath - Current position filename vector pointer
|
|
147
|
+
* @param {string} targetSymbolName - Targeted semantic variable signature name
|
|
148
|
+
* @param {Map} activeProjectGraph - Global active module maps directory registry
|
|
149
|
+
* @param {Set} [protectionStack] - Avoids cyclic validation traps inside self-referencing links
|
|
150
|
+
*/
|
|
151
|
+
async determineSymbolDeclarationOrigin(contextFilePath, targetSymbolName, activeProjectGraph, protectionStack = new Set()) {
|
|
152
|
+
if (protectionStack.has(contextFilePath)) {
|
|
153
|
+
return { originFile: contextFilePath, originSymbol: targetSymbolName };
|
|
154
|
+
}
|
|
155
|
+
protectionStack.add(contextFilePath);
|
|
156
|
+
|
|
157
|
+
const spec = await this.parseBarrelSpecification(contextFilePath);
|
|
158
|
+
if (!spec.isBarrelInstance) {
|
|
159
|
+
return { originFile: contextFilePath, originSymbol: targetSymbolName };
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// Rule A: Settle boundary immediately if local declaration matches token signature name
|
|
163
|
+
if (spec.declaredLocalExports.has(targetSymbolName)) {
|
|
164
|
+
return { originFile: contextFilePath, originSymbol: targetSymbolName };
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// Rule B: Evaluate explicit named re-export mappings (export { A as B } from 'module')
|
|
168
|
+
if (spec.forwardedNamedExports.has(targetSymbolName)) {
|
|
169
|
+
const routingRule = spec.forwardedNamedExports.get(targetSymbolName);
|
|
170
|
+
const fullyResolvedPath = this.resolver.resolveModulePath(contextFilePath, routingRule.targetModule);
|
|
171
|
+
if (fullyResolvedPath) {
|
|
172
|
+
return this.determineSymbolDeclarationOrigin(fullyResolvedPath, routingRule.sourceSymbol, activeProjectGraph, protectionStack);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// Rule C: Evaluate structural namespace alias groupings (export * as name from 'module')
|
|
177
|
+
for (const [namespaceAlias, relativeModule] of spec.namespacedWildcardExports.entries()) {
|
|
178
|
+
if (targetSymbolName === namespaceAlias) {
|
|
179
|
+
const fullyResolvedPath = this.resolver.resolveModulePath(contextFilePath, relativeModule);
|
|
180
|
+
return { originFile: fullyResolvedPath, originSymbol: '*' };
|
|
181
|
+
}
|
|
182
|
+
if (targetSymbolName.startsWith(`${namespaceAlias}.`)) {
|
|
183
|
+
const originalSymbol = targetSymbolName.substring(namespaceAlias.length + 1);
|
|
184
|
+
const fullyResolvedPath = this.resolver.resolveModulePath(contextFilePath, relativeModule);
|
|
185
|
+
if (fullyResolvedPath) {
|
|
186
|
+
return this.determineSymbolDeclarationOrigin(fullyResolvedPath, originalSymbol, activeProjectGraph, protectionStack);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// Rule D: Sweep through anonymous star re-exports vectors (export * from 'module')
|
|
192
|
+
for (const relativePath of spec.wildcardExports) {
|
|
193
|
+
const fullyResolvedPath = this.resolver.resolveModulePath(contextFilePath, relativePath);
|
|
194
|
+
|
|
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
|
+
|
|
206
|
+
const continuousResolutionTrace = await this.determineSymbolDeclarationOrigin(
|
|
207
|
+
fullyResolvedPath,
|
|
208
|
+
targetSymbolName,
|
|
209
|
+
activeProjectGraph,
|
|
210
|
+
new Set(protectionStack) // Kopie verhindert geschwisterliche Branch-Blockaden
|
|
211
|
+
);
|
|
212
|
+
|
|
213
|
+
if (!continuousResolutionTrace) continue;
|
|
214
|
+
if (continuousResolutionTrace.originFile === contextFilePath) continue;
|
|
215
|
+
|
|
216
|
+
const originSpec = await this.parseBarrelSpecification(continuousResolutionTrace.originFile);
|
|
217
|
+
if (originSpec.declaredLocalExports.has(continuousResolutionTrace.originSymbol) ||
|
|
218
|
+
continuousResolutionTrace.originSymbol === '*') {
|
|
219
|
+
return continuousResolutionTrace;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
if (originSpec.isBarrelInstance) {
|
|
223
|
+
return continuousResolutionTrace;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
return { originFile: contextFilePath, originSymbol: targetSymbolName };
|
|
229
|
+
}
|
|
230
|
+
}
|
|
@@ -10,8 +10,10 @@ 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
|
-
//
|
|
14
|
-
|
|
13
|
+
// ADVANCED: Framework-agnostic entry point detection
|
|
14
|
+
// Respects explicit entries, side-effect files, and dynamic entry points
|
|
15
|
+
if (node.isEntry || node.isLibraryEntry || node.isDynamicEntry || node.hasSideEffects ||
|
|
16
|
+
node.isNextJsRoute || node.isSvelteComponent || node.isAstroPage) {
|
|
15
17
|
entryPoints.add(filePath);
|
|
16
18
|
}
|
|
17
19
|
}
|
|
@@ -63,11 +65,11 @@ export class DeadCodeDetector {
|
|
|
63
65
|
if (exportName === '*' || exportName === 'default') continue; // Skip wildcards for now
|
|
64
66
|
|
|
65
67
|
let isUsed = false;
|
|
66
|
-
//
|
|
67
|
-
// (Handles cases where edges might be missing or complex)
|
|
68
|
+
// ADVANCED: Global usage tracking including dynamic property access
|
|
68
69
|
for (const [otherPath, otherNode] of graph.entries()) {
|
|
69
70
|
if (otherNode.importedSymbols.has(`${filePath}:${exportName}`) ||
|
|
70
|
-
otherNode.importedSymbols.has(`${filePath}:*`)
|
|
71
|
+
otherNode.importedSymbols.has(`${filePath}:*`) ||
|
|
72
|
+
otherNode.instantiatedIdentifiers.has(exportName)) {
|
|
71
73
|
isUsed = true;
|
|
72
74
|
break;
|
|
73
75
|
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
export class DeadCodeDetector {
|
|
2
|
+
constructor(context) {
|
|
3
|
+
this.context = context;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
detectDeadCode(graph) {
|
|
7
|
+
const deadFiles = [];
|
|
8
|
+
const deadExports = [];
|
|
9
|
+
|
|
10
|
+
// Find all entry points
|
|
11
|
+
const entryPoints = new Set();
|
|
12
|
+
for (const [filePath, node] of graph.entries()) {
|
|
13
|
+
// ADVANCED: Framework-agnostic entry point detection
|
|
14
|
+
// Respects explicit entries, side-effect files, and dynamic entry points
|
|
15
|
+
if (node.isEntry || node.isLibraryEntry || node.isDynamicEntry || node.hasSideEffects ||
|
|
16
|
+
node.isNextJsRoute || node.isSvelteComponent || node.isAstroPage) {
|
|
17
|
+
entryPoints.add(filePath);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// UPGRADE 5: Cycle-tolerant Graph Traversal.
|
|
22
|
+
// We use a robust recursive traversal with a visited set to handle cycles.
|
|
23
|
+
const usedFiles = new Set();
|
|
24
|
+
const visited = new Set();
|
|
25
|
+
|
|
26
|
+
const traverse = (filePath) => {
|
|
27
|
+
if (visited.has(filePath)) return;
|
|
28
|
+
visited.add(filePath);
|
|
29
|
+
usedFiles.add(filePath);
|
|
30
|
+
|
|
31
|
+
const node = graph.get(filePath);
|
|
32
|
+
if (node && node.outgoingEdges) {
|
|
33
|
+
for (const edge of node.outgoingEdges) {
|
|
34
|
+
traverse(edge);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
for (const entry of entryPoints) {
|
|
40
|
+
traverse(entry);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Identify dead files
|
|
44
|
+
for (const filePath of graph.keys()) {
|
|
45
|
+
if (!usedFiles.has(filePath)) {
|
|
46
|
+
deadFiles.push(filePath);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Identify dead exports in used files
|
|
51
|
+
for (const filePath of usedFiles) {
|
|
52
|
+
const node = graph.get(filePath);
|
|
53
|
+
if (!node) continue;
|
|
54
|
+
|
|
55
|
+
// If it's an entry point, we consider its exports used (unless strictly configured otherwise)
|
|
56
|
+
// NIGHTMARE FIX: Even in entry points, we might want to find unused exports if they are not re-exported
|
|
57
|
+
if (node.isEntry && !node.isLibraryEntry) {
|
|
58
|
+
// If it's a main entry but not a library, its exports are usually unused
|
|
59
|
+
// unless it's a barrel file. Let's proceed to check usage.
|
|
60
|
+
} else if (node.isLibraryEntry) {
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
for (const [exportName, exportInfo] of node.internalExports.entries()) {
|
|
65
|
+
if (exportName === '*' || exportName === 'default') continue; // Skip wildcards for now
|
|
66
|
+
|
|
67
|
+
let isUsed = false;
|
|
68
|
+
// ADVANCED: Global usage tracking including dynamic property access
|
|
69
|
+
for (const [otherPath, otherNode] of graph.entries()) {
|
|
70
|
+
if (otherNode.importedSymbols.has(`${filePath}:${exportName}`) ||
|
|
71
|
+
otherNode.importedSymbols.has(`${filePath}:*`) ||
|
|
72
|
+
otherNode.instantiatedIdentifiers.has(exportName)) {
|
|
73
|
+
isUsed = true;
|
|
74
|
+
break;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Additional check for library entries or index files
|
|
79
|
+
// NIGHTMARE FIX: Don't blindly protect 'index' files if they are not real entries
|
|
80
|
+
if (!isUsed && (node.isLibraryEntry)) {
|
|
81
|
+
isUsed = true;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (!isUsed) {
|
|
85
|
+
deadExports.push({ file: filePath, symbol: exportName, line: exportInfo.start });
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return { deadFiles, deadExports };
|
|
91
|
+
}
|
|
92
|
+
}
|
package/src/ast/MagicDetector.js
CHANGED
|
@@ -6,7 +6,15 @@ import { PluginRegistry } from '../plugins/PluginRegistry.js';
|
|
|
6
6
|
* Ecosystem Entry Point Manifest & Dynamic Framework Router Heuristic Validator
|
|
7
7
|
* Intercepts implicit conventions to handle cases where direct import statements are absent.
|
|
8
8
|
* Now refactored to use a pluggable architecture.
|
|
9
|
-
*
|
|
9
|
+
*
|
|
10
|
+
* Improvements over v1:
|
|
11
|
+
* - Extended config-file detection list (Biome, Oxlint, tsup, unbuild, etc.)
|
|
12
|
+
* - Next.js App Router conventions (page.tsx, layout.tsx, loading.tsx, error.tsx, etc.)
|
|
13
|
+
* - Remix conventions (route files under app/routes/)
|
|
14
|
+
* - SvelteKit conventions (+page.svelte, +layout.svelte, etc.)
|
|
15
|
+
* - Astro page/layout conventions
|
|
16
|
+
* - Common entry-point patterns (bin/, cli.ts, server.ts, main.ts, app.ts)
|
|
17
|
+
* - Test file patterns extended to cover Vitest workspace files
|
|
10
18
|
*/
|
|
11
19
|
export class MagicDetector {
|
|
12
20
|
constructor(context) {
|
|
@@ -23,26 +31,18 @@ export class MagicDetector {
|
|
|
23
31
|
|
|
24
32
|
/**
|
|
25
33
|
* Audits the project context to map active micro-framework ecosystems.
|
|
34
|
+
* @param {string} baseContextDirectory - Root file workspace context execution vector path
|
|
26
35
|
*/
|
|
27
36
|
async identifyActiveProjectEcosystems(baseContextDirectory) {
|
|
28
37
|
await this.ensureInitialized(baseContextDirectory);
|
|
29
38
|
const activePlugins = await this.registry.getActivePlugins(baseContextDirectory);
|
|
30
39
|
const activeFrameworkFlags = activePlugins.map(p => p.name);
|
|
40
|
+
|
|
41
|
+
// Universal infrastructure overrides (testing platforms and common bundlers)
|
|
31
42
|
activeFrameworkFlags.push('universal-tooling-vectors');
|
|
32
43
|
return activeFrameworkFlags;
|
|
33
44
|
}
|
|
34
45
|
|
|
35
|
-
/**
|
|
36
|
-
* Version 5.4.0: Delegates entry point detection to the plugin registry.
|
|
37
|
-
* @param {string} content - The file content
|
|
38
|
-
* @param {string} filePath - The absolute path to the file
|
|
39
|
-
* @returns {Array<string>} List of detected entry points
|
|
40
|
-
*/
|
|
41
|
-
async detectEntryPointsFromPlugins(content, filePath) {
|
|
42
|
-
await this.ensureInitialized();
|
|
43
|
-
return this.registry.detectEntryPointsFromContent(content, filePath);
|
|
44
|
-
}
|
|
45
|
-
|
|
46
46
|
/**
|
|
47
47
|
* Assesses if a file path acts as an implicit route entry point.
|
|
48
48
|
*/
|
|
@@ -60,6 +60,7 @@ export class MagicDetector {
|
|
|
60
60
|
}
|
|
61
61
|
}
|
|
62
62
|
|
|
63
|
+
// Apply baseline platform rules (Test suites, lint parameters, continuous integration files)
|
|
63
64
|
if (this.isCoreToolingSuiteElement(normalizedSystemPath)) {
|
|
64
65
|
return true;
|
|
65
66
|
}
|
|
@@ -67,46 +68,49 @@ export class MagicDetector {
|
|
|
67
68
|
return false;
|
|
68
69
|
}
|
|
69
70
|
|
|
70
|
-
/**
|
|
71
|
-
* Version 5.3.0: Delegates content analysis to the plugin registry.
|
|
72
|
-
*/
|
|
73
|
-
async runPluginContentAnalysis(node, absolutePath) {
|
|
74
|
-
await this.ensureInitialized();
|
|
75
|
-
await this.registry.runPluginAnalysis(node, absolutePath);
|
|
76
|
-
}
|
|
77
|
-
|
|
78
71
|
isCoreToolingSuiteElement(normalizedPath) {
|
|
72
|
+
// ── Test / spec files ──────────────────────────────────────────────────────
|
|
79
73
|
if (/\.(test|spec|e2e|cy)\.(js|ts|tsx|jsx)$/i.test(normalizedPath)) return true;
|
|
80
74
|
if (/\.stories\.(js|ts|tsx|jsx)$/i.test(normalizedPath)) return true;
|
|
81
75
|
|
|
76
|
+
// ── Build / bundler config files ───────────────────────────────────────────
|
|
82
77
|
const configFragments = [
|
|
78
|
+
// Test runners
|
|
83
79
|
'jest.config.', 'vitest.config.', 'vitest.workspace.',
|
|
84
80
|
'playwright.config.', 'cypress.config.',
|
|
81
|
+
// Bundlers
|
|
85
82
|
'webpack.config.', 'vite.config.', 'rollup.config.',
|
|
86
83
|
'esbuild.config.', 'parcel.config.',
|
|
87
84
|
'tsup.config.', 'unbuild.config.', 'pkgroll.config.',
|
|
85
|
+
// CSS / styling
|
|
88
86
|
'tailwind.config.', 'postcss.config.', '.postcssrc.',
|
|
87
|
+
// Linters / formatters
|
|
89
88
|
'.eslintrc.', 'eslint.config.', 'prettier.config.', '.prettierrc.',
|
|
90
89
|
'.stylelintrc.', 'stylelint.config.',
|
|
91
90
|
'biome.json', '.oxlintrc.',
|
|
91
|
+
// Babel / transpilation
|
|
92
92
|
'.babelrc.', 'babel.config.',
|
|
93
|
+
// Commit / git hooks
|
|
93
94
|
'.commitlintrc.', 'commitlint.config.',
|
|
94
95
|
'.lintstagedrc.', 'lint-staged.config.',
|
|
96
|
+
// Documentation
|
|
95
97
|
'typedoc.config.', 'typedoc.json',
|
|
98
|
+
// Monorepo tools
|
|
96
99
|
'turbo.json', 'nx.json', 'lerna.json',
|
|
100
|
+
// Misc tooling
|
|
97
101
|
'knip.config.', 'knip.json',
|
|
98
102
|
'syncpack.config.',
|
|
103
|
+
// Internal worker
|
|
99
104
|
'WorkerTaskRunner.js'
|
|
100
105
|
];
|
|
101
106
|
if (configFragments.some(f => normalizedPath.includes(f))) return true;
|
|
102
107
|
|
|
108
|
+
// ── Common application entry points ───────────────────────────────────────
|
|
103
109
|
const entryPatterns = [
|
|
104
110
|
// CLI binaries
|
|
105
111
|
'/bin/cli.js', '/bin/cli.ts', '/bin/cli.mjs',
|
|
106
112
|
'/bin/index.js', '/bin/index.ts',
|
|
107
|
-
//
|
|
108
|
-
'/index.js', '/index.ts', '/index.jsx', '/index.tsx', '/index.mjs', '/index.cjs',
|
|
109
|
-
// Server / app entry points
|
|
113
|
+
// Server / app entry points (Reduced in v3.3.8 to avoid false positives in libraries)
|
|
110
114
|
'/src/main.ts', '/src/main.js',
|
|
111
115
|
'/src/app.ts', '/src/app.js',
|
|
112
116
|
'/src/api/HeadlessAPI.js', '/src/api/PluginSDK.js',
|
|
@@ -115,37 +119,50 @@ export class MagicDetector {
|
|
|
115
119
|
];
|
|
116
120
|
if (entryPatterns.some(p => normalizedPath.endsWith(p))) return true;
|
|
117
121
|
|
|
122
|
+
// ── Next.js App Router conventions ────────────────────────────────────────
|
|
123
|
+
// Files under app/ directory with Next.js special names
|
|
118
124
|
if (/\/app\/(page|layout|loading|error|not-found|template|default|route|middleware)\.(js|ts|tsx|jsx)$/.test(normalizedPath)) return true;
|
|
125
|
+
// Next.js Pages Router
|
|
119
126
|
if (/\/pages\/.*\.(js|ts|tsx|jsx)$/.test(normalizedPath)) return true;
|
|
127
|
+
// Next.js API routes
|
|
120
128
|
if (/\/pages\/api\/.*\.(js|ts)$/.test(normalizedPath)) return true;
|
|
129
|
+
// Next.js middleware
|
|
121
130
|
if (/\/middleware\.(js|ts)$/.test(normalizedPath)) return true;
|
|
131
|
+
// Next.js config
|
|
122
132
|
if (/\/next\.config\.(js|ts|mjs|cjs)$/.test(normalizedPath)) return true;
|
|
123
133
|
|
|
134
|
+
// ── Remix conventions ─────────────────────────────────────────────────────
|
|
124
135
|
if (/\/app\/routes\/.*\.(js|ts|tsx|jsx)$/.test(normalizedPath)) return true;
|
|
125
136
|
if (/\/app\/root\.(js|ts|tsx|jsx)$/.test(normalizedPath)) return true;
|
|
126
137
|
if (/\/app\/entry\.(client|server)\.(js|ts|tsx|jsx)$/.test(normalizedPath)) return true;
|
|
127
138
|
|
|
139
|
+
// ── SvelteKit conventions ─────────────────────────────────────────────────
|
|
128
140
|
if (/\/\+page(\.(server|client))?\.(svelte|js|ts)$/.test(normalizedPath)) return true;
|
|
129
141
|
if (/\/\+layout(\.(server|client))?\.(svelte|js|ts)$/.test(normalizedPath)) return true;
|
|
130
142
|
if (/\/\+error\.(svelte|js|ts)$/.test(normalizedPath)) return true;
|
|
131
143
|
if (/\/\+server\.(js|ts)$/.test(normalizedPath)) return true;
|
|
132
144
|
if (/\/svelte\.config\.(js|ts|mjs)$/.test(normalizedPath)) return true;
|
|
133
145
|
|
|
146
|
+
// ── Astro conventions ─────────────────────────────────────────────────────
|
|
134
147
|
if (/\/src\/pages\/.*\.astro$/.test(normalizedPath)) return true;
|
|
135
148
|
if (/\/src\/layouts\/.*\.astro$/.test(normalizedPath)) return true;
|
|
136
149
|
if (/\/astro\.config\.(mjs|js|ts)$/.test(normalizedPath)) return true;
|
|
137
150
|
|
|
151
|
+
// ── Nuxt conventions ──────────────────────────────────────────────────────
|
|
138
152
|
if (/\/pages\/.*\.vue$/.test(normalizedPath)) return true;
|
|
139
153
|
if (/\/layouts\/.*\.vue$/.test(normalizedPath)) return true;
|
|
140
154
|
if (/\/server\/api\/.*\.(js|ts)$/.test(normalizedPath)) return true;
|
|
141
155
|
if (/\/nuxt\.config\.(js|ts|mjs)$/.test(normalizedPath)) return true;
|
|
142
156
|
|
|
157
|
+
// ── React / Vite entry points ─────────────────────────────────────────────
|
|
143
158
|
if (/\/vite\.config\.(js|ts|mjs)$/.test(normalizedPath)) return true;
|
|
144
159
|
|
|
160
|
+
// ── Angular conventions ───────────────────────────────────────────────────
|
|
145
161
|
if (/\/main\.(ts|js)$/.test(normalizedPath)) return true;
|
|
146
162
|
if (/\/app\.module\.(ts|js)$/.test(normalizedPath)) return true;
|
|
147
163
|
if (/\/angular\.json$/.test(normalizedPath)) return true;
|
|
148
164
|
|
|
165
|
+
// ── Expo / React Native ───────────────────────────────────────────────────
|
|
149
166
|
if (/\/app\/_layout\.(js|ts|tsx|jsx)$/.test(normalizedPath)) return true;
|
|
150
167
|
if (/\/app\/index\.(js|ts|tsx|jsx)$/.test(normalizedPath)) return true;
|
|
151
168
|
|
|
@@ -159,8 +176,10 @@ export class MagicDetector {
|
|
|
159
176
|
await this.ensureInitialized();
|
|
160
177
|
if (!await this.isImplicitlyRequiredByEcosystem(filePath, activeFrameworks)) return;
|
|
161
178
|
|
|
179
|
+
// Retain entry point elements within memory to keep verification safe
|
|
162
180
|
fileNode.isEntry = true;
|
|
163
181
|
|
|
182
|
+
// Apply dynamic exports coverage metrics based on active platform contracts
|
|
164
183
|
const normalizedPath = filePath.replace(/\\/g, '/');
|
|
165
184
|
const plugins = this.registry.getPlugins();
|
|
166
185
|
|
|
@@ -173,6 +192,7 @@ export class MagicDetector {
|
|
|
173
192
|
const contracts = plugin.getRequiredSystemContracts();
|
|
174
193
|
contracts.forEach(contractMethodToken => {
|
|
175
194
|
if (fileNode.internalExports.has(contractMethodToken)) {
|
|
195
|
+
// Emulate active local reference linkages to protect the export
|
|
176
196
|
fileNode.instantiatedIdentifiers.add(contractMethodToken);
|
|
177
197
|
}
|
|
178
198
|
});
|