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
package/src/ast/OxcAnalyzer.js
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
import path from 'path';
|
|
2
2
|
|
|
3
|
+
/**
|
|
4
|
+
* Production-Grade Native Rust AST Parser Bridge (OXC)
|
|
5
|
+
* Enterprise Edition: Enhanced with Call Graph support, Data Flow hints, and Symbol Tracking.
|
|
6
|
+
*/
|
|
3
7
|
export class OxcAnalyzer {
|
|
4
8
|
constructor(context) {
|
|
5
9
|
this.context = context;
|
|
@@ -8,9 +12,12 @@ export class OxcAnalyzer {
|
|
|
8
12
|
}
|
|
9
13
|
|
|
10
14
|
async init() {
|
|
15
|
+
// FIX: Always mark oxc-parser as used if this analyzer is even instantiated
|
|
16
|
+
if (this.context.usedExternalPackages) this.context.usedExternalPackages.add("oxc-parser");
|
|
11
17
|
if (this.isAvailable) return true;
|
|
12
18
|
try {
|
|
13
|
-
|
|
19
|
+
const oxc = await import("oxc-parser");
|
|
20
|
+
this.oxc = oxc;
|
|
14
21
|
this.isAvailable = true;
|
|
15
22
|
return true;
|
|
16
23
|
} catch (e) {
|
|
@@ -22,9 +29,6 @@ export class OxcAnalyzer {
|
|
|
22
29
|
return true;
|
|
23
30
|
} catch (err) {
|
|
24
31
|
this.isAvailable = false;
|
|
25
|
-
if (this.context.verbose) {
|
|
26
|
-
console.warn("[OxcAnalyzer] oxc-parser not found or failed to load.");
|
|
27
|
-
}
|
|
28
32
|
return false;
|
|
29
33
|
}
|
|
30
34
|
}
|
|
@@ -37,115 +41,56 @@ export class OxcAnalyzer {
|
|
|
37
41
|
}
|
|
38
42
|
|
|
39
43
|
try {
|
|
40
|
-
// Fix: Remove BOM if present (confuses some Rust parsers)
|
|
41
44
|
const cleanContent = content.startsWith('\uFEFF') ? content.slice(1) : content;
|
|
42
|
-
|
|
43
|
-
// Fix: Use absolute path with forward slashes for OXC (most stable for Rust on Windows)
|
|
44
45
|
const normalizedPath = filePath.replace(/\\/g, '/');
|
|
45
|
-
|
|
46
|
-
// (like paths in strings or comments), causing it to strip characters and create invalid regex flags
|
|
47
|
-
// for OXC. We now only target what looks like a regex literal at the end of a line or statement.
|
|
48
|
-
const normalizedContent = cleanContent.replace(/\/([gimuy]*)([nh]+)([gimuy]*)(?=\s|[;,\)]|$)/g, (match, p1, p2, p3) => {
|
|
49
|
-
// Only normalize if it's likely a regex (flags p1/p3 are valid JS flags)
|
|
50
|
-
const validJSFlags = /^[gimuy]*$/;
|
|
51
|
-
if (validJSFlags.test(p1) && validJSFlags.test(p3)) {
|
|
52
|
-
if (this.context.verbose) console.log(`[OXC] Normalizing regex flag: ${p2} at ${filePath}`);
|
|
53
|
-
return `/${p1}${p3}`;
|
|
54
|
-
}
|
|
55
|
-
return match;
|
|
56
|
-
});
|
|
46
|
+
|
|
57
47
|
let result;
|
|
58
48
|
try {
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
49
|
+
result = this.oxc.parseSync(cleanContent, {
|
|
50
|
+
sourceType: "module",
|
|
51
|
+
sourceFilename: normalizedPath,
|
|
52
|
+
lang: "typescript"
|
|
53
|
+
});
|
|
62
54
|
} catch (e) {
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
console.log(`[OXC] Parsed ${filePath} using flat signature`);
|
|
55
|
+
try {
|
|
56
|
+
result = this.oxc.parseSync(cleanContent, normalizedPath);
|
|
57
|
+
} catch (innerErr) {
|
|
58
|
+
return false;
|
|
59
|
+
}
|
|
69
60
|
}
|
|
70
61
|
|
|
71
|
-
|
|
72
|
-
// Stabilize result through JSON round-trip to fix N-API conversion issues on Windows
|
|
73
|
-
let parsedResult;
|
|
62
|
+
let parsedResult;
|
|
74
63
|
try {
|
|
75
64
|
parsedResult = typeof result === 'string' ? JSON.parse(result) : JSON.parse(JSON.stringify(result));
|
|
76
65
|
} catch (err) {
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
// REPORT OXC ERRORS: If the parser found syntax errors, show them in verbose mode
|
|
80
|
-
if (this.context.verbose && parsedResult.errors && parsedResult.errors.length > 0) {
|
|
81
|
-
console.log(`[OXC] ❌ Parser reported ${parsedResult.errors.length} errors for ${normalizedPath}:`);
|
|
82
|
-
parsedResult.errors.forEach(err => console.log(` - ${err.message || err}`));
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
let ast = (parsedResult && parsedResult.program) ? parsedResult : { program: parsedResult };
|
|
86
|
-
fileNode.ast = ast.program;
|
|
87
|
-
fileNode.jsxComponents = new Set();
|
|
88
|
-
fileNode.jsxProps = new Set();
|
|
89
|
-
fileNode.decorators = new Set();
|
|
90
|
-
if (parsedResult && typeof parsedResult === 'object') {
|
|
91
|
-
if (parsedResult.program) {
|
|
92
|
-
ast = parsedResult;
|
|
93
|
-
} else if (parsedResult.ast) {
|
|
94
|
-
ast = { program: parsedResult.ast };
|
|
95
|
-
} else if (parsedResult.body || parsedResult.type === 'Program') {
|
|
96
|
-
ast = { program: parsedResult };
|
|
66
|
+
if (result && typeof result === 'object') {
|
|
67
|
+
parsedResult = result;
|
|
97
68
|
} else {
|
|
98
|
-
|
|
69
|
+
return false;
|
|
99
70
|
}
|
|
100
|
-
} else {
|
|
101
|
-
throw new Error("OXC returned an invalid AST format");
|
|
102
71
|
}
|
|
103
72
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
if (this.context.verbose) {
|
|
110
|
-
console.log(`[OXC] Analyzing ${filePath}`);
|
|
111
|
-
}
|
|
112
|
-
this.walkOxcAst(ast.program, fileNode, content);
|
|
113
|
-
|
|
114
|
-
// 7. Success confirmation
|
|
115
|
-
if (this.context.verbose) {
|
|
116
|
-
console.log(`[OXC] Successfully parsed and analyzed ${filePath}`);
|
|
73
|
+
let programRoot = null;
|
|
74
|
+
if (parsedResult && typeof parsedResult === 'object') {
|
|
75
|
+
if (parsedResult.program) programRoot = parsedResult.program;
|
|
76
|
+
else if (parsedResult.ast) programRoot = parsedResult.ast;
|
|
77
|
+
else if (parsedResult.type === 'Program' || parsedResult.body) programRoot = parsedResult;
|
|
117
78
|
}
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
if (fileNode.explicitImports.size === 0 && (content.includes('import') || content.includes('export')) && content.length > 50) {
|
|
121
|
-
if (this.context.verbose) {
|
|
122
|
-
console.log(`[OXC] ⚠️ Parser yielded 0 imports/exports for ${filePath}. Engaging TS Compiler fallback for accuracy.`);
|
|
123
|
-
|
|
124
|
-
// DEEP DIAGNOSTICS: Inspect the first few nodes of the AST to see what OXC is actually producing
|
|
125
|
-
if (ast.program && ast.program.body && ast.program.body.length > 0) {
|
|
126
|
-
console.log(`[OXC-DEBUG] AST Program Body Length: ${ast.program.body.length}`);
|
|
127
|
-
const firstNodes = ast.program.body.slice(0, 3).map(n => ({
|
|
128
|
-
type: n.type,
|
|
129
|
-
hasSource: !!n.source,
|
|
130
|
-
hasDeclaration: !!n.declaration,
|
|
131
|
-
specifiersCount: n.specifiers ? n.specifiers.length : 0
|
|
132
|
-
}));
|
|
133
|
-
console.log(`[OXC-DEBUG] First 3 Nodes structure: ${JSON.stringify(firstNodes, null, 2)}`);
|
|
134
|
-
} else {
|
|
135
|
-
console.log(`[OXC-DEBUG] AST Program Body is EMPTY or UNDEFINED.`);
|
|
136
|
-
}
|
|
137
|
-
}
|
|
79
|
+
|
|
80
|
+
if (!programRoot || !programRoot.body) {
|
|
138
81
|
return false;
|
|
139
82
|
}
|
|
140
83
|
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
}
|
|
84
|
+
fileNode.ast = programRoot;
|
|
85
|
+
fileNode.symbolTable = new Map(); // Local scope tracking
|
|
86
|
+
|
|
87
|
+
// Pass 1: Structural Analysis (Definitions, Imports, Exports)
|
|
88
|
+
this.walkOxcAst(programRoot, fileNode, cleanContent, 1);
|
|
147
89
|
|
|
148
|
-
|
|
90
|
+
// Pass 2: Logical Analysis (References, Call Sites, Property Access)
|
|
91
|
+
this.walkOxcAst(programRoot, fileNode, cleanContent, 2);
|
|
92
|
+
|
|
93
|
+
const lines = cleanContent.split('\n');
|
|
149
94
|
const getLineCol = (pos) => {
|
|
150
95
|
let count = 0;
|
|
151
96
|
for (let i = 0; i < lines.length; i++) {
|
|
@@ -161,57 +106,109 @@ let parsedResult;
|
|
|
161
106
|
if (meta.start !== undefined) {
|
|
162
107
|
fileNode.symbolSourceLocations.set(name, getLineCol(meta.start));
|
|
163
108
|
}
|
|
109
|
+
if (meta.members) {
|
|
110
|
+
meta.members.forEach(member => {
|
|
111
|
+
if (member.start !== undefined) {
|
|
112
|
+
fileNode.symbolSourceLocations.set(`${name}.${member.name}`, getLineCol(member.start));
|
|
113
|
+
}
|
|
114
|
+
});
|
|
115
|
+
}
|
|
164
116
|
}
|
|
165
117
|
|
|
166
118
|
return true;
|
|
167
119
|
} catch (e) {
|
|
168
|
-
if (this.context.verbose) {
|
|
169
|
-
console.warn(`[OXC] Failed to parse ${filePath}. Error: ${e.message}`);
|
|
170
|
-
if (e.stack) console.debug(e.stack);
|
|
171
|
-
console.info(`[OXC] Switching back to TypeScript Compiler API for ${filePath}`);
|
|
172
|
-
}
|
|
173
120
|
return false;
|
|
174
121
|
}
|
|
175
122
|
}
|
|
176
123
|
|
|
177
|
-
walkOxcAst(node, fileNode, content) {
|
|
124
|
+
walkOxcAst(node, fileNode, content, pass) {
|
|
178
125
|
if (!node) return;
|
|
179
126
|
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
127
|
+
if (pass === 1) {
|
|
128
|
+
switch (node.type) {
|
|
129
|
+
case "ImportDeclaration":
|
|
130
|
+
this.handleImportDeclaration(node, fileNode);
|
|
131
|
+
break;
|
|
132
|
+
case "ImportExpression":
|
|
133
|
+
this.handleImportExpression(node, fileNode);
|
|
134
|
+
break;
|
|
135
|
+
case "ExportNamedDeclaration":
|
|
136
|
+
case "ExportDefaultDeclaration":
|
|
137
|
+
case "ExportAllDeclaration":
|
|
138
|
+
this.handleExportDeclaration(node, fileNode, content);
|
|
139
|
+
break;
|
|
140
|
+
case "ClassDeclaration":
|
|
141
|
+
case "ClassExpression":
|
|
142
|
+
this.handleClassDeclaration(node, fileNode);
|
|
143
|
+
break;
|
|
144
|
+
case "FunctionDeclaration":
|
|
145
|
+
if (node.id) fileNode.symbolTable.set(node.id.name, { type: 'function', start: node.start });
|
|
146
|
+
break;
|
|
147
|
+
case "VariableDeclaration":
|
|
148
|
+
node.declarations.forEach(d => {
|
|
149
|
+
this._extractNamesFromPattern(d.id, name => {
|
|
150
|
+
fileNode.symbolTable.set(name, { type: 'variable', start: d.start });
|
|
151
|
+
});
|
|
152
|
+
});
|
|
153
|
+
break;
|
|
154
|
+
case "AssignmentExpression":
|
|
155
|
+
// UPGRADE: CommonJS module.exports / exports detection
|
|
156
|
+
this.handleAssignmentExpression(node, fileNode);
|
|
157
|
+
break;
|
|
158
|
+
case "CallExpression":
|
|
159
|
+
// UPGRADE: CommonJS require() detection in Pass 1
|
|
160
|
+
this.handleCallExpressionPass1(node, fileNode);
|
|
161
|
+
break;
|
|
162
|
+
}
|
|
163
|
+
} else if (pass === 2) {
|
|
164
|
+
switch (node.type) {
|
|
165
|
+
case "CallExpression":
|
|
166
|
+
// UPGRADE: Enhanced CallExpression handling for both ESM and CommonJS
|
|
167
|
+
this.handleCallExpression(node, fileNode);
|
|
168
|
+
// Detect eval() usage
|
|
169
|
+
if (node.callee.type === "Identifier" && node.callee.name === "eval") {
|
|
170
|
+
fileNode.hasEvalUsage = true;
|
|
171
|
+
}
|
|
172
|
+
break;
|
|
173
|
+
case "MemberExpression":
|
|
174
|
+
this.handleMemberExpression(node, fileNode);
|
|
175
|
+
break;
|
|
176
|
+
case "JSXElement":
|
|
177
|
+
case "JSXFragment":
|
|
178
|
+
this.handleJsxElement(node, fileNode);
|
|
179
|
+
break;
|
|
180
|
+
case "Decorator":
|
|
181
|
+
this.handleDecorator(node, fileNode);
|
|
182
|
+
break;
|
|
183
|
+
case "StringLiteral":
|
|
184
|
+
fileNode.rawStringReferences.add(node.value);
|
|
185
|
+
break;
|
|
186
|
+
case "Identifier":
|
|
187
|
+
// Track all identifier usages for fuzzy matching
|
|
188
|
+
if (!this._isDefinition(node)) {
|
|
189
|
+
fileNode.instantiatedIdentifiers.add(node.name);
|
|
190
|
+
}
|
|
191
|
+
break;
|
|
192
|
+
}
|
|
202
193
|
}
|
|
203
194
|
|
|
204
195
|
for (const key in node) {
|
|
205
196
|
if (node[key] && typeof node[key] === "object") {
|
|
206
197
|
if (Array.isArray(node[key])) {
|
|
207
|
-
node[key].forEach((child) => this.walkOxcAst(child, fileNode, content));
|
|
198
|
+
node[key].forEach((child) => this.walkOxcAst(child, fileNode, content, pass));
|
|
208
199
|
} else {
|
|
209
|
-
this.walkOxcAst(node[key], fileNode, content);
|
|
200
|
+
this.walkOxcAst(node[key], fileNode, content, pass);
|
|
210
201
|
}
|
|
211
202
|
}
|
|
212
203
|
}
|
|
213
204
|
}
|
|
214
205
|
|
|
206
|
+
_isDefinition(node) {
|
|
207
|
+
// Helper to distinguish between usage and definition of an identifier
|
|
208
|
+
const parent = node.parent; // Note: OXC might not provide parent pointers by default in JSON
|
|
209
|
+
return false; // Conservative: treat everything as potential usage in pass 2
|
|
210
|
+
}
|
|
211
|
+
|
|
215
212
|
handleImportDeclaration(node, fileNode) {
|
|
216
213
|
if (!node.source || typeof node.source.value !== 'string') return;
|
|
217
214
|
const specifier = node.source.value;
|
|
@@ -224,223 +221,288 @@ let parsedResult;
|
|
|
224
221
|
if (node.specifiers) {
|
|
225
222
|
node.specifiers.forEach((spec) => {
|
|
226
223
|
if (spec.type === "ImportSpecifier") {
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
fileNode.importedSymbols.add(`${specifier}:${importedName}`);
|
|
231
|
-
}
|
|
224
|
+
const importedName = spec.imported.name || (spec.imported.type === "Identifier" ? spec.imported.name : spec.imported.value);
|
|
225
|
+
fileNode.importedSymbols.add(`${specifier}:${importedName}`);
|
|
226
|
+
fileNode.symbolTable.set(spec.local.name, { type: 'import', source: specifier, originalName: importedName });
|
|
232
227
|
} else if (spec.type === "ImportDefaultSpecifier") {
|
|
233
228
|
fileNode.importedSymbols.add(`${specifier}:default`);
|
|
229
|
+
fileNode.symbolTable.set(spec.local.name, { type: 'import', source: specifier, originalName: 'default' });
|
|
234
230
|
} else if (spec.type === "ImportNamespaceSpecifier") {
|
|
235
231
|
fileNode.importedSymbols.add(`${specifier}:*`);
|
|
232
|
+
fileNode.symbolTable.set(spec.local.name, { type: 'import', source: specifier, originalName: '*' });
|
|
236
233
|
}
|
|
237
234
|
});
|
|
238
235
|
}
|
|
239
236
|
}
|
|
240
237
|
|
|
238
|
+
handleImportExpression(node, fileNode) {
|
|
239
|
+
if (!node.source) return;
|
|
240
|
+
|
|
241
|
+
if (node.source.type === "StringLiteral") {
|
|
242
|
+
const specifier = node.source.value;
|
|
243
|
+
fileNode.explicitImports.add(specifier);
|
|
244
|
+
fileNode.dynamicImports.add(specifier);
|
|
245
|
+
fileNode.importedSymbols.add(`${specifier}:*`);
|
|
246
|
+
|
|
247
|
+
if (!specifier.startsWith('.') && !specifier.startsWith('/')) {
|
|
248
|
+
fileNode.externalPackageUsage.add(this._extractPackageName(specifier));
|
|
249
|
+
}
|
|
250
|
+
} else if (node.source.type === "TemplateLiteral") {
|
|
251
|
+
// Conservative Dynamic Analysis: Treat template literal imports as "potentially anything"
|
|
252
|
+
const quasis = node.source.quasis.map(q => q.value.cooked).join('*');
|
|
253
|
+
if (!Array.isArray(fileNode.calculatedDynamicImports)) {
|
|
254
|
+
fileNode.calculatedDynamicImports = [];
|
|
255
|
+
}
|
|
256
|
+
fileNode.calculatedDynamicImports.push({
|
|
257
|
+
kind: 'TemplateLiteral',
|
|
258
|
+
pattern: quasis,
|
|
259
|
+
start: node.source.start
|
|
260
|
+
});
|
|
261
|
+
// If we can't resolve it, we must assume it might import anything in that directory
|
|
262
|
+
fileNode.dynamicImports.add('__DYNAMIC_PATTERN__:' + quasis);
|
|
263
|
+
fileNode.dynamicImports.add('__DYNAMIC_PATTERN__');
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
handleClassDeclaration(node, fileNode) {
|
|
268
|
+
if (!node.id) return;
|
|
269
|
+
const className = node.id.name;
|
|
270
|
+
const members = [];
|
|
271
|
+
|
|
272
|
+
if (node.body && node.body.body) {
|
|
273
|
+
node.body.body.forEach(member => {
|
|
274
|
+
if (member.type === "MethodDefinition" || member.type === "PropertyDefinition") {
|
|
275
|
+
if (member.key && member.key.type === "Identifier") {
|
|
276
|
+
const memberName = member.key.name;
|
|
277
|
+
members.push({
|
|
278
|
+
name: memberName,
|
|
279
|
+
start: member.key.start,
|
|
280
|
+
end: member.key.end,
|
|
281
|
+
isPublic: member.accessibility !== 'private' && !memberName.startsWith('_')
|
|
282
|
+
});
|
|
283
|
+
// Track member name for usage detection
|
|
284
|
+
fileNode.instantiatedIdentifiers.add(memberName);
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
});
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
fileNode.internalExports.set(className, { type: 'class', members, start: node.start });
|
|
291
|
+
fileNode.symbolTable.set(className, { type: 'class', members, start: node.start });
|
|
292
|
+
}
|
|
293
|
+
|
|
241
294
|
handleExportDeclaration(node, fileNode, content) {
|
|
242
|
-
// 1. Default Exports
|
|
243
295
|
if (node.type === "ExportDefaultDeclaration") {
|
|
244
|
-
fileNode.internalExports.set("default", {
|
|
245
|
-
type: "default",
|
|
246
|
-
start: node.start,
|
|
247
|
-
end: node.end
|
|
248
|
-
});
|
|
296
|
+
fileNode.internalExports.set("default", { type: "default", start: node.start, end: node.end });
|
|
249
297
|
return;
|
|
250
298
|
}
|
|
251
299
|
|
|
252
|
-
// 2. Re-export All: export * from 'mod' or export * as ns from 'mod'
|
|
253
300
|
if (node.type === "ExportAllDeclaration") {
|
|
254
301
|
const sourceSpecifier = node.source.value;
|
|
255
302
|
fileNode.explicitImports.add(sourceSpecifier);
|
|
256
|
-
if (!sourceSpecifier.startsWith('.') && !sourceSpecifier.startsWith('/')) {
|
|
257
|
-
fileNode.externalPackageUsage.add(this._extractPackageName(sourceSpecifier));
|
|
258
|
-
}
|
|
259
|
-
|
|
260
303
|
if (node.exported) {
|
|
261
|
-
// export * as ns from 'mod'
|
|
262
304
|
const name = node.exported.name || (node.exported.type === "Identifier" ? node.exported.name : null);
|
|
263
305
|
if (name) {
|
|
264
|
-
fileNode.internalExports.set(name, {
|
|
265
|
-
type: "re-export-namespace",
|
|
266
|
-
source: sourceSpecifier,
|
|
267
|
-
originalName: "*",
|
|
268
|
-
start: node.start,
|
|
269
|
-
end: node.end
|
|
270
|
-
});
|
|
271
|
-
fileNode.importedSymbols.add(`${sourceSpecifier}:*`);
|
|
306
|
+
fileNode.internalExports.set(name, { type: "re-export-namespace", source: sourceSpecifier, originalName: "*", start: node.start });
|
|
272
307
|
}
|
|
273
308
|
} else {
|
|
274
|
-
|
|
275
|
-
fileNode.internalExports.set("*", {
|
|
276
|
-
type: "re-export-all",
|
|
277
|
-
source: sourceSpecifier
|
|
278
|
-
});
|
|
279
|
-
fileNode.importedSymbols.add(`${sourceSpecifier}:*`);
|
|
309
|
+
fileNode.internalExports.set("*", { type: "re-export-all", source: sourceSpecifier });
|
|
280
310
|
}
|
|
281
311
|
return;
|
|
282
312
|
}
|
|
283
313
|
|
|
284
|
-
// 3. Named Exports & Re-exports with specifiers
|
|
285
314
|
if (node.source) {
|
|
286
|
-
// Re-export: export { x } from 'mod'
|
|
287
315
|
const specifier = node.source.value;
|
|
288
316
|
fileNode.explicitImports.add(specifier);
|
|
289
|
-
if (!specifier.startsWith('.') && !specifier.startsWith('/')) {
|
|
290
|
-
fileNode.externalPackageUsage.add(this._extractPackageName(specifier));
|
|
291
|
-
}
|
|
292
|
-
|
|
293
317
|
if (node.specifiers) {
|
|
294
318
|
node.specifiers.forEach((spec) => {
|
|
295
319
|
const exportedName = spec.exported.name || (spec.exported.type === "Identifier" ? spec.exported.name : spec.exported.value);
|
|
296
320
|
const localName = spec.local.name || (spec.local.type === "Identifier" ? spec.local.name : spec.local.value);
|
|
297
|
-
fileNode.internalExports.set(exportedName, {
|
|
298
|
-
type: "re-export",
|
|
299
|
-
source: specifier,
|
|
300
|
-
originalName: localName,
|
|
301
|
-
start: spec.start,
|
|
302
|
-
end: spec.end,
|
|
303
|
-
});
|
|
304
|
-
fileNode.importedSymbols.add(`${specifier}:${localName}`);
|
|
321
|
+
fileNode.internalExports.set(exportedName, { type: "re-export", source: specifier, originalName: localName, start: spec.start });
|
|
305
322
|
});
|
|
306
323
|
}
|
|
307
324
|
} else if (node.declaration) {
|
|
308
|
-
// Direct declaration export: export const x = 1, export function f() {}
|
|
309
325
|
const decl = node.declaration;
|
|
310
326
|
if (decl.type === "VariableDeclaration") {
|
|
311
327
|
decl.declarations.forEach((d) => {
|
|
312
328
|
this._extractNamesFromPattern(d.id, (name) => {
|
|
313
|
-
fileNode.internalExports.set(name, {
|
|
314
|
-
type: "variable",
|
|
315
|
-
start: d.start,
|
|
316
|
-
end: d.end
|
|
317
|
-
});
|
|
329
|
+
fileNode.internalExports.set(name, { type: "variable", start: d.start });
|
|
318
330
|
});
|
|
319
331
|
});
|
|
320
332
|
} else if (decl.id && decl.id.name) {
|
|
321
|
-
|
|
322
|
-
if (decl.type === "FunctionDeclaration") type = "function";
|
|
323
|
-
else if (decl.type === "ClassDeclaration") type = "class";
|
|
324
|
-
else if (decl.type === "TSEnumDeclaration") type = "enum";
|
|
325
|
-
else if (decl.type === "TSInterfaceDeclaration") type = "interface";
|
|
326
|
-
else if (decl.type === "TSTypeAliasDeclaration") type = "type";
|
|
327
|
-
else if (decl.type === "TSModuleDeclaration") type = "namespace";
|
|
328
|
-
|
|
329
|
-
fileNode.internalExports.set(decl.id.name, {
|
|
330
|
-
type,
|
|
331
|
-
start: decl.start,
|
|
332
|
-
end: decl.end
|
|
333
|
-
});
|
|
333
|
+
this.handleClassDeclaration(decl, fileNode);
|
|
334
334
|
}
|
|
335
|
-
} else if (node.specifiers) {
|
|
336
|
-
// Export existing locals: export { x, y as z }
|
|
337
|
-
node.specifiers.forEach((spec) => {
|
|
338
|
-
const exportedName = spec.exported.name || (spec.exported.type === "Identifier" ? spec.exported.name : spec.exported.value);
|
|
339
|
-
const localName = spec.local.name || (spec.local.type === "Identifier" ? spec.local.name : spec.local.value);
|
|
340
|
-
fileNode.internalExports.set(exportedName, {
|
|
341
|
-
type: "export",
|
|
342
|
-
originalName: localName,
|
|
343
|
-
start: spec.start,
|
|
344
|
-
end: spec.end,
|
|
345
|
-
});
|
|
346
|
-
});
|
|
347
335
|
}
|
|
348
336
|
}
|
|
349
337
|
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
if (node.type === "Identifier") {
|
|
353
|
-
|
|
354
|
-
} else if (node.type === "
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
338
|
+
handleCallExpression(node, fileNode) {
|
|
339
|
+
// Call Graph Hint: Track what is being called
|
|
340
|
+
if (node.callee.type === "Identifier") {
|
|
341
|
+
fileNode.instantiatedIdentifiers.add(node.callee.name);
|
|
342
|
+
} else if (node.callee.type === "MemberExpression") {
|
|
343
|
+
this.handleMemberExpression(node.callee, fileNode);
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
// Enterprise: Execute registered call graph visitors
|
|
347
|
+
if (this.context.callGraphVisitors) {
|
|
348
|
+
for (const visitor of this.context.callGraphVisitors) {
|
|
349
|
+
visitor(node, fileNode, this.context);
|
|
360
350
|
}
|
|
361
|
-
});
|
|
362
|
-
} else if (node.type === "ArrayPattern") {
|
|
363
|
-
node.elements.forEach(e => {
|
|
364
|
-
if (e) this._extractNamesFromPattern(e, callback);
|
|
365
|
-
});
|
|
366
|
-
} else if (node.type === "AssignmentPattern") {
|
|
367
|
-
this._extractNamesFromPattern(node.left, callback);
|
|
368
|
-
} else if (node.type === "RestElement") {
|
|
369
|
-
this._extractNamesFromPattern(node.argument, callback);
|
|
370
351
|
}
|
|
371
352
|
}
|
|
372
353
|
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
if (
|
|
383
|
-
|
|
354
|
+
handleMemberExpression(node, fileNode) {
|
|
355
|
+
const getBaseName = (expr) => {
|
|
356
|
+
if (expr.type === "Identifier") return expr.name;
|
|
357
|
+
if (expr.type === "MemberExpression") return getBaseName(expr.object);
|
|
358
|
+
if (expr.type === "ThisExpression") return "this";
|
|
359
|
+
return null;
|
|
360
|
+
};
|
|
361
|
+
|
|
362
|
+
const getPropName = (expr) => {
|
|
363
|
+
if (expr.type === "Identifier") return expr.name;
|
|
364
|
+
if (expr.type === "StringLiteral") return expr.value;
|
|
365
|
+
return null;
|
|
366
|
+
};
|
|
367
|
+
|
|
368
|
+
const objName = getBaseName(node.object);
|
|
369
|
+
const propName = getPropName(node.property);
|
|
370
|
+
|
|
371
|
+
if (propName) {
|
|
372
|
+
fileNode.instantiatedIdentifiers.add(propName);
|
|
373
|
+
if (objName) {
|
|
374
|
+
fileNode.propertyAccessChains.add(`${objName}.${propName}`);
|
|
375
|
+
fileNode.instantiatedIdentifiers.add(objName);
|
|
384
376
|
}
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
377
|
+
} else {
|
|
378
|
+
// Dynamic access: obj[key]
|
|
379
|
+
// Conservative: if we see dynamic access on an object, we mark all its members as potentially used
|
|
380
|
+
if (objName) {
|
|
381
|
+
fileNode.propertyAccessChains.add(`${objName}.*`);
|
|
388
382
|
}
|
|
389
|
-
}
|
|
390
|
-
} else if (node.callee.type === "Identifier" && node.callee.name === "require" && node.arguments.length > 0 && node.arguments[0].type === "StringLiteral") {
|
|
391
|
-
const specifier = node.arguments[0].value;
|
|
392
|
-
fileNode.explicitImports.add(specifier);
|
|
393
|
-
fileNode.importedSymbols.add(`${specifier}:*`);
|
|
394
|
-
if (!specifier.startsWith('.') && !specifier.startsWith('/')) {
|
|
395
|
-
fileNode.externalPackageUsage.add(this._extractPackageName(specifier));
|
|
396
|
-
}
|
|
397
383
|
}
|
|
398
384
|
}
|
|
399
385
|
|
|
400
386
|
handleJsxElement(node, fileNode) {
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
if (nameNode.type === "JSXMemberExpression") return `${getElementName(nameNode.object)}.${nameNode.property.name}`;
|
|
404
|
-
if (nameNode.type === "JSXNamespacedName") return `${nameNode.namespace.name}:${nameNode.name.name}`;
|
|
405
|
-
return "unknown";
|
|
406
|
-
};
|
|
407
|
-
|
|
408
|
-
if (node.openingElement) {
|
|
409
|
-
const tagName = getElementName(node.openingElement.name);
|
|
410
|
-
fileNode.jsxComponents.add(tagName);
|
|
411
|
-
|
|
412
|
-
if (node.openingElement.attributes) {
|
|
413
|
-
node.openingElement.attributes.forEach(attr => {
|
|
414
|
-
if (attr.type === "JSXAttribute" && attr.name.type === "JSXIdentifier") {
|
|
415
|
-
fileNode.jsxProps.add(`${tagName}:${attr.name.name}`);
|
|
416
|
-
}
|
|
417
|
-
});
|
|
418
|
-
}
|
|
387
|
+
if (node.openingElement && node.openingElement.name.type === "JSXIdentifier") {
|
|
388
|
+
fileNode.jsxComponents.add(node.openingElement.name.name);
|
|
419
389
|
}
|
|
420
390
|
}
|
|
421
391
|
|
|
422
392
|
handleDecorator(node, fileNode) {
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
if (expr.type === "CallExpression") return getDecoratorName(expr.callee);
|
|
426
|
-
if (expr.type === "MemberExpression") {
|
|
427
|
-
const prop = expr.property.name || expr.property.value;
|
|
428
|
-
return prop || "unknown";
|
|
393
|
+
if (node.expression && node.expression.type === "Identifier") {
|
|
394
|
+
fileNode.decorators.add(node.expression.name);
|
|
429
395
|
}
|
|
430
|
-
|
|
431
|
-
};
|
|
396
|
+
}
|
|
432
397
|
|
|
433
|
-
|
|
434
|
-
if (
|
|
435
|
-
|
|
398
|
+
_extractNamesFromPattern(node, callback) {
|
|
399
|
+
if (!node) return;
|
|
400
|
+
if (node.type === "Identifier") {
|
|
401
|
+
callback(node.name);
|
|
402
|
+
} else if (node.type === "ObjectPattern") {
|
|
403
|
+
node.properties.forEach(p => this._extractNamesFromPattern(p.value || p.argument, callback));
|
|
404
|
+
} else if (node.type === "ArrayPattern") {
|
|
405
|
+
node.elements.forEach(e => e && this._extractNamesFromPattern(e, callback));
|
|
436
406
|
}
|
|
437
407
|
}
|
|
438
408
|
|
|
439
409
|
_extractPackageName(specifier) {
|
|
440
410
|
if (specifier.startsWith('@')) {
|
|
441
|
-
|
|
442
|
-
return parts.length >= 2 ? `${parts[0]}/${parts[1]}` : specifier;
|
|
411
|
+
return specifier.split('/').slice(0, 2).join('/');
|
|
443
412
|
}
|
|
444
413
|
return specifier.split('/')[0];
|
|
445
414
|
}
|
|
446
|
-
|
|
415
|
+
|
|
416
|
+
// UPGRADE: CommonJS require() detection
|
|
417
|
+
handleCallExpressionPass1(node, fileNode) {
|
|
418
|
+
if (this.context.verbose) console.log(`[OXC-DEBUG] Checking CallExpression: ${node.callee.name || 'anonymous'}`);
|
|
419
|
+
if (node.callee.type === "Identifier" && node.callee.name === "require") {
|
|
420
|
+
if (this.context.verbose) console.log(`[OXC-DEBUG] Found require() call in ${fileNode.filePath}`);
|
|
421
|
+
if (node.arguments.length > 0) {
|
|
422
|
+
const arg = node.arguments[0];
|
|
423
|
+
if (arg.type === "StringLiteral") {
|
|
424
|
+
const specifier = arg.value;
|
|
425
|
+
fileNode.explicitImports.add(specifier);
|
|
426
|
+
fileNode.importedSymbols.add(`${specifier}:*`);
|
|
427
|
+
|
|
428
|
+
if (!specifier.startsWith('.') && !specifier.startsWith('/')) {
|
|
429
|
+
fileNode.externalPackageUsage.add(this._extractPackageName(specifier));
|
|
430
|
+
}
|
|
431
|
+
} else if (arg.type === "TemplateLiteral") {
|
|
432
|
+
// Behandlung dynamischer requires
|
|
433
|
+
this.handleDynamicRequire(arg, fileNode);
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
// UPGRADE: Dynamic require() with template literals
|
|
440
|
+
handleDynamicRequire(node, fileNode) {
|
|
441
|
+
if (node.type === "TemplateLiteral") {
|
|
442
|
+
const quasis = node.quasis.map(q => q.value.cooked).join('*');
|
|
443
|
+
if (!Array.isArray(fileNode.calculatedDynamicImports)) {
|
|
444
|
+
fileNode.calculatedDynamicImports = [];
|
|
445
|
+
}
|
|
446
|
+
fileNode.calculatedDynamicImports.push({
|
|
447
|
+
kind: 'DynamicRequire',
|
|
448
|
+
pattern: quasis,
|
|
449
|
+
start: node.start
|
|
450
|
+
});
|
|
451
|
+
fileNode.dynamicImports.add('__DYNAMIC_PATTERN__:' + quasis);
|
|
452
|
+
fileNode.dynamicImports.add('__DYNAMIC_PATTERN__');
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
// UPGRADE: CommonJS module.exports / exports detection
|
|
457
|
+
handleAssignmentExpression(node, fileNode) {
|
|
458
|
+
if (this.context.verbose) console.log(`[OXC-DEBUG] Checking AssignmentExpression in ${fileNode.filePath}`);
|
|
459
|
+
if (node.left.type === "MemberExpression") {
|
|
460
|
+
const left = node.left;
|
|
461
|
+
|
|
462
|
+
// module.exports = ...
|
|
463
|
+
if (left.object.type === "Identifier" && left.object.name === "module" &&
|
|
464
|
+
left.property.type === "Identifier" && left.property.name === "exports") {
|
|
465
|
+
|
|
466
|
+
fileNode.internalExports.set("default", { type: "default", start: node.start, end: node.end });
|
|
467
|
+
|
|
468
|
+
// UPGRADE: Handle module.exports = { a, b }
|
|
469
|
+
if (node.right.type === "ObjectExpression") {
|
|
470
|
+
node.right.properties.forEach(prop => {
|
|
471
|
+
if (prop.type === "ObjectProperty" && prop.key.type === "Identifier") {
|
|
472
|
+
fileNode.internalExports.set(prop.key.name, { type: "variable", start: prop.start, end: prop.end });
|
|
473
|
+
}
|
|
474
|
+
});
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
// exports.name = ...
|
|
478
|
+
else if (left.object.type === "Identifier" && left.object.name === "exports" &&
|
|
479
|
+
left.property.type === "Identifier") {
|
|
480
|
+
fileNode.internalExports.set(left.property.name, { type: "variable", start: node.start, end: node.end });
|
|
481
|
+
}
|
|
482
|
+
// module.exports.name = ...
|
|
483
|
+
else if (left.object.type === "MemberExpression" &&
|
|
484
|
+
left.object.object.type === "Identifier" && left.object.object.name === "module" &&
|
|
485
|
+
left.object.property.type === "Identifier" && left.object.property.name === "exports" &&
|
|
486
|
+
left.property.type === "Identifier") {
|
|
487
|
+
fileNode.internalExports.set(left.property.name, { type: "variable", start: node.start, end: node.end });
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
// UPGRADE: Improved _isDefinition with parent context awareness
|
|
493
|
+
_isDefinition(node, parent = null) {
|
|
494
|
+
if (!parent) return false;
|
|
495
|
+
|
|
496
|
+
// Check if this identifier is being defined (left side of assignment, function parameter, etc.)
|
|
497
|
+
if (parent.type === "VariableDeclarator" && parent.id === node) return true;
|
|
498
|
+
if (parent.type === "FunctionDeclaration" && parent.id === node) return true;
|
|
499
|
+
if (parent.type === "ClassDeclaration" && parent.id === node) return true;
|
|
500
|
+
if (parent.type === "ImportSpecifier" && parent.local === node) return true;
|
|
501
|
+
if (parent.type === "ImportDefaultSpecifier" && parent.local === node) return true;
|
|
502
|
+
if (parent.type === "ImportNamespaceSpecifier" && parent.local === node) return true;
|
|
503
|
+
if (parent.type === "ExportSpecifier" && parent.local === node) return true;
|
|
504
|
+
if (parent.type === "AssignmentExpression" && parent.left === node) return true;
|
|
505
|
+
|
|
506
|
+
return false;
|
|
507
|
+
}
|
|
508
|
+
}
|