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.
@@ -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,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
- // FIX: The previous regex was too aggressive and matched patterns that are NOT regex literals
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
- // ONLY USE FLAT SIGNATURE: parseSync(sourceText, sourceFilename)
60
- // This avoids the "rust type String" conversion error seen with configuration objects.
61
- result = this.oxc.parseSync(normalizedContent, normalizedPath);
49
+ result = this.oxc.parseSync(cleanContent, {
50
+ sourceType: "module",
51
+ sourceFilename: normalizedPath,
52
+ lang: "typescript"
53
+ });
62
54
  } catch (e) {
63
- // Silent fallback to TS Compiler if OXC fails
64
- return false;
65
- }
66
-
67
- if (this.context.verbose) {
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
- // Fix: Handle cases where OXC returns a JSON string instead of an object
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
- parsedResult = result;
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
- ast = { program: parsedResult };
69
+ return false;
99
70
  }
100
- } else {
101
- throw new Error("OXC returned an invalid AST format");
102
71
  }
103
72
 
104
- fileNode.ast = ast.program; // Store the AST for advanced analysis
105
- fileNode.jsxComponents = new Set();
106
- fileNode.jsxProps = new Set();
107
- fileNode.decorators = new Set();
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
- // Fallback: If OXC found 0 imports but the file has content that looks like it has imports,
119
- // return false to trigger the TS Compiler API fallback. This is a safety anchor.
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
- if (this.context.verbose) {
142
- console.log(`[OXC] Found ${fileNode.explicitImports.size} imports in ${filePath}`);
143
- if (fileNode.explicitImports.size > 0) {
144
- console.log(`[OXC] Imports for ${filePath}: ${Array.from(fileNode.explicitImports).join(', ')}`);
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
- const lines = content.split('\n');
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
- switch (node.type) {
181
- case "ImportDeclaration":
182
- this.handleImportDeclaration(node, fileNode);
183
- break;
184
- case "ExportNamedDeclaration":
185
- case "ExportDefaultDeclaration":
186
- case "ExportAllDeclaration":
187
- this.handleExportDeclaration(node, fileNode, content);
188
- break;
189
- case "CallExpression":
190
- this.handleCallExpression(node, fileNode);
191
- break;
192
- case "JSXElement":
193
- case "JSXFragment":
194
- this.handleJsxElement(node, fileNode);
195
- break;
196
- case "Decorator":
197
- this.handleDecorator(node, fileNode);
198
- break;
199
- case "StringLiteral":
200
- fileNode.rawStringReferences.add(node.value);
201
- 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
+ }
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
- // In OXC, imported name can be in .imported.name or .imported.value
228
- const importedName = spec.imported.name || spec.imported.value || (spec.imported.type === "Identifier" ? spec.imported.name : null);
229
- if (importedName) {
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
- // export * from 'mod'
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
- let type = "unknown";
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
- _extractNamesFromPattern(node, callback) {
351
- if (!node) return;
352
- if (node.type === "Identifier") {
353
- callback(node.name);
354
- } else if (node.type === "ObjectPattern") {
355
- node.properties.forEach(p => {
356
- if (p.type === "Property") {
357
- this._extractNamesFromPattern(p.value, callback);
358
- } else if (p.type === "RestElement") {
359
- 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);
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
- handleCallExpression(node, fileNode) {
374
- // Dynamic import(): import('./module')
375
- if (node.callee.type === "Import" && node.arguments.length > 0) {
376
- const arg = node.arguments[0];
377
- if (arg.type === "StringLiteral") {
378
- const specifier = arg.value;
379
- fileNode.explicitImports.add(specifier);
380
- fileNode.dynamicImports.add(specifier);
381
- fileNode.importedSymbols.add(`${specifier}:*`); // Dynamic import usually consumes the whole namespace
382
- if (!specifier.startsWith('.') && !specifier.startsWith('/')) {
383
- 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);
384
373
  }
385
- } else {
386
- if (fileNode.calculatedDynamicImports) {
387
- 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}.*`);
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
- const getElementName = (nameNode) => {
402
- if (nameNode.type === "JSXIdentifier") return nameNode.name;
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
- const getDecoratorName = (expr) => {
424
- if (expr.type === "Identifier") return expr.name;
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
- return "unknown";
431
- };
393
+ }
432
394
 
433
- const decoratorName = getDecoratorName(node.expression);
434
- if (decoratorName !== "unknown") {
435
- 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));
436
403
  }
437
404
  }
438
405
 
439
406
  _extractPackageName(specifier) {
440
407
  if (specifier.startsWith('@')) {
441
- const parts = specifier.split('/');
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
+ }