tree-sitter-analyzer 0.2.0__py3-none-any.whl → 0.4.0__py3-none-any.whl
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.
Potentially problematic release.
This version of tree-sitter-analyzer might be problematic. Click here for more details.
- tree_sitter_analyzer/__init__.py +134 -121
- tree_sitter_analyzer/__main__.py +11 -12
- tree_sitter_analyzer/api.py +533 -539
- tree_sitter_analyzer/cli/__init__.py +39 -39
- tree_sitter_analyzer/cli/__main__.py +12 -13
- tree_sitter_analyzer/cli/commands/__init__.py +26 -27
- tree_sitter_analyzer/cli/commands/advanced_command.py +88 -88
- tree_sitter_analyzer/cli/commands/base_command.py +160 -155
- tree_sitter_analyzer/cli/commands/default_command.py +18 -19
- tree_sitter_analyzer/cli/commands/partial_read_command.py +141 -133
- tree_sitter_analyzer/cli/commands/query_command.py +81 -82
- tree_sitter_analyzer/cli/commands/structure_command.py +138 -121
- tree_sitter_analyzer/cli/commands/summary_command.py +101 -93
- tree_sitter_analyzer/cli/commands/table_command.py +235 -233
- tree_sitter_analyzer/cli/info_commands.py +120 -121
- tree_sitter_analyzer/cli_main.py +278 -276
- tree_sitter_analyzer/core/__init__.py +15 -20
- tree_sitter_analyzer/core/analysis_engine.py +555 -574
- tree_sitter_analyzer/core/cache_service.py +320 -330
- tree_sitter_analyzer/core/engine.py +559 -560
- tree_sitter_analyzer/core/parser.py +293 -288
- tree_sitter_analyzer/core/query.py +502 -502
- tree_sitter_analyzer/encoding_utils.py +456 -460
- tree_sitter_analyzer/exceptions.py +337 -340
- tree_sitter_analyzer/file_handler.py +210 -222
- tree_sitter_analyzer/formatters/__init__.py +1 -1
- tree_sitter_analyzer/formatters/base_formatter.py +167 -168
- tree_sitter_analyzer/formatters/formatter_factory.py +78 -74
- tree_sitter_analyzer/formatters/java_formatter.py +291 -270
- tree_sitter_analyzer/formatters/python_formatter.py +259 -235
- tree_sitter_analyzer/interfaces/__init__.py +9 -10
- tree_sitter_analyzer/interfaces/cli.py +528 -557
- tree_sitter_analyzer/interfaces/cli_adapter.py +343 -319
- tree_sitter_analyzer/interfaces/mcp_adapter.py +206 -170
- tree_sitter_analyzer/interfaces/mcp_server.py +405 -416
- tree_sitter_analyzer/java_analyzer.py +187 -219
- tree_sitter_analyzer/language_detector.py +398 -400
- tree_sitter_analyzer/language_loader.py +224 -228
- tree_sitter_analyzer/languages/__init__.py +10 -11
- tree_sitter_analyzer/languages/java_plugin.py +1174 -1113
- tree_sitter_analyzer/{plugins → languages}/javascript_plugin.py +446 -439
- tree_sitter_analyzer/languages/python_plugin.py +747 -712
- tree_sitter_analyzer/mcp/__init__.py +31 -32
- tree_sitter_analyzer/mcp/resources/__init__.py +44 -47
- tree_sitter_analyzer/mcp/resources/code_file_resource.py +209 -213
- tree_sitter_analyzer/mcp/resources/project_stats_resource.py +555 -550
- tree_sitter_analyzer/mcp/server.py +333 -345
- tree_sitter_analyzer/mcp/tools/__init__.py +30 -31
- tree_sitter_analyzer/mcp/tools/analyze_scale_tool.py +654 -557
- tree_sitter_analyzer/mcp/tools/analyze_scale_tool_cli_compatible.py +247 -245
- tree_sitter_analyzer/mcp/tools/base_tool.py +54 -55
- tree_sitter_analyzer/mcp/tools/read_partial_tool.py +300 -302
- tree_sitter_analyzer/mcp/tools/table_format_tool.py +362 -359
- tree_sitter_analyzer/mcp/tools/universal_analyze_tool.py +543 -476
- tree_sitter_analyzer/mcp/utils/__init__.py +107 -106
- tree_sitter_analyzer/mcp/utils/error_handler.py +549 -549
- tree_sitter_analyzer/models.py +470 -481
- tree_sitter_analyzer/output_manager.py +255 -264
- tree_sitter_analyzer/plugins/__init__.py +280 -334
- tree_sitter_analyzer/plugins/base.py +496 -446
- tree_sitter_analyzer/plugins/manager.py +379 -355
- tree_sitter_analyzer/queries/__init__.py +26 -27
- tree_sitter_analyzer/queries/java.py +391 -394
- tree_sitter_analyzer/queries/javascript.py +148 -149
- tree_sitter_analyzer/queries/python.py +285 -286
- tree_sitter_analyzer/queries/typescript.py +229 -230
- tree_sitter_analyzer/query_loader.py +257 -260
- tree_sitter_analyzer/table_formatter.py +471 -448
- tree_sitter_analyzer/utils.py +277 -277
- {tree_sitter_analyzer-0.2.0.dist-info → tree_sitter_analyzer-0.4.0.dist-info}/METADATA +23 -8
- tree_sitter_analyzer-0.4.0.dist-info/RECORD +73 -0
- {tree_sitter_analyzer-0.2.0.dist-info → tree_sitter_analyzer-0.4.0.dist-info}/entry_points.txt +2 -1
- tree_sitter_analyzer/plugins/java_plugin.py +0 -625
- tree_sitter_analyzer/plugins/plugin_loader.py +0 -83
- tree_sitter_analyzer/plugins/python_plugin.py +0 -598
- tree_sitter_analyzer/plugins/registry.py +0 -366
- tree_sitter_analyzer-0.2.0.dist-info/RECORD +0 -77
- {tree_sitter_analyzer-0.2.0.dist-info → tree_sitter_analyzer-0.4.0.dist-info}/WHEEL +0 -0
|
@@ -1,712 +1,747 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
from ..core.analysis_engine import AnalysisRequest
|
|
15
|
-
from ..models import AnalysisResult
|
|
16
|
-
|
|
17
|
-
from ..
|
|
18
|
-
from ..
|
|
19
|
-
from ..utils import
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
class PythonElementExtractor(ElementExtractor):
|
|
23
|
-
"""Python-specific element extractor with comprehensive analysis"""
|
|
24
|
-
|
|
25
|
-
def __init__(self) -> None:
|
|
26
|
-
"""Initialize the Python element extractor."""
|
|
27
|
-
self.current_module: str = ""
|
|
28
|
-
self.current_file: str = ""
|
|
29
|
-
self.source_code: str = ""
|
|
30
|
-
self.imports:
|
|
31
|
-
|
|
32
|
-
def extract_functions(
|
|
33
|
-
self, tree: "tree_sitter.Tree", source_code: str
|
|
34
|
-
) ->
|
|
35
|
-
"""Extract Python function definitions with comprehensive analysis"""
|
|
36
|
-
self.source_code = source_code
|
|
37
|
-
functions:
|
|
38
|
-
|
|
39
|
-
# Function definition queries
|
|
40
|
-
function_queries = [
|
|
41
|
-
# Regular function definitions
|
|
42
|
-
"""
|
|
43
|
-
(function_definition
|
|
44
|
-
name: (identifier) @function.name
|
|
45
|
-
parameters: (parameters) @function.params
|
|
46
|
-
body: (block) @function.body) @function.definition
|
|
47
|
-
""",
|
|
48
|
-
# Async function definitions
|
|
49
|
-
"""
|
|
50
|
-
(function_definition
|
|
51
|
-
"async"
|
|
52
|
-
name: (identifier) @async_function.name
|
|
53
|
-
parameters: (parameters) @async_function.params
|
|
54
|
-
body: (block) @async_function.body) @async_function.definition
|
|
55
|
-
""",
|
|
56
|
-
]
|
|
57
|
-
|
|
58
|
-
try:
|
|
59
|
-
language = tree.language if hasattr(tree, "language") else None
|
|
60
|
-
if language:
|
|
61
|
-
for query_string in function_queries:
|
|
62
|
-
query = language.query(query_string)
|
|
63
|
-
captures = query.captures(tree.root_node)
|
|
64
|
-
|
|
65
|
-
if isinstance(captures, dict):
|
|
66
|
-
# Process regular functions
|
|
67
|
-
function_nodes = captures.get("function.definition", [])
|
|
68
|
-
for node in function_nodes:
|
|
69
|
-
function = self._extract_detailed_function_info(
|
|
70
|
-
node, source_code, is_async=False
|
|
71
|
-
)
|
|
72
|
-
if function:
|
|
73
|
-
functions.append(function)
|
|
74
|
-
|
|
75
|
-
# Process async functions
|
|
76
|
-
async_nodes = captures.get("async_function.definition", [])
|
|
77
|
-
for node in async_nodes:
|
|
78
|
-
function = self._extract_detailed_function_info(
|
|
79
|
-
node, source_code, is_async=True
|
|
80
|
-
)
|
|
81
|
-
if function:
|
|
82
|
-
functions.append(function)
|
|
83
|
-
|
|
84
|
-
except Exception as e:
|
|
85
|
-
log_warning(f"Could not extract Python functions: {e}")
|
|
86
|
-
|
|
87
|
-
return functions
|
|
88
|
-
|
|
89
|
-
def extract_classes(
|
|
90
|
-
self, tree: "tree_sitter.Tree", source_code: str
|
|
91
|
-
) ->
|
|
92
|
-
"""Extract Python class definitions with comprehensive analysis"""
|
|
93
|
-
self.source_code = source_code
|
|
94
|
-
classes:
|
|
95
|
-
|
|
96
|
-
# Class definition query
|
|
97
|
-
query_string = """
|
|
98
|
-
(class_definition
|
|
99
|
-
name: (identifier) @class.name
|
|
100
|
-
superclasses: (argument_list)? @class.superclasses
|
|
101
|
-
body: (block) @class.body) @class.definition
|
|
102
|
-
"""
|
|
103
|
-
|
|
104
|
-
try:
|
|
105
|
-
language = tree.language if hasattr(tree, "language") else None
|
|
106
|
-
if language:
|
|
107
|
-
query = language.query(query_string)
|
|
108
|
-
captures = query.captures(tree.root_node)
|
|
109
|
-
|
|
110
|
-
if isinstance(captures, dict):
|
|
111
|
-
class_nodes = captures.get("class.definition", [])
|
|
112
|
-
for node in class_nodes:
|
|
113
|
-
cls = self._extract_detailed_class_info(node, source_code)
|
|
114
|
-
if cls:
|
|
115
|
-
classes.append(cls)
|
|
116
|
-
|
|
117
|
-
except Exception as e:
|
|
118
|
-
log_warning(f"Could not extract Python classes: {e}")
|
|
119
|
-
|
|
120
|
-
return classes
|
|
121
|
-
|
|
122
|
-
def extract_variables(
|
|
123
|
-
self, tree: "tree_sitter.Tree", source_code: str
|
|
124
|
-
) ->
|
|
125
|
-
"""Extract Python variable definitions (class attributes only)"""
|
|
126
|
-
variables:
|
|
127
|
-
|
|
128
|
-
# Only extract class-level attributes, not function-level variables
|
|
129
|
-
try:
|
|
130
|
-
# Find class declarations
|
|
131
|
-
class_query = """
|
|
132
|
-
(class_definition
|
|
133
|
-
body: (block) @class.body) @class.definition
|
|
134
|
-
"""
|
|
135
|
-
|
|
136
|
-
language = tree.language if hasattr(tree, "language") else None
|
|
137
|
-
if language:
|
|
138
|
-
query = language.query(class_query)
|
|
139
|
-
captures = query.captures(tree.root_node)
|
|
140
|
-
|
|
141
|
-
if isinstance(captures, dict):
|
|
142
|
-
class_bodies = captures.get("class.body", [])
|
|
143
|
-
|
|
144
|
-
# For each class body, extract attribute assignments
|
|
145
|
-
for class_body in class_bodies:
|
|
146
|
-
variables.extend(
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
""
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
"""
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
"""
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
# Extract
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
)
|
|
346
|
-
|
|
347
|
-
#
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
module_name=
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
class
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
return
|
|
600
|
-
|
|
601
|
-
def
|
|
602
|
-
"""Get
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Python Language Plugin
|
|
4
|
+
|
|
5
|
+
Provides Python-specific parsing and element extraction functionality.
|
|
6
|
+
Migrated to the new plugin architecture with enhanced query integration.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from typing import TYPE_CHECKING, Optional
|
|
10
|
+
|
|
11
|
+
if TYPE_CHECKING:
|
|
12
|
+
import tree_sitter
|
|
13
|
+
|
|
14
|
+
from ..core.analysis_engine import AnalysisRequest
|
|
15
|
+
from ..models import AnalysisResult
|
|
16
|
+
|
|
17
|
+
from ..models import Class, CodeElement, Function, Import, Variable
|
|
18
|
+
from ..plugins.base import ElementExtractor, LanguagePlugin
|
|
19
|
+
from ..utils import log_error, log_warning
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class PythonElementExtractor(ElementExtractor):
|
|
23
|
+
"""Python-specific element extractor with comprehensive analysis"""
|
|
24
|
+
|
|
25
|
+
def __init__(self) -> None:
|
|
26
|
+
"""Initialize the Python element extractor."""
|
|
27
|
+
self.current_module: str = ""
|
|
28
|
+
self.current_file: str = ""
|
|
29
|
+
self.source_code: str = ""
|
|
30
|
+
self.imports: list[str] = []
|
|
31
|
+
|
|
32
|
+
def extract_functions(
|
|
33
|
+
self, tree: "tree_sitter.Tree", source_code: str
|
|
34
|
+
) -> list[Function]:
|
|
35
|
+
"""Extract Python function definitions with comprehensive analysis"""
|
|
36
|
+
self.source_code = source_code
|
|
37
|
+
functions: list[Function] = []
|
|
38
|
+
|
|
39
|
+
# Function definition queries
|
|
40
|
+
function_queries = [
|
|
41
|
+
# Regular function definitions
|
|
42
|
+
"""
|
|
43
|
+
(function_definition
|
|
44
|
+
name: (identifier) @function.name
|
|
45
|
+
parameters: (parameters) @function.params
|
|
46
|
+
body: (block) @function.body) @function.definition
|
|
47
|
+
""",
|
|
48
|
+
# Async function definitions
|
|
49
|
+
"""
|
|
50
|
+
(function_definition
|
|
51
|
+
"async"
|
|
52
|
+
name: (identifier) @async_function.name
|
|
53
|
+
parameters: (parameters) @async_function.params
|
|
54
|
+
body: (block) @async_function.body) @async_function.definition
|
|
55
|
+
""",
|
|
56
|
+
]
|
|
57
|
+
|
|
58
|
+
try:
|
|
59
|
+
language = tree.language if hasattr(tree, "language") else None
|
|
60
|
+
if language:
|
|
61
|
+
for query_string in function_queries:
|
|
62
|
+
query = language.query(query_string)
|
|
63
|
+
captures = query.captures(tree.root_node)
|
|
64
|
+
|
|
65
|
+
if isinstance(captures, dict):
|
|
66
|
+
# Process regular functions
|
|
67
|
+
function_nodes = captures.get("function.definition", [])
|
|
68
|
+
for node in function_nodes:
|
|
69
|
+
function = self._extract_detailed_function_info(
|
|
70
|
+
node, source_code, is_async=False
|
|
71
|
+
)
|
|
72
|
+
if function:
|
|
73
|
+
functions.append(function)
|
|
74
|
+
|
|
75
|
+
# Process async functions
|
|
76
|
+
async_nodes = captures.get("async_function.definition", [])
|
|
77
|
+
for node in async_nodes:
|
|
78
|
+
function = self._extract_detailed_function_info(
|
|
79
|
+
node, source_code, is_async=True
|
|
80
|
+
)
|
|
81
|
+
if function:
|
|
82
|
+
functions.append(function)
|
|
83
|
+
|
|
84
|
+
except Exception as e:
|
|
85
|
+
log_warning(f"Could not extract Python functions: {e}")
|
|
86
|
+
|
|
87
|
+
return functions
|
|
88
|
+
|
|
89
|
+
def extract_classes(
|
|
90
|
+
self, tree: "tree_sitter.Tree", source_code: str
|
|
91
|
+
) -> list[Class]:
|
|
92
|
+
"""Extract Python class definitions with comprehensive analysis"""
|
|
93
|
+
self.source_code = source_code
|
|
94
|
+
classes: list[Class] = []
|
|
95
|
+
|
|
96
|
+
# Class definition query
|
|
97
|
+
query_string = """
|
|
98
|
+
(class_definition
|
|
99
|
+
name: (identifier) @class.name
|
|
100
|
+
superclasses: (argument_list)? @class.superclasses
|
|
101
|
+
body: (block) @class.body) @class.definition
|
|
102
|
+
"""
|
|
103
|
+
|
|
104
|
+
try:
|
|
105
|
+
language = tree.language if hasattr(tree, "language") else None
|
|
106
|
+
if language:
|
|
107
|
+
query = language.query(query_string)
|
|
108
|
+
captures = query.captures(tree.root_node)
|
|
109
|
+
|
|
110
|
+
if isinstance(captures, dict):
|
|
111
|
+
class_nodes = captures.get("class.definition", [])
|
|
112
|
+
for node in class_nodes:
|
|
113
|
+
cls = self._extract_detailed_class_info(node, source_code)
|
|
114
|
+
if cls:
|
|
115
|
+
classes.append(cls)
|
|
116
|
+
|
|
117
|
+
except Exception as e:
|
|
118
|
+
log_warning(f"Could not extract Python classes: {e}")
|
|
119
|
+
|
|
120
|
+
return classes
|
|
121
|
+
|
|
122
|
+
def extract_variables(
|
|
123
|
+
self, tree: "tree_sitter.Tree", source_code: str
|
|
124
|
+
) -> list[Variable]:
|
|
125
|
+
"""Extract Python variable definitions (class attributes only)"""
|
|
126
|
+
variables: list[Variable] = []
|
|
127
|
+
|
|
128
|
+
# Only extract class-level attributes, not function-level variables
|
|
129
|
+
try:
|
|
130
|
+
# Find class declarations
|
|
131
|
+
class_query = """
|
|
132
|
+
(class_definition
|
|
133
|
+
body: (block) @class.body) @class.definition
|
|
134
|
+
"""
|
|
135
|
+
|
|
136
|
+
language = tree.language if hasattr(tree, "language") else None
|
|
137
|
+
if language:
|
|
138
|
+
query = language.query(class_query)
|
|
139
|
+
captures = query.captures(tree.root_node)
|
|
140
|
+
|
|
141
|
+
if isinstance(captures, dict):
|
|
142
|
+
class_bodies = captures.get("class.body", [])
|
|
143
|
+
|
|
144
|
+
# For each class body, extract attribute assignments
|
|
145
|
+
for class_body in class_bodies:
|
|
146
|
+
variables.extend(
|
|
147
|
+
self._extract_class_attributes(class_body, source_code)
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
except Exception as e:
|
|
151
|
+
log_warning(f"Could not extract Python class attributes: {e}")
|
|
152
|
+
|
|
153
|
+
return variables
|
|
154
|
+
|
|
155
|
+
def _extract_class_attributes(
|
|
156
|
+
self, class_body_node: "tree_sitter.Node", source_code: str
|
|
157
|
+
) -> list[Variable]:
|
|
158
|
+
"""Extract class-level attribute assignments"""
|
|
159
|
+
attributes: list[Variable] = []
|
|
160
|
+
|
|
161
|
+
try:
|
|
162
|
+
# Look for assignments directly under class body
|
|
163
|
+
for child in class_body_node.children:
|
|
164
|
+
if child.type == "expression_statement":
|
|
165
|
+
# Check if it's an assignment
|
|
166
|
+
for grandchild in child.children:
|
|
167
|
+
if grandchild.type == "assignment":
|
|
168
|
+
attribute = self._extract_class_attribute_info(
|
|
169
|
+
grandchild, source_code
|
|
170
|
+
)
|
|
171
|
+
if attribute:
|
|
172
|
+
attributes.append(attribute)
|
|
173
|
+
elif child.type == "assignment":
|
|
174
|
+
attribute = self._extract_class_attribute_info(child, source_code)
|
|
175
|
+
if attribute:
|
|
176
|
+
attributes.append(attribute)
|
|
177
|
+
|
|
178
|
+
except Exception as e:
|
|
179
|
+
log_warning(f"Could not extract class attributes: {e}")
|
|
180
|
+
|
|
181
|
+
return attributes
|
|
182
|
+
|
|
183
|
+
def _extract_class_attribute_info(
|
|
184
|
+
self, node: "tree_sitter.Node", source_code: str
|
|
185
|
+
) -> Variable | None:
|
|
186
|
+
"""Extract class attribute information from assignment node"""
|
|
187
|
+
try:
|
|
188
|
+
# Get the full assignment text
|
|
189
|
+
assignment_text = source_code[node.start_byte : node.end_byte]
|
|
190
|
+
|
|
191
|
+
# Extract attribute name and type annotation
|
|
192
|
+
if "=" in assignment_text:
|
|
193
|
+
left_part = assignment_text.split("=")[0].strip()
|
|
194
|
+
|
|
195
|
+
# Handle type annotations (e.g., "name: str = ...")
|
|
196
|
+
if ":" in left_part:
|
|
197
|
+
name_part, type_part = left_part.split(":", 1)
|
|
198
|
+
attr_name = name_part.strip()
|
|
199
|
+
attr_type = type_part.strip()
|
|
200
|
+
else:
|
|
201
|
+
attr_name = left_part
|
|
202
|
+
attr_type = None
|
|
203
|
+
|
|
204
|
+
return Variable(
|
|
205
|
+
name=attr_name,
|
|
206
|
+
start_line=node.start_point[0] + 1,
|
|
207
|
+
end_line=node.end_point[0] + 1,
|
|
208
|
+
raw_text=assignment_text,
|
|
209
|
+
language="python",
|
|
210
|
+
variable_type=attr_type,
|
|
211
|
+
)
|
|
212
|
+
|
|
213
|
+
except Exception as e:
|
|
214
|
+
log_warning(f"Could not extract class attribute info: {e}")
|
|
215
|
+
|
|
216
|
+
return None
|
|
217
|
+
|
|
218
|
+
def extract_imports(
|
|
219
|
+
self, tree: "tree_sitter.Tree", source_code: str
|
|
220
|
+
) -> list[Import]:
|
|
221
|
+
"""Extract Python import statements"""
|
|
222
|
+
imports: list[Import] = []
|
|
223
|
+
|
|
224
|
+
# Import statement queries
|
|
225
|
+
import_queries = [
|
|
226
|
+
# Regular import statements
|
|
227
|
+
"""
|
|
228
|
+
(import_statement
|
|
229
|
+
name: (dotted_name) @import.name) @import.statement
|
|
230
|
+
""",
|
|
231
|
+
# From import statements
|
|
232
|
+
"""
|
|
233
|
+
(import_from_statement
|
|
234
|
+
module_name: (dotted_name) @from_import.module
|
|
235
|
+
name: (dotted_name) @from_import.name) @from_import.statement
|
|
236
|
+
""",
|
|
237
|
+
# Aliased imports
|
|
238
|
+
"""
|
|
239
|
+
(aliased_import
|
|
240
|
+
name: (dotted_name) @aliased_import.name
|
|
241
|
+
alias: (identifier) @aliased_import.alias) @aliased_import.statement
|
|
242
|
+
""",
|
|
243
|
+
]
|
|
244
|
+
|
|
245
|
+
try:
|
|
246
|
+
language = tree.language if hasattr(tree, "language") else None
|
|
247
|
+
if language:
|
|
248
|
+
for query_string in import_queries:
|
|
249
|
+
query = language.query(query_string)
|
|
250
|
+
captures = query.captures(tree.root_node)
|
|
251
|
+
|
|
252
|
+
if isinstance(captures, dict):
|
|
253
|
+
# Process different types of imports
|
|
254
|
+
for key, nodes in captures.items():
|
|
255
|
+
if key.endswith("statement"):
|
|
256
|
+
import_type = key.split(".")[0]
|
|
257
|
+
for node in nodes:
|
|
258
|
+
imp = self._extract_import_info(
|
|
259
|
+
node, source_code, import_type
|
|
260
|
+
)
|
|
261
|
+
if imp:
|
|
262
|
+
imports.append(imp)
|
|
263
|
+
|
|
264
|
+
except Exception as e:
|
|
265
|
+
log_warning(f"Could not extract Python imports: {e}")
|
|
266
|
+
|
|
267
|
+
return imports
|
|
268
|
+
|
|
269
|
+
def _extract_detailed_function_info(
|
|
270
|
+
self, node: "tree_sitter.Node", source_code: str, is_async: bool = False
|
|
271
|
+
) -> Function | None:
|
|
272
|
+
"""Extract comprehensive function information from AST node"""
|
|
273
|
+
try:
|
|
274
|
+
# Extract basic information
|
|
275
|
+
name = self._extract_name_from_node(node, source_code)
|
|
276
|
+
if not name:
|
|
277
|
+
return None
|
|
278
|
+
|
|
279
|
+
# Extract parameters
|
|
280
|
+
parameters = self._extract_parameters_from_node(node, source_code)
|
|
281
|
+
|
|
282
|
+
# Extract decorators
|
|
283
|
+
decorators = self._extract_decorators_from_node(node, source_code)
|
|
284
|
+
|
|
285
|
+
# Extract return type hint
|
|
286
|
+
return_type = self._extract_return_type_from_node(node, source_code)
|
|
287
|
+
|
|
288
|
+
# Extract docstring
|
|
289
|
+
# docstring = self._extract_docstring_from_node(node, source_code) # Not used currently
|
|
290
|
+
|
|
291
|
+
# Extract function body
|
|
292
|
+
# body = self._extract_function_body(node, source_code) # Not used currently
|
|
293
|
+
|
|
294
|
+
# Calculate complexity (simplified)
|
|
295
|
+
# complexity_score = self._calculate_complexity(body) # Not used currently
|
|
296
|
+
|
|
297
|
+
# Determine visibility (Python conventions)
|
|
298
|
+
visibility = "public"
|
|
299
|
+
if name.startswith("__") and name.endswith("__"):
|
|
300
|
+
visibility = "magic" # Magic methods
|
|
301
|
+
elif name.startswith("_"):
|
|
302
|
+
visibility = "private"
|
|
303
|
+
|
|
304
|
+
# 安全地提取原始文本,避免索引越界
|
|
305
|
+
start_byte = min(node.start_byte, len(source_code))
|
|
306
|
+
end_byte = min(node.end_byte, len(source_code))
|
|
307
|
+
raw_text = (
|
|
308
|
+
source_code[start_byte:end_byte]
|
|
309
|
+
if start_byte < end_byte
|
|
310
|
+
else source_code
|
|
311
|
+
)
|
|
312
|
+
|
|
313
|
+
return Function(
|
|
314
|
+
name=name,
|
|
315
|
+
start_line=node.start_point[0] + 1,
|
|
316
|
+
end_line=node.end_point[0] + 1,
|
|
317
|
+
raw_text=raw_text,
|
|
318
|
+
language="python",
|
|
319
|
+
parameters=parameters,
|
|
320
|
+
return_type=return_type or "Any",
|
|
321
|
+
modifiers=decorators,
|
|
322
|
+
is_static="staticmethod" in decorators,
|
|
323
|
+
is_private=visibility == "private",
|
|
324
|
+
is_public=visibility == "public",
|
|
325
|
+
)
|
|
326
|
+
|
|
327
|
+
except Exception as e:
|
|
328
|
+
log_warning(f"Could not extract detailed function info: {e}")
|
|
329
|
+
return None
|
|
330
|
+
|
|
331
|
+
def _extract_detailed_class_info(
|
|
332
|
+
self, node: "tree_sitter.Node", source_code: str
|
|
333
|
+
) -> Class | None:
|
|
334
|
+
"""Extract comprehensive class information from AST node"""
|
|
335
|
+
try:
|
|
336
|
+
# Extract basic information
|
|
337
|
+
name = self._extract_name_from_node(node, source_code)
|
|
338
|
+
if not name:
|
|
339
|
+
return None
|
|
340
|
+
|
|
341
|
+
# Extract superclasses
|
|
342
|
+
superclasses = self._extract_superclasses_from_node(node, source_code)
|
|
343
|
+
|
|
344
|
+
# Extract decorators
|
|
345
|
+
decorators = self._extract_decorators_from_node(node, source_code)
|
|
346
|
+
|
|
347
|
+
# Extract docstring
|
|
348
|
+
# docstring = self._extract_docstring_from_node(node, source_code) # Not used currently
|
|
349
|
+
|
|
350
|
+
# Generate fully qualified name
|
|
351
|
+
full_qualified_name = (
|
|
352
|
+
f"{self.current_module}.{name}" if self.current_module else name
|
|
353
|
+
)
|
|
354
|
+
|
|
355
|
+
# Determine visibility
|
|
356
|
+
# visibility = "public"
|
|
357
|
+
# if name.startswith("_"):
|
|
358
|
+
# visibility = "private" # Not used currently
|
|
359
|
+
|
|
360
|
+
return Class(
|
|
361
|
+
name=name,
|
|
362
|
+
start_line=node.start_point[0] + 1,
|
|
363
|
+
end_line=node.end_point[0] + 1,
|
|
364
|
+
raw_text=source_code[node.start_byte : node.end_byte],
|
|
365
|
+
language="python",
|
|
366
|
+
class_type="class",
|
|
367
|
+
full_qualified_name=full_qualified_name,
|
|
368
|
+
package_name=self.current_module,
|
|
369
|
+
superclass=superclasses[0] if superclasses else None,
|
|
370
|
+
interfaces=superclasses[1:] if len(superclasses) > 1 else [],
|
|
371
|
+
modifiers=decorators,
|
|
372
|
+
)
|
|
373
|
+
|
|
374
|
+
except Exception as e:
|
|
375
|
+
log_warning(f"Could not extract detailed class info: {e}")
|
|
376
|
+
return None
|
|
377
|
+
|
|
378
|
+
def _extract_variable_info(
|
|
379
|
+
self, node: "tree_sitter.Node", source_code: str, assignment_type: str
|
|
380
|
+
) -> Variable | None:
|
|
381
|
+
"""Extract detailed variable information from AST node"""
|
|
382
|
+
try:
|
|
383
|
+
if not self._validate_node(node):
|
|
384
|
+
return None
|
|
385
|
+
|
|
386
|
+
# Extract variable text
|
|
387
|
+
variable_text = source_code[node.start_byte : node.end_byte]
|
|
388
|
+
|
|
389
|
+
# Extract variable name (simplified)
|
|
390
|
+
if "=" in variable_text:
|
|
391
|
+
name_part = variable_text.split("=")[0].strip()
|
|
392
|
+
if assignment_type == "multiple_assignment" and "," in name_part:
|
|
393
|
+
name = name_part.split(",")[0].strip()
|
|
394
|
+
else:
|
|
395
|
+
name = name_part
|
|
396
|
+
else:
|
|
397
|
+
name = "variable"
|
|
398
|
+
|
|
399
|
+
return Variable(
|
|
400
|
+
name=name,
|
|
401
|
+
start_line=node.start_point[0] + 1,
|
|
402
|
+
end_line=node.end_point[0] + 1,
|
|
403
|
+
raw_text=variable_text,
|
|
404
|
+
language="python",
|
|
405
|
+
)
|
|
406
|
+
|
|
407
|
+
except Exception as e:
|
|
408
|
+
log_warning(f"Could not extract variable info: {e}")
|
|
409
|
+
return None
|
|
410
|
+
|
|
411
|
+
def _extract_import_info(
|
|
412
|
+
self, node: "tree_sitter.Node", source_code: str, import_type: str
|
|
413
|
+
) -> Import | None:
|
|
414
|
+
"""Extract detailed import information from AST node"""
|
|
415
|
+
try:
|
|
416
|
+
if not self._validate_node(node):
|
|
417
|
+
return None
|
|
418
|
+
|
|
419
|
+
# 安全地提取导入文本,避免索引越界
|
|
420
|
+
start_byte = min(node.start_byte, len(source_code))
|
|
421
|
+
end_byte = min(node.end_byte, len(source_code))
|
|
422
|
+
import_text = (
|
|
423
|
+
source_code[start_byte:end_byte]
|
|
424
|
+
if start_byte < end_byte
|
|
425
|
+
else source_code
|
|
426
|
+
)
|
|
427
|
+
|
|
428
|
+
# Extract import name and module name (simplified)
|
|
429
|
+
if import_type == "from_import":
|
|
430
|
+
if "from" in import_text and "import" in import_text:
|
|
431
|
+
parts = import_text.split("import")
|
|
432
|
+
module_name = parts[0].replace("from", "").strip()
|
|
433
|
+
import_name = parts[1].strip()
|
|
434
|
+
else:
|
|
435
|
+
module_name = ""
|
|
436
|
+
import_name = import_text
|
|
437
|
+
elif import_type == "aliased_import":
|
|
438
|
+
module_name = ""
|
|
439
|
+
import_name = import_text
|
|
440
|
+
else:
|
|
441
|
+
module_name = ""
|
|
442
|
+
import_name = import_text.replace("import", "").strip()
|
|
443
|
+
|
|
444
|
+
return Import(
|
|
445
|
+
name=import_name,
|
|
446
|
+
start_line=node.start_point[0] + 1,
|
|
447
|
+
end_line=node.end_point[0] + 1,
|
|
448
|
+
raw_text=import_text,
|
|
449
|
+
language="python",
|
|
450
|
+
module_name=module_name,
|
|
451
|
+
)
|
|
452
|
+
|
|
453
|
+
except Exception as e:
|
|
454
|
+
log_warning(f"Could not extract import info: {e}")
|
|
455
|
+
return None
|
|
456
|
+
|
|
457
|
+
# Helper methods
|
|
458
|
+
def _validate_node(self, node: "tree_sitter.Node") -> bool:
|
|
459
|
+
"""Validate that a node has required attributes"""
|
|
460
|
+
required_attrs = ["start_byte", "end_byte", "start_point", "end_point"]
|
|
461
|
+
for attr in required_attrs:
|
|
462
|
+
if not hasattr(node, attr) or getattr(node, attr) is None:
|
|
463
|
+
return False
|
|
464
|
+
return True
|
|
465
|
+
|
|
466
|
+
def _extract_name_from_node(
|
|
467
|
+
self, node: "tree_sitter.Node", source_code: str
|
|
468
|
+
) -> str | None:
|
|
469
|
+
"""Extract name from AST node"""
|
|
470
|
+
for child in node.children:
|
|
471
|
+
if child.type == "identifier":
|
|
472
|
+
return source_code[child.start_byte : child.end_byte]
|
|
473
|
+
return None
|
|
474
|
+
|
|
475
|
+
def _extract_parameters_from_node(
|
|
476
|
+
self, node: "tree_sitter.Node", source_code: str
|
|
477
|
+
) -> list[str]:
|
|
478
|
+
"""Extract parameters from function node"""
|
|
479
|
+
parameters: list[str] = []
|
|
480
|
+
for child in node.children:
|
|
481
|
+
if child.type == "parameters":
|
|
482
|
+
for param_child in child.children:
|
|
483
|
+
if param_child.type in [
|
|
484
|
+
"identifier",
|
|
485
|
+
"typed_parameter",
|
|
486
|
+
"default_parameter",
|
|
487
|
+
]:
|
|
488
|
+
param_text = source_code[
|
|
489
|
+
param_child.start_byte : param_child.end_byte
|
|
490
|
+
]
|
|
491
|
+
parameters.append(param_text)
|
|
492
|
+
return parameters
|
|
493
|
+
|
|
494
|
+
def _extract_decorators_from_node(
|
|
495
|
+
self, node: "tree_sitter.Node", source_code: str
|
|
496
|
+
) -> list[str]:
|
|
497
|
+
"""Extract decorators from node"""
|
|
498
|
+
decorators: list[str] = []
|
|
499
|
+
|
|
500
|
+
# Decorators are before function/class definitions
|
|
501
|
+
if hasattr(node, "parent") and node.parent:
|
|
502
|
+
for sibling in node.parent.children:
|
|
503
|
+
if (
|
|
504
|
+
sibling.type == "decorator"
|
|
505
|
+
and sibling.end_point[0] < node.start_point[0]
|
|
506
|
+
):
|
|
507
|
+
decorator_text = source_code[sibling.start_byte : sibling.end_byte]
|
|
508
|
+
# Remove @
|
|
509
|
+
if decorator_text.startswith("@"):
|
|
510
|
+
decorator_text = decorator_text[1:].strip()
|
|
511
|
+
decorators.append(decorator_text)
|
|
512
|
+
|
|
513
|
+
return decorators
|
|
514
|
+
|
|
515
|
+
def _extract_return_type_from_node(
|
|
516
|
+
self, node: "tree_sitter.Node", source_code: str
|
|
517
|
+
) -> str | None:
|
|
518
|
+
"""Extract return type annotation from function node"""
|
|
519
|
+
for child in node.children:
|
|
520
|
+
if child.type == "type":
|
|
521
|
+
return source_code[child.start_byte : child.end_byte]
|
|
522
|
+
return None
|
|
523
|
+
|
|
524
|
+
def _extract_docstring_from_node(
|
|
525
|
+
self, node: "tree_sitter.Node", source_code: str
|
|
526
|
+
) -> str | None:
|
|
527
|
+
"""Extract docstring from function/class node"""
|
|
528
|
+
for child in node.children:
|
|
529
|
+
if child.type == "block":
|
|
530
|
+
# Check if the first statement in the block is a docstring
|
|
531
|
+
for stmt in child.children:
|
|
532
|
+
if stmt.type == "expression_statement":
|
|
533
|
+
for expr in stmt.children:
|
|
534
|
+
if expr.type == "string":
|
|
535
|
+
if self._validate_node(expr):
|
|
536
|
+
docstring = source_code[
|
|
537
|
+
expr.start_byte : expr.end_byte
|
|
538
|
+
]
|
|
539
|
+
# Remove quotes
|
|
540
|
+
if docstring.startswith(
|
|
541
|
+
'"""'
|
|
542
|
+
) or docstring.startswith("'''"):
|
|
543
|
+
return docstring[3:-3].strip()
|
|
544
|
+
elif docstring.startswith(
|
|
545
|
+
'"'
|
|
546
|
+
) or docstring.startswith("'"):
|
|
547
|
+
return docstring[1:-1].strip()
|
|
548
|
+
return docstring
|
|
549
|
+
break
|
|
550
|
+
break
|
|
551
|
+
return None
|
|
552
|
+
|
|
553
|
+
def _extract_function_body(self, node: "tree_sitter.Node", source_code: str) -> str:
|
|
554
|
+
"""Extract function body"""
|
|
555
|
+
for child in node.children:
|
|
556
|
+
if child.type == "block":
|
|
557
|
+
return source_code[child.start_byte : child.end_byte]
|
|
558
|
+
return ""
|
|
559
|
+
|
|
560
|
+
def _extract_superclasses_from_node(
|
|
561
|
+
self, node: "tree_sitter.Node", source_code: str
|
|
562
|
+
) -> list[str]:
|
|
563
|
+
"""Extract superclasses from class node"""
|
|
564
|
+
superclasses: list[str] = []
|
|
565
|
+
for child in node.children:
|
|
566
|
+
if child.type == "argument_list":
|
|
567
|
+
for arg in child.children:
|
|
568
|
+
if arg.type == "identifier":
|
|
569
|
+
superclasses.append(source_code[arg.start_byte : arg.end_byte])
|
|
570
|
+
return superclasses
|
|
571
|
+
|
|
572
|
+
def _calculate_complexity(self, body: str) -> int:
|
|
573
|
+
"""Calculate cyclomatic complexity (simplified)"""
|
|
574
|
+
complexity = 1 # Base complexity
|
|
575
|
+
keywords = ["if", "elif", "for", "while", "try", "except", "with", "and", "or"]
|
|
576
|
+
for keyword in keywords:
|
|
577
|
+
complexity += body.count(f" {keyword} ") + body.count(f"\n{keyword} ")
|
|
578
|
+
return complexity
|
|
579
|
+
|
|
580
|
+
|
|
581
|
+
class PythonPlugin(LanguagePlugin):
|
|
582
|
+
"""Python language plugin for the new architecture"""
|
|
583
|
+
|
|
584
|
+
def __init__(self) -> None:
|
|
585
|
+
"""Initialize the Python plugin"""
|
|
586
|
+
super().__init__()
|
|
587
|
+
self._language_cache: tree_sitter.Language | None = None
|
|
588
|
+
|
|
589
|
+
def get_language_name(self) -> str:
|
|
590
|
+
"""Return the name of the programming language this plugin supports"""
|
|
591
|
+
return "python"
|
|
592
|
+
|
|
593
|
+
def get_file_extensions(self) -> list[str]:
|
|
594
|
+
"""Return list of file extensions this plugin supports"""
|
|
595
|
+
return [".py", ".pyw", ".pyi"]
|
|
596
|
+
|
|
597
|
+
def create_extractor(self) -> ElementExtractor:
|
|
598
|
+
"""Create and return an element extractor for this language"""
|
|
599
|
+
return PythonElementExtractor()
|
|
600
|
+
|
|
601
|
+
def get_tree_sitter_language(self) -> Optional["tree_sitter.Language"]:
|
|
602
|
+
"""Get the Tree-sitter language object for Python"""
|
|
603
|
+
if self._language_cache is None:
|
|
604
|
+
try:
|
|
605
|
+
import tree_sitter
|
|
606
|
+
import tree_sitter_python as tspython
|
|
607
|
+
|
|
608
|
+
# PyCapsuleオブジェクトをLanguageオブジェクトに変換
|
|
609
|
+
language_capsule = tspython.language()
|
|
610
|
+
self._language_cache = tree_sitter.Language(language_capsule)
|
|
611
|
+
except ImportError:
|
|
612
|
+
log_error("tree-sitter-python not available")
|
|
613
|
+
return None
|
|
614
|
+
except Exception as e:
|
|
615
|
+
log_error(f"Failed to load Python language: {e}")
|
|
616
|
+
return None
|
|
617
|
+
return self._language_cache
|
|
618
|
+
|
|
619
|
+
def get_supported_queries(self) -> list[str]:
|
|
620
|
+
"""Get list of supported query names for this language"""
|
|
621
|
+
return ["class", "function", "variable", "import"]
|
|
622
|
+
|
|
623
|
+
def is_applicable(self, file_path: str) -> bool:
|
|
624
|
+
"""Check if this plugin is applicable for the given file"""
|
|
625
|
+
return any(
|
|
626
|
+
file_path.lower().endswith(ext.lower())
|
|
627
|
+
for ext in self.get_file_extensions()
|
|
628
|
+
)
|
|
629
|
+
|
|
630
|
+
def get_plugin_info(self) -> dict:
|
|
631
|
+
"""Get information about this plugin"""
|
|
632
|
+
return {
|
|
633
|
+
"name": "Python Plugin",
|
|
634
|
+
"language": self.get_language_name(),
|
|
635
|
+
"extensions": self.get_file_extensions(),
|
|
636
|
+
"version": "2.0.0",
|
|
637
|
+
"supported_queries": self.get_supported_queries(),
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
async def analyze_file(
|
|
641
|
+
self, file_path: str, request: "AnalysisRequest"
|
|
642
|
+
) -> "AnalysisResult":
|
|
643
|
+
"""
|
|
644
|
+
Analyze a Python file and return analysis results.
|
|
645
|
+
|
|
646
|
+
Args:
|
|
647
|
+
file_path: Path to the Python file to analyze
|
|
648
|
+
request: Analysis request object
|
|
649
|
+
|
|
650
|
+
Returns:
|
|
651
|
+
AnalysisResult object containing the analysis results
|
|
652
|
+
"""
|
|
653
|
+
try:
|
|
654
|
+
from ..core.parser import Parser
|
|
655
|
+
from ..models import AnalysisResult
|
|
656
|
+
|
|
657
|
+
# Read file content
|
|
658
|
+
with open(file_path, encoding="utf-8") as f:
|
|
659
|
+
source_code = f.read()
|
|
660
|
+
|
|
661
|
+
# Parse the file
|
|
662
|
+
parser = Parser()
|
|
663
|
+
parse_result = parser.parse_code(source_code, "python")
|
|
664
|
+
|
|
665
|
+
if not parse_result.success:
|
|
666
|
+
return AnalysisResult(
|
|
667
|
+
file_path=file_path,
|
|
668
|
+
language="python",
|
|
669
|
+
line_count=len(source_code.splitlines()),
|
|
670
|
+
elements=[],
|
|
671
|
+
node_count=0,
|
|
672
|
+
query_results={},
|
|
673
|
+
source_code=source_code,
|
|
674
|
+
success=False,
|
|
675
|
+
error_message=parse_result.error_message,
|
|
676
|
+
)
|
|
677
|
+
|
|
678
|
+
# Extract elements
|
|
679
|
+
extractor = self.create_extractor()
|
|
680
|
+
if parse_result.tree:
|
|
681
|
+
functions = extractor.extract_functions(parse_result.tree, source_code)
|
|
682
|
+
classes = extractor.extract_classes(parse_result.tree, source_code)
|
|
683
|
+
variables = extractor.extract_variables(parse_result.tree, source_code)
|
|
684
|
+
imports = extractor.extract_imports(parse_result.tree, source_code)
|
|
685
|
+
else:
|
|
686
|
+
functions = []
|
|
687
|
+
classes = []
|
|
688
|
+
variables = []
|
|
689
|
+
imports = []
|
|
690
|
+
|
|
691
|
+
# Combine all elements
|
|
692
|
+
all_elements: list[CodeElement] = []
|
|
693
|
+
all_elements.extend(functions)
|
|
694
|
+
all_elements.extend(classes)
|
|
695
|
+
all_elements.extend(variables)
|
|
696
|
+
all_elements.extend(imports)
|
|
697
|
+
|
|
698
|
+
return AnalysisResult(
|
|
699
|
+
file_path=file_path,
|
|
700
|
+
language="python",
|
|
701
|
+
line_count=len(source_code.splitlines()),
|
|
702
|
+
elements=all_elements,
|
|
703
|
+
node_count=(
|
|
704
|
+
parse_result.tree.root_node.child_count if parse_result.tree else 0
|
|
705
|
+
),
|
|
706
|
+
query_results={},
|
|
707
|
+
source_code=source_code,
|
|
708
|
+
success=True,
|
|
709
|
+
error_message=None,
|
|
710
|
+
)
|
|
711
|
+
|
|
712
|
+
except Exception as e:
|
|
713
|
+
log_error(f"Failed to analyze Python file {file_path}: {e}")
|
|
714
|
+
return AnalysisResult(
|
|
715
|
+
file_path=file_path,
|
|
716
|
+
language="python",
|
|
717
|
+
line_count=0,
|
|
718
|
+
elements=[],
|
|
719
|
+
node_count=0,
|
|
720
|
+
query_results={},
|
|
721
|
+
source_code="",
|
|
722
|
+
success=False,
|
|
723
|
+
error_message=str(e),
|
|
724
|
+
)
|
|
725
|
+
|
|
726
|
+
def execute_query(self, tree: "tree_sitter.Tree", query_name: str) -> dict:
|
|
727
|
+
"""Execute a specific query on the tree"""
|
|
728
|
+
try:
|
|
729
|
+
language = self.get_tree_sitter_language()
|
|
730
|
+
if not language:
|
|
731
|
+
return {"error": "Language not available"}
|
|
732
|
+
|
|
733
|
+
# Simple query execution for testing
|
|
734
|
+
if query_name == "function":
|
|
735
|
+
query_string = "(function_definition) @function"
|
|
736
|
+
elif query_name == "class":
|
|
737
|
+
query_string = "(class_definition) @class"
|
|
738
|
+
else:
|
|
739
|
+
return {"error": f"Unknown query: {query_name}"}
|
|
740
|
+
|
|
741
|
+
query = language.query(query_string)
|
|
742
|
+
captures = query.captures(tree.root_node)
|
|
743
|
+
return {"captures": captures, "query": query_string}
|
|
744
|
+
|
|
745
|
+
except Exception as e:
|
|
746
|
+
log_error(f"Query execution failed: {e}")
|
|
747
|
+
return {"error": str(e)}
|