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.
@@ -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
- this.oxc = await import("oxc-parser");
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
- const normalizedContent = cleanContent.replace(/\/([gimuy]*)([nh]+)([gimuy]*)/g, (match, p1, p2, p3) => {
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
- // ONLY USE FLAT SIGNATURE: parseSync(sourceText, sourceFilename)
52
- // This avoids the "rust type String" conversion error seen with configuration objects.
53
- result = this.oxc.parseSync(normalizedContent, normalizedPath);
49
+ result = this.oxc.parseSync(cleanContent, {
50
+ sourceType: "module",
51
+ sourceFilename: normalizedPath,
52
+ lang: "typescript"
53
+ });
54
54
  } catch (e) {
55
- // Silent fallback to TS Compiler if OXC fails
56
- return false;
57
- }
58
-
59
- if (this.context.verbose) {
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
- // Fix: Handle cases where OXC returns a JSON string instead of an object
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
- parsedResult = result;
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
- ast = { program: parsedResult };
69
+ return false;
91
70
  }
92
- } else {
93
- throw new Error("OXC returned an invalid AST format");
94
71
  }
95
72
 
96
- fileNode.ast = ast.program; // Store the AST for advanced analysis
97
- fileNode.jsxComponents = new Set();
98
- fileNode.jsxProps = new Set();
99
- fileNode.decorators = new Set();
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
- // Fallback: If OXC found 0 imports but the file has content that looks like it has imports,
111
- // return false to trigger the TS Compiler API fallback. This is a safety anchor.
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
- if (this.context.verbose) {
134
- console.log(`[OXC] Found ${fileNode.explicitImports.size} imports in ${filePath}`);
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
- const lines = content.split('\n');
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
- switch (node.type) {
173
- case "ImportDeclaration":
174
- this.handleImportDeclaration(node, fileNode);
175
- break;
176
- case "ExportNamedDeclaration":
177
- case "ExportDefaultDeclaration":
178
- case "ExportAllDeclaration":
179
- this.handleExportDeclaration(node, fileNode, content);
180
- break;
181
- case "CallExpression":
182
- this.handleCallExpression(node, fileNode);
183
- break;
184
- case "JSXElement":
185
- case "JSXFragment":
186
- this.handleJsxElement(node, fileNode);
187
- break;
188
- case "Decorator":
189
- this.handleDecorator(node, fileNode);
190
- break;
191
- case "StringLiteral":
192
- fileNode.rawStringReferences.add(node.value);
193
- break;
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
- // In OXC, imported name can be in .imported.name or .imported.value
220
- const importedName = spec.imported.name || spec.imported.value || (spec.imported.type === "Identifier" ? spec.imported.name : null);
221
- if (importedName) {
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
- // export * from 'mod'
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
- let type = "unknown";
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
- _extractNamesFromPattern(node, callback) {
343
- if (!node) return;
344
- if (node.type === "Identifier") {
345
- callback(node.name);
346
- } else if (node.type === "ObjectPattern") {
347
- node.properties.forEach(p => {
348
- if (p.type === "Property") {
349
- this._extractNamesFromPattern(p.value, callback);
350
- } else if (p.type === "RestElement") {
351
- this._extractNamesFromPattern(p.argument, callback);
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
- handleCallExpression(node, fileNode) {
366
- // Dynamic import(): import('./module')
367
- if (node.callee.type === "Import" && node.arguments.length > 0) {
368
- const arg = node.arguments[0];
369
- if (arg.type === "StringLiteral") {
370
- const specifier = arg.value;
371
- fileNode.explicitImports.add(specifier);
372
- fileNode.dynamicImports.add(specifier);
373
- fileNode.importedSymbols.add(`${specifier}:*`); // Dynamic import usually consumes the whole namespace
374
- if (!specifier.startsWith('.') && !specifier.startsWith('/')) {
375
- fileNode.externalPackageUsage.add(this._extractPackageName(specifier));
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
- } else {
378
- if (fileNode.calculatedDynamicImports) {
379
- fileNode.calculatedDynamicImports.push({ kind: arg.type, start: arg.start });
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
- const getElementName = (nameNode) => {
394
- if (nameNode.type === "JSXIdentifier") return nameNode.name;
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
- const getDecoratorName = (expr) => {
416
- if (expr.type === "Identifier") return expr.name;
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
- return "unknown";
423
- };
393
+ }
424
394
 
425
- const decoratorName = getDecoratorName(node.expression);
426
- if (decoratorName !== "unknown") {
427
- fileNode.decorators.add(decoratorName);
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
- const parts = specifier.split('/');
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
  }