devbonzai 2.1.0 → 2.1.2

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.
@@ -0,0 +1,726 @@
1
+ const fs = require('fs');
2
+
3
+ let babelParser = null;
4
+ try {
5
+ babelParser = require('../node_modules/@babel/parser');
6
+ } catch (e) {
7
+ // Babel parser not available, will fall back gracefully
8
+ }
9
+
10
+ // Extract functions, classes, and methods from a Python file
11
+ function extractPythonFunctions(filePath) {
12
+ try {
13
+ const content = fs.readFileSync(filePath, 'utf8');
14
+ const lines = content.split('\n');
15
+ const functions = [];
16
+ const classes = [];
17
+ let currentFunction = null;
18
+ let currentClass = null;
19
+ let decorators = [];
20
+ let classIndent = -1;
21
+
22
+ for (let i = 0; i < lines.length; i++) {
23
+ const line = lines[i];
24
+ const trimmed = line.trim();
25
+
26
+ // Calculate indentation level
27
+ const match = line.match(/^\s*/);
28
+ const currentIndent = match ? match[0].length : 0;
29
+
30
+ // Check for decorators (only at top level, before function/class)
31
+ if (trimmed.startsWith('@') && currentIndent === 0) {
32
+ decorators.push(line);
33
+ continue;
34
+ }
35
+
36
+ // Check if this is a top-level class definition
37
+ const classMatch = trimmed.match(/^class\s+(\w+)/);
38
+ if (classMatch && currentIndent === 0) {
39
+ // Save previous function/class if exists
40
+ if (currentFunction) {
41
+ currentFunction.content = currentFunction.content.trim();
42
+ functions.push(currentFunction);
43
+ currentFunction = null;
44
+ }
45
+ if (currentClass) {
46
+ currentClass.content = currentClass.content.trim();
47
+ classes.push(currentClass);
48
+ }
49
+
50
+ // Start new class
51
+ const className = classMatch[1];
52
+ let classContent = '';
53
+
54
+ // Add decorators if any
55
+ if (decorators.length > 0) {
56
+ classContent = decorators.join('\n') + '\n';
57
+ decorators = [];
58
+ }
59
+
60
+ classContent += line;
61
+ classIndent = currentIndent;
62
+
63
+ currentClass = {
64
+ name: className,
65
+ content: classContent,
66
+ methods: [],
67
+ startLine: i + 1,
68
+ endLine: i + 1
69
+ };
70
+ continue;
71
+ }
72
+
73
+ // Check if this is a method definition (inside a class)
74
+ const methodMatch = trimmed.match(/^def\s+(\w+)\s*\(/);
75
+ if (methodMatch && currentClass && currentIndent > classIndent) {
76
+ // Save previous method if exists
77
+ if (currentFunction) {
78
+ currentFunction.content = currentFunction.content.trim();
79
+ currentClass.methods.push(currentFunction);
80
+ currentFunction = null;
81
+ }
82
+
83
+ // Start new method
84
+ const methodName = methodMatch[1];
85
+ let methodContent = '';
86
+
87
+ // Add decorators if any
88
+ if (decorators.length > 0) {
89
+ methodContent = decorators.join('\n') + '\n';
90
+ decorators = [];
91
+ }
92
+
93
+ methodContent += line;
94
+
95
+ currentFunction = {
96
+ name: currentClass.name + '.' + methodName,
97
+ content: methodContent,
98
+ startLine: i + 1,
99
+ endLine: i + 1,
100
+ isMethod: true,
101
+ className: currentClass.name,
102
+ methodName: methodName
103
+ };
104
+ continue;
105
+ }
106
+
107
+ // Check if this is a top-level function definition
108
+ const funcMatch = trimmed.match(/^def\s+(\w+)\s*\(/);
109
+
110
+ if (funcMatch && currentIndent === 0) {
111
+ // Save previous function/class if exists
112
+ if (currentFunction) {
113
+ currentFunction.content = currentFunction.content.trim();
114
+ if (currentFunction.isMethod && currentClass) {
115
+ currentClass.methods.push(currentFunction);
116
+ } else {
117
+ functions.push(currentFunction);
118
+ }
119
+ currentFunction = null;
120
+ }
121
+ if (currentClass) {
122
+ currentClass.content = currentClass.content.trim();
123
+ classes.push(currentClass);
124
+ currentClass = null;
125
+ classIndent = -1;
126
+ }
127
+
128
+ // Start new function
129
+ const functionName = funcMatch[1];
130
+ let functionContent = '';
131
+
132
+ // Add decorators if any
133
+ if (decorators.length > 0) {
134
+ functionContent = decorators.join('\n') + '\n';
135
+ decorators = [];
136
+ }
137
+
138
+ functionContent += line;
139
+
140
+ currentFunction = {
141
+ name: functionName,
142
+ content: functionContent,
143
+ startLine: i + 1,
144
+ endLine: i + 1
145
+ };
146
+ } else if (currentFunction || currentClass) {
147
+ // We're processing lines after a function/class definition
148
+ if (currentIndent === 0 && trimmed && !trimmed.startsWith('#')) {
149
+ // Back to top level with non-comment content - function/class ended
150
+ if (currentFunction) {
151
+ currentFunction.content = currentFunction.content.trim();
152
+ if (currentFunction.isMethod && currentClass) {
153
+ currentClass.methods.push(currentFunction);
154
+ } else {
155
+ functions.push(currentFunction);
156
+ }
157
+ currentFunction = null;
158
+ }
159
+ if (currentClass) {
160
+ currentClass.content = currentClass.content.trim();
161
+ classes.push(currentClass);
162
+ currentClass = null;
163
+ classIndent = -1;
164
+ }
165
+
166
+ // Check if this line starts a new function/class
167
+ if (funcMatch) {
168
+ const functionName = funcMatch[1];
169
+ let functionContent = '';
170
+
171
+ if (decorators.length > 0) {
172
+ functionContent = decorators.join('\n') + '\n';
173
+ decorators = [];
174
+ }
175
+
176
+ functionContent += line;
177
+
178
+ currentFunction = {
179
+ name: functionName,
180
+ content: functionContent,
181
+ startLine: i + 1,
182
+ endLine: i + 1
183
+ };
184
+ } else if (classMatch) {
185
+ const className = classMatch[1];
186
+ let classContent = '';
187
+
188
+ if (decorators.length > 0) {
189
+ classContent = decorators.join('\n') + '\n';
190
+ decorators = [];
191
+ }
192
+
193
+ classContent += line;
194
+ classIndent = currentIndent;
195
+
196
+ currentClass = {
197
+ name: className,
198
+ content: classContent,
199
+ methods: [],
200
+ startLine: i + 1,
201
+ endLine: i + 1
202
+ };
203
+ } else if (trimmed.startsWith('@')) {
204
+ decorators.push(line);
205
+ }
206
+ } else {
207
+ // Still inside function/class (indented or empty/comment line)
208
+ if (currentFunction) {
209
+ currentFunction.content += '\n' + line;
210
+ currentFunction.endLine = i + 1;
211
+ }
212
+ if (currentClass) {
213
+ currentClass.content += '\n' + line;
214
+ currentClass.endLine = i + 1;
215
+ }
216
+ }
217
+ }
218
+ }
219
+
220
+ // Don't forget the last function/class
221
+ if (currentFunction) {
222
+ currentFunction.content = currentFunction.content.trim();
223
+ if (currentFunction.isMethod && currentClass) {
224
+ currentClass.methods.push(currentFunction);
225
+ } else {
226
+ functions.push(currentFunction);
227
+ }
228
+ }
229
+ if (currentClass) {
230
+ currentClass.content = currentClass.content.trim();
231
+ classes.push(currentClass);
232
+ }
233
+
234
+ return { functions, classes };
235
+ } catch (e) {
236
+ // If parsing fails (invalid Python, etc.), return empty arrays
237
+ console.warn('Failed to parse Python file:', filePath, e.message);
238
+ return { functions: [], classes: [] };
239
+ }
240
+ }
241
+
242
+ // Extract functions, classes, and methods from a JavaScript/TypeScript file
243
+ function extractJavaScriptFunctions(filePath) {
244
+ try {
245
+ if (!babelParser) {
246
+ return { functions: [], classes: [] };
247
+ }
248
+
249
+ // Skip .d.ts files, minified files, and node_modules
250
+ if (filePath.endsWith('.d.ts') || filePath.endsWith('.min.js') || filePath.includes('node_modules')) {
251
+ return { functions: [], classes: [] };
252
+ }
253
+
254
+ const content = fs.readFileSync(filePath, 'utf8');
255
+ const functions = [];
256
+ const classes = [];
257
+
258
+ // Determine if it's TypeScript
259
+ const isTypeScript = filePath.endsWith('.ts') || filePath.endsWith('.tsx');
260
+
261
+ try {
262
+ const ast = babelParser.parse(content, {
263
+ sourceType: 'module',
264
+ plugins: [
265
+ 'typescript',
266
+ 'jsx',
267
+ 'decorators-legacy',
268
+ 'classProperties',
269
+ 'objectRestSpread',
270
+ 'asyncGenerators',
271
+ 'functionBind',
272
+ 'exportDefaultFrom',
273
+ 'exportNamespaceFrom',
274
+ 'dynamicImport',
275
+ 'nullishCoalescingOperator',
276
+ 'optionalChaining'
277
+ ]
278
+ });
279
+
280
+ // Helper to extract code snippet from source
281
+ const getCode = (node) => {
282
+ return content.substring(node.start, node.end);
283
+ };
284
+
285
+ // Track visited nodes to avoid duplicates
286
+ const visitedNodes = new Set();
287
+
288
+ // Traverse AST
289
+ function traverse(node, parentType = null) {
290
+ if (!node) return;
291
+
292
+ // Skip if already visited (avoid processing same node twice)
293
+ if (visitedNodes.has(node)) return;
294
+ visitedNodes.add(node);
295
+
296
+ // Function declarations: function myFunc() {}
297
+ // Skip if inside ExportNamedDeclaration (will be handled below)
298
+ if (node.type === 'FunctionDeclaration' && node.id && parentType !== 'ExportNamedDeclaration') {
299
+ functions.push({
300
+ name: node.id.name,
301
+ content: getCode(node),
302
+ startLine: node.loc ? node.loc.start.line : 0,
303
+ endLine: node.loc ? node.loc.end.line : 0
304
+ });
305
+ }
306
+
307
+ // Arrow functions: const myFunc = () => {}
308
+ if (node.type === 'VariableDeclarator' &&
309
+ node.init &&
310
+ (node.init.type === 'ArrowFunctionExpression' || node.init.type === 'FunctionExpression') &&
311
+ node.id && node.id.type === 'Identifier') {
312
+ const funcContent = getCode(node);
313
+ functions.push({
314
+ name: node.id.name,
315
+ content: funcContent,
316
+ startLine: node.loc ? node.loc.start.line : 0,
317
+ endLine: node.loc ? node.loc.end.line : 0
318
+ });
319
+ }
320
+
321
+ // Helper function to extract methods from a class body
322
+ const extractClassMethods = (classNode, className) => {
323
+ const methods = [];
324
+ if (classNode.body && classNode.body.body && Array.isArray(classNode.body.body)) {
325
+ for (const member of classNode.body.body) {
326
+ // Handle MethodDefinition (regular methods, constructors, getters, setters, static methods)
327
+ if (member && member.type === 'MethodDefinition' && member.key) {
328
+ let methodName;
329
+ if (member.key.type === 'Identifier') {
330
+ methodName = member.key.name;
331
+ } else if (member.key.type === 'PrivateName') {
332
+ methodName = '#' + member.key.id.name;
333
+ } else if (member.key.type === 'StringLiteral' || member.key.type === 'NumericLiteral') {
334
+ methodName = String(member.key.value);
335
+ } else {
336
+ methodName = String(member.key.value || member.key.name || 'unknown');
337
+ }
338
+
339
+ // Include kind (constructor, get, set, method) in the name for clarity
340
+ const kind = member.kind || 'method';
341
+ const isStatic = member.static || false;
342
+
343
+ // For getters and setters, include the kind in the method name to distinguish them
344
+ // e.g., "value" getter vs "value" setter -> "get value" and "set value"
345
+ let fullMethodName = methodName;
346
+ if (kind === 'get') {
347
+ fullMethodName = 'get ' + methodName;
348
+ } else if (kind === 'set') {
349
+ fullMethodName = 'set ' + methodName;
350
+ } else if (kind === 'constructor') {
351
+ fullMethodName = 'constructor';
352
+ } else if (isStatic) {
353
+ fullMethodName = 'static ' + methodName;
354
+ }
355
+
356
+ methods.push({
357
+ name: className + '.' + methodName,
358
+ content: getCode(member),
359
+ startLine: member.loc ? member.loc.start.line : 0,
360
+ endLine: member.loc ? member.loc.end.line : 0,
361
+ isMethod: true,
362
+ className: className,
363
+ methodName: methodName,
364
+ kind: kind,
365
+ static: isStatic
366
+ });
367
+ }
368
+ }
369
+ }
370
+ return methods;
371
+ };
372
+
373
+ // Class declarations: class User { ... }
374
+ // Skip if inside ExportNamedDeclaration or ExportDefaultDeclaration (will be handled below)
375
+ if (node.type === 'ClassDeclaration' && node.id &&
376
+ parentType !== 'ExportNamedDeclaration' && parentType !== 'ExportDefaultDeclaration') {
377
+ const className = node.id.name;
378
+ const methods = extractClassMethods(node, className);
379
+
380
+ classes.push({
381
+ name: className,
382
+ content: getCode(node),
383
+ methods: methods,
384
+ startLine: node.loc ? node.loc.start.line : 0,
385
+ endLine: node.loc ? node.loc.end.line : 0
386
+ });
387
+ }
388
+
389
+ // Export declarations: export function, export class
390
+ if (node.type === 'ExportNamedDeclaration' && node.declaration) {
391
+ if (node.declaration.type === 'FunctionDeclaration' && node.declaration.id) {
392
+ functions.push({
393
+ name: node.declaration.id.name,
394
+ content: getCode(node.declaration),
395
+ startLine: node.declaration.loc ? node.declaration.loc.start.line : 0,
396
+ endLine: node.declaration.loc ? node.declaration.loc.end.line : 0,
397
+ isExported: true
398
+ });
399
+ // Mark as visited to avoid duplicate processing
400
+ visitedNodes.add(node.declaration);
401
+ } else if (node.declaration.type === 'ClassDeclaration' && node.declaration.id) {
402
+ const className = node.declaration.id.name;
403
+ const methods = extractClassMethods(node.declaration, className);
404
+
405
+ classes.push({
406
+ name: className,
407
+ content: getCode(node.declaration),
408
+ methods: methods,
409
+ startLine: node.declaration.loc ? node.declaration.loc.start.line : 0,
410
+ endLine: node.declaration.loc ? node.declaration.loc.end.line : 0,
411
+ isExported: true
412
+ });
413
+ // Mark as visited to avoid duplicate processing
414
+ visitedNodes.add(node.declaration);
415
+ }
416
+ }
417
+
418
+ // Export default declarations: export default class
419
+ if (node.type === 'ExportDefaultDeclaration' && node.declaration) {
420
+ if (node.declaration.type === 'ClassDeclaration' && node.declaration.id) {
421
+ const className = node.declaration.id.name;
422
+ const methods = extractClassMethods(node.declaration, className);
423
+
424
+ classes.push({
425
+ name: className,
426
+ content: getCode(node.declaration),
427
+ methods: methods,
428
+ startLine: node.declaration.loc ? node.declaration.loc.start.line : 0,
429
+ endLine: node.declaration.loc ? node.declaration.loc.end.line : 0,
430
+ isExported: true,
431
+ isDefaultExport: true
432
+ });
433
+ // Mark as visited to avoid duplicate processing
434
+ visitedNodes.add(node.declaration);
435
+ } else if (node.declaration.type === 'FunctionDeclaration' && node.declaration.id) {
436
+ functions.push({
437
+ name: node.declaration.id.name,
438
+ content: getCode(node.declaration),
439
+ startLine: node.declaration.loc ? node.declaration.loc.start.line : 0,
440
+ endLine: node.declaration.loc ? node.declaration.loc.end.line : 0,
441
+ isExported: true,
442
+ isDefaultExport: true
443
+ });
444
+ visitedNodes.add(node.declaration);
445
+ }
446
+ }
447
+
448
+ // Recursively traverse children
449
+ for (const key in node) {
450
+ if (key === 'parent' || key === 'leadingComments' || key === 'trailingComments') continue;
451
+ const child = node[key];
452
+ if (Array.isArray(child)) {
453
+ child.forEach(c => traverse(c, node.type));
454
+ } else if (child && typeof child === 'object' && child.type) {
455
+ traverse(child, node.type);
456
+ }
457
+ }
458
+ }
459
+
460
+ traverse(ast);
461
+ } catch (parseError) {
462
+ // Silently skip parsing errors - these are expected for some files
463
+ // Only log if it's not in node_modules or a known problematic file type
464
+ if (!filePath.includes('node_modules') && !filePath.endsWith('.d.ts') && !filePath.endsWith('.min.js')) {
465
+ // Suppress warnings for common parsing issues
466
+ const errorMsg = parseError.message || '';
467
+ if (!errorMsg.includes('outside of function') &&
468
+ !errorMsg.includes('Missing initializer') &&
469
+ !errorMsg.includes('Export') &&
470
+ !errorMsg.includes('Unexpected token')) {
471
+ // Only log unexpected errors
472
+ }
473
+ }
474
+ return { functions: [], classes: [] };
475
+ }
476
+
477
+ return { functions, classes };
478
+ } catch (e) {
479
+ console.warn('Failed to read JavaScript/TypeScript file:', filePath, e.message);
480
+ return { functions: [], classes: [] };
481
+ }
482
+ }
483
+
484
+ // Extract script content from Vue file and parse it
485
+ function extractVueFunctions(filePath) {
486
+ try {
487
+ const content = fs.readFileSync(filePath, 'utf8');
488
+
489
+ // Extract <script> section from Vue file
490
+ const scriptMatch = content.match(/<script[^>]*>([\s\S]*?)<\/script>/);
491
+ if (!scriptMatch) {
492
+ return { functions: [], classes: [] };
493
+ }
494
+
495
+ const scriptContent = scriptMatch[1];
496
+
497
+ // Create a temporary file path for parsing (just for reference)
498
+ // Parse the script content as JavaScript/TypeScript
499
+ if (!babelParser) {
500
+ return { functions: [], classes: [] };
501
+ }
502
+
503
+ const functions = [];
504
+ const classes = [];
505
+
506
+ // Check if it's TypeScript
507
+ const isTypeScript = scriptMatch[0].includes('lang="ts"') || scriptMatch[0].includes("lang='ts'");
508
+
509
+ try {
510
+ const ast = babelParser.parse(scriptContent, {
511
+ sourceType: 'module',
512
+ plugins: [
513
+ 'typescript',
514
+ 'jsx',
515
+ 'decorators-legacy',
516
+ 'classProperties',
517
+ 'objectRestSpread',
518
+ 'asyncGenerators',
519
+ 'functionBind',
520
+ 'exportDefaultFrom',
521
+ 'exportNamespaceFrom',
522
+ 'dynamicImport',
523
+ 'nullishCoalescingOperator',
524
+ 'optionalChaining'
525
+ ]
526
+ });
527
+
528
+ // Helper to extract code snippet from source
529
+ const getCode = (node) => {
530
+ return scriptContent.substring(node.start, node.end);
531
+ };
532
+
533
+ // Track visited nodes to avoid duplicates
534
+ const visitedNodes = new Set();
535
+
536
+ // Traverse AST (same logic as JavaScript parser)
537
+ function traverse(node, parentType = null) {
538
+ if (!node) return;
539
+
540
+ // Skip if already visited (avoid processing same node twice)
541
+ if (visitedNodes.has(node)) return;
542
+ visitedNodes.add(node);
543
+
544
+ // Function declarations: function myFunc() {}
545
+ // Skip if inside ExportNamedDeclaration (will be handled below)
546
+ if (node.type === 'FunctionDeclaration' && node.id && parentType !== 'ExportNamedDeclaration') {
547
+ functions.push({
548
+ name: node.id.name,
549
+ content: getCode(node),
550
+ startLine: node.loc ? node.loc.start.line : 0,
551
+ endLine: node.loc ? node.loc.end.line : 0
552
+ });
553
+ }
554
+
555
+ // Arrow functions: const myFunc = () => {}
556
+ if (node.type === 'VariableDeclarator' &&
557
+ node.init &&
558
+ (node.init.type === 'ArrowFunctionExpression' || node.init.type === 'FunctionExpression') &&
559
+ node.id && node.id.type === 'Identifier') {
560
+ const funcContent = getCode(node);
561
+ functions.push({
562
+ name: node.id.name,
563
+ content: funcContent,
564
+ startLine: node.loc ? node.loc.start.line : 0,
565
+ endLine: node.loc ? node.loc.end.line : 0
566
+ });
567
+ }
568
+
569
+ // Helper function to extract methods from a class body
570
+ const extractClassMethods = (classNode, className) => {
571
+ const methods = [];
572
+ if (classNode.body && classNode.body.body && Array.isArray(classNode.body.body)) {
573
+ for (const member of classNode.body.body) {
574
+ // Handle MethodDefinition (regular methods, constructors, getters, setters, static methods)
575
+ if (member && member.type === 'MethodDefinition' && member.key) {
576
+ let methodName;
577
+ if (member.key.type === 'Identifier') {
578
+ methodName = member.key.name;
579
+ } else if (member.key.type === 'PrivateName') {
580
+ methodName = '#' + member.key.id.name;
581
+ } else if (member.key.type === 'StringLiteral' || member.key.type === 'NumericLiteral') {
582
+ methodName = String(member.key.value);
583
+ } else {
584
+ methodName = String(member.key.value || member.key.name || 'unknown');
585
+ }
586
+
587
+ // Include kind (constructor, get, set, method) in the name for clarity
588
+ const kind = member.kind || 'method';
589
+ const isStatic = member.static || false;
590
+
591
+ // For getters and setters, include the kind in the method name to distinguish them
592
+ // e.g., "value" getter vs "value" setter -> "get value" and "set value"
593
+ let fullMethodName = methodName;
594
+ if (kind === 'get') {
595
+ fullMethodName = 'get ' + methodName;
596
+ } else if (kind === 'set') {
597
+ fullMethodName = 'set ' + methodName;
598
+ } else if (kind === 'constructor') {
599
+ fullMethodName = 'constructor';
600
+ } else if (isStatic) {
601
+ fullMethodName = 'static ' + methodName;
602
+ }
603
+
604
+ methods.push({
605
+ name: className + '.' + methodName,
606
+ content: getCode(member),
607
+ startLine: member.loc ? member.loc.start.line : 0,
608
+ endLine: member.loc ? member.loc.end.line : 0,
609
+ isMethod: true,
610
+ className: className,
611
+ methodName: methodName,
612
+ kind: kind,
613
+ static: isStatic
614
+ });
615
+ }
616
+ }
617
+ }
618
+ return methods;
619
+ };
620
+
621
+ // Class declarations: class User { ... }
622
+ // Skip if inside ExportNamedDeclaration or ExportDefaultDeclaration (will be handled below)
623
+ if (node.type === 'ClassDeclaration' && node.id &&
624
+ parentType !== 'ExportNamedDeclaration' && parentType !== 'ExportDefaultDeclaration') {
625
+ const className = node.id.name;
626
+ const methods = extractClassMethods(node, className);
627
+
628
+ classes.push({
629
+ name: className,
630
+ content: getCode(node),
631
+ methods: methods,
632
+ startLine: node.loc ? node.loc.start.line : 0,
633
+ endLine: node.loc ? node.loc.end.line : 0
634
+ });
635
+ }
636
+
637
+ // Export declarations: export function, export class
638
+ if (node.type === 'ExportNamedDeclaration' && node.declaration) {
639
+ if (node.declaration.type === 'FunctionDeclaration' && node.declaration.id) {
640
+ functions.push({
641
+ name: node.declaration.id.name,
642
+ content: getCode(node.declaration),
643
+ startLine: node.declaration.loc ? node.declaration.loc.start.line : 0,
644
+ endLine: node.declaration.loc ? node.declaration.loc.end.line : 0,
645
+ isExported: true
646
+ });
647
+ // Mark as visited to avoid duplicate processing
648
+ visitedNodes.add(node.declaration);
649
+ } else if (node.declaration.type === 'ClassDeclaration' && node.declaration.id) {
650
+ const className = node.declaration.id.name;
651
+ const methods = extractClassMethods(node.declaration, className);
652
+
653
+ classes.push({
654
+ name: className,
655
+ content: getCode(node.declaration),
656
+ methods: methods,
657
+ startLine: node.declaration.loc ? node.declaration.loc.start.line : 0,
658
+ endLine: node.declaration.loc ? node.declaration.loc.end.line : 0,
659
+ isExported: true
660
+ });
661
+ // Mark as visited to avoid duplicate processing
662
+ visitedNodes.add(node.declaration);
663
+ }
664
+ }
665
+
666
+ // Export default declarations: export default class
667
+ if (node.type === 'ExportDefaultDeclaration' && node.declaration) {
668
+ if (node.declaration.type === 'ClassDeclaration' && node.declaration.id) {
669
+ const className = node.declaration.id.name;
670
+ const methods = extractClassMethods(node.declaration, className);
671
+
672
+ classes.push({
673
+ name: className,
674
+ content: getCode(node.declaration),
675
+ methods: methods,
676
+ startLine: node.declaration.loc ? node.declaration.loc.start.line : 0,
677
+ endLine: node.declaration.loc ? node.declaration.loc.end.line : 0,
678
+ isExported: true,
679
+ isDefaultExport: true
680
+ });
681
+ // Mark as visited to avoid duplicate processing
682
+ visitedNodes.add(node.declaration);
683
+ } else if (node.declaration.type === 'FunctionDeclaration' && node.declaration.id) {
684
+ functions.push({
685
+ name: node.declaration.id.name,
686
+ content: getCode(node.declaration),
687
+ startLine: node.declaration.loc ? node.declaration.loc.start.line : 0,
688
+ endLine: node.declaration.loc ? node.declaration.loc.end.line : 0,
689
+ isExported: true,
690
+ isDefaultExport: true
691
+ });
692
+ visitedNodes.add(node.declaration);
693
+ }
694
+ }
695
+
696
+ // Recursively traverse children
697
+ for (const key in node) {
698
+ if (key === 'parent' || key === 'leadingComments' || key === 'trailingComments') continue;
699
+ const child = node[key];
700
+ if (Array.isArray(child)) {
701
+ child.forEach(c => traverse(c, node.type));
702
+ } else if (child && typeof child === 'object' && child.type) {
703
+ traverse(child, node.type);
704
+ }
705
+ }
706
+ }
707
+
708
+ traverse(ast);
709
+ } catch (parseError) {
710
+ // Silently skip parsing errors for Vue files
711
+ return { functions: [], classes: [] };
712
+ }
713
+
714
+ return { functions, classes };
715
+ } catch (e) {
716
+ console.warn('Failed to read Vue file:', filePath, e.message);
717
+ return { functions: [], classes: [] };
718
+ }
719
+ }
720
+
721
+ module.exports = {
722
+ extractPythonFunctions,
723
+ extractJavaScriptFunctions,
724
+ extractVueFunctions
725
+ };
726
+