entkapp 4.5.1 → 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 +329 -273
- 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,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,282 @@ 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
|
+
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
|
+
|
|
241
291
|
handleExportDeclaration(node, fileNode, content) {
|
|
242
|
-
// 1. Default Exports
|
|
243
292
|
if (node.type === "ExportDefaultDeclaration") {
|
|
244
|
-
fileNode.internalExports.set("default", {
|
|
245
|
-
type: "default",
|
|
246
|
-
start: node.start,
|
|
247
|
-
end: node.end
|
|
248
|
-
});
|
|
293
|
+
fileNode.internalExports.set("default", { type: "default", start: node.start, end: node.end });
|
|
249
294
|
return;
|
|
250
295
|
}
|
|
251
296
|
|
|
252
|
-
// 2. Re-export All: export * from 'mod' or export * as ns from 'mod'
|
|
253
297
|
if (node.type === "ExportAllDeclaration") {
|
|
254
298
|
const sourceSpecifier = node.source.value;
|
|
255
299
|
fileNode.explicitImports.add(sourceSpecifier);
|
|
256
|
-
if (!sourceSpecifier.startsWith('.') && !sourceSpecifier.startsWith('/')) {
|
|
257
|
-
fileNode.externalPackageUsage.add(this._extractPackageName(sourceSpecifier));
|
|
258
|
-
}
|
|
259
|
-
|
|
260
300
|
if (node.exported) {
|
|
261
|
-
// export * as ns from 'mod'
|
|
262
301
|
const name = node.exported.name || (node.exported.type === "Identifier" ? node.exported.name : null);
|
|
263
302
|
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}:*`);
|
|
303
|
+
fileNode.internalExports.set(name, { type: "re-export-namespace", source: sourceSpecifier, originalName: "*", start: node.start });
|
|
272
304
|
}
|
|
273
305
|
} else {
|
|
274
|
-
|
|
275
|
-
fileNode.internalExports.set("*", {
|
|
276
|
-
type: "re-export-all",
|
|
277
|
-
source: sourceSpecifier
|
|
278
|
-
});
|
|
279
|
-
fileNode.importedSymbols.add(`${sourceSpecifier}:*`);
|
|
306
|
+
fileNode.internalExports.set("*", { type: "re-export-all", source: sourceSpecifier });
|
|
280
307
|
}
|
|
281
308
|
return;
|
|
282
309
|
}
|
|
283
310
|
|
|
284
|
-
// 3. Named Exports & Re-exports with specifiers
|
|
285
311
|
if (node.source) {
|
|
286
|
-
// Re-export: export { x } from 'mod'
|
|
287
312
|
const specifier = node.source.value;
|
|
288
313
|
fileNode.explicitImports.add(specifier);
|
|
289
|
-
if (!specifier.startsWith('.') && !specifier.startsWith('/')) {
|
|
290
|
-
fileNode.externalPackageUsage.add(this._extractPackageName(specifier));
|
|
291
|
-
}
|
|
292
|
-
|
|
293
314
|
if (node.specifiers) {
|
|
294
315
|
node.specifiers.forEach((spec) => {
|
|
295
316
|
const exportedName = spec.exported.name || (spec.exported.type === "Identifier" ? spec.exported.name : spec.exported.value);
|
|
296
317
|
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}`);
|
|
318
|
+
fileNode.internalExports.set(exportedName, { type: "re-export", source: specifier, originalName: localName, start: spec.start });
|
|
305
319
|
});
|
|
306
320
|
}
|
|
307
321
|
} else if (node.declaration) {
|
|
308
|
-
// Direct declaration export: export const x = 1, export function f() {}
|
|
309
322
|
const decl = node.declaration;
|
|
310
323
|
if (decl.type === "VariableDeclaration") {
|
|
311
324
|
decl.declarations.forEach((d) => {
|
|
312
325
|
this._extractNamesFromPattern(d.id, (name) => {
|
|
313
|
-
fileNode.internalExports.set(name, {
|
|
314
|
-
type: "variable",
|
|
315
|
-
start: d.start,
|
|
316
|
-
end: d.end
|
|
317
|
-
});
|
|
326
|
+
fileNode.internalExports.set(name, { type: "variable", start: d.start });
|
|
318
327
|
});
|
|
319
328
|
});
|
|
320
329
|
} 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
|
-
});
|
|
330
|
+
this.handleClassDeclaration(decl, fileNode);
|
|
334
331
|
}
|
|
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
332
|
}
|
|
348
333
|
}
|
|
349
334
|
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
if (node.type === "Identifier") {
|
|
353
|
-
|
|
354
|
-
} else if (node.type === "
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
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);
|
|
360
347
|
}
|
|
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
348
|
}
|
|
371
349
|
}
|
|
372
350
|
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
if (
|
|
383
|
-
|
|
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);
|
|
384
373
|
}
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
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}.*`);
|
|
388
379
|
}
|
|
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
380
|
}
|
|
398
381
|
}
|
|
399
382
|
|
|
400
383
|
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
|
-
}
|
|
384
|
+
if (node.openingElement && node.openingElement.name.type === "JSXIdentifier") {
|
|
385
|
+
fileNode.jsxComponents.add(node.openingElement.name.name);
|
|
419
386
|
}
|
|
420
387
|
}
|
|
421
388
|
|
|
422
389
|
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";
|
|
390
|
+
if (node.expression && node.expression.type === "Identifier") {
|
|
391
|
+
fileNode.decorators.add(node.expression.name);
|
|
429
392
|
}
|
|
430
|
-
|
|
431
|
-
};
|
|
393
|
+
}
|
|
432
394
|
|
|
433
|
-
|
|
434
|
-
if (
|
|
435
|
-
|
|
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));
|
|
436
403
|
}
|
|
437
404
|
}
|
|
438
405
|
|
|
439
406
|
_extractPackageName(specifier) {
|
|
440
407
|
if (specifier.startsWith('@')) {
|
|
441
|
-
|
|
442
|
-
return parts.length >= 2 ? `${parts[0]}/${parts[1]}` : specifier;
|
|
408
|
+
return specifier.split('/').slice(0, 2).join('/');
|
|
443
409
|
}
|
|
444
410
|
return specifier.split('/')[0];
|
|
445
411
|
}
|
|
446
|
-
|
|
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
|
+
}
|
|
502
|
+
}
|