tywrap 0.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.
Files changed (137) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +319 -0
  3. package/dist/cli.d.ts +3 -0
  4. package/dist/cli.d.ts.map +1 -0
  5. package/dist/cli.js +263 -0
  6. package/dist/cli.js.map +1 -0
  7. package/dist/config/index.d.ts +41 -0
  8. package/dist/config/index.d.ts.map +1 -0
  9. package/dist/config/index.js +234 -0
  10. package/dist/config/index.js.map +1 -0
  11. package/dist/core/analyzer.d.ts +63 -0
  12. package/dist/core/analyzer.d.ts.map +1 -0
  13. package/dist/core/analyzer.js +676 -0
  14. package/dist/core/analyzer.js.map +1 -0
  15. package/dist/core/discovery.d.ts +85 -0
  16. package/dist/core/discovery.d.ts.map +1 -0
  17. package/dist/core/discovery.js +344 -0
  18. package/dist/core/discovery.js.map +1 -0
  19. package/dist/core/generator.d.ts +37 -0
  20. package/dist/core/generator.d.ts.map +1 -0
  21. package/dist/core/generator.js +368 -0
  22. package/dist/core/generator.js.map +1 -0
  23. package/dist/core/mapper.d.ts +40 -0
  24. package/dist/core/mapper.d.ts.map +1 -0
  25. package/dist/core/mapper.js +401 -0
  26. package/dist/core/mapper.js.map +1 -0
  27. package/dist/core/validation.d.ts +79 -0
  28. package/dist/core/validation.d.ts.map +1 -0
  29. package/dist/core/validation.js +486 -0
  30. package/dist/core/validation.js.map +1 -0
  31. package/dist/index.d.ts +26 -0
  32. package/dist/index.d.ts.map +1 -0
  33. package/dist/index.js +29 -0
  34. package/dist/index.js.map +1 -0
  35. package/dist/runtime/base.d.ts +12 -0
  36. package/dist/runtime/base.d.ts.map +1 -0
  37. package/dist/runtime/base.js +6 -0
  38. package/dist/runtime/base.js.map +1 -0
  39. package/dist/runtime/errors.d.ts +17 -0
  40. package/dist/runtime/errors.d.ts.map +1 -0
  41. package/dist/runtime/errors.js +21 -0
  42. package/dist/runtime/errors.js.map +1 -0
  43. package/dist/runtime/http.d.ts +22 -0
  44. package/dist/runtime/http.d.ts.map +1 -0
  45. package/dist/runtime/http.js +79 -0
  46. package/dist/runtime/http.js.map +1 -0
  47. package/dist/runtime/index.d.ts +8 -0
  48. package/dist/runtime/index.d.ts.map +1 -0
  49. package/dist/runtime/index.js +17 -0
  50. package/dist/runtime/index.js.map +1 -0
  51. package/dist/runtime/node.d.ts +48 -0
  52. package/dist/runtime/node.d.ts.map +1 -0
  53. package/dist/runtime/node.js +328 -0
  54. package/dist/runtime/node.js.map +1 -0
  55. package/dist/runtime/optimized-node.d.ts +131 -0
  56. package/dist/runtime/optimized-node.d.ts.map +1 -0
  57. package/dist/runtime/optimized-node.js +622 -0
  58. package/dist/runtime/optimized-node.js.map +1 -0
  59. package/dist/runtime/protocol.d.ts +3 -0
  60. package/dist/runtime/protocol.d.ts.map +1 -0
  61. package/dist/runtime/protocol.js +3 -0
  62. package/dist/runtime/protocol.js.map +1 -0
  63. package/dist/runtime/pyodide.d.ts +27 -0
  64. package/dist/runtime/pyodide.d.ts.map +1 -0
  65. package/dist/runtime/pyodide.js +191 -0
  66. package/dist/runtime/pyodide.js.map +1 -0
  67. package/dist/tools/python_suite.js +173 -0
  68. package/dist/tools/python_suite.js.map +1 -0
  69. package/dist/types/index.d.ts +299 -0
  70. package/dist/types/index.d.ts.map +1 -0
  71. package/dist/types/index.js +5 -0
  72. package/dist/types/index.js.map +1 -0
  73. package/dist/tywrap.d.ts +29 -0
  74. package/dist/tywrap.d.ts.map +1 -0
  75. package/dist/tywrap.js +589 -0
  76. package/dist/tywrap.js.map +1 -0
  77. package/dist/utils/bundle-optimizer.d.ts +182 -0
  78. package/dist/utils/bundle-optimizer.d.ts.map +1 -0
  79. package/dist/utils/bundle-optimizer.js +680 -0
  80. package/dist/utils/bundle-optimizer.js.map +1 -0
  81. package/dist/utils/cache.d.ts +149 -0
  82. package/dist/utils/cache.d.ts.map +1 -0
  83. package/dist/utils/cache.js +492 -0
  84. package/dist/utils/cache.js.map +1 -0
  85. package/dist/utils/codec.d.ts +94 -0
  86. package/dist/utils/codec.d.ts.map +1 -0
  87. package/dist/utils/codec.js +150 -0
  88. package/dist/utils/codec.js.map +1 -0
  89. package/dist/utils/logger.d.ts +42 -0
  90. package/dist/utils/logger.d.ts.map +1 -0
  91. package/dist/utils/logger.js +140 -0
  92. package/dist/utils/logger.js.map +1 -0
  93. package/dist/utils/memory-profiler.d.ts +123 -0
  94. package/dist/utils/memory-profiler.d.ts.map +1 -0
  95. package/dist/utils/memory-profiler.js +426 -0
  96. package/dist/utils/memory-profiler.js.map +1 -0
  97. package/dist/utils/parallel-processor.d.ts +146 -0
  98. package/dist/utils/parallel-processor.d.ts.map +1 -0
  99. package/dist/utils/parallel-processor.js +636 -0
  100. package/dist/utils/parallel-processor.js.map +1 -0
  101. package/dist/utils/python.d.ts +8 -0
  102. package/dist/utils/python.d.ts.map +1 -0
  103. package/dist/utils/python.js +56 -0
  104. package/dist/utils/python.js.map +1 -0
  105. package/dist/utils/runtime.d.ts +153 -0
  106. package/dist/utils/runtime.d.ts.map +1 -0
  107. package/dist/utils/runtime.js +519 -0
  108. package/dist/utils/runtime.js.map +1 -0
  109. package/package.json +136 -0
  110. package/runtime/python_bridge.py +476 -0
  111. package/src/cli.ts +331 -0
  112. package/src/config/index.ts +289 -0
  113. package/src/core/analyzer.ts +788 -0
  114. package/src/core/discovery.ts +415 -0
  115. package/src/core/generator.ts +447 -0
  116. package/src/core/mapper.ts +504 -0
  117. package/src/core/validation.ts +693 -0
  118. package/src/index.ts +83 -0
  119. package/src/runtime/base.ts +32 -0
  120. package/src/runtime/errors.ts +19 -0
  121. package/src/runtime/http.ts +132 -0
  122. package/src/runtime/index.ts +24 -0
  123. package/src/runtime/node.ts +431 -0
  124. package/src/runtime/optimized-node.ts +838 -0
  125. package/src/runtime/protocol.ts +2 -0
  126. package/src/runtime/pyodide.ts +228 -0
  127. package/src/types/global.d.ts +60 -0
  128. package/src/types/index.ts +410 -0
  129. package/src/tywrap.ts +669 -0
  130. package/src/utils/bundle-optimizer.ts +876 -0
  131. package/src/utils/cache.ts +634 -0
  132. package/src/utils/codec.ts +275 -0
  133. package/src/utils/logger.ts +201 -0
  134. package/src/utils/memory-profiler.ts +582 -0
  135. package/src/utils/parallel-processor.ts +879 -0
  136. package/src/utils/python.ts +79 -0
  137. package/src/utils/runtime.ts +616 -0
@@ -0,0 +1,788 @@
1
+ /**
2
+ * PyAnalyzer - Python AST analysis and type extraction
3
+ */
4
+
5
+ import type { SyntaxNode } from 'tree-sitter';
6
+ import Parser from 'tree-sitter';
7
+ import Python from 'tree-sitter-python';
8
+
9
+ import type {
10
+ AnalysisResult,
11
+ AnalysisError,
12
+ AnalysisWarning,
13
+ AnalysisStatistics,
14
+ PythonModule,
15
+ PythonFunction,
16
+ PythonClass,
17
+ PythonImport,
18
+ PythonType,
19
+ Parameter,
20
+ Property,
21
+ FunctionSignature,
22
+ } from '../types/index.js';
23
+ import { globalCache } from '../utils/cache.js';
24
+ import { getComponentLogger } from '../utils/logger.js';
25
+
26
+ const log = getComponentLogger('Analyzer');
27
+
28
+ const UNKNOWN_TYPE: PythonType = { kind: 'custom', name: 'Any', module: 'typing' };
29
+
30
+ export class PyAnalyzer {
31
+ private parser: Parser;
32
+ private initialized = false;
33
+
34
+ constructor() {
35
+ this.parser = new Parser();
36
+ }
37
+
38
+ /**
39
+ * Initialize the tree-sitter parser with Python grammar
40
+ */
41
+ async initialize(): Promise<void> {
42
+ if (this.initialized) {
43
+ return;
44
+ }
45
+
46
+ try {
47
+ await this.parser.setLanguage(Python);
48
+ this.initialized = true;
49
+ } catch (error) {
50
+ throw new Error(`Failed to initialize Python parser: ${error}`);
51
+ }
52
+ }
53
+
54
+ /**
55
+ * Analyze Python module source code and extract structure with caching
56
+ */
57
+ async analyzePythonModule(source: string, modulePath?: string): Promise<AnalysisResult> {
58
+ await this.initialize();
59
+
60
+ // Check cache first
61
+ const cached = await globalCache.getCachedAnalysis(source, modulePath ?? 'unknown');
62
+ if (cached) {
63
+ return cached;
64
+ }
65
+
66
+ const startTime = performance.now();
67
+ const errors: AnalysisError[] = [];
68
+ const warnings: AnalysisWarning[] = [];
69
+ const dependencies: string[] = [];
70
+
71
+ try {
72
+ const tree = this.parser.parse(source);
73
+ const rootNode = tree.rootNode;
74
+
75
+ // Check for syntax errors
76
+ if (rootNode.hasError) {
77
+ this.collectSyntaxErrors(rootNode, errors);
78
+ }
79
+
80
+ // Extract module components
81
+ const functions = await this.extractFunctions(rootNode);
82
+ const classes = await this.extractClasses(rootNode);
83
+ const imports = this.extractImports(rootNode);
84
+
85
+ // Collect dependencies from imports (deduplicated)
86
+ const uniqueDependencies = [...new Set(imports.map(imp => imp.module))];
87
+ dependencies.push(...uniqueDependencies);
88
+
89
+ // Generate statistics
90
+ const statistics = this.generateStatistics(functions, classes, source);
91
+
92
+ const module: PythonModule = {
93
+ name: this.extractModuleName(modulePath),
94
+ path: modulePath,
95
+ functions,
96
+ classes,
97
+ imports,
98
+ exports: this.extractExports(rootNode),
99
+ };
100
+
101
+ const result = {
102
+ module,
103
+ errors,
104
+ warnings,
105
+ dependencies,
106
+ statistics,
107
+ };
108
+
109
+ // Cache the successful result
110
+ const computeTime = performance.now() - startTime;
111
+ await globalCache.setCachedAnalysis(source, modulePath ?? 'unknown', result, computeTime);
112
+
113
+ return result;
114
+ } catch (error) {
115
+ errors.push({
116
+ type: 'syntax',
117
+ message: `Failed to parse Python module: ${error}`,
118
+ file: modulePath,
119
+ });
120
+
121
+ return {
122
+ module: {
123
+ name: this.extractModuleName(modulePath),
124
+ path: modulePath,
125
+ functions: [],
126
+ classes: [],
127
+ imports: [],
128
+ exports: [],
129
+ },
130
+ errors,
131
+ warnings,
132
+ dependencies,
133
+ statistics: this.generateStatistics([], [], source),
134
+ };
135
+ }
136
+ }
137
+
138
+ /**
139
+ * Extract function definitions from AST
140
+ */
141
+ async extractFunctions(node: SyntaxNode): Promise<PythonFunction[]> {
142
+ const functions: PythonFunction[] = [];
143
+
144
+ const functionNodes = this.findNodesByType(node, 'function_definition');
145
+
146
+ for (const funcNode of functionNodes) {
147
+ try {
148
+ const func = await this.extractFunction(funcNode);
149
+ functions.push(func);
150
+ } catch (error) {
151
+ log.warn('Failed to extract function', { error: String(error) });
152
+ }
153
+ }
154
+
155
+ return functions;
156
+ }
157
+
158
+ /**
159
+ * Extract class definitions from AST
160
+ */
161
+ async extractClasses(node: SyntaxNode): Promise<PythonClass[]> {
162
+ const classes: PythonClass[] = [];
163
+
164
+ const classNodes = this.findNodesByType(node, 'class_definition');
165
+
166
+ for (const classNode of classNodes) {
167
+ try {
168
+ const cls = await this.extractClass(classNode);
169
+ classes.push(cls);
170
+ } catch (error) {
171
+ log.warn('Failed to extract class', { error: String(error) });
172
+ }
173
+ }
174
+
175
+ return classes;
176
+ }
177
+
178
+ /**
179
+ * Extract single function from function_definition node
180
+ */
181
+ private async extractFunction(node: SyntaxNode): Promise<PythonFunction> {
182
+ const nameNode = node.childForFieldName('name');
183
+ const parametersNode = node.childForFieldName('parameters');
184
+ const returnTypeNode = node.childForFieldName('return_type');
185
+ const bodyNode = node.childForFieldName('body');
186
+
187
+ const name = nameNode?.text ?? 'unknown';
188
+ const parameters = parametersNode ? this.extractParameters(parametersNode) : [];
189
+ const returnType = returnTypeNode ? this.parseTypeAnnotation(returnTypeNode) : UNKNOWN_TYPE;
190
+
191
+ // Extract decorators
192
+ const decorators = this.extractDecorators(node);
193
+
194
+ // Determine if async
195
+ const isAsync = node.text.trimStart().startsWith('async def');
196
+
197
+ // Check if generator (contains yield)
198
+ const isGenerator = bodyNode ? this.containsYield(bodyNode) : false;
199
+
200
+ // Extract docstring
201
+ const docstring = bodyNode ? this.extractDocstring(bodyNode) : undefined;
202
+
203
+ const signature: FunctionSignature = {
204
+ parameters,
205
+ returnType,
206
+ isAsync,
207
+ isGenerator,
208
+ };
209
+
210
+ return {
211
+ name,
212
+ signature,
213
+ docstring,
214
+ decorators,
215
+ isAsync,
216
+ isGenerator,
217
+ returnType,
218
+ parameters,
219
+ };
220
+ }
221
+
222
+ /**
223
+ * Extract single class from class_definition node
224
+ */
225
+ private async extractClass(node: SyntaxNode): Promise<PythonClass> {
226
+ const nameNode = node.childForFieldName('name');
227
+ const superclassesNode = node.childForFieldName('superclasses');
228
+ const bodyNode = node.childForFieldName('body');
229
+
230
+ const name = nameNode?.text ?? 'unknown';
231
+ const bases = superclassesNode ? this.extractBases(superclassesNode) : [];
232
+
233
+ const decorators = this.extractDecorators(node);
234
+ const docstring = bodyNode ? this.extractDocstring(bodyNode) : undefined;
235
+
236
+ // Extract methods and properties from body
237
+ const methods: PythonFunction[] = [];
238
+ const properties: Property[] = [];
239
+
240
+ if (bodyNode) {
241
+ const methodNodes = this.findNodesByType(bodyNode, 'function_definition');
242
+ for (const methodNode of methodNodes) {
243
+ try {
244
+ const method = await this.extractFunction(methodNode);
245
+ methods.push(method);
246
+ } catch (error) {
247
+ log.warn('Failed to extract method', { error: String(error) });
248
+ }
249
+ }
250
+
251
+ // Extract properties (assignments with type annotations or @property decorators)
252
+ const assignmentNodes = this.findNodesByType(bodyNode, 'assignment');
253
+ for (const assignNode of assignmentNodes) {
254
+ const prop = this.extractProperty(assignNode);
255
+ if (prop) {
256
+ properties.push(prop);
257
+ }
258
+ }
259
+ }
260
+
261
+ return {
262
+ name,
263
+ bases,
264
+ methods,
265
+ properties,
266
+ docstring,
267
+ decorators,
268
+ };
269
+ }
270
+
271
+ /**
272
+ * Extract function parameters from parameters node
273
+ */
274
+ private extractParameters(node: SyntaxNode): Parameter[] {
275
+ const parameters: Parameter[] = [];
276
+
277
+ // Handle different parameter types
278
+ for (const child of node.namedChildren) {
279
+ if (child.type === 'identifier') {
280
+ // Simple parameter
281
+ parameters.push({
282
+ name: child.text,
283
+ type: UNKNOWN_TYPE,
284
+ optional: false,
285
+ varArgs: false,
286
+ kwArgs: false,
287
+ });
288
+ } else if (child.type === 'typed_parameter') {
289
+ // Parameter with type annotation - check multiple possible field names
290
+ const patternNode = child.childForFieldName('pattern');
291
+ let nameNode = patternNode ?? child.child(0) ?? null;
292
+ const typeFieldNode = child.childForFieldName('type');
293
+ const typeNode = typeFieldNode ?? child.child(2) ?? null;
294
+
295
+ // If pattern field doesn't exist, try to find identifier directly
296
+ if (!nameNode || nameNode.type !== 'identifier') {
297
+ nameNode = this.findNodesByType(child, 'identifier')[0] ?? null;
298
+ }
299
+
300
+ parameters.push({
301
+ name: nameNode?.text ?? 'unknown',
302
+ type: typeNode ? this.parseTypeAnnotation(typeNode) : UNKNOWN_TYPE,
303
+ optional: false,
304
+ varArgs: false,
305
+ kwArgs: false,
306
+ });
307
+ } else if (child.type === 'default_parameter') {
308
+ // Parameter with default value
309
+ const nameFieldNode = child.childForFieldName('name');
310
+ const nameNode = nameFieldNode ?? child.child(0) ?? null;
311
+ let valueNode = child.childForFieldName('value') ?? null;
312
+
313
+ // Find value node if field name doesn't work
314
+ if (!valueNode) {
315
+ const equalIndex = child.children.findIndex(c => c.text === '=');
316
+ if (equalIndex >= 0 && equalIndex + 1 < child.children.length) {
317
+ valueNode = child.children[equalIndex + 1] ?? null;
318
+ }
319
+ }
320
+
321
+ parameters.push({
322
+ name: nameNode?.text ?? 'unknown',
323
+ type: UNKNOWN_TYPE,
324
+ optional: true,
325
+ defaultValue: valueNode?.text,
326
+ varArgs: false,
327
+ kwArgs: false,
328
+ });
329
+ } else if (child.type === 'typed_default_parameter') {
330
+ // Parameter with type and default
331
+ const patternNode = child.childForFieldName('pattern');
332
+ let nameNode = patternNode ?? child.child(0) ?? null;
333
+ let typeNode = child.childForFieldName('type') ?? null;
334
+ let valueNode = child.childForFieldName('value') ?? null;
335
+
336
+ // If pattern field doesn't exist, try to find identifier directly
337
+ if (!nameNode || nameNode.type !== 'identifier') {
338
+ nameNode = this.findNodesByType(child, 'identifier')[0] ?? null;
339
+ }
340
+
341
+ // Find type and value nodes if field names don't work
342
+ if (!typeNode || !valueNode) {
343
+ const colonIndex = child.children.findIndex(c => c.text === ':');
344
+ const equalIndex = child.children.findIndex(c => c.text === '=');
345
+
346
+ if (colonIndex >= 0 && colonIndex + 1 < child.children.length) {
347
+ typeNode = child.children[colonIndex + 1] ?? null;
348
+ }
349
+ if (equalIndex >= 0 && equalIndex + 1 < child.children.length) {
350
+ valueNode = child.children[equalIndex + 1] ?? null;
351
+ }
352
+ }
353
+
354
+ parameters.push({
355
+ name: nameNode?.text ?? 'unknown',
356
+ type: typeNode ? this.parseTypeAnnotation(typeNode) : UNKNOWN_TYPE,
357
+ optional: true,
358
+ defaultValue: valueNode?.text,
359
+ varArgs: false,
360
+ kwArgs: false,
361
+ });
362
+ } else if (child.type === 'list_splat_pattern') {
363
+ // *args parameter
364
+ const nameNode = child.child(1); // Skip the *
365
+ parameters.push({
366
+ name: nameNode?.text ?? 'args',
367
+ type: { kind: 'collection', name: 'tuple', itemTypes: [] },
368
+ optional: false,
369
+ varArgs: true,
370
+ kwArgs: false,
371
+ });
372
+ } else if (child.type === 'dictionary_splat_pattern') {
373
+ // **kwargs parameter
374
+ const nameNode = child.child(1); // Skip the **
375
+ parameters.push({
376
+ name: nameNode?.text ?? 'kwargs',
377
+ type: { kind: 'collection', name: 'dict', itemTypes: [] },
378
+ optional: false,
379
+ varArgs: false,
380
+ kwArgs: true,
381
+ });
382
+ }
383
+ }
384
+
385
+ return parameters;
386
+ }
387
+
388
+ /**
389
+ * Parse type annotation node into PythonType
390
+ */
391
+ parseTypeAnnotation(node: SyntaxNode): PythonType {
392
+ const typeText = node.text.trim();
393
+
394
+ // Handle primitive types
395
+ const primitiveTypes = ['int', 'float', 'str', 'bool', 'bytes', 'None'] as const;
396
+ if (primitiveTypes.includes(typeText as (typeof primitiveTypes)[number])) {
397
+ return {
398
+ kind: 'primitive',
399
+ name: typeText as 'int' | 'float' | 'str' | 'bool' | 'bytes' | 'None',
400
+ };
401
+ }
402
+
403
+ // Handle collection types
404
+ const collectionTypes = [
405
+ 'dict',
406
+ 'Dict',
407
+ 'list',
408
+ 'List',
409
+ 'tuple',
410
+ 'Tuple',
411
+ 'set',
412
+ 'Set',
413
+ ] as const;
414
+ if (collectionTypes.includes(typeText as (typeof collectionTypes)[number])) {
415
+ const normalizedName =
416
+ typeText.toLowerCase() === 'dict' || typeText === 'Dict'
417
+ ? 'dict'
418
+ : typeText.toLowerCase() === 'list' || typeText === 'List'
419
+ ? 'list'
420
+ : typeText.toLowerCase() === 'tuple' || typeText === 'Tuple'
421
+ ? 'tuple'
422
+ : typeText.toLowerCase() === 'set' || typeText === 'Set'
423
+ ? 'set'
424
+ : 'dict';
425
+ return { kind: 'collection', name: normalizedName, itemTypes: [] };
426
+ }
427
+
428
+ // Handle List, Dict, etc.
429
+ if (typeText.startsWith('List[') || typeText.startsWith('list[')) {
430
+ return this.parseGenericType(typeText, 'list');
431
+ }
432
+ if (typeText.startsWith('Dict[') || typeText.startsWith('dict[')) {
433
+ return this.parseGenericType(typeText, 'dict');
434
+ }
435
+ if (typeText.startsWith('Tuple[') || typeText.startsWith('tuple[')) {
436
+ return this.parseGenericType(typeText, 'tuple');
437
+ }
438
+ if (typeText.startsWith('Set[') || typeText.startsWith('set[')) {
439
+ return this.parseGenericType(typeText, 'set');
440
+ }
441
+
442
+ // Handle Union types
443
+ if (typeText.includes(' | ') || typeText.startsWith('Union[')) {
444
+ return this.parseUnionType(typeText);
445
+ }
446
+
447
+ // Handle Optional
448
+ if (typeText.startsWith('Optional[')) {
449
+ const innerType = typeText.slice(9, -1);
450
+ const parsedInnerType = this.parseTypeAnnotation({ text: innerType } as SyntaxNode);
451
+
452
+ // For Optional[Dict] -> ensure Dict is treated as collection, not custom
453
+ if (innerType === 'Dict' || innerType === 'dict') {
454
+ return {
455
+ kind: 'optional',
456
+ type: { kind: 'collection', name: 'dict', itemTypes: [] },
457
+ };
458
+ }
459
+
460
+ return {
461
+ kind: 'optional',
462
+ type: parsedInnerType,
463
+ };
464
+ }
465
+
466
+ // Custom/module type - clean up quotes
467
+ let cleanedName = typeText;
468
+ if (
469
+ (cleanedName.startsWith("'") && cleanedName.endsWith("'")) ||
470
+ (cleanedName.startsWith('"') && cleanedName.endsWith('"'))
471
+ ) {
472
+ cleanedName = cleanedName.slice(1, -1);
473
+ }
474
+
475
+ return {
476
+ kind: 'custom',
477
+ name: cleanedName,
478
+ };
479
+ }
480
+
481
+ private parseGenericType(
482
+ typeText: string,
483
+ collectionName: 'list' | 'dict' | 'tuple' | 'set'
484
+ ): PythonType {
485
+ const bracketStart = typeText.indexOf('[');
486
+ const bracketEnd = typeText.lastIndexOf(']');
487
+
488
+ if (bracketStart === -1 || bracketEnd === -1) {
489
+ return { kind: 'collection', name: collectionName, itemTypes: [] };
490
+ }
491
+
492
+ const genericPart = typeText.slice(bracketStart + 1, bracketEnd);
493
+ const itemTypes = this.parseGenericArguments(genericPart);
494
+
495
+ return {
496
+ kind: 'collection',
497
+ name: collectionName,
498
+ itemTypes,
499
+ };
500
+ }
501
+
502
+ private parseUnionType(typeText: string): PythonType {
503
+ let unionPart: string;
504
+
505
+ if (typeText.startsWith('Union[')) {
506
+ unionPart = typeText.slice(6, -1);
507
+ } else {
508
+ unionPart = typeText;
509
+ }
510
+
511
+ const types = this.parseGenericArguments(unionPart);
512
+ return { kind: 'union', types };
513
+ }
514
+
515
+ private parseGenericArguments(argsText: string): PythonType[] {
516
+ // Nested-aware argument splitter for generics (handles [] and ())
517
+ const results: string[] = [];
518
+ let depthSquare = 0;
519
+ let depthParen = 0;
520
+ let current = '';
521
+ for (let i = 0; i < argsText.length; i++) {
522
+ const ch = String(argsText.charAt(i));
523
+ if (ch === '[') {
524
+ depthSquare++;
525
+ }
526
+ if (ch === ']') {
527
+ depthSquare = Math.max(0, depthSquare - 1);
528
+ }
529
+ if (ch === '(') {
530
+ depthParen++;
531
+ }
532
+ if (ch === ')') {
533
+ depthParen = Math.max(0, depthParen - 1);
534
+ }
535
+ if (ch === ',' && depthSquare === 0 && depthParen === 0) {
536
+ results.push(current.trim());
537
+ current = '';
538
+ continue;
539
+ }
540
+ current += ch;
541
+ }
542
+ if (current.trim().length > 0) {
543
+ results.push(current.trim());
544
+ }
545
+ return results.map(arg => this.parseTypeAnnotation({ text: arg } as SyntaxNode));
546
+ }
547
+
548
+ /**
549
+ * Extract import statements
550
+ */
551
+ private extractImports(node: SyntaxNode): PythonImport[] {
552
+ const imports: PythonImport[] = [];
553
+ const seen = new Set<string>();
554
+
555
+ // Only look for import statements at the module level (not nested)
556
+ const moduleChildren = node.namedChildren;
557
+ const importNodes: SyntaxNode[] = [];
558
+
559
+ for (const child of moduleChildren) {
560
+ if (child.type === 'import_statement' || child.type === 'import_from_statement') {
561
+ importNodes.push(child);
562
+ }
563
+ }
564
+
565
+ for (const importNode of importNodes) {
566
+ if (importNode.type === 'import_statement') {
567
+ // import module [as alias]
568
+ const nameNode = importNode.child(1);
569
+ if (nameNode && (nameNode.type === 'dotted_name' || nameNode.type === 'aliased_import')) {
570
+ let moduleName: string | undefined;
571
+ if (nameNode.type === 'aliased_import') {
572
+ // Handle 'import module as alias' - extract module name before 'as'
573
+ const parts = nameNode.text.split(' as ');
574
+ const firstPart = parts[0];
575
+ if (firstPart) {
576
+ moduleName = firstPart.split('.')[0]; // Get base module name
577
+ }
578
+ } else {
579
+ // Handle 'import module' - extract module name
580
+ moduleName = nameNode.text.split('.')[0]; // Get base module name
581
+ }
582
+
583
+ if (moduleName) {
584
+ const key = `${moduleName}:import`;
585
+ if (!seen.has(key)) {
586
+ seen.add(key);
587
+ imports.push({
588
+ module: moduleName,
589
+ fromImport: false,
590
+ });
591
+ }
592
+ }
593
+ }
594
+ } else if (importNode.type === 'import_from_statement') {
595
+ // from module import name [as alias]
596
+ const moduleNameIndex = importNode.children.findIndex(c => c.text === 'from');
597
+ const importIndex = importNode.children.findIndex(c => c.text === 'import');
598
+ if (moduleNameIndex >= 0 && importIndex > moduleNameIndex) {
599
+ const moduleNode = importNode.children[moduleNameIndex + 1];
600
+
601
+ if (moduleNode && moduleNode.type === 'dotted_name') {
602
+ const moduleName = moduleNode.text.split('.')[0];
603
+
604
+ if (moduleName) {
605
+ // Collect all import names after the 'import' keyword
606
+ const importNames: string[] = [];
607
+
608
+ for (let i = importIndex + 1; i < importNode.children.length; i++) {
609
+ const child = importNode.children.at(i);
610
+ if (child && (child.type === 'dotted_name' || child.type === 'identifier')) {
611
+ importNames.push(child.text);
612
+ } else if (child && child.type === 'wildcard_import') {
613
+ importNames.push('*');
614
+ }
615
+ // Skip comma separators
616
+ }
617
+
618
+ // Create separate import entries for each imported name
619
+ for (const importName of importNames) {
620
+ const key = `${moduleName}:${importName}`;
621
+ if (!seen.has(key)) {
622
+ seen.add(key);
623
+ imports.push({
624
+ module: moduleName,
625
+ name: importName,
626
+ fromImport: true,
627
+ });
628
+ }
629
+ }
630
+ }
631
+ }
632
+ }
633
+ }
634
+ }
635
+
636
+ return imports;
637
+ }
638
+
639
+ /**
640
+ * Utility methods
641
+ */
642
+ private findNodesByType(node: SyntaxNode, type: string): SyntaxNode[] {
643
+ const results: SyntaxNode[] = [];
644
+
645
+ const traverse = (n: SyntaxNode): void => {
646
+ if (n.type === type) {
647
+ results.push(n);
648
+ }
649
+
650
+ for (const child of n.children) {
651
+ traverse(child);
652
+ }
653
+ };
654
+
655
+ traverse(node);
656
+ return results;
657
+ }
658
+
659
+ private extractModuleName(path?: string): string {
660
+ if (!path) {
661
+ return 'unknown';
662
+ }
663
+ const parts = path.split('/').pop()?.replace('.py', '') ?? 'unknown';
664
+ return parts;
665
+ }
666
+
667
+ private extractDecorators(node: SyntaxNode): string[] {
668
+ const decorators: string[] = [];
669
+
670
+ // Look for decorated_definition parent
671
+ if (node.parent?.type === 'decorated_definition') {
672
+ const decoratorNodes = this.findNodesByType(node.parent, 'decorator');
673
+ decorators.push(...decoratorNodes.map(d => d.text));
674
+ }
675
+
676
+ return decorators;
677
+ }
678
+
679
+ private extractDocstring(bodyNode: SyntaxNode): string | undefined {
680
+ // First statement in body might be a string (docstring)
681
+ const firstChild = bodyNode.namedChildren[0];
682
+ if (firstChild?.type === 'expression_statement') {
683
+ const expr = firstChild.child(0);
684
+ if (expr?.type === 'string') {
685
+ let docstring = expr.text;
686
+ // Remove outer quotes and any remaining inner quotes
687
+ if (docstring.startsWith('"""') && docstring.endsWith('"""')) {
688
+ docstring = docstring.slice(3, -3);
689
+ } else if (docstring.startsWith("'''") && docstring.endsWith("'''")) {
690
+ docstring = docstring.slice(3, -3);
691
+ } else if (docstring.startsWith('"') && docstring.endsWith('"')) {
692
+ docstring = docstring.slice(1, -1);
693
+ } else if (docstring.startsWith("'") && docstring.endsWith("'")) {
694
+ docstring = docstring.slice(1, -1);
695
+ }
696
+ return docstring.trim();
697
+ }
698
+ }
699
+ return undefined;
700
+ }
701
+
702
+ private containsYield(node: SyntaxNode): boolean {
703
+ const yieldNodes = this.findNodesByType(node, 'yield');
704
+ return yieldNodes.length > 0;
705
+ }
706
+
707
+ private extractBases(node: SyntaxNode): string[] {
708
+ const bases: string[] = [];
709
+ for (const child of node.namedChildren) {
710
+ if (child.type === 'identifier') {
711
+ bases.push(child.text);
712
+ }
713
+ }
714
+ return bases;
715
+ }
716
+
717
+ private extractProperty(node: SyntaxNode): Property | null {
718
+ // Simple property extraction from assignments
719
+ const leftNode = node.child(0);
720
+ if (leftNode?.type === 'identifier') {
721
+ return {
722
+ name: leftNode.text,
723
+ type: UNKNOWN_TYPE,
724
+ readonly: false,
725
+ };
726
+ }
727
+ return null;
728
+ }
729
+
730
+ private extractExports(node: SyntaxNode): string[] {
731
+ // Look for __all__ definition
732
+ const assignments = this.findNodesByType(node, 'assignment');
733
+ for (const assignment of assignments) {
734
+ const leftNode = assignment.child(0);
735
+ if (leftNode?.text === '__all__') {
736
+ const rightNode = assignment.child(2);
737
+ if (rightNode?.type === 'list') {
738
+ return rightNode.namedChildren
739
+ .filter(child => child.type === 'string')
740
+ .map(child => child.text.slice(1, -1)); // Remove quotes
741
+ }
742
+ }
743
+ }
744
+ return [];
745
+ }
746
+
747
+ private collectSyntaxErrors(node: SyntaxNode, errors: AnalysisError[]): void {
748
+ if (node.hasError) {
749
+ errors.push({
750
+ type: 'syntax',
751
+ message: 'Syntax error in Python code',
752
+ line: node.startPosition.row + 1,
753
+ column: node.startPosition.column + 1,
754
+ });
755
+ }
756
+
757
+ for (const child of node.children) {
758
+ this.collectSyntaxErrors(child, errors);
759
+ }
760
+ }
761
+
762
+ private generateStatistics(
763
+ functions: PythonFunction[],
764
+ classes: PythonClass[],
765
+ source: string
766
+ ): AnalysisStatistics {
767
+ // Count only module-level functions, not class methods in the main count
768
+ const totalFunctions = functions.length;
769
+ const totalClasses = classes.length;
770
+
771
+ // Simple type hint coverage calculation
772
+ const functionsWithTypes = functions.filter(
773
+ f =>
774
+ f.parameters.some(p => p.type.kind !== 'primitive' || p.type.name !== 'None') ||
775
+ f.returnType.kind !== 'primitive' ||
776
+ f.returnType.name !== 'None'
777
+ ).length;
778
+
779
+ const typeHintsCoverage = totalFunctions > 0 ? (functionsWithTypes / totalFunctions) * 100 : 0;
780
+
781
+ return {
782
+ functionsAnalyzed: totalFunctions,
783
+ classesAnalyzed: totalClasses,
784
+ typeHintsCoverage,
785
+ estimatedComplexity: Math.min(source.length / 1000, 10), // Simple complexity estimate
786
+ };
787
+ }
788
+ }