tree-sitter-analyzer 0.1.1__py3-none-any.whl → 0.1.2__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.

@@ -1,598 +1,598 @@
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
+ # -*- 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 {}