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