tree-sitter-analyzer 0.2.0__py3-none-any.whl → 0.3.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 +133 -121
  2. tree_sitter_analyzer/__main__.py +11 -12
  3. tree_sitter_analyzer/api.py +531 -539
  4. tree_sitter_analyzer/cli/__init__.py +39 -39
  5. tree_sitter_analyzer/cli/__main__.py +12 -13
  6. tree_sitter_analyzer/cli/commands/__init__.py +26 -27
  7. tree_sitter_analyzer/cli/commands/advanced_command.py +88 -88
  8. tree_sitter_analyzer/cli/commands/base_command.py +160 -155
  9. tree_sitter_analyzer/cli/commands/default_command.py +18 -19
  10. tree_sitter_analyzer/cli/commands/partial_read_command.py +141 -133
  11. tree_sitter_analyzer/cli/commands/query_command.py +81 -82
  12. tree_sitter_analyzer/cli/commands/structure_command.py +138 -121
  13. tree_sitter_analyzer/cli/commands/summary_command.py +101 -93
  14. tree_sitter_analyzer/cli/commands/table_command.py +232 -233
  15. tree_sitter_analyzer/cli/info_commands.py +120 -121
  16. tree_sitter_analyzer/cli_main.py +277 -276
  17. tree_sitter_analyzer/core/__init__.py +15 -20
  18. tree_sitter_analyzer/core/analysis_engine.py +591 -574
  19. tree_sitter_analyzer/core/cache_service.py +320 -330
  20. tree_sitter_analyzer/core/engine.py +557 -560
  21. tree_sitter_analyzer/core/parser.py +293 -288
  22. tree_sitter_analyzer/core/query.py +494 -502
  23. tree_sitter_analyzer/encoding_utils.py +458 -460
  24. tree_sitter_analyzer/exceptions.py +337 -340
  25. tree_sitter_analyzer/file_handler.py +217 -222
  26. tree_sitter_analyzer/formatters/__init__.py +1 -1
  27. tree_sitter_analyzer/formatters/base_formatter.py +167 -168
  28. tree_sitter_analyzer/formatters/formatter_factory.py +78 -74
  29. tree_sitter_analyzer/formatters/java_formatter.py +287 -270
  30. tree_sitter_analyzer/formatters/python_formatter.py +255 -235
  31. tree_sitter_analyzer/interfaces/__init__.py +9 -10
  32. tree_sitter_analyzer/interfaces/cli.py +528 -557
  33. tree_sitter_analyzer/interfaces/cli_adapter.py +322 -319
  34. tree_sitter_analyzer/interfaces/mcp_adapter.py +180 -170
  35. tree_sitter_analyzer/interfaces/mcp_server.py +405 -416
  36. tree_sitter_analyzer/java_analyzer.py +218 -219
  37. tree_sitter_analyzer/language_detector.py +398 -400
  38. tree_sitter_analyzer/language_loader.py +224 -228
  39. tree_sitter_analyzer/languages/__init__.py +10 -11
  40. tree_sitter_analyzer/languages/java_plugin.py +1129 -1113
  41. tree_sitter_analyzer/languages/python_plugin.py +737 -712
  42. tree_sitter_analyzer/mcp/__init__.py +31 -32
  43. tree_sitter_analyzer/mcp/resources/__init__.py +44 -47
  44. tree_sitter_analyzer/mcp/resources/code_file_resource.py +212 -213
  45. tree_sitter_analyzer/mcp/resources/project_stats_resource.py +560 -550
  46. tree_sitter_analyzer/mcp/server.py +333 -345
  47. tree_sitter_analyzer/mcp/tools/__init__.py +30 -31
  48. tree_sitter_analyzer/mcp/tools/analyze_scale_tool.py +621 -557
  49. tree_sitter_analyzer/mcp/tools/analyze_scale_tool_cli_compatible.py +242 -245
  50. tree_sitter_analyzer/mcp/tools/base_tool.py +54 -55
  51. tree_sitter_analyzer/mcp/tools/read_partial_tool.py +300 -302
  52. tree_sitter_analyzer/mcp/tools/table_format_tool.py +362 -359
  53. tree_sitter_analyzer/mcp/tools/universal_analyze_tool.py +543 -476
  54. tree_sitter_analyzer/mcp/utils/__init__.py +105 -106
  55. tree_sitter_analyzer/mcp/utils/error_handler.py +549 -549
  56. tree_sitter_analyzer/models.py +470 -481
  57. tree_sitter_analyzer/output_manager.py +261 -264
  58. tree_sitter_analyzer/plugins/__init__.py +333 -334
  59. tree_sitter_analyzer/plugins/base.py +477 -446
  60. tree_sitter_analyzer/plugins/java_plugin.py +608 -625
  61. tree_sitter_analyzer/plugins/javascript_plugin.py +446 -439
  62. tree_sitter_analyzer/plugins/manager.py +362 -355
  63. tree_sitter_analyzer/plugins/plugin_loader.py +85 -83
  64. tree_sitter_analyzer/plugins/python_plugin.py +606 -598
  65. tree_sitter_analyzer/plugins/registry.py +374 -366
  66. tree_sitter_analyzer/queries/__init__.py +26 -27
  67. tree_sitter_analyzer/queries/java.py +391 -394
  68. tree_sitter_analyzer/queries/javascript.py +148 -149
  69. tree_sitter_analyzer/queries/python.py +285 -286
  70. tree_sitter_analyzer/queries/typescript.py +229 -230
  71. tree_sitter_analyzer/query_loader.py +254 -260
  72. tree_sitter_analyzer/table_formatter.py +468 -448
  73. tree_sitter_analyzer/utils.py +277 -277
  74. {tree_sitter_analyzer-0.2.0.dist-info → tree_sitter_analyzer-0.3.0.dist-info}/METADATA +21 -6
  75. tree_sitter_analyzer-0.3.0.dist-info/RECORD +77 -0
  76. tree_sitter_analyzer-0.2.0.dist-info/RECORD +0 -77
  77. {tree_sitter_analyzer-0.2.0.dist-info → tree_sitter_analyzer-0.3.0.dist-info}/WHEEL +0 -0
  78. {tree_sitter_analyzer-0.2.0.dist-info → tree_sitter_analyzer-0.3.0.dist-info}/entry_points.txt +0 -0
@@ -1,598 +1,606 @@
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
- Integrates with the existing Python queries for comprehensive analysis.
8
- """
9
-
10
- import re
11
- from typing import TYPE_CHECKING, List, Optional
12
-
13
- if TYPE_CHECKING:
14
- import tree_sitter
15
-
16
- from ..language_loader import loader
17
- from ..models import Class, Function, Import, Variable
18
- from ..queries.python import ALL_QUERIES, get_query
19
- from ..utils import log_debug, log_error, log_warning
20
- from . import ElementExtractor, LanguagePlugin
21
-
22
-
23
- class PythonElementExtractor(ElementExtractor):
24
- """Python-specific element extractor with comprehensive analysis"""
25
-
26
- def __init__(self) -> None:
27
- # 分析コンテキスト
28
- self.current_module: str = ""
29
- self.current_file: str = ""
30
- self.source_code: str = ""
31
- self.imports: List[str] = []
32
-
33
- def extract_functions(
34
- self, tree: "tree_sitter.Tree", source_code: str
35
- ) -> List[Function]:
36
- """Extract Python function definitions with comprehensive analysis"""
37
- self.source_code = source_code
38
- functions: List[Function] = []
39
-
40
- try:
41
- language = tree.language if hasattr(tree, "language") else None
42
- if language:
43
- # 関数定義クエリを使用
44
- query_string = get_query("functions")
45
- query = language.query(query_string)
46
- captures = query.captures(tree.root_node)
47
-
48
- if captures is not None and isinstance(captures, dict):
49
- # 関数定義を処理
50
- function_nodes = captures.get("function.definition", [])
51
- for node in function_nodes:
52
- function = self._extract_detailed_function_info(
53
- node, source_code
54
- )
55
- if function:
56
- functions.append(function)
57
-
58
- # async関数も処理
59
- async_nodes = captures.get("function.async", [])
60
- for node in async_nodes:
61
- function = self._extract_detailed_function_info(
62
- node, source_code, is_async=True
63
- )
64
- if function:
65
- functions.append(function)
66
-
67
- except Exception as e:
68
- log_warning(f"Could not extract Python functions: {e}")
69
-
70
- return functions
71
-
72
- def extract_classes(
73
- self, tree: "tree_sitter.Tree", source_code: str
74
- ) -> List[Class]:
75
- """Extract Python class definitions with comprehensive analysis"""
76
- self.source_code = source_code
77
- classes: List[Class] = []
78
-
79
- try:
80
- language = tree.language if hasattr(tree, "language") else None
81
- if language:
82
- # クラス定義クエリを使用
83
- query_string = get_query("classes")
84
- query = language.query(query_string)
85
- captures = query.captures(tree.root_node)
86
-
87
- if captures is not None and isinstance(captures, dict):
88
- class_nodes = captures.get("class.definition", [])
89
- for node in class_nodes:
90
- cls = self._extract_detailed_class_info(node, source_code)
91
- if cls:
92
- classes.append(cls)
93
-
94
- except Exception as e:
95
- log_warning(f"Could not extract Python classes: {e}")
96
-
97
- return classes
98
-
99
- def extract_variables(
100
- self, tree: "tree_sitter.Tree", source_code: str
101
- ) -> List[Variable]:
102
- """Extract Python variable definitions"""
103
- variables: List[Variable] = []
104
-
105
- try:
106
- language = tree.language if hasattr(tree, "language") else None
107
- if language:
108
- # 変数代入クエリを使用
109
- query_string = get_query("variables")
110
- query = language.query(query_string)
111
- captures = query.captures(tree.root_node)
112
-
113
- if captures is not None and isinstance(captures, dict):
114
- # 通常の代入
115
- assignment_nodes = captures.get("variable.assignment", [])
116
- for node in assignment_nodes:
117
- variable = self._extract_variable_info(node, source_code)
118
- if variable:
119
- variables.append(variable)
120
-
121
- # 複数代入
122
- multiple_nodes = captures.get("variable.multiple", [])
123
- for node in multiple_nodes:
124
- variable = self._extract_variable_info(
125
- node, source_code, is_multiple=True
126
- )
127
- if variable:
128
- variables.append(variable)
129
-
130
- # 拡張代入
131
- augmented_nodes = captures.get("variable.augmented", [])
132
- for node in augmented_nodes:
133
- variable = self._extract_variable_info(
134
- node, source_code, is_augmented=True
135
- )
136
- if variable:
137
- variables.append(variable)
138
-
139
- except Exception as e:
140
- log_warning(f"Could not extract Python variables: {e}")
141
-
142
- return variables
143
-
144
- def extract_imports(
145
- self, tree: "tree_sitter.Tree", source_code: str
146
- ) -> List[Import]:
147
- """Extract Python import statements"""
148
- imports: List[Import] = []
149
-
150
- try:
151
- language = tree.language if hasattr(tree, "language") else None
152
- if language:
153
- # インポート文クエリを使用
154
- query_string = get_query("imports")
155
- query = language.query(query_string)
156
- captures = query.captures(tree.root_node)
157
-
158
- if captures is not None and isinstance(captures, dict):
159
- # 通常のimport
160
- import_nodes = captures.get("import.statement", [])
161
- for node in import_nodes:
162
- imp = self._extract_import_info(node, source_code)
163
- if imp:
164
- imports.append(imp)
165
-
166
- # from import
167
- from_nodes = captures.get("import.from", [])
168
- for node in from_nodes:
169
- imp = self._extract_import_info(node, source_code, is_from=True)
170
- if imp:
171
- imports.append(imp)
172
-
173
- # from import list
174
- from_list_nodes = captures.get("import.from_list", [])
175
- for node in from_list_nodes:
176
- imp = self._extract_import_info(
177
- node, source_code, is_from_list=True
178
- )
179
- if imp:
180
- imports.append(imp)
181
-
182
- # aliased import
183
- aliased_nodes = captures.get("import.aliased", [])
184
- for node in aliased_nodes:
185
- imp = self._extract_import_info(
186
- node, source_code, is_aliased=True
187
- )
188
- if imp:
189
- imports.append(imp)
190
-
191
- except Exception as e:
192
- log_warning(f"Could not extract Python imports: {e}")
193
-
194
- return imports
195
-
196
- def _extract_detailed_function_info(
197
- self, node: "tree_sitter.Node", source_code: str, is_async: bool = False
198
- ) -> Optional[Function]:
199
- """Extract comprehensive function information from AST node"""
200
- try:
201
- # 基本情報の抽出
202
- name = self._extract_name_from_node(node, source_code)
203
- if not name:
204
- return None
205
-
206
- # パラメータの抽出
207
- parameters = self._extract_parameters_from_node(node, source_code)
208
-
209
- # デコレータの抽出
210
- decorators = self._extract_decorators_from_node(node, source_code)
211
-
212
- # 戻り値の型ヒントの抽出
213
- return_type = self._extract_return_type_from_node(node, source_code)
214
-
215
- # docstringの抽出
216
- docstring = self._extract_docstring_from_node(node, source_code)
217
-
218
- # 関数ボディの抽出
219
- body = self._extract_function_body(node, source_code)
220
-
221
- # 複雑度の簡易計算
222
- complexity_score = self._calculate_complexity(body)
223
-
224
- # 可視性の判定(Pythonの慣例に基づく)
225
- visibility = "public"
226
- if name.startswith("__") and name.endswith("__"):
227
- visibility = "magic" # マジックメソッド
228
- elif name.startswith("_"):
229
- visibility = "private"
230
-
231
- start_byte = min(node.start_byte, len(source_code))
232
- end_byte = min(node.end_byte, len(source_code))
233
- raw_text = source_code[start_byte:end_byte] if start_byte < end_byte else source_code
234
-
235
- return Function(
236
- name=name,
237
- start_line=node.start_point[0] + 1,
238
- end_line=node.end_point[0] + 1,
239
- raw_text=raw_text,
240
- language="python",
241
- parameters=parameters,
242
- return_type=return_type or "Any",
243
- modifiers=decorators,
244
- is_static="staticmethod" in decorators,
245
- is_private=visibility == "private",
246
- is_public=visibility == "public",
247
- is_async=is_async,
248
- docstring=docstring,
249
- complexity_score=complexity_score,
250
- )
251
-
252
- except Exception as e:
253
- log_warning(f"Could not extract detailed function info: {e}")
254
- return None
255
-
256
- def _extract_detailed_class_info(
257
- self, node: "tree_sitter.Node", source_code: str
258
- ) -> Optional[Class]:
259
- """Extract comprehensive class information from AST node"""
260
- try:
261
- # 基本情報の抽出
262
- name = self._extract_name_from_node(node, source_code)
263
- if not name:
264
- return None
265
-
266
- # スーパークラスの抽出
267
- superclasses = self._extract_superclasses_from_node(node, source_code)
268
-
269
- # デコレータの抽出
270
- decorators = self._extract_decorators_from_node(node, source_code)
271
-
272
- # docstringの抽出
273
- docstring = self._extract_docstring_from_node(node, source_code)
274
-
275
- # 完全修飾名の生成
276
- full_qualified_name = (
277
- f"{self.current_module}.{name}" if self.current_module else name
278
- )
279
-
280
- # 可視性の判定
281
- visibility = "public"
282
- if name.startswith("_"):
283
- visibility = "private"
284
-
285
- return Class(
286
- name=name,
287
- start_line=node.start_point[0] + 1,
288
- end_line=node.end_point[0] + 1,
289
- raw_text=source_code[node.start_byte : node.end_byte],
290
- language="python",
291
- class_type="class",
292
- full_qualified_name=full_qualified_name,
293
- package_name=self.current_module,
294
- superclass=superclasses[0] if superclasses else None,
295
- interfaces=superclasses[1:] if len(superclasses) > 1 else [],
296
- modifiers=decorators,
297
- docstring=docstring,
298
- )
299
-
300
- except Exception as e:
301
- log_warning(f"Could not extract detailed class info: {e}")
302
- return None
303
-
304
- def _extract_name_from_node(
305
- self, node: "tree_sitter.Node", source_code: str
306
- ) -> Optional[str]:
307
- """Extract name from AST node"""
308
- for child in node.children:
309
- if child.type == "identifier":
310
- return source_code[child.start_byte : child.end_byte]
311
- return None
312
-
313
- def _extract_parameters_from_node(
314
- self, node: "tree_sitter.Node", source_code: str
315
- ) -> List[str]:
316
- """Extract parameters from function node"""
317
- parameters: List[str] = []
318
- for child in node.children:
319
- if child.type == "parameters":
320
- for param_child in child.children:
321
- if param_child.type in [
322
- "identifier",
323
- "typed_parameter",
324
- "default_parameter",
325
- ]:
326
- param_text = source_code[
327
- param_child.start_byte : param_child.end_byte
328
- ]
329
- parameters.append(param_text)
330
- return parameters
331
-
332
- def _extract_decorators_from_node(
333
- self, node: "tree_sitter.Node", source_code: str
334
- ) -> List[str]:
335
- """Extract decorators from node"""
336
- decorators: List[str] = []
337
-
338
- # デコレータは関数/クラス定義の前にある
339
- if hasattr(node, "parent") and node.parent:
340
- for sibling in node.parent.children:
341
- if (
342
- sibling.type == "decorator"
343
- and sibling.end_point[0] < node.start_point[0]
344
- ):
345
- decorator_text = source_code[sibling.start_byte : sibling.end_byte]
346
- # @を除去
347
- if decorator_text.startswith("@"):
348
- decorator_text = decorator_text[1:].strip()
349
- decorators.append(decorator_text)
350
-
351
- return decorators
352
-
353
- def _extract_return_type_from_node(
354
- self, node: "tree_sitter.Node", source_code: str
355
- ) -> Optional[str]:
356
- """Extract return type annotation from function node"""
357
- for child in node.children:
358
- if child.type == "type":
359
- return source_code[child.start_byte : child.end_byte]
360
- return None
361
-
362
- def _extract_docstring_from_node(
363
- self, node: "tree_sitter.Node", source_code: str
364
- ) -> Optional[str]:
365
- """Extract docstring from function/class node"""
366
- for child in node.children:
367
- if child.type == "block":
368
- # ブロックの最初の文がdocstringかチェック
369
- for stmt in child.children:
370
- if stmt.type == "expression_statement":
371
- for expr in stmt.children:
372
- if expr.type == "string":
373
- # start_byteとend_byteが整数であることを確認
374
- if (hasattr(expr, 'start_byte') and hasattr(expr, 'end_byte') and
375
- isinstance(expr.start_byte, int) and isinstance(expr.end_byte, int)):
376
- docstring = source_code[expr.start_byte : expr.end_byte]
377
- else:
378
- return None
379
- # クォートを除去
380
- if docstring.startswith('"""') or docstring.startswith(
381
- "'''"
382
- ):
383
- return docstring[3:-3].strip()
384
- elif docstring.startswith('"') or docstring.startswith(
385
- "'"
386
- ):
387
- return docstring[1:-1].strip()
388
- return docstring
389
- break
390
- break
391
- return None
392
-
393
- def _extract_function_body(self, node: "tree_sitter.Node", source_code: str) -> str:
394
- """Extract function body"""
395
- for child in node.children:
396
- if child.type == "block":
397
- return source_code[child.start_byte : child.end_byte]
398
- return ""
399
-
400
- def _extract_superclasses_from_node(
401
- self, node: "tree_sitter.Node", source_code: str
402
- ) -> List[str]:
403
- """Extract superclasses from class node"""
404
- superclasses: List[str] = []
405
- for child in node.children:
406
- if child.type == "argument_list":
407
- for arg in child.children:
408
- if arg.type == "identifier":
409
- superclasses.append(source_code[arg.start_byte : arg.end_byte])
410
- return superclasses
411
-
412
- def _calculate_complexity(self, body: str) -> int:
413
- """Calculate cyclomatic complexity (simplified)"""
414
- complexity = 1 # 基本複雑度
415
- keywords = ["if", "elif", "for", "while", "try", "except", "with", "and", "or"]
416
- for keyword in keywords:
417
- complexity += body.count(f" {keyword} ") + body.count(f"\n{keyword} ")
418
- return complexity
419
-
420
- def _extract_variable_info(
421
- self,
422
- node: "tree_sitter.Node",
423
- source_code: str,
424
- is_multiple: bool = False,
425
- is_augmented: bool = False,
426
- ) -> Optional[Variable]:
427
- """Extract detailed variable information from AST node"""
428
- try:
429
- if (
430
- not hasattr(node, "start_byte")
431
- or not hasattr(node, "end_byte")
432
- or not hasattr(node, "start_point")
433
- or not hasattr(node, "end_point")
434
- ):
435
- return None
436
- if (
437
- node.start_byte is None
438
- or node.end_byte is None
439
- or node.start_point is None
440
- or node.end_point is None
441
- ):
442
- return None
443
-
444
- # 変数名の抽出(簡略化)
445
- variable_text = source_code[node.start_byte : node.end_byte]
446
-
447
- # 変数名を抽出(=の左側)
448
- if "=" in variable_text:
449
- name_part = variable_text.split("=")[0].strip()
450
- if is_multiple and "," in name_part:
451
- name = name_part.split(",")[0].strip()
452
- else:
453
- name = name_part
454
- else:
455
- name = "variable"
456
-
457
- return Variable(
458
- name=name,
459
- start_line=node.start_point[0] + 1,
460
- end_line=node.end_point[0] + 1,
461
- raw_text=variable_text,
462
- language="python",
463
- variable_type=(
464
- "multiple"
465
- if is_multiple
466
- else "augmented" if is_augmented else "assignment"
467
- ),
468
- )
469
-
470
- except Exception as e:
471
- log_warning(f"Could not extract variable info: {e}")
472
- return None
473
-
474
- def _extract_import_info(
475
- self,
476
- node: "tree_sitter.Node",
477
- source_code: str,
478
- is_from: bool = False,
479
- is_from_list: bool = False,
480
- is_aliased: bool = False,
481
- ) -> Optional[Import]:
482
- """Extract detailed import information from AST node"""
483
- try:
484
- if (
485
- not hasattr(node, "start_byte")
486
- or not hasattr(node, "end_byte")
487
- or not hasattr(node, "start_point")
488
- or not hasattr(node, "end_point")
489
- ):
490
- return None
491
- if (
492
- node.start_byte is None
493
- or node.end_byte is None
494
- or node.start_point is None
495
- or node.end_point is None
496
- ):
497
- return None
498
-
499
- # テスト環境での安全な境界処理
500
- source_len = len(source_code)
501
- if node.start_byte >= source_len or node.end_byte > source_len:
502
- # ノードの範囲がソースコードを超える場合(テスト環境など)、全体を使用
503
- import_text = source_code
504
- else:
505
- start_byte = node.start_byte
506
- end_byte = node.end_byte
507
- import_text = source_code[start_byte:end_byte] if start_byte < end_byte else source_code
508
-
509
- # インポート名とモジュール名を抽出(完全なインポート文を保持)
510
- if is_from:
511
- import_type = "from_import"
512
- if "from" in import_text and "import" in import_text:
513
- parts = import_text.split("import")
514
- module_name = parts[0].replace("from", "").strip()
515
- # from importの場合は完全な文を保持
516
- import_name = import_text
517
- else:
518
- module_name = ""
519
- import_name = import_text
520
- elif is_aliased:
521
- import_type = "aliased_import"
522
- module_name = ""
523
- # エイリアスインポートも完全な文を保持
524
- import_name = import_text
525
- else:
526
- import_type = "import"
527
- module_name = ""
528
- # 通常のインポートも完全な文を保持
529
- import_name = import_text
530
-
531
- return Import(
532
- name=import_name,
533
- start_line=node.start_point[0] + 1,
534
- end_line=node.end_point[0] + 1,
535
- raw_text=import_text,
536
- language="python",
537
- module_name=module_name,
538
- import_statement=import_text,
539
- line_number=node.start_point[0] + 1,
540
- )
541
-
542
- except Exception as e:
543
- log_warning(f"Could not extract import info: {e}")
544
- return None
545
-
546
-
547
- class PythonPlugin(LanguagePlugin):
548
- """Python language plugin"""
549
-
550
- def __init__(self) -> None:
551
- self._extractor = PythonElementExtractor()
552
- self._language: Optional["tree_sitter.Language"] = None
553
-
554
- @property
555
- def language_name(self) -> str:
556
- return "python"
557
-
558
- @property
559
- def file_extensions(self) -> List[str]:
560
- return [".py", ".pyw", ".pyi"]
561
-
562
- def get_language_name(self) -> str:
563
- """Return the name of the programming language this plugin supports"""
564
- return "python"
565
-
566
- def get_file_extensions(self) -> List[str]:
567
- """Return list of file extensions this plugin supports"""
568
- return [".py", ".pyw", ".pyi"]
569
-
570
- def create_extractor(self) -> ElementExtractor:
571
- """Create and return an element extractor for this language"""
572
- return PythonElementExtractor()
573
-
574
- def get_extractor(self) -> ElementExtractor:
575
- return self._extractor
576
-
577
- def get_tree_sitter_language(self) -> Optional["tree_sitter.Language"]:
578
- """Load and return Python tree-sitter language"""
579
- if self._language is None:
580
- self._language = loader.load_language("python")
581
- return self._language
582
-
583
- def get_supported_queries(self) -> List[str]:
584
- """Get list of supported query types for Python"""
585
- return list(ALL_QUERIES.keys())
586
-
587
- def execute_query(self, tree: "tree_sitter.Tree", query_name: str) -> dict:
588
- """Execute a specific query on the tree"""
589
- try:
590
- query_string = get_query(query_name)
591
- language = self.get_tree_sitter_language()
592
- if language:
593
- query = language.query(query_string)
594
- captures = query.captures(tree.root_node)
595
- return captures if isinstance(captures, dict) else {}
596
- except Exception as e:
597
- log_warning(f"Could not execute query '{query_name}': {e}")
598
- return {}
1
+ #!/usr/bin/env python3
2
+ """
3
+ Python Language Plugin
4
+
5
+ Provides Python-specific parsing and element extraction functionality.
6
+ Integrates with the existing Python queries for comprehensive analysis.
7
+ """
8
+
9
+ from typing import TYPE_CHECKING, Optional
10
+
11
+ if TYPE_CHECKING:
12
+ import tree_sitter
13
+
14
+ from ..language_loader import loader
15
+ from ..models import Class, Function, Import, Variable
16
+ from ..queries.python import ALL_QUERIES, get_query
17
+ from ..utils import log_warning
18
+ from . import ElementExtractor, LanguagePlugin
19
+
20
+
21
+ class PythonElementExtractor(ElementExtractor):
22
+ """Python-specific element extractor with comprehensive analysis"""
23
+
24
+ def __init__(self) -> None:
25
+ # 分析コンテキスト
26
+ self.current_module: str = ""
27
+ self.current_file: str = ""
28
+ self.source_code: str = ""
29
+ self.imports: list[str] = []
30
+
31
+ def extract_functions(
32
+ self, tree: "tree_sitter.Tree", source_code: str
33
+ ) -> list[Function]:
34
+ """Extract Python function definitions with comprehensive analysis"""
35
+ self.source_code = source_code
36
+ functions: list[Function] = []
37
+
38
+ try:
39
+ language = tree.language if hasattr(tree, "language") else None
40
+ if language:
41
+ # 関数定義クエリを使用
42
+ query_string = get_query("functions")
43
+ query = language.query(query_string)
44
+ captures = query.captures(tree.root_node)
45
+
46
+ if captures is not None and isinstance(captures, dict):
47
+ # 関数定義を処理
48
+ function_nodes = captures.get("function.definition", [])
49
+ for node in function_nodes:
50
+ function = self._extract_detailed_function_info(
51
+ node, source_code
52
+ )
53
+ if function:
54
+ functions.append(function)
55
+
56
+ # async関数も処理
57
+ async_nodes = captures.get("function.async", [])
58
+ for node in async_nodes:
59
+ function = self._extract_detailed_function_info(
60
+ node, source_code, is_async=True
61
+ )
62
+ if function:
63
+ functions.append(function)
64
+
65
+ except Exception as e:
66
+ log_warning(f"Could not extract Python functions: {e}")
67
+
68
+ return functions
69
+
70
+ def extract_classes(
71
+ self, tree: "tree_sitter.Tree", source_code: str
72
+ ) -> list[Class]:
73
+ """Extract Python class definitions with comprehensive analysis"""
74
+ self.source_code = source_code
75
+ classes: list[Class] = []
76
+
77
+ try:
78
+ language = tree.language if hasattr(tree, "language") else None
79
+ if language:
80
+ # クラス定義クエリを使用
81
+ query_string = get_query("classes")
82
+ query = language.query(query_string)
83
+ captures = query.captures(tree.root_node)
84
+
85
+ if captures is not None and isinstance(captures, dict):
86
+ class_nodes = captures.get("class.definition", [])
87
+ for node in class_nodes:
88
+ cls = self._extract_detailed_class_info(node, source_code)
89
+ if cls:
90
+ classes.append(cls)
91
+
92
+ except Exception as e:
93
+ log_warning(f"Could not extract Python classes: {e}")
94
+
95
+ return classes
96
+
97
+ def extract_variables(
98
+ self, tree: "tree_sitter.Tree", source_code: str
99
+ ) -> list[Variable]:
100
+ """Extract Python variable definitions"""
101
+ variables: list[Variable] = []
102
+
103
+ try:
104
+ language = tree.language if hasattr(tree, "language") else None
105
+ if language:
106
+ # 変数代入クエリを使用
107
+ query_string = get_query("variables")
108
+ query = language.query(query_string)
109
+ captures = query.captures(tree.root_node)
110
+
111
+ if captures is not None and isinstance(captures, dict):
112
+ # 通常の代入
113
+ assignment_nodes = captures.get("variable.assignment", [])
114
+ for node in assignment_nodes:
115
+ variable = self._extract_variable_info(node, source_code)
116
+ if variable:
117
+ variables.append(variable)
118
+
119
+ # 複数代入
120
+ multiple_nodes = captures.get("variable.multiple", [])
121
+ for node in multiple_nodes:
122
+ variable = self._extract_variable_info(
123
+ node, source_code, is_multiple=True
124
+ )
125
+ if variable:
126
+ variables.append(variable)
127
+
128
+ # 拡張代入
129
+ augmented_nodes = captures.get("variable.augmented", [])
130
+ for node in augmented_nodes:
131
+ variable = self._extract_variable_info(
132
+ node, source_code, is_augmented=True
133
+ )
134
+ if variable:
135
+ variables.append(variable)
136
+
137
+ except Exception as e:
138
+ log_warning(f"Could not extract Python variables: {e}")
139
+
140
+ return variables
141
+
142
+ def extract_imports(
143
+ self, tree: "tree_sitter.Tree", source_code: str
144
+ ) -> list[Import]:
145
+ """Extract Python import statements"""
146
+ imports: list[Import] = []
147
+
148
+ try:
149
+ language = tree.language if hasattr(tree, "language") else None
150
+ if language:
151
+ # インポート文クエリを使用
152
+ query_string = get_query("imports")
153
+ query = language.query(query_string)
154
+ captures = query.captures(tree.root_node)
155
+
156
+ if captures is not None and isinstance(captures, dict):
157
+ # 通常のimport
158
+ import_nodes = captures.get("import.statement", [])
159
+ for node in import_nodes:
160
+ imp = self._extract_import_info(node, source_code)
161
+ if imp:
162
+ imports.append(imp)
163
+
164
+ # from import
165
+ from_nodes = captures.get("import.from", [])
166
+ for node in from_nodes:
167
+ imp = self._extract_import_info(node, source_code, is_from=True)
168
+ if imp:
169
+ imports.append(imp)
170
+
171
+ # from import list
172
+ from_list_nodes = captures.get("import.from_list", [])
173
+ for node in from_list_nodes:
174
+ imp = self._extract_import_info(
175
+ node, source_code, is_from_list=True
176
+ )
177
+ if imp:
178
+ imports.append(imp)
179
+
180
+ # aliased import
181
+ aliased_nodes = captures.get("import.aliased", [])
182
+ for node in aliased_nodes:
183
+ imp = self._extract_import_info(
184
+ node, source_code, is_aliased=True
185
+ )
186
+ if imp:
187
+ imports.append(imp)
188
+
189
+ except Exception as e:
190
+ log_warning(f"Could not extract Python imports: {e}")
191
+
192
+ return imports
193
+
194
+ def _extract_detailed_function_info(
195
+ self, node: "tree_sitter.Node", source_code: str, is_async: bool = False
196
+ ) -> Function | None:
197
+ """Extract comprehensive function information from AST node"""
198
+ try:
199
+ # 基本情報の抽出
200
+ name = self._extract_name_from_node(node, source_code)
201
+ if not name:
202
+ return None
203
+
204
+ # パラメータの抽出
205
+ parameters = self._extract_parameters_from_node(node, source_code)
206
+
207
+ # デコレータの抽出
208
+ decorators = self._extract_decorators_from_node(node, source_code)
209
+
210
+ # 戻り値の型ヒントの抽出
211
+ return_type = self._extract_return_type_from_node(node, source_code)
212
+
213
+ # docstringの抽出
214
+ docstring = self._extract_docstring_from_node(node, source_code)
215
+
216
+ # 関数ボディの抽出
217
+ body = self._extract_function_body(node, source_code)
218
+
219
+ # 複雑度の簡易計算
220
+ complexity_score = self._calculate_complexity(body)
221
+
222
+ # 可視性の判定(Pythonの慣例に基づく)
223
+ visibility = "public"
224
+ if name.startswith("__") and name.endswith("__"):
225
+ visibility = "magic" # マジックメソッド
226
+ elif name.startswith("_"):
227
+ visibility = "private"
228
+
229
+ start_byte = min(node.start_byte, len(source_code))
230
+ end_byte = min(node.end_byte, len(source_code))
231
+ raw_text = (
232
+ source_code[start_byte:end_byte]
233
+ if start_byte < end_byte
234
+ else source_code
235
+ )
236
+
237
+ return Function(
238
+ name=name,
239
+ start_line=node.start_point[0] + 1,
240
+ end_line=node.end_point[0] + 1,
241
+ raw_text=raw_text,
242
+ language="python",
243
+ parameters=parameters,
244
+ return_type=return_type or "Any",
245
+ modifiers=decorators,
246
+ is_static="staticmethod" in decorators,
247
+ is_private=visibility == "private",
248
+ is_public=visibility == "public",
249
+ is_async=is_async,
250
+ docstring=docstring,
251
+ complexity_score=complexity_score,
252
+ )
253
+
254
+ except Exception as e:
255
+ log_warning(f"Could not extract detailed function info: {e}")
256
+ return None
257
+
258
+ def _extract_detailed_class_info(
259
+ self, node: "tree_sitter.Node", source_code: str
260
+ ) -> Class | None:
261
+ """Extract comprehensive class information from AST node"""
262
+ try:
263
+ # 基本情報の抽出
264
+ name = self._extract_name_from_node(node, source_code)
265
+ if not name:
266
+ return None
267
+
268
+ # スーパークラスの抽出
269
+ superclasses = self._extract_superclasses_from_node(node, source_code)
270
+
271
+ # デコレータの抽出
272
+ decorators = self._extract_decorators_from_node(node, source_code)
273
+
274
+ # docstringの抽出
275
+ docstring = self._extract_docstring_from_node(node, source_code)
276
+
277
+ # 完全修飾名の生成
278
+ full_qualified_name = (
279
+ f"{self.current_module}.{name}" if self.current_module else name
280
+ )
281
+
282
+ # 可視性の判定
283
+ # visibility = "public"
284
+ # if name.startswith("_"):
285
+ # visibility = "private" # Not used currently
286
+
287
+ return Class(
288
+ name=name,
289
+ start_line=node.start_point[0] + 1,
290
+ end_line=node.end_point[0] + 1,
291
+ raw_text=source_code[node.start_byte : node.end_byte],
292
+ language="python",
293
+ class_type="class",
294
+ full_qualified_name=full_qualified_name,
295
+ package_name=self.current_module,
296
+ superclass=superclasses[0] if superclasses else None,
297
+ interfaces=superclasses[1:] if len(superclasses) > 1 else [],
298
+ modifiers=decorators,
299
+ docstring=docstring,
300
+ )
301
+
302
+ except Exception as e:
303
+ log_warning(f"Could not extract detailed class info: {e}")
304
+ return None
305
+
306
+ def _extract_name_from_node(
307
+ self, node: "tree_sitter.Node", source_code: str
308
+ ) -> str | None:
309
+ """Extract name from AST node"""
310
+ for child in node.children:
311
+ if child.type == "identifier":
312
+ return source_code[child.start_byte : child.end_byte]
313
+ return None
314
+
315
+ def _extract_parameters_from_node(
316
+ self, node: "tree_sitter.Node", source_code: str
317
+ ) -> list[str]:
318
+ """Extract parameters from function node"""
319
+ parameters: list[str] = []
320
+ for child in node.children:
321
+ if child.type == "parameters":
322
+ for param_child in child.children:
323
+ if param_child.type in [
324
+ "identifier",
325
+ "typed_parameter",
326
+ "default_parameter",
327
+ ]:
328
+ param_text = source_code[
329
+ param_child.start_byte : param_child.end_byte
330
+ ]
331
+ parameters.append(param_text)
332
+ return parameters
333
+
334
+ def _extract_decorators_from_node(
335
+ self, node: "tree_sitter.Node", source_code: str
336
+ ) -> list[str]:
337
+ """Extract decorators from node"""
338
+ decorators: list[str] = []
339
+
340
+ # デコレータは関数/クラス定義の前にある
341
+ if hasattr(node, "parent") and node.parent:
342
+ for sibling in node.parent.children:
343
+ if (
344
+ sibling.type == "decorator"
345
+ and sibling.end_point[0] < node.start_point[0]
346
+ ):
347
+ decorator_text = source_code[sibling.start_byte : sibling.end_byte]
348
+ # @を除去
349
+ if decorator_text.startswith("@"):
350
+ decorator_text = decorator_text[1:].strip()
351
+ decorators.append(decorator_text)
352
+
353
+ return decorators
354
+
355
+ def _extract_return_type_from_node(
356
+ self, node: "tree_sitter.Node", source_code: str
357
+ ) -> str | None:
358
+ """Extract return type annotation from function node"""
359
+ for child in node.children:
360
+ if child.type == "type":
361
+ return source_code[child.start_byte : child.end_byte]
362
+ return None
363
+
364
+ def _extract_docstring_from_node(
365
+ self, node: "tree_sitter.Node", source_code: str
366
+ ) -> str | None:
367
+ """Extract docstring from function/class node"""
368
+ for child in node.children:
369
+ if child.type == "block":
370
+ # ブロックの最初の文がdocstringかチェック
371
+ for stmt in child.children:
372
+ if stmt.type == "expression_statement":
373
+ for expr in stmt.children:
374
+ if expr.type == "string":
375
+ # start_byteend_byteが整数であることを確認
376
+ if (
377
+ hasattr(expr, "start_byte")
378
+ and hasattr(expr, "end_byte")
379
+ and isinstance(expr.start_byte, int)
380
+ and isinstance(expr.end_byte, int)
381
+ ):
382
+ docstring = source_code[
383
+ expr.start_byte : expr.end_byte
384
+ ]
385
+ else:
386
+ return None
387
+ # クォートを除去
388
+ if docstring.startswith('"""') or docstring.startswith(
389
+ "'''"
390
+ ):
391
+ return docstring[3:-3].strip()
392
+ elif docstring.startswith('"') or docstring.startswith(
393
+ "'"
394
+ ):
395
+ return docstring[1:-1].strip()
396
+ return docstring
397
+ break
398
+ break
399
+ return None
400
+
401
+ def _extract_function_body(self, node: "tree_sitter.Node", source_code: str) -> str:
402
+ """Extract function body"""
403
+ for child in node.children:
404
+ if child.type == "block":
405
+ return source_code[child.start_byte : child.end_byte]
406
+ return ""
407
+
408
+ def _extract_superclasses_from_node(
409
+ self, node: "tree_sitter.Node", source_code: str
410
+ ) -> list[str]:
411
+ """Extract superclasses from class node"""
412
+ superclasses: list[str] = []
413
+ for child in node.children:
414
+ if child.type == "argument_list":
415
+ for arg in child.children:
416
+ if arg.type == "identifier":
417
+ superclasses.append(source_code[arg.start_byte : arg.end_byte])
418
+ return superclasses
419
+
420
+ def _calculate_complexity(self, body: str) -> int:
421
+ """Calculate cyclomatic complexity (simplified)"""
422
+ complexity = 1 # 基本複雑度
423
+ keywords = ["if", "elif", "for", "while", "try", "except", "with", "and", "or"]
424
+ for keyword in keywords:
425
+ complexity += body.count(f" {keyword} ") + body.count(f"\n{keyword} ")
426
+ return complexity
427
+
428
+ def _extract_variable_info(
429
+ self,
430
+ node: "tree_sitter.Node",
431
+ source_code: str,
432
+ is_multiple: bool = False,
433
+ is_augmented: bool = False,
434
+ ) -> Variable | None:
435
+ """Extract detailed variable information from AST node"""
436
+ try:
437
+ if (
438
+ not hasattr(node, "start_byte")
439
+ or not hasattr(node, "end_byte")
440
+ or not hasattr(node, "start_point")
441
+ or not hasattr(node, "end_point")
442
+ or node.start_byte is None
443
+ or node.end_byte is None
444
+ or node.start_point is None
445
+ or node.end_point is None
446
+ ):
447
+ return None
448
+
449
+ # 変数名の抽出(簡略化)
450
+ variable_text = source_code[node.start_byte : node.end_byte]
451
+
452
+ # 変数名を抽出(=の左側)
453
+ if "=" in variable_text:
454
+ name_part = variable_text.split("=")[0].strip()
455
+ if is_multiple and "," in name_part:
456
+ name = name_part.split(",")[0].strip()
457
+ else:
458
+ name = name_part
459
+ else:
460
+ name = "variable"
461
+
462
+ return Variable(
463
+ name=name,
464
+ start_line=node.start_point[0] + 1,
465
+ end_line=node.end_point[0] + 1,
466
+ raw_text=variable_text,
467
+ language="python",
468
+ variable_type=(
469
+ "multiple"
470
+ if is_multiple
471
+ else "augmented"
472
+ if is_augmented
473
+ else "assignment"
474
+ ),
475
+ )
476
+
477
+ except Exception as e:
478
+ log_warning(f"Could not extract variable info: {e}")
479
+ return None
480
+
481
+ def _extract_import_info(
482
+ self,
483
+ node: "tree_sitter.Node",
484
+ source_code: str,
485
+ is_from: bool = False,
486
+ is_from_list: bool = False,
487
+ is_aliased: bool = False,
488
+ ) -> Import | None:
489
+ """Extract detailed import information from AST node"""
490
+ try:
491
+ if (
492
+ not hasattr(node, "start_byte")
493
+ or not hasattr(node, "end_byte")
494
+ or not hasattr(node, "start_point")
495
+ or not hasattr(node, "end_point")
496
+ or node.start_byte is None
497
+ or node.end_byte is None
498
+ or node.start_point is None
499
+ or node.end_point is None
500
+ ):
501
+ return None
502
+
503
+ # テスト環境での安全な境界処理
504
+ source_len = len(source_code)
505
+ if node.start_byte >= source_len or node.end_byte > source_len:
506
+ # ノードの範囲がソースコードを超える場合(テスト環境など)、全体を使用
507
+ import_text = source_code
508
+ else:
509
+ start_byte = node.start_byte
510
+ end_byte = node.end_byte
511
+ import_text = (
512
+ source_code[start_byte:end_byte]
513
+ if start_byte < end_byte
514
+ else source_code
515
+ )
516
+
517
+ # インポート名とモジュール名を抽出(完全なインポート文を保持)
518
+ if is_from:
519
+ # import_type = "from_import" # Not used currently
520
+ if "from" in import_text and "import" in import_text:
521
+ parts = import_text.split("import")
522
+ module_name = parts[0].replace("from", "").strip()
523
+ # from importの場合は完全な文を保持
524
+ import_name = import_text
525
+ else:
526
+ module_name = ""
527
+ import_name = import_text
528
+ elif is_aliased:
529
+ # import_type = "aliased_import" # Not used currently
530
+ module_name = ""
531
+ # エイリアスインポートも完全な文を保持
532
+ import_name = import_text
533
+ else:
534
+ # import_type = "import" # Not used currently
535
+ module_name = ""
536
+ # 通常のインポートも完全な文を保持
537
+ import_name = import_text
538
+
539
+ return Import(
540
+ name=import_name,
541
+ start_line=node.start_point[0] + 1,
542
+ end_line=node.end_point[0] + 1,
543
+ raw_text=import_text,
544
+ language="python",
545
+ module_name=module_name,
546
+ import_statement=import_text,
547
+ line_number=node.start_point[0] + 1,
548
+ )
549
+
550
+ except Exception as e:
551
+ log_warning(f"Could not extract import info: {e}")
552
+ return None
553
+
554
+
555
+ class PythonPlugin(LanguagePlugin):
556
+ """Python language plugin"""
557
+
558
+ def __init__(self) -> None:
559
+ self._extractor = PythonElementExtractor()
560
+ self._language: tree_sitter.Language | None = None
561
+
562
+ @property
563
+ def language_name(self) -> str:
564
+ return "python"
565
+
566
+ @property
567
+ def file_extensions(self) -> list[str]:
568
+ return [".py", ".pyw", ".pyi"]
569
+
570
+ def get_language_name(self) -> str:
571
+ """Return the name of the programming language this plugin supports"""
572
+ return "python"
573
+
574
+ def get_file_extensions(self) -> list[str]:
575
+ """Return list of file extensions this plugin supports"""
576
+ return [".py", ".pyw", ".pyi"]
577
+
578
+ def create_extractor(self) -> ElementExtractor:
579
+ """Create and return an element extractor for this language"""
580
+ return PythonElementExtractor()
581
+
582
+ def get_extractor(self) -> ElementExtractor:
583
+ return self._extractor
584
+
585
+ def get_tree_sitter_language(self) -> Optional["tree_sitter.Language"]:
586
+ """Load and return Python tree-sitter language"""
587
+ if self._language is None:
588
+ self._language = loader.load_language("python")
589
+ return self._language
590
+
591
+ def get_supported_queries(self) -> list[str]:
592
+ """Get list of supported query types for Python"""
593
+ return list(ALL_QUERIES.keys())
594
+
595
+ def execute_query(self, tree: "tree_sitter.Tree", query_name: str) -> dict:
596
+ """Execute a specific query on the tree"""
597
+ try:
598
+ query_string = get_query(query_name)
599
+ language = self.get_tree_sitter_language()
600
+ if language:
601
+ query = language.query(query_string)
602
+ captures = query.captures(tree.root_node)
603
+ return captures if isinstance(captures, dict) else {}
604
+ except Exception as e:
605
+ log_warning(f"Could not execute query '{query_name}': {e}")
606
+ return {}