tree-sitter-analyzer 0.1.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.

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