bonzai-tools 1.0.92

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