entkapp 4.5.1 → 5.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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,288 @@ 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
+ if (!Array.isArray(fileNode.calculatedDynamicImports)) {
254
+ fileNode.calculatedDynamicImports = [];
255
+ }
256
+ fileNode.calculatedDynamicImports.push({
257
+ kind: 'TemplateLiteral',
258
+ pattern: quasis,
259
+ start: node.source.start
260
+ });
261
+ // If we can't resolve it, we must assume it might import anything in that directory
262
+ fileNode.dynamicImports.add('__DYNAMIC_PATTERN__:' + quasis);
263
+ fileNode.dynamicImports.add('__DYNAMIC_PATTERN__');
264
+ }
265
+ }
266
+
267
+ handleClassDeclaration(node, fileNode) {
268
+ if (!node.id) return;
269
+ const className = node.id.name;
270
+ const members = [];
271
+
272
+ if (node.body && node.body.body) {
273
+ node.body.body.forEach(member => {
274
+ if (member.type === "MethodDefinition" || member.type === "PropertyDefinition") {
275
+ if (member.key && member.key.type === "Identifier") {
276
+ const memberName = member.key.name;
277
+ members.push({
278
+ name: memberName,
279
+ start: member.key.start,
280
+ end: member.key.end,
281
+ isPublic: member.accessibility !== 'private' && !memberName.startsWith('_')
282
+ });
283
+ // Track member name for usage detection
284
+ fileNode.instantiatedIdentifiers.add(memberName);
285
+ }
286
+ }
287
+ });
288
+ }
289
+
290
+ fileNode.internalExports.set(className, { type: 'class', members, start: node.start });
291
+ fileNode.symbolTable.set(className, { type: 'class', members, start: node.start });
292
+ }
293
+
241
294
  handleExportDeclaration(node, fileNode, content) {
242
- // 1. Default Exports
243
295
  if (node.type === "ExportDefaultDeclaration") {
244
- fileNode.internalExports.set("default", {
245
- type: "default",
246
- start: node.start,
247
- end: node.end
248
- });
296
+ fileNode.internalExports.set("default", { type: "default", start: node.start, end: node.end });
249
297
  return;
250
298
  }
251
299
 
252
- // 2. Re-export All: export * from 'mod' or export * as ns from 'mod'
253
300
  if (node.type === "ExportAllDeclaration") {
254
301
  const sourceSpecifier = node.source.value;
255
302
  fileNode.explicitImports.add(sourceSpecifier);
256
- if (!sourceSpecifier.startsWith('.') && !sourceSpecifier.startsWith('/')) {
257
- fileNode.externalPackageUsage.add(this._extractPackageName(sourceSpecifier));
258
- }
259
-
260
303
  if (node.exported) {
261
- // export * as ns from 'mod'
262
304
  const name = node.exported.name || (node.exported.type === "Identifier" ? node.exported.name : null);
263
305
  if (name) {
264
- fileNode.internalExports.set(name, {
265
- type: "re-export-namespace",
266
- source: sourceSpecifier,
267
- originalName: "*",
268
- start: node.start,
269
- end: node.end
270
- });
271
- fileNode.importedSymbols.add(`${sourceSpecifier}:*`);
306
+ fileNode.internalExports.set(name, { type: "re-export-namespace", source: sourceSpecifier, originalName: "*", start: node.start });
272
307
  }
273
308
  } else {
274
- // export * from 'mod'
275
- fileNode.internalExports.set("*", {
276
- type: "re-export-all",
277
- source: sourceSpecifier
278
- });
279
- fileNode.importedSymbols.add(`${sourceSpecifier}:*`);
309
+ fileNode.internalExports.set("*", { type: "re-export-all", source: sourceSpecifier });
280
310
  }
281
311
  return;
282
312
  }
283
313
 
284
- // 3. Named Exports & Re-exports with specifiers
285
314
  if (node.source) {
286
- // Re-export: export { x } from 'mod'
287
315
  const specifier = node.source.value;
288
316
  fileNode.explicitImports.add(specifier);
289
- if (!specifier.startsWith('.') && !specifier.startsWith('/')) {
290
- fileNode.externalPackageUsage.add(this._extractPackageName(specifier));
291
- }
292
-
293
317
  if (node.specifiers) {
294
318
  node.specifiers.forEach((spec) => {
295
319
  const exportedName = spec.exported.name || (spec.exported.type === "Identifier" ? spec.exported.name : spec.exported.value);
296
320
  const localName = spec.local.name || (spec.local.type === "Identifier" ? spec.local.name : spec.local.value);
297
- fileNode.internalExports.set(exportedName, {
298
- type: "re-export",
299
- source: specifier,
300
- originalName: localName,
301
- start: spec.start,
302
- end: spec.end,
303
- });
304
- fileNode.importedSymbols.add(`${specifier}:${localName}`);
321
+ fileNode.internalExports.set(exportedName, { type: "re-export", source: specifier, originalName: localName, start: spec.start });
305
322
  });
306
323
  }
307
324
  } else if (node.declaration) {
308
- // Direct declaration export: export const x = 1, export function f() {}
309
325
  const decl = node.declaration;
310
326
  if (decl.type === "VariableDeclaration") {
311
327
  decl.declarations.forEach((d) => {
312
328
  this._extractNamesFromPattern(d.id, (name) => {
313
- fileNode.internalExports.set(name, {
314
- type: "variable",
315
- start: d.start,
316
- end: d.end
317
- });
329
+ fileNode.internalExports.set(name, { type: "variable", start: d.start });
318
330
  });
319
331
  });
320
332
  } else if (decl.id && decl.id.name) {
321
- 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
- });
333
+ this.handleClassDeclaration(decl, fileNode);
334
334
  }
335
- } else if (node.specifiers) {
336
- // Export existing locals: export { x, y as z }
337
- node.specifiers.forEach((spec) => {
338
- const exportedName = spec.exported.name || (spec.exported.type === "Identifier" ? spec.exported.name : spec.exported.value);
339
- const localName = spec.local.name || (spec.local.type === "Identifier" ? spec.local.name : spec.local.value);
340
- fileNode.internalExports.set(exportedName, {
341
- type: "export",
342
- originalName: localName,
343
- start: spec.start,
344
- end: spec.end,
345
- });
346
- });
347
335
  }
348
336
  }
349
337
 
350
- _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);
338
+ handleCallExpression(node, fileNode) {
339
+ // Call Graph Hint: Track what is being called
340
+ if (node.callee.type === "Identifier") {
341
+ fileNode.instantiatedIdentifiers.add(node.callee.name);
342
+ } else if (node.callee.type === "MemberExpression") {
343
+ this.handleMemberExpression(node.callee, fileNode);
344
+ }
345
+
346
+ // Enterprise: Execute registered call graph visitors
347
+ if (this.context.callGraphVisitors) {
348
+ for (const visitor of this.context.callGraphVisitors) {
349
+ visitor(node, fileNode, this.context);
360
350
  }
361
- });
362
- } else if (node.type === "ArrayPattern") {
363
- node.elements.forEach(e => {
364
- if (e) this._extractNamesFromPattern(e, callback);
365
- });
366
- } else if (node.type === "AssignmentPattern") {
367
- this._extractNamesFromPattern(node.left, callback);
368
- } else if (node.type === "RestElement") {
369
- this._extractNamesFromPattern(node.argument, callback);
370
351
  }
371
352
  }
372
353
 
373
- 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));
354
+ handleMemberExpression(node, fileNode) {
355
+ const getBaseName = (expr) => {
356
+ if (expr.type === "Identifier") return expr.name;
357
+ if (expr.type === "MemberExpression") return getBaseName(expr.object);
358
+ if (expr.type === "ThisExpression") return "this";
359
+ return null;
360
+ };
361
+
362
+ const getPropName = (expr) => {
363
+ if (expr.type === "Identifier") return expr.name;
364
+ if (expr.type === "StringLiteral") return expr.value;
365
+ return null;
366
+ };
367
+
368
+ const objName = getBaseName(node.object);
369
+ const propName = getPropName(node.property);
370
+
371
+ if (propName) {
372
+ fileNode.instantiatedIdentifiers.add(propName);
373
+ if (objName) {
374
+ fileNode.propertyAccessChains.add(`${objName}.${propName}`);
375
+ fileNode.instantiatedIdentifiers.add(objName);
384
376
  }
385
- } else {
386
- if (fileNode.calculatedDynamicImports) {
387
- fileNode.calculatedDynamicImports.push({ kind: arg.type, start: arg.start });
377
+ } else {
378
+ // Dynamic access: obj[key]
379
+ // Conservative: if we see dynamic access on an object, we mark all its members as potentially used
380
+ if (objName) {
381
+ fileNode.propertyAccessChains.add(`${objName}.*`);
388
382
  }
389
- }
390
- } else if (node.callee.type === "Identifier" && node.callee.name === "require" && node.arguments.length > 0 && node.arguments[0].type === "StringLiteral") {
391
- const specifier = node.arguments[0].value;
392
- fileNode.explicitImports.add(specifier);
393
- fileNode.importedSymbols.add(`${specifier}:*`);
394
- if (!specifier.startsWith('.') && !specifier.startsWith('/')) {
395
- fileNode.externalPackageUsage.add(this._extractPackageName(specifier));
396
- }
397
383
  }
398
384
  }
399
385
 
400
386
  handleJsxElement(node, fileNode) {
401
- 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
- }
387
+ if (node.openingElement && node.openingElement.name.type === "JSXIdentifier") {
388
+ fileNode.jsxComponents.add(node.openingElement.name.name);
419
389
  }
420
390
  }
421
391
 
422
392
  handleDecorator(node, fileNode) {
423
- 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";
393
+ if (node.expression && node.expression.type === "Identifier") {
394
+ fileNode.decorators.add(node.expression.name);
429
395
  }
430
- return "unknown";
431
- };
396
+ }
432
397
 
433
- const decoratorName = getDecoratorName(node.expression);
434
- if (decoratorName !== "unknown") {
435
- fileNode.decorators.add(decoratorName);
398
+ _extractNamesFromPattern(node, callback) {
399
+ if (!node) return;
400
+ if (node.type === "Identifier") {
401
+ callback(node.name);
402
+ } else if (node.type === "ObjectPattern") {
403
+ node.properties.forEach(p => this._extractNamesFromPattern(p.value || p.argument, callback));
404
+ } else if (node.type === "ArrayPattern") {
405
+ node.elements.forEach(e => e && this._extractNamesFromPattern(e, callback));
436
406
  }
437
407
  }
438
408
 
439
409
  _extractPackageName(specifier) {
440
410
  if (specifier.startsWith('@')) {
441
- const parts = specifier.split('/');
442
- return parts.length >= 2 ? `${parts[0]}/${parts[1]}` : specifier;
411
+ return specifier.split('/').slice(0, 2).join('/');
443
412
  }
444
413
  return specifier.split('/')[0];
445
414
  }
446
- }
415
+
416
+ // UPGRADE: CommonJS require() detection
417
+ handleCallExpressionPass1(node, fileNode) {
418
+ if (this.context.verbose) console.log(`[OXC-DEBUG] Checking CallExpression: ${node.callee.name || 'anonymous'}`);
419
+ if (node.callee.type === "Identifier" && node.callee.name === "require") {
420
+ if (this.context.verbose) console.log(`[OXC-DEBUG] Found require() call in ${fileNode.filePath}`);
421
+ if (node.arguments.length > 0) {
422
+ const arg = node.arguments[0];
423
+ if (arg.type === "StringLiteral") {
424
+ const specifier = arg.value;
425
+ fileNode.explicitImports.add(specifier);
426
+ fileNode.importedSymbols.add(`${specifier}:*`);
427
+
428
+ if (!specifier.startsWith('.') && !specifier.startsWith('/')) {
429
+ fileNode.externalPackageUsage.add(this._extractPackageName(specifier));
430
+ }
431
+ } else if (arg.type === "TemplateLiteral") {
432
+ // Behandlung dynamischer requires
433
+ this.handleDynamicRequire(arg, fileNode);
434
+ }
435
+ }
436
+ }
437
+ }
438
+
439
+ // UPGRADE: Dynamic require() with template literals
440
+ handleDynamicRequire(node, fileNode) {
441
+ if (node.type === "TemplateLiteral") {
442
+ const quasis = node.quasis.map(q => q.value.cooked).join('*');
443
+ if (!Array.isArray(fileNode.calculatedDynamicImports)) {
444
+ fileNode.calculatedDynamicImports = [];
445
+ }
446
+ fileNode.calculatedDynamicImports.push({
447
+ kind: 'DynamicRequire',
448
+ pattern: quasis,
449
+ start: node.start
450
+ });
451
+ fileNode.dynamicImports.add('__DYNAMIC_PATTERN__:' + quasis);
452
+ fileNode.dynamicImports.add('__DYNAMIC_PATTERN__');
453
+ }
454
+ }
455
+
456
+ // UPGRADE: CommonJS module.exports / exports detection
457
+ handleAssignmentExpression(node, fileNode) {
458
+ if (this.context.verbose) console.log(`[OXC-DEBUG] Checking AssignmentExpression in ${fileNode.filePath}`);
459
+ if (node.left.type === "MemberExpression") {
460
+ const left = node.left;
461
+
462
+ // module.exports = ...
463
+ if (left.object.type === "Identifier" && left.object.name === "module" &&
464
+ left.property.type === "Identifier" && left.property.name === "exports") {
465
+
466
+ fileNode.internalExports.set("default", { type: "default", start: node.start, end: node.end });
467
+
468
+ // UPGRADE: Handle module.exports = { a, b }
469
+ if (node.right.type === "ObjectExpression") {
470
+ node.right.properties.forEach(prop => {
471
+ if (prop.type === "ObjectProperty" && prop.key.type === "Identifier") {
472
+ fileNode.internalExports.set(prop.key.name, { type: "variable", start: prop.start, end: prop.end });
473
+ }
474
+ });
475
+ }
476
+ }
477
+ // exports.name = ...
478
+ else if (left.object.type === "Identifier" && left.object.name === "exports" &&
479
+ left.property.type === "Identifier") {
480
+ fileNode.internalExports.set(left.property.name, { type: "variable", start: node.start, end: node.end });
481
+ }
482
+ // module.exports.name = ...
483
+ else if (left.object.type === "MemberExpression" &&
484
+ left.object.object.type === "Identifier" && left.object.object.name === "module" &&
485
+ left.object.property.type === "Identifier" && left.object.property.name === "exports" &&
486
+ left.property.type === "Identifier") {
487
+ fileNode.internalExports.set(left.property.name, { type: "variable", start: node.start, end: node.end });
488
+ }
489
+ }
490
+ }
491
+
492
+ // UPGRADE: Improved _isDefinition with parent context awareness
493
+ _isDefinition(node, parent = null) {
494
+ if (!parent) return false;
495
+
496
+ // Check if this identifier is being defined (left side of assignment, function parameter, etc.)
497
+ if (parent.type === "VariableDeclarator" && parent.id === node) return true;
498
+ if (parent.type === "FunctionDeclaration" && parent.id === node) return true;
499
+ if (parent.type === "ClassDeclaration" && parent.id === node) return true;
500
+ if (parent.type === "ImportSpecifier" && parent.local === node) return true;
501
+ if (parent.type === "ImportDefaultSpecifier" && parent.local === node) return true;
502
+ if (parent.type === "ImportNamespaceSpecifier" && parent.local === node) return true;
503
+ if (parent.type === "ExportSpecifier" && parent.local === node) return true;
504
+ if (parent.type === "AssignmentExpression" && parent.left === node) return true;
505
+
506
+ return false;
507
+ }
508
+ }