tree-sitter-analyzer 0.2.0__py3-none-any.whl → 0.4.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of tree-sitter-analyzer might be problematic. Click here for more details.
- tree_sitter_analyzer/__init__.py +134 -121
- tree_sitter_analyzer/__main__.py +11 -12
- tree_sitter_analyzer/api.py +533 -539
- tree_sitter_analyzer/cli/__init__.py +39 -39
- tree_sitter_analyzer/cli/__main__.py +12 -13
- tree_sitter_analyzer/cli/commands/__init__.py +26 -27
- tree_sitter_analyzer/cli/commands/advanced_command.py +88 -88
- tree_sitter_analyzer/cli/commands/base_command.py +160 -155
- tree_sitter_analyzer/cli/commands/default_command.py +18 -19
- tree_sitter_analyzer/cli/commands/partial_read_command.py +141 -133
- tree_sitter_analyzer/cli/commands/query_command.py +81 -82
- tree_sitter_analyzer/cli/commands/structure_command.py +138 -121
- tree_sitter_analyzer/cli/commands/summary_command.py +101 -93
- tree_sitter_analyzer/cli/commands/table_command.py +235 -233
- tree_sitter_analyzer/cli/info_commands.py +120 -121
- tree_sitter_analyzer/cli_main.py +278 -276
- tree_sitter_analyzer/core/__init__.py +15 -20
- tree_sitter_analyzer/core/analysis_engine.py +555 -574
- tree_sitter_analyzer/core/cache_service.py +320 -330
- tree_sitter_analyzer/core/engine.py +559 -560
- tree_sitter_analyzer/core/parser.py +293 -288
- tree_sitter_analyzer/core/query.py +502 -502
- tree_sitter_analyzer/encoding_utils.py +456 -460
- tree_sitter_analyzer/exceptions.py +337 -340
- tree_sitter_analyzer/file_handler.py +210 -222
- tree_sitter_analyzer/formatters/__init__.py +1 -1
- tree_sitter_analyzer/formatters/base_formatter.py +167 -168
- tree_sitter_analyzer/formatters/formatter_factory.py +78 -74
- tree_sitter_analyzer/formatters/java_formatter.py +291 -270
- tree_sitter_analyzer/formatters/python_formatter.py +259 -235
- tree_sitter_analyzer/interfaces/__init__.py +9 -10
- tree_sitter_analyzer/interfaces/cli.py +528 -557
- tree_sitter_analyzer/interfaces/cli_adapter.py +343 -319
- tree_sitter_analyzer/interfaces/mcp_adapter.py +206 -170
- tree_sitter_analyzer/interfaces/mcp_server.py +405 -416
- tree_sitter_analyzer/java_analyzer.py +187 -219
- tree_sitter_analyzer/language_detector.py +398 -400
- tree_sitter_analyzer/language_loader.py +224 -228
- tree_sitter_analyzer/languages/__init__.py +10 -11
- tree_sitter_analyzer/languages/java_plugin.py +1174 -1113
- tree_sitter_analyzer/{plugins → languages}/javascript_plugin.py +446 -439
- tree_sitter_analyzer/languages/python_plugin.py +747 -712
- tree_sitter_analyzer/mcp/__init__.py +31 -32
- tree_sitter_analyzer/mcp/resources/__init__.py +44 -47
- tree_sitter_analyzer/mcp/resources/code_file_resource.py +209 -213
- tree_sitter_analyzer/mcp/resources/project_stats_resource.py +555 -550
- tree_sitter_analyzer/mcp/server.py +333 -345
- tree_sitter_analyzer/mcp/tools/__init__.py +30 -31
- tree_sitter_analyzer/mcp/tools/analyze_scale_tool.py +654 -557
- tree_sitter_analyzer/mcp/tools/analyze_scale_tool_cli_compatible.py +247 -245
- tree_sitter_analyzer/mcp/tools/base_tool.py +54 -55
- tree_sitter_analyzer/mcp/tools/read_partial_tool.py +300 -302
- tree_sitter_analyzer/mcp/tools/table_format_tool.py +362 -359
- tree_sitter_analyzer/mcp/tools/universal_analyze_tool.py +543 -476
- tree_sitter_analyzer/mcp/utils/__init__.py +107 -106
- tree_sitter_analyzer/mcp/utils/error_handler.py +549 -549
- tree_sitter_analyzer/models.py +470 -481
- tree_sitter_analyzer/output_manager.py +255 -264
- tree_sitter_analyzer/plugins/__init__.py +280 -334
- tree_sitter_analyzer/plugins/base.py +496 -446
- tree_sitter_analyzer/plugins/manager.py +379 -355
- tree_sitter_analyzer/queries/__init__.py +26 -27
- tree_sitter_analyzer/queries/java.py +391 -394
- tree_sitter_analyzer/queries/javascript.py +148 -149
- tree_sitter_analyzer/queries/python.py +285 -286
- tree_sitter_analyzer/queries/typescript.py +229 -230
- tree_sitter_analyzer/query_loader.py +257 -260
- tree_sitter_analyzer/table_formatter.py +471 -448
- tree_sitter_analyzer/utils.py +277 -277
- {tree_sitter_analyzer-0.2.0.dist-info → tree_sitter_analyzer-0.4.0.dist-info}/METADATA +23 -8
- tree_sitter_analyzer-0.4.0.dist-info/RECORD +73 -0
- {tree_sitter_analyzer-0.2.0.dist-info → tree_sitter_analyzer-0.4.0.dist-info}/entry_points.txt +2 -1
- tree_sitter_analyzer/plugins/java_plugin.py +0 -625
- tree_sitter_analyzer/plugins/plugin_loader.py +0 -83
- tree_sitter_analyzer/plugins/python_plugin.py +0 -598
- tree_sitter_analyzer/plugins/registry.py +0 -366
- tree_sitter_analyzer-0.2.0.dist-info/RECORD +0 -77
- {tree_sitter_analyzer-0.2.0.dist-info → tree_sitter_analyzer-0.4.0.dist-info}/WHEEL +0 -0
|
@@ -1,83 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
# -*- coding: utf-8 -*-
|
|
3
|
-
"""
|
|
4
|
-
Plugin Loader for Tree-sitter Analyzer
|
|
5
|
-
|
|
6
|
-
Automatically loads and registers all available language plugins.
|
|
7
|
-
"""
|
|
8
|
-
|
|
9
|
-
from typing import List
|
|
10
|
-
|
|
11
|
-
from ..utils import log_debug, log_error, log_info
|
|
12
|
-
from . import plugin_registry
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
def load_all_plugins() -> List[str]:
|
|
16
|
-
"""Load and register all available language plugins"""
|
|
17
|
-
loaded_plugins = []
|
|
18
|
-
|
|
19
|
-
try:
|
|
20
|
-
# Import and register Java plugin
|
|
21
|
-
from .java_plugin import JavaPlugin
|
|
22
|
-
|
|
23
|
-
java_plugin = JavaPlugin()
|
|
24
|
-
plugin_registry.register_plugin(java_plugin)
|
|
25
|
-
loaded_plugins.append("java")
|
|
26
|
-
log_debug("Loaded Java plugin")
|
|
27
|
-
except Exception as e:
|
|
28
|
-
log_error(f"Failed to load Java plugin: {e}")
|
|
29
|
-
|
|
30
|
-
try:
|
|
31
|
-
# Import and register JavaScript plugin
|
|
32
|
-
from .javascript_plugin import JavaScriptPlugin
|
|
33
|
-
|
|
34
|
-
js_plugin = JavaScriptPlugin()
|
|
35
|
-
plugin_registry.register_plugin(js_plugin)
|
|
36
|
-
loaded_plugins.append("javascript")
|
|
37
|
-
log_debug("Loaded JavaScript plugin")
|
|
38
|
-
except Exception as e:
|
|
39
|
-
log_error(f"Failed to load JavaScript plugin: {e}")
|
|
40
|
-
|
|
41
|
-
try:
|
|
42
|
-
# Import and register Python plugin
|
|
43
|
-
from .python_plugin import PythonPlugin
|
|
44
|
-
|
|
45
|
-
python_plugin = PythonPlugin()
|
|
46
|
-
plugin_registry.register_plugin(python_plugin)
|
|
47
|
-
loaded_plugins.append("python")
|
|
48
|
-
log_debug("Loaded Python plugin")
|
|
49
|
-
except Exception as e:
|
|
50
|
-
log_error(f"Failed to load Python plugin: {e}")
|
|
51
|
-
|
|
52
|
-
if loaded_plugins:
|
|
53
|
-
log_info(
|
|
54
|
-
f"Successfully loaded {len(loaded_plugins)} language plugins: {', '.join(loaded_plugins)}"
|
|
55
|
-
)
|
|
56
|
-
else:
|
|
57
|
-
log_error("No language plugins were loaded successfully")
|
|
58
|
-
|
|
59
|
-
return loaded_plugins
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
def get_supported_languages() -> List[str]:
|
|
63
|
-
"""Get list of all supported languages"""
|
|
64
|
-
return plugin_registry.list_supported_languages()
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
def get_supported_extensions() -> List[str]:
|
|
68
|
-
"""Get list of all supported file extensions"""
|
|
69
|
-
return plugin_registry.list_supported_extensions()
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
def get_plugin_for_file(file_path: str):
|
|
73
|
-
"""Get appropriate plugin for a file"""
|
|
74
|
-
return plugin_registry.get_plugin_for_file(file_path)
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
def get_plugin_by_language(language: str):
|
|
78
|
-
"""Get plugin by language name"""
|
|
79
|
-
return plugin_registry.get_plugin(language)
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
# Auto-load plugins when module is imported
|
|
83
|
-
_loaded_plugins = load_all_plugins()
|
|
@@ -1,598 +0,0 @@
|
|
|
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 {}
|