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