tywrap 0.7.0 → 0.9.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 (165) hide show
  1. package/README.md +36 -11
  2. package/SECURITY.md +39 -0
  3. package/dist/config/index.d.ts.map +1 -1
  4. package/dist/config/index.js +8 -0
  5. package/dist/config/index.js.map +1 -1
  6. package/dist/core/annotation-parser.d.ts +2 -1
  7. package/dist/core/annotation-parser.d.ts.map +1 -1
  8. package/dist/core/annotation-parser.js +6 -3
  9. package/dist/core/annotation-parser.js.map +1 -1
  10. package/dist/core/emit-call.d.ts.map +1 -1
  11. package/dist/core/emit-call.js +1 -1
  12. package/dist/core/emit-call.js.map +1 -1
  13. package/dist/core/generator.d.ts +23 -4
  14. package/dist/core/generator.d.ts.map +1 -1
  15. package/dist/core/generator.js +243 -170
  16. package/dist/core/generator.js.map +1 -1
  17. package/dist/core/mapper.d.ts +3 -2
  18. package/dist/core/mapper.d.ts.map +1 -1
  19. package/dist/core/mapper.js +5 -5
  20. package/dist/core/mapper.js.map +1 -1
  21. package/dist/dev.d.ts.map +1 -1
  22. package/dist/dev.js +1 -3
  23. package/dist/dev.js.map +1 -1
  24. package/dist/index.d.ts +7 -5
  25. package/dist/index.d.ts.map +1 -1
  26. package/dist/index.js +6 -4
  27. package/dist/index.js.map +1 -1
  28. package/dist/runtime/base-bridge.d.ts +3 -7
  29. package/dist/runtime/base-bridge.d.ts.map +1 -1
  30. package/dist/runtime/base-bridge.js +4 -17
  31. package/dist/runtime/base-bridge.js.map +1 -1
  32. package/dist/runtime/bounded-context.d.ts +3 -22
  33. package/dist/runtime/bounded-context.d.ts.map +1 -1
  34. package/dist/runtime/bounded-context.js +13 -53
  35. package/dist/runtime/bounded-context.js.map +1 -1
  36. package/dist/runtime/bridge-codec.d.ts +1 -1
  37. package/dist/runtime/bridge-codec.d.ts.map +1 -1
  38. package/dist/runtime/bridge-codec.js +78 -55
  39. package/dist/runtime/bridge-codec.js.map +1 -1
  40. package/dist/runtime/errors.d.ts +16 -0
  41. package/dist/runtime/errors.d.ts.map +1 -1
  42. package/dist/runtime/errors.js +17 -0
  43. package/dist/runtime/errors.js.map +1 -1
  44. package/dist/runtime/frame-codec.d.ts +111 -0
  45. package/dist/runtime/frame-codec.d.ts.map +1 -0
  46. package/dist/runtime/frame-codec.js +352 -0
  47. package/dist/runtime/frame-codec.js.map +1 -0
  48. package/dist/runtime/http-transport.d.ts +1 -1
  49. package/dist/runtime/http-transport.d.ts.map +1 -1
  50. package/dist/runtime/http-transport.js +1 -1
  51. package/dist/runtime/http-transport.js.map +1 -1
  52. package/dist/runtime/index.d.ts +2 -1
  53. package/dist/runtime/index.d.ts.map +1 -1
  54. package/dist/runtime/index.js +2 -1
  55. package/dist/runtime/index.js.map +1 -1
  56. package/dist/runtime/node.d.ts +7 -23
  57. package/dist/runtime/node.d.ts.map +1 -1
  58. package/dist/runtime/node.js +5 -78
  59. package/dist/runtime/node.js.map +1 -1
  60. package/dist/runtime/pooled-transport.d.ts +120 -59
  61. package/dist/runtime/pooled-transport.d.ts.map +1 -1
  62. package/dist/runtime/pooled-transport.js +345 -78
  63. package/dist/runtime/pooled-transport.js.map +1 -1
  64. package/dist/runtime/pyodide-bootstrap-core.generated.d.ts.map +1 -1
  65. package/dist/runtime/pyodide-bootstrap-core.generated.js +1 -1
  66. package/dist/runtime/pyodide-bootstrap-core.generated.js.map +1 -1
  67. package/dist/runtime/pyodide-transport.d.ts +1 -1
  68. package/dist/runtime/pyodide-transport.d.ts.map +1 -1
  69. package/dist/runtime/pyodide-transport.js +2 -3
  70. package/dist/runtime/pyodide-transport.js.map +1 -1
  71. package/dist/runtime/rpc-client.d.ts +7 -36
  72. package/dist/runtime/rpc-client.d.ts.map +1 -1
  73. package/dist/runtime/rpc-client.js +37 -72
  74. package/dist/runtime/rpc-client.js.map +1 -1
  75. package/dist/runtime/subprocess-transport.d.ts +134 -8
  76. package/dist/runtime/subprocess-transport.d.ts.map +1 -1
  77. package/dist/runtime/subprocess-transport.js +490 -65
  78. package/dist/runtime/subprocess-transport.js.map +1 -1
  79. package/dist/runtime/timed-out-request-tracker.d.ts +2 -1
  80. package/dist/runtime/timed-out-request-tracker.d.ts.map +1 -1
  81. package/dist/runtime/transport.d.ts +90 -18
  82. package/dist/runtime/transport.d.ts.map +1 -1
  83. package/dist/runtime/transport.js +21 -1
  84. package/dist/runtime/transport.js.map +1 -1
  85. package/dist/runtime/validators.d.ts +49 -0
  86. package/dist/runtime/validators.d.ts.map +1 -1
  87. package/dist/runtime/validators.js +152 -0
  88. package/dist/runtime/validators.js.map +1 -1
  89. package/dist/types/index.d.ts +14 -15
  90. package/dist/types/index.d.ts.map +1 -1
  91. package/dist/tywrap.d.ts +3 -2
  92. package/dist/tywrap.d.ts.map +1 -1
  93. package/dist/tywrap.js +140 -21
  94. package/dist/tywrap.js.map +1 -1
  95. package/dist/utils/cache.d.ts +3 -16
  96. package/dist/utils/cache.d.ts.map +1 -1
  97. package/dist/utils/codec.d.ts +1 -0
  98. package/dist/utils/codec.d.ts.map +1 -1
  99. package/dist/utils/codec.js +165 -17
  100. package/dist/utils/codec.js.map +1 -1
  101. package/dist/utils/ir-cache.d.ts +2 -1
  102. package/dist/utils/ir-cache.d.ts.map +1 -1
  103. package/dist/utils/runtime.d.ts +0 -29
  104. package/dist/utils/runtime.d.ts.map +1 -1
  105. package/dist/utils/runtime.js +16 -107
  106. package/dist/utils/runtime.js.map +1 -1
  107. package/dist/version.js +1 -1
  108. package/package.json +7 -6
  109. package/runtime/frame_codec.py +430 -0
  110. package/runtime/python_bridge.py +213 -54
  111. package/runtime/tywrap_bridge_core.py +217 -147
  112. package/src/config/index.ts +11 -0
  113. package/src/core/annotation-parser.ts +9 -5
  114. package/src/core/emit-call.ts +1 -7
  115. package/src/core/generator.ts +315 -205
  116. package/src/core/mapper.ts +8 -8
  117. package/src/dev.ts +1 -3
  118. package/src/index.ts +7 -3
  119. package/src/runtime/base-bridge.ts +5 -30
  120. package/src/runtime/bounded-context.ts +12 -67
  121. package/src/runtime/bridge-codec.ts +94 -65
  122. package/src/runtime/errors.ts +21 -0
  123. package/src/runtime/frame-codec.ts +469 -0
  124. package/src/runtime/http-transport.ts +6 -1
  125. package/src/runtime/index.ts +7 -6
  126. package/src/runtime/node.ts +17 -104
  127. package/src/runtime/pooled-transport.ts +424 -90
  128. package/src/runtime/pyodide-bootstrap-core.generated.ts +1 -1
  129. package/src/runtime/pyodide-transport.ts +7 -3
  130. package/src/runtime/rpc-client.ts +58 -93
  131. package/src/runtime/subprocess-transport.ts +585 -80
  132. package/src/runtime/timed-out-request-tracker.ts +1 -1
  133. package/src/runtime/transport.ts +112 -22
  134. package/src/runtime/validators.ts +204 -0
  135. package/src/types/index.ts +21 -35
  136. package/src/tywrap.ts +157 -30
  137. package/src/utils/cache.ts +3 -3
  138. package/src/utils/codec.ts +205 -16
  139. package/src/utils/ir-cache.ts +1 -1
  140. package/src/utils/runtime.ts +17 -128
  141. package/src/version.ts +1 -1
  142. package/dist/core/discovery.d.ts +0 -103
  143. package/dist/core/discovery.d.ts.map +0 -1
  144. package/dist/core/discovery.js +0 -380
  145. package/dist/core/discovery.js.map +0 -1
  146. package/dist/core/validation.d.ts +0 -102
  147. package/dist/core/validation.d.ts.map +0 -1
  148. package/dist/core/validation.js +0 -490
  149. package/dist/core/validation.js.map +0 -1
  150. package/dist/runtime/base.d.ts +0 -22
  151. package/dist/runtime/base.d.ts.map +0 -1
  152. package/dist/runtime/base.js +0 -23
  153. package/dist/runtime/base.js.map +0 -1
  154. package/dist/runtime/transport-pool.d.ts +0 -196
  155. package/dist/runtime/transport-pool.d.ts.map +0 -1
  156. package/dist/runtime/transport-pool.js +0 -418
  157. package/dist/runtime/transport-pool.js.map +0 -1
  158. package/runtime/__pycache__/_tywrap_member_fixtures.cpython-311.pyc +0 -0
  159. package/runtime/__pycache__/safe_codec.cpython-311.pyc +0 -0
  160. package/runtime/__pycache__/tywrap_bridge_core.cpython-311.pyc +0 -0
  161. package/runtime/safe_codec.py +0 -352
  162. package/src/core/discovery.ts +0 -477
  163. package/src/core/validation.ts +0 -729
  164. package/src/runtime/base.ts +0 -24
  165. package/src/runtime/transport-pool.ts +0 -538
@@ -1,729 +0,0 @@
1
- /**
2
- * Error Handling & Validation Framework
3
- *
4
- * Comprehensive error handling, syntax validation, and analysis reporting system
5
- */
6
-
7
- import type {
8
- AnalysisError,
9
- AnalysisWarning,
10
- // AnalysisStatistics,
11
- PythonFunction,
12
- PythonClass,
13
- PythonType,
14
- } from '../types/index.js';
15
-
16
- export interface ValidationConfig {
17
- strictTypeChecking: boolean;
18
- allowMissingTypeHints: boolean;
19
- maxComplexityScore: number;
20
- deprecatedPatterns: string[];
21
- requiredDocstrings: boolean;
22
- }
23
-
24
- export interface ValidationReport {
25
- errors: AnalysisError[];
26
- warnings: AnalysisWarning[];
27
- statistics: ValidationStatistics;
28
- recommendations: ValidationRecommendation[];
29
- }
30
-
31
- export interface ValidationStatistics {
32
- totalIssues: number;
33
- errorCount: number;
34
- warningCount: number;
35
- typeHintCoverage: number;
36
- docstringCoverage: number;
37
- complexityScore: number;
38
- qualityScore: number; // 0-100
39
- }
40
-
41
- export interface ValidationRecommendation {
42
- type: 'performance' | 'maintainability' | 'type-safety' | 'documentation';
43
- severity: 'low' | 'medium' | 'high' | 'critical';
44
- message: string;
45
- suggestion: string;
46
- file?: string;
47
- line?: number;
48
- impact: string;
49
- }
50
-
51
- export class ValidationEngine {
52
- private config: ValidationConfig;
53
-
54
- constructor(config: Partial<ValidationConfig> = {}) {
55
- this.config = {
56
- strictTypeChecking: config.strictTypeChecking ?? true,
57
- allowMissingTypeHints: config.allowMissingTypeHints ?? false,
58
- maxComplexityScore: 10,
59
- deprecatedPatterns: [],
60
- requiredDocstrings: false,
61
- ...config,
62
- };
63
- }
64
-
65
- /**
66
- * Validate Python functions for type safety and best practices
67
- */
68
- validateFunction(func: PythonFunction, filePath?: string): ValidationReport {
69
- const errors: AnalysisError[] = [];
70
- const warnings: AnalysisWarning[] = [];
71
- const recommendations: ValidationRecommendation[] = [];
72
-
73
- // Check type hints
74
- this.validateTypeHints(func, errors, warnings);
75
-
76
- // Check docstring
77
- this.validateDocstring(func, warnings, filePath);
78
-
79
- // Check parameter patterns
80
- this.validateParameters(func, warnings, filePath);
81
-
82
- // Check for deprecated patterns
83
- this.validateDeprecatedPatterns(func, warnings, filePath);
84
-
85
- // Check complexity
86
- this.validateComplexity(func, warnings, filePath);
87
-
88
- // Generate recommendations
89
- this.generateFunctionRecommendations(func, recommendations, filePath);
90
-
91
- const statistics = this.calculateFunctionStatistics(func, errors, warnings);
92
-
93
- return {
94
- errors,
95
- warnings,
96
- statistics,
97
- recommendations,
98
- };
99
- }
100
-
101
- /**
102
- * Validate Python classes for structure and best practices
103
- */
104
- validateClass(cls: PythonClass, filePath?: string): ValidationReport {
105
- const errors: AnalysisError[] = [];
106
- const warnings: AnalysisWarning[] = [];
107
- const recommendations: ValidationRecommendation[] = [];
108
-
109
- // Validate each method
110
- for (const method of cls.methods) {
111
- const methodReport = this.validateFunction(method, filePath);
112
- errors.push(...methodReport.errors);
113
- warnings.push(...methodReport.warnings);
114
- recommendations.push(...methodReport.recommendations);
115
- }
116
-
117
- // Validate class-specific patterns
118
- this.validateClassStructure(cls, warnings, filePath);
119
- this.validateInheritance(cls, warnings, filePath);
120
-
121
- const statistics = this.calculateClassStatistics(cls, errors, warnings);
122
-
123
- return {
124
- errors,
125
- warnings,
126
- statistics,
127
- recommendations,
128
- };
129
- }
130
-
131
- /**
132
- * Validate type annotations for correctness
133
- */
134
- validateTypeAnnotation(type: PythonType, context: string = ''): AnalysisError[] {
135
- const errors: AnalysisError[] = [];
136
-
137
- try {
138
- this.validateTypeStructure(type, errors, context);
139
- } catch (error) {
140
- errors.push({
141
- type: 'type',
142
- message: `Type validation failed: ${error}`,
143
- });
144
- }
145
-
146
- return errors;
147
- }
148
-
149
- /**
150
- * Generate comprehensive analysis report
151
- */
152
- generateReport(
153
- functions: PythonFunction[],
154
- classes: PythonClass[],
155
- filePath?: string
156
- ): ValidationReport {
157
- const errors: AnalysisError[] = [];
158
- const warnings: AnalysisWarning[] = [];
159
- const recommendations: ValidationRecommendation[] = [];
160
-
161
- // Validate all functions
162
- for (const func of functions) {
163
- const funcReport = this.validateFunction(func, filePath);
164
- errors.push(...funcReport.errors);
165
- warnings.push(...funcReport.warnings);
166
- recommendations.push(...funcReport.recommendations);
167
- }
168
-
169
- // Validate all classes
170
- for (const cls of classes) {
171
- const classReport = this.validateClass(cls, filePath);
172
- errors.push(...classReport.errors);
173
- warnings.push(...classReport.warnings);
174
- recommendations.push(...classReport.recommendations);
175
- }
176
-
177
- // Generate module-level recommendations
178
- this.generateModuleRecommendations(functions, classes, recommendations, filePath);
179
-
180
- const statistics = this.calculateModuleStatistics(functions, classes, errors, warnings);
181
-
182
- return {
183
- errors,
184
- warnings,
185
- statistics,
186
- recommendations,
187
- };
188
- }
189
-
190
- /**
191
- * Private validation methods
192
- */
193
- private validateTypeHints(
194
- func: PythonFunction,
195
- errors: AnalysisError[],
196
- warnings: AnalysisWarning[]
197
- ): void {
198
- this.checkReturnTypeHint(func, errors, warnings);
199
-
200
- // Check parameter types (skip 'self' and 'cls' parameters)
201
- for (const param of func.parameters) {
202
- this.checkParameterTypeHint(func, param, errors, warnings);
203
- this.checkParameterTypeStructure(param, errors);
204
- }
205
-
206
- this.checkReturnTypeStructure(func, errors);
207
- }
208
-
209
- /**
210
- * Emit a missing-type warning, and—in strict mode—an accompanying error.
211
- * In strict mode without allowMissingTypeHints, we warn first then error.
212
- */
213
- private reportMissingType(
214
- warningMessage: string,
215
- errorMessage: string,
216
- warnings: AnalysisWarning[],
217
- errors: AnalysisError[]
218
- ): void {
219
- if (this.config.allowMissingTypeHints) {
220
- warnings.push({ type: 'missing-type', message: warningMessage });
221
- } else if (this.config.strictTypeChecking) {
222
- warnings.push({ type: 'missing-type', message: warningMessage });
223
- errors.push({ type: 'type', message: errorMessage });
224
- }
225
- }
226
-
227
- /**
228
- * Check the function return type annotation (skip __init__ methods as they
229
- * conventionally don't need return type annotations).
230
- */
231
- private checkReturnTypeHint(
232
- func: PythonFunction,
233
- errors: AnalysisError[],
234
- warnings: AnalysisWarning[]
235
- ): void {
236
- if (this.isEmptyType(func.returnType) && func.name !== '__init__') {
237
- this.reportMissingType(
238
- `Function '${func.name}' is missing return type annotation`,
239
- `Function '${func.name}' requires return type annotation`,
240
- warnings,
241
- errors
242
- );
243
- }
244
- }
245
-
246
- /**
247
- * Check a single parameter's type annotation (skip 'self' and 'cls').
248
- */
249
- private checkParameterTypeHint(
250
- func: PythonFunction,
251
- param: PythonFunction['parameters'][number],
252
- errors: AnalysisError[],
253
- warnings: AnalysisWarning[]
254
- ): void {
255
- if (this.isEmptyType(param.type) && param.name !== 'self' && param.name !== 'cls') {
256
- this.reportMissingType(
257
- `Parameter '${param.name}' in function '${func.name}' is missing type annotation`,
258
- `Parameter '${param.name}' in function '${func.name}' requires type annotation`,
259
- warnings,
260
- errors
261
- );
262
- }
263
- }
264
-
265
- /**
266
- * Validate a single parameter's type structure only in strict mode
267
- * (skip 'self' and 'cls' parameters).
268
- */
269
- private checkParameterTypeStructure(
270
- param: PythonFunction['parameters'][number],
271
- errors: AnalysisError[]
272
- ): void {
273
- if (
274
- this.config.strictTypeChecking &&
275
- !this.config.allowMissingTypeHints &&
276
- param.name !== 'self' &&
277
- param.name !== 'cls'
278
- ) {
279
- const typeErrors = this.validateTypeAnnotation(param.type, `parameter '${param.name}'`);
280
- errors.push(...typeErrors);
281
- }
282
- }
283
-
284
- /**
285
- * Validate the return type structure only in strict mode.
286
- */
287
- private checkReturnTypeStructure(func: PythonFunction, errors: AnalysisError[]): void {
288
- if (this.config.strictTypeChecking && !this.config.allowMissingTypeHints) {
289
- const returnTypeErrors = this.validateTypeAnnotation(func.returnType, 'return type');
290
- errors.push(...returnTypeErrors);
291
- }
292
- }
293
-
294
- private validateDocstring(
295
- func: PythonFunction,
296
- warnings: AnalysisWarning[],
297
- filePath?: string
298
- ): void {
299
- if (this.config.requiredDocstrings && !func.docstring) {
300
- warnings.push({
301
- type: 'missing-type',
302
- message: `Function '${func.name}' is missing docstring`,
303
- file: filePath,
304
- });
305
- }
306
-
307
- // Check docstring quality
308
- if (func.docstring && func.docstring.length < 10) {
309
- warnings.push({
310
- type: 'missing-type',
311
- message: `Function '${func.name}' has very short docstring`,
312
- file: filePath,
313
- });
314
- }
315
- }
316
-
317
- private validateParameters(
318
- func: PythonFunction,
319
- warnings: AnalysisWarning[],
320
- filePath?: string
321
- ): void {
322
- // Check for too many parameters
323
- if (func.parameters.length > 8) {
324
- warnings.push({
325
- type: 'performance',
326
- message: `Function '${func.name}' has too many parameters (${func.parameters.length})`,
327
- file: filePath,
328
- });
329
- }
330
-
331
- // Check parameter naming
332
- for (const param of func.parameters) {
333
- if (param.name.length < 2 && !['x', 'y', 'z', 'i', 'j', 'k'].includes(param.name)) {
334
- warnings.push({
335
- type: 'missing-type',
336
- message: `Parameter '${param.name}' in function '${func.name}' should have a more descriptive name`,
337
- file: filePath,
338
- });
339
- }
340
- }
341
- }
342
-
343
- private validateDeprecatedPatterns(
344
- func: PythonFunction,
345
- warnings: AnalysisWarning[],
346
- filePath?: string
347
- ): void {
348
- for (const pattern of this.config.deprecatedPatterns) {
349
- if (func.name.includes(pattern)) {
350
- warnings.push({
351
- type: 'deprecated',
352
- message: `Function '${func.name}' uses deprecated pattern '${pattern}'`,
353
- file: filePath,
354
- });
355
- }
356
- }
357
- }
358
-
359
- private validateComplexity(
360
- func: PythonFunction,
361
- warnings: AnalysisWarning[],
362
- filePath?: string
363
- ): void {
364
- // Simple complexity estimation based on parameters and async/generator flags
365
- let complexity = func.parameters.length * 0.5;
366
- if (func.isAsync) {
367
- complexity += 1;
368
- }
369
- if (func.isGenerator) {
370
- complexity += 1;
371
- }
372
- if (func.decorators.length > 0) {
373
- complexity += func.decorators.length * 0.5;
374
- }
375
-
376
- if (complexity > this.config.maxComplexityScore) {
377
- warnings.push({
378
- type: 'performance',
379
- message: `Function '${func.name}' has high complexity (${complexity.toFixed(1)})`,
380
- file: filePath,
381
- });
382
- }
383
- }
384
-
385
- private validateClassStructure(
386
- cls: PythonClass,
387
- warnings: AnalysisWarning[],
388
- filePath?: string
389
- ): void {
390
- // Check for empty classes
391
- if (cls.methods.length === 0 && cls.properties.length === 0) {
392
- warnings.push({
393
- type: 'missing-type',
394
- message: `Class '${cls.name}' appears to be empty`,
395
- file: filePath,
396
- });
397
- }
398
-
399
- // Check for missing __init__ method
400
- const hasInit = cls.methods.some(method => method.name === '__init__');
401
- if (!hasInit && cls.properties.length > 0) {
402
- warnings.push({
403
- type: 'missing-type',
404
- message: `Class '${cls.name}' has properties but no __init__ method`,
405
- file: filePath,
406
- });
407
- }
408
- }
409
-
410
- private validateInheritance(
411
- cls: PythonClass,
412
- warnings: AnalysisWarning[],
413
- filePath?: string
414
- ): void {
415
- // Check for deep inheritance chains
416
- if (cls.bases.length > 3) {
417
- warnings.push({
418
- type: 'compatibility',
419
- message: `Class '${cls.name}' inherits from many classes (${cls.bases.length})`,
420
- file: filePath,
421
- });
422
- }
423
-
424
- // Check for diamond inheritance pattern
425
- if (cls.bases.length > 1) {
426
- warnings.push({
427
- type: 'compatibility',
428
- message: `Class '${cls.name}' uses multiple inheritance, consider composition`,
429
- file: filePath,
430
- });
431
- }
432
- }
433
-
434
- private validateTypeStructure(type: PythonType, errors: AnalysisError[], context: string): void {
435
- switch (type.kind) {
436
- case 'union':
437
- if (type.types.length < 2) {
438
- errors.push({
439
- type: 'type',
440
- message: `Union type in ${context} should have at least 2 types`,
441
- });
442
- }
443
- // Recursively validate union member types
444
- for (const unionType of type.types) {
445
- this.validateTypeStructure(unionType, errors, context);
446
- }
447
- break;
448
-
449
- case 'collection':
450
- if (type.itemTypes.length === 0 && ['list', 'dict', 'set'].includes(type.name)) {
451
- errors.push({
452
- type: 'type',
453
- message: `Collection type '${type.name}' in ${context} should specify item types`,
454
- });
455
- }
456
- // Recursively validate item types
457
- for (const itemType of type.itemTypes) {
458
- this.validateTypeStructure(itemType, errors, context);
459
- }
460
- break;
461
-
462
- case 'optional':
463
- this.validateTypeStructure(type.type, errors, context);
464
- break;
465
-
466
- case 'generic':
467
- // Validate generic arguments
468
- for (const arg of type.typeArgs) {
469
- this.validateTypeStructure(arg, errors, context);
470
- }
471
- break;
472
-
473
- case 'custom':
474
- // Could add checks for known custom types
475
- break;
476
-
477
- case 'primitive':
478
- // Basic types are always valid
479
- break;
480
- }
481
- }
482
-
483
- private isEmptyType(type: PythonType): boolean {
484
- return type.kind === 'primitive' && type.name === 'None';
485
- }
486
-
487
- private generateFunctionRecommendations(
488
- func: PythonFunction,
489
- recommendations: ValidationRecommendation[],
490
- filePath?: string
491
- ): void {
492
- // Recommend async for I/O operations
493
- if (
494
- (!func.isAsync && func.name.includes('read')) ||
495
- func.name.includes('write') ||
496
- func.name.includes('fetch')
497
- ) {
498
- recommendations.push({
499
- type: 'performance',
500
- severity: 'medium',
501
- message: `Consider making '${func.name}' async for I/O operations`,
502
- suggestion: `Add 'async def' instead of 'def' and use await for I/O operations`,
503
- file: filePath,
504
- impact: 'Improved performance for I/O-bound operations',
505
- });
506
- }
507
-
508
- // Recommend type hints if missing
509
- if (func.parameters.some(p => this.isEmptyType(p.type)) || this.isEmptyType(func.returnType)) {
510
- recommendations.push({
511
- type: 'type-safety',
512
- severity: 'high',
513
- message: `Add type hints to '${func.name}' for better IDE support`,
514
- suggestion: 'Add type annotations to parameters and return type',
515
- file: filePath,
516
- impact: 'Better IDE support, type checking, and documentation',
517
- });
518
- }
519
-
520
- // Recommend docstrings
521
- if (!func.docstring) {
522
- recommendations.push({
523
- type: 'documentation',
524
- severity: 'medium',
525
- message: `Add docstring to '${func.name}'`,
526
- suggestion: 'Add a docstring explaining the function purpose, parameters, and return value',
527
- file: filePath,
528
- impact: 'Better code documentation and IDE support',
529
- });
530
- }
531
- }
532
-
533
- private generateModuleRecommendations(
534
- functions: PythonFunction[],
535
- classes: PythonClass[],
536
- recommendations: ValidationRecommendation[],
537
- filePath?: string
538
- ): void {
539
- const totalFunctions =
540
- functions.length + classes.reduce((acc, cls) => acc + cls.methods.length, 0);
541
-
542
- // Recommend breaking up large modules
543
- if (totalFunctions > 50) {
544
- recommendations.push({
545
- type: 'maintainability',
546
- severity: 'medium',
547
- message: 'Module has many functions, consider breaking it up',
548
- suggestion: 'Split module into smaller, focused modules',
549
- file: filePath,
550
- impact: 'Improved maintainability and code organization',
551
- });
552
- }
553
-
554
- // Check type hint coverage
555
- const functionsWithTypes = functions.filter(
556
- f => !this.isEmptyType(f.returnType) || f.parameters.some(p => !this.isEmptyType(p.type))
557
- );
558
-
559
- const typeHintCoverage =
560
- totalFunctions > 0 ? (functionsWithTypes.length / totalFunctions) * 100 : 100;
561
-
562
- if (typeHintCoverage < 50) {
563
- recommendations.push({
564
- type: 'type-safety',
565
- severity: 'high',
566
- message: `Low type hint coverage (${typeHintCoverage.toFixed(1)}%)`,
567
- suggestion: 'Add type annotations to improve type safety',
568
- file: filePath,
569
- impact: 'Better type checking and IDE support',
570
- });
571
- }
572
- }
573
-
574
- private calculateFunctionStatistics(
575
- func: PythonFunction,
576
- errors: AnalysisError[],
577
- warnings: AnalysisWarning[]
578
- ): ValidationStatistics {
579
- const hasTypeHints =
580
- !this.isEmptyType(func.returnType) || func.parameters.some(p => !this.isEmptyType(p.type));
581
-
582
- return {
583
- totalIssues: errors.length + warnings.length,
584
- errorCount: errors.length,
585
- warningCount: warnings.length,
586
- typeHintCoverage: hasTypeHints ? 100 : 0,
587
- docstringCoverage: func.docstring ? 100 : 0,
588
- complexityScore: this.calculateComplexity(func),
589
- qualityScore: this.calculateQualityScore(func, errors, warnings),
590
- };
591
- }
592
-
593
- private calculateClassStatistics(
594
- cls: PythonClass,
595
- errors: AnalysisError[],
596
- warnings: AnalysisWarning[]
597
- ): ValidationStatistics {
598
- const methodsWithTypes = cls.methods.filter(
599
- m => !this.isEmptyType(m.returnType) || m.parameters.some(p => !this.isEmptyType(p.type))
600
- ).length;
601
-
602
- const methodsWithDocstrings = cls.methods.filter(m => m.docstring).length;
603
-
604
- const typeHintCoverage =
605
- cls.methods.length > 0 ? (methodsWithTypes / cls.methods.length) * 100 : 100;
606
- const docstringCoverage =
607
- cls.methods.length > 0 ? (methodsWithDocstrings / cls.methods.length) * 100 : 100;
608
-
609
- return {
610
- totalIssues: errors.length + warnings.length,
611
- errorCount: errors.length,
612
- warningCount: warnings.length,
613
- typeHintCoverage,
614
- docstringCoverage,
615
- complexityScore: Math.max(...cls.methods.map(m => this.calculateComplexity(m)), 0),
616
- qualityScore: this.calculateClassQualityScore(cls, errors, warnings),
617
- };
618
- }
619
-
620
- private calculateModuleStatistics(
621
- functions: PythonFunction[],
622
- classes: PythonClass[],
623
- errors: AnalysisError[],
624
- warnings: AnalysisWarning[]
625
- ): ValidationStatistics {
626
- const allMethods = classes.flatMap(cls => cls.methods);
627
- const allFunctions = [...functions, ...allMethods];
628
-
629
- const functionsWithTypes = allFunctions.filter(
630
- f => !this.isEmptyType(f.returnType) || f.parameters.some(p => !this.isEmptyType(p.type))
631
- ).length;
632
-
633
- const functionsWithDocstrings = allFunctions.filter(f => f.docstring).length;
634
-
635
- const typeHintCoverage =
636
- allFunctions.length > 0 ? (functionsWithTypes / allFunctions.length) * 100 : 100;
637
- const docstringCoverage =
638
- allFunctions.length > 0 ? (functionsWithDocstrings / allFunctions.length) * 100 : 100;
639
-
640
- return {
641
- totalIssues: errors.length + warnings.length,
642
- errorCount: errors.length,
643
- warningCount: warnings.length,
644
- typeHintCoverage,
645
- docstringCoverage,
646
- complexityScore: Math.max(...allFunctions.map(f => this.calculateComplexity(f)), 0),
647
- qualityScore: this.calculateModuleQualityScore(allFunctions, errors, warnings),
648
- };
649
- }
650
-
651
- private calculateComplexity(func: PythonFunction): number {
652
- let complexity = 1; // Base complexity
653
- complexity += func.parameters.length * 0.5;
654
- complexity += func.isAsync ? 1 : 0;
655
- complexity += func.isGenerator ? 1 : 0;
656
- complexity += func.decorators.length * 0.5;
657
- return Math.round(complexity * 10) / 10;
658
- }
659
-
660
- private calculateQualityScore(
661
- func: PythonFunction,
662
- errors: AnalysisError[],
663
- warnings: AnalysisWarning[]
664
- ): number {
665
- let score = 100;
666
-
667
- // Deduct points for errors and warnings
668
- score -= errors.length * 20;
669
- score -= warnings.length * 10;
670
-
671
- // Add points for good practices
672
- if (!this.isEmptyType(func.returnType)) {
673
- score += 10;
674
- }
675
- if (func.parameters.every(p => !this.isEmptyType(p.type))) {
676
- score += 10;
677
- }
678
- if (func.docstring) {
679
- score += 10;
680
- }
681
-
682
- return Math.max(0, Math.min(100, score));
683
- }
684
-
685
- private calculateClassQualityScore(
686
- cls: PythonClass,
687
- errors: AnalysisError[],
688
- warnings: AnalysisWarning[]
689
- ): number {
690
- let score = 100;
691
-
692
- // Deduct points for errors and warnings
693
- score -= errors.length * 15;
694
- score -= warnings.length * 8;
695
-
696
- // Add points for good practices
697
- if (cls.docstring) {
698
- score += 10;
699
- }
700
- if (cls.methods.some(m => m.name === '__init__')) {
701
- score += 10;
702
- }
703
- if (cls.methods.length > 0) {
704
- score += 5;
705
- }
706
-
707
- return Math.max(0, Math.min(100, score));
708
- }
709
-
710
- private calculateModuleQualityScore(
711
- functions: PythonFunction[],
712
- errors: AnalysisError[],
713
- warnings: AnalysisWarning[]
714
- ): number {
715
- if (functions.length === 0) {
716
- return 100;
717
- }
718
-
719
- const functionScores = functions.map(f =>
720
- this.calculateQualityScore(
721
- f,
722
- errors.filter(e => e.message?.includes(f.name)),
723
- warnings.filter(w => w.message?.includes(f.name))
724
- )
725
- );
726
-
727
- return functionScores.reduce((sum, score) => sum + score, 0) / functionScores.length;
728
- }
729
- }