entkapp 5.0.0 → 5.2.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 +63 -20
- package/bin/cli.js +2 -2
- package/index.js +38 -2241
- package/package.json +1 -1
- package/src/EngineContext.js +1 -1
- package/src/Initializer.js +82 -0
- package/src/analyzers/CodeSmellAnalyzer.js +106 -0
- package/src/ast/ASTAnalyzer.js +29 -14
- package/src/ast/BarrelParser.js +22 -20
- package/src/ast/OxcAnalyzer.js +33 -409
- package/src/index.js +78 -8
- package/src/performance/WorkerTaskRunner.js +7 -7
- package/src/plugins/BasePlugin.js +171 -2
- package/src/plugins/PluginRegistry.js +193 -81
- package/src/plugins/ecosystems/BackendServices.js +168 -32
- package/src/plugins/ecosystems/GenericPlugins.js +51 -34
- package/src/plugins/ecosystems/ModernFrameworks.js +97 -94
- package/src/plugins/ecosystems/MorePlugins.js +429 -51
- package/src/plugins/ecosystems/NewPlugins.js +526 -0
- package/src/plugins/ecosystems/NextJsPlugin.js +18 -6
- package/src/plugins/ecosystems/PluginLoader.js +190 -17
- package/src/plugins/ecosystems/TypeScriptPlugin.js +10 -10
- package/src/plugins/ecosystems/UltimateBundle.js +168 -0
- package/src/resolution/BuildOrchestrator.js +46 -0
- package/src/resolution/CircularDetector.js +64 -25
- package/src/resolution/ConfigGenerator.js +83 -0
- package/src/resolution/DepencyResolver.js +12 -1
- package/src/resolution/DependencyFixer.js +88 -0
- package/src/resolution/EntryPointDetector.js +4 -4
- package/src/resolution/GraphAnalyzer.js +80 -0
- package/src/resolution/MigrationAnalyzer.js +60 -0
- package/src/resolution/PathMapper.js +47 -3
- package/src/resolution/WorkSpaceGraph.js +4 -1
- package/docs.zip +0 -0
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
3
3
|
"name": "entkapp",
|
|
4
|
-
"version": "5.
|
|
4
|
+
"version": "5.2.0",
|
|
5
5
|
"description": "The Ultimate Enterprise Codebase Janitor. Faster than Knip with OXC integration, type-aware analysis, and automated structural healing. Fully standalone - solving what Knip cannot.",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"main": "index.js",
|
package/src/EngineContext.js
CHANGED
|
@@ -189,7 +189,7 @@ export class EngineContext {
|
|
|
189
189
|
});
|
|
190
190
|
|
|
191
191
|
// --- PLUGIN-BASED ECOSYSTEM DETECTION ---
|
|
192
|
-
// Plugins will now handle their own
|
|
192
|
+
// Plugins will now handle their own reachability and dependency validation.
|
|
193
193
|
if (this.pluginRegistry) {
|
|
194
194
|
const activePlugins = await this.pluginRegistry.getActivePlugins(pkgDir);
|
|
195
195
|
for (const plugin of activePlugins) {
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import fs from 'fs/promises';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { glob } from 'glob';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Entkapp Initializer: Analyzes the codebase to generate a package.json
|
|
7
|
+
* with correctly categorized dependencies and devDependencies.
|
|
8
|
+
*/
|
|
9
|
+
export class Initializer {
|
|
10
|
+
constructor(context) {
|
|
11
|
+
this.context = context;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
async run() {
|
|
15
|
+
console.log("🚀 Starting entkapp intelligent initialization...");
|
|
16
|
+
const cwd = this.context.cwd || process.cwd();
|
|
17
|
+
|
|
18
|
+
// 1. Scan for all JS/TS files
|
|
19
|
+
const files = await glob('**/*.{js,ts,jsx,tsx,vue,svelte,astro}', {
|
|
20
|
+
ignore: ['node_modules/**', 'dist/**', '.git/**'],
|
|
21
|
+
cwd
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
const dependencies = new Set();
|
|
25
|
+
const devDependencies = new Set();
|
|
26
|
+
|
|
27
|
+
// Common dev tool indicators
|
|
28
|
+
const devIndicators = ['test', 'spec', 'config', 'stories', 'mock'];
|
|
29
|
+
|
|
30
|
+
console.log(`🔍 Analyzing ${files.length} files for imports...`);
|
|
31
|
+
|
|
32
|
+
for (const file of files) {
|
|
33
|
+
const content = await fs.readFile(path.join(cwd, file), 'utf8');
|
|
34
|
+
const isDevFile = devIndicators.some(ind => file.toLowerCase().includes(ind));
|
|
35
|
+
|
|
36
|
+
// Simple but effective regex for imports
|
|
37
|
+
const importMatches = content.matchAll(/(?:import|from|require)\s*\(?\s*['"]([^' "./][^'"]*)['"]/g);
|
|
38
|
+
|
|
39
|
+
for (const match of importMatches) {
|
|
40
|
+
const pkg = this.extractPackageName(match[1]);
|
|
41
|
+
if (pkg && !this.isBuiltIn(pkg)) {
|
|
42
|
+
if (isDevFile) devDependencies.add(pkg);
|
|
43
|
+
else dependencies.add(pkg);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// 2. Generate package.json
|
|
49
|
+
const pkgJson = {
|
|
50
|
+
name: path.basename(cwd),
|
|
51
|
+
version: "1.0.0",
|
|
52
|
+
description: "Initialized with entkapp",
|
|
53
|
+
main: "index.js",
|
|
54
|
+
scripts: {
|
|
55
|
+
"entkapp:run": "entkapp -r",
|
|
56
|
+
"entkapp:check": "entkapp --verbose"
|
|
57
|
+
},
|
|
58
|
+
dependencies: Object.fromEntries([...dependencies].sort().map(d => [d, "latest"])),
|
|
59
|
+
devDependencies: Object.fromEntries([...devDependencies].sort().map(d => [d, "latest"]))
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
await fs.writeFile(path.join(cwd, 'package.json'), JSON.stringify(pkgJson, null, 2));
|
|
63
|
+
console.log("✅ package.json generated with analyzed dependencies.");
|
|
64
|
+
|
|
65
|
+
// 3. Create /entkapp folder
|
|
66
|
+
await fs.mkdir(path.join(cwd, 'entkapp'), { recursive: true });
|
|
67
|
+
console.log("✅ /entkapp configuration folder created.");
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
extractPackageName(specifier) {
|
|
71
|
+
if (specifier.startsWith('@')) {
|
|
72
|
+
const parts = specifier.split('/');
|
|
73
|
+
return parts.length >= 2 ? `${parts[0]}/${parts[1]}` : null;
|
|
74
|
+
}
|
|
75
|
+
return specifier.split('/')[0];
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
isBuiltIn(pkg) {
|
|
79
|
+
const builtins = ['path', 'fs', 'os', 'crypto', 'http', 'https', 'stream', 'util', 'events', 'module', 'process'];
|
|
80
|
+
return builtins.includes(pkg) || pkg.startsWith('node:');
|
|
81
|
+
}
|
|
82
|
+
}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CodeSmellAnalyzer: Performs deep static analysis to find runtime risks and logic smells.
|
|
3
|
+
*/
|
|
4
|
+
export class CodeSmellAnalyzer {
|
|
5
|
+
constructor(context) {
|
|
6
|
+
this.context = context;
|
|
7
|
+
this.issues = [];
|
|
8
|
+
this.rules = {
|
|
9
|
+
'potential-null-pointer': {
|
|
10
|
+
message: 'Potential Null Pointer Risk: Accessing property on an object that might be undefined.',
|
|
11
|
+
link: 'https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Errors/Cant_access_property'
|
|
12
|
+
},
|
|
13
|
+
'infinite-loop-risk': {
|
|
14
|
+
message: 'Potential Infinite Loop: Loop condition does not seem to change within the body.',
|
|
15
|
+
link: 'https://en.wikipedia.org/wiki/Infinite_loop'
|
|
16
|
+
},
|
|
17
|
+
'implicit-type-coercion': {
|
|
18
|
+
message: 'Implicit Type Coercion: Using loose equality (==) can lead to unexpected runtime behavior.',
|
|
19
|
+
link: 'https://dorey.github.io/JavaScript-Equality-Table/'
|
|
20
|
+
}
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
analyze(fileNode) {
|
|
25
|
+
if (!fileNode.ast) return;
|
|
26
|
+
this.walk(fileNode.ast, fileNode);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
walk(node, fileNode) {
|
|
30
|
+
if (!node) return;
|
|
31
|
+
|
|
32
|
+
// 1. Check for Loose Equality (==)
|
|
33
|
+
if (node.type === 'BinaryExpression' && (node.operator === '==' || node.operator === '!=')) {
|
|
34
|
+
this.addIssue('implicit-type-coercion', node, fileNode);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// 2. Check for Potential Infinite Loops (While loops with static conditions)
|
|
38
|
+
if (node.type === 'WhileStatement' && node.test.type === 'Literal' && node.test.value === true) {
|
|
39
|
+
// Check if there is a break or return inside
|
|
40
|
+
if (!this.hasExitStatement(node.body)) {
|
|
41
|
+
this.addIssue('infinite-loop-risk', node, fileNode);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// 3. Check for Null Pointer Risks (Accessing properties on potentially uninitialized vars)
|
|
46
|
+
if (node.type === 'MemberExpression' && node.object.type === 'Identifier') {
|
|
47
|
+
const varName = node.object.name;
|
|
48
|
+
if (this.isPotentiallyNull(varName, fileNode)) {
|
|
49
|
+
this.addIssue('potential-null-pointer', node, fileNode);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Recursively walk the AST
|
|
54
|
+
for (const key in node) {
|
|
55
|
+
if (node[key] && typeof node[key] === 'object') {
|
|
56
|
+
if (Array.isArray(node[key])) {
|
|
57
|
+
node[key].forEach(child => this.walk(child, fileNode));
|
|
58
|
+
} else {
|
|
59
|
+
this.walk(node[key], fileNode);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
addIssue(ruleId, node, fileNode) {
|
|
66
|
+
const rule = this.rules[ruleId];
|
|
67
|
+
fileNode.diagnostics = fileNode.diagnostics || [];
|
|
68
|
+
fileNode.diagnostics.push({
|
|
69
|
+
ruleId,
|
|
70
|
+
message: rule.message,
|
|
71
|
+
link: rule.link,
|
|
72
|
+
line: node.start ? this.getLineNumber(node.start, fileNode.content) : 0,
|
|
73
|
+
severity: 'warning'
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
hasExitStatement(node) {
|
|
78
|
+
let found = false;
|
|
79
|
+
const check = (n) => {
|
|
80
|
+
if (!n || found) return;
|
|
81
|
+
if (n.type === 'BreakStatement' || n.type === 'ReturnStatement' || n.type === 'ThrowStatement') {
|
|
82
|
+
found = true;
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
for (const key in n) {
|
|
86
|
+
if (n[key] && typeof n[key] === 'object') {
|
|
87
|
+
if (Array.isArray(n[key])) n[key].forEach(check);
|
|
88
|
+
else check(n[key]);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
check(node);
|
|
93
|
+
return found;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
isPotentiallyNull(name, fileNode) {
|
|
97
|
+
// Simple heuristic: if it's a variable declared without init or in a try-catch
|
|
98
|
+
const symbol = fileNode.symbolTable?.get(name);
|
|
99
|
+
return symbol && symbol.type === 'variable' && !symbol.initialized;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
getLineNumber(pos, content) {
|
|
103
|
+
if (!content) return 0;
|
|
104
|
+
return content.substring(0, pos).split('\n').length;
|
|
105
|
+
}
|
|
106
|
+
}
|
package/src/ast/ASTAnalyzer.js
CHANGED
|
@@ -234,7 +234,11 @@ export class ASTAnalyzer {
|
|
|
234
234
|
}
|
|
235
235
|
}
|
|
236
236
|
break;
|
|
237
|
+
|
|
237
238
|
case ts.SyntaxKind.CallExpression:
|
|
239
|
+
// FIX: Führe zuerst die generische Aufrufanalyse aus, die vorher blockiert war
|
|
240
|
+
this.handleCallExpression(node, fileNode, sourceFile);
|
|
241
|
+
|
|
238
242
|
if (node.expression.kind === ts.SyntaxKind.ImportKeyword) {
|
|
239
243
|
const arg = node.arguments[0];
|
|
240
244
|
if (arg) {
|
|
@@ -244,14 +248,12 @@ export class ASTAnalyzer {
|
|
|
244
248
|
fileNode.explicitImports.add(specifier);
|
|
245
249
|
} else {
|
|
246
250
|
// UPGRADE: Handle Dynamic Imports with variables/expressions
|
|
247
|
-
// We mark the file as having "calculated" dynamic imports to be conservative
|
|
248
251
|
fileNode.hasCalculatedDynamicImports = true;
|
|
249
252
|
const exprText = arg.getText(sourceFile);
|
|
250
|
-
if (!fileNode.calculatedDynamicImports
|
|
253
|
+
if (!fileNode.calculatedDynamicImports || Array.isArray(fileNode.calculatedDynamicImports)) {
|
|
254
|
+
fileNode.calculatedDynamicImports = new Set();
|
|
255
|
+
}
|
|
251
256
|
fileNode.calculatedDynamicImports.add(exprText);
|
|
252
|
-
|
|
253
|
-
// Conservative approach: If we see `import(path)`, any file in the same or sub directory could be a target.
|
|
254
|
-
// This is a simplified version of what knip/entkapp does for plugins.
|
|
255
257
|
fileNode.dynamicImports.add('__DYNAMIC_PATTERN__');
|
|
256
258
|
}
|
|
257
259
|
}
|
|
@@ -269,12 +271,23 @@ export class ASTAnalyzer {
|
|
|
269
271
|
// Dynamic require with template literal
|
|
270
272
|
fileNode.hasCalculatedDynamicImports = true;
|
|
271
273
|
const exprText = arg.getText(sourceFile);
|
|
272
|
-
|
|
273
|
-
|
|
274
|
+
|
|
275
|
+
// FIX: Typ-Integrität als Set erzwingen (kein Array [])
|
|
276
|
+
if (!fileNode.calculatedDynamicImports || Array.isArray(fileNode.calculatedDynamicImports)) {
|
|
277
|
+
fileNode.calculatedDynamicImports = new Set();
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
// FIX: .add() statt .push() verwenden, um Absturz in Pass 2 zu verhindern
|
|
281
|
+
fileNode.calculatedDynamicImports.add({
|
|
282
|
+
kind: 'DynamicRequire',
|
|
283
|
+
expression: exprText,
|
|
284
|
+
start: arg.getStart(sourceFile)
|
|
285
|
+
});
|
|
274
286
|
fileNode.dynamicImports.add('__DYNAMIC_PATTERN__');
|
|
275
287
|
}
|
|
276
288
|
}
|
|
277
289
|
break;
|
|
290
|
+
|
|
278
291
|
case ts.SyntaxKind.PropertyAccessExpression:
|
|
279
292
|
{
|
|
280
293
|
const chain = node.getText(sourceFile);
|
|
@@ -284,9 +297,7 @@ export class ASTAnalyzer {
|
|
|
284
297
|
}
|
|
285
298
|
}
|
|
286
299
|
break;
|
|
287
|
-
|
|
288
|
-
this.handleCallExpression(node, fileNode, sourceFile);
|
|
289
|
-
break;
|
|
300
|
+
|
|
290
301
|
case ts.SyntaxKind.JsxElement:
|
|
291
302
|
case ts.SyntaxKind.JsxSelfClosingElement:
|
|
292
303
|
this.handleJsxElement(node, fileNode, sourceFile);
|
|
@@ -580,7 +591,9 @@ export class ASTAnalyzer {
|
|
|
580
591
|
}
|
|
581
592
|
} else {
|
|
582
593
|
// Non-literal dynamic import
|
|
583
|
-
if (!fileNode.calculatedDynamicImports)
|
|
594
|
+
if (!Array.isArray(fileNode.calculatedDynamicImports)) {
|
|
595
|
+
fileNode.calculatedDynamicImports = [];
|
|
596
|
+
}
|
|
584
597
|
fileNode.calculatedDynamicImports.push({ kind: ts.SyntaxKind[arg.kind], start: arg.getStart(sourceFile) });
|
|
585
598
|
|
|
586
599
|
// UPGRADE 2: Try to perform Glob-Analysis on Template Strings
|
|
@@ -605,11 +618,13 @@ export class ASTAnalyzer {
|
|
|
605
618
|
if (ts.isTemplateExpression(node)) {
|
|
606
619
|
const head = node.head.text;
|
|
607
620
|
if (head.startsWith('./') || head.startsWith('../')) {
|
|
608
|
-
//
|
|
621
|
+
// FIX: Typ-Integrität als Set erzwingen, kein Array!
|
|
622
|
+
if (!fileNode.globImports || Array.isArray(fileNode.globImports)) {
|
|
623
|
+
fileNode.globImports = new Set();
|
|
624
|
+
}
|
|
609
625
|
const dirPath = path.dirname(head);
|
|
610
626
|
if (dirPath && dirPath !== '.') {
|
|
611
|
-
|
|
612
|
-
fileNode.globImports.add(dirPath);
|
|
627
|
+
fileNode.globImports.add(dirPath); // Jetzt klappt .add() garantiert!
|
|
613
628
|
}
|
|
614
629
|
}
|
|
615
630
|
}
|
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
|
}
|
|
@@ -140,10 +148,6 @@ export class BarrelParser {
|
|
|
140
148
|
* @param {Map} activeProjectGraph - Global active module maps directory registry
|
|
141
149
|
* @param {Set} [protectionStack] - Avoids cyclic validation traps inside self-referencing links
|
|
142
150
|
*/
|
|
143
|
-
/**
|
|
144
|
-
* UPGRADE 3: Cross-Barrel Symbol Tracking.
|
|
145
|
-
* Resolves the original source of a symbol through re-exports and aliases.
|
|
146
|
-
*/
|
|
147
151
|
async determineSymbolDeclarationOrigin(contextFilePath, targetSymbolName, activeProjectGraph, protectionStack = new Set()) {
|
|
148
152
|
if (protectionStack.has(contextFilePath)) {
|
|
149
153
|
return { originFile: contextFilePath, originSymbol: targetSymbolName };
|
|
@@ -172,7 +176,6 @@ export class BarrelParser {
|
|
|
172
176
|
// Rule C: Evaluate structural namespace alias groupings (export * as name from 'module')
|
|
173
177
|
for (const [namespaceAlias, relativeModule] of spec.namespacedWildcardExports.entries()) {
|
|
174
178
|
if (targetSymbolName === namespaceAlias) {
|
|
175
|
-
// The symbol IS the namespace itself
|
|
176
179
|
const fullyResolvedPath = this.resolver.resolveModulePath(contextFilePath, relativeModule);
|
|
177
180
|
return { originFile: fullyResolvedPath, originSymbol: '*' };
|
|
178
181
|
}
|
|
@@ -190,7 +193,7 @@ export class BarrelParser {
|
|
|
190
193
|
const fullyResolvedPath = this.resolver.resolveModulePath(contextFilePath, relativePath);
|
|
191
194
|
|
|
192
195
|
if (fullyResolvedPath) {
|
|
193
|
-
//
|
|
196
|
+
// Verbinde Graph-Kanten direkt während der Wildcard-Traversierung
|
|
194
197
|
const contextNode = activeProjectGraph.get(contextFilePath);
|
|
195
198
|
if (contextNode) {
|
|
196
199
|
contextNode.outgoingEdges.add(fullyResolvedPath);
|
|
@@ -204,13 +207,12 @@ export class BarrelParser {
|
|
|
204
207
|
fullyResolvedPath,
|
|
205
208
|
targetSymbolName,
|
|
206
209
|
activeProjectGraph,
|
|
207
|
-
new Set(protectionStack) //
|
|
210
|
+
new Set(protectionStack) // Kopie verhindert geschwisterliche Branch-Blockaden
|
|
208
211
|
);
|
|
209
212
|
|
|
210
213
|
if (!continuousResolutionTrace) continue;
|
|
211
214
|
if (continuousResolutionTrace.originFile === contextFilePath) continue;
|
|
212
215
|
|
|
213
|
-
// Verify that the resolved origin actually declares the symbol.
|
|
214
216
|
const originSpec = await this.parseBarrelSpecification(continuousResolutionTrace.originFile);
|
|
215
217
|
if (originSpec.declaredLocalExports.has(continuousResolutionTrace.originSymbol) ||
|
|
216
218
|
continuousResolutionTrace.originSymbol === '*') {
|
|
@@ -225,4 +227,4 @@ export class BarrelParser {
|
|
|
225
227
|
|
|
226
228
|
return { originFile: contextFilePath, originSymbol: targetSymbolName };
|
|
227
229
|
}
|
|
228
|
-
}
|
|
230
|
+
}
|