tree-sitter-analyzer 0.4.0__py3-none-any.whl → 0.6.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 +1 -3
- tree_sitter_analyzer/__main__.py +2 -2
- tree_sitter_analyzer/cli/commands/default_command.py +1 -1
- tree_sitter_analyzer/cli/commands/query_command.py +5 -5
- tree_sitter_analyzer/cli/commands/table_command.py +3 -3
- tree_sitter_analyzer/cli/info_commands.py +14 -13
- tree_sitter_analyzer/cli_main.py +49 -30
- tree_sitter_analyzer/core/analysis_engine.py +21 -21
- tree_sitter_analyzer/core/cache_service.py +31 -31
- tree_sitter_analyzer/core/query.py +502 -502
- tree_sitter_analyzer/encoding_utils.py +5 -2
- tree_sitter_analyzer/file_handler.py +3 -3
- tree_sitter_analyzer/formatters/base_formatter.py +18 -18
- tree_sitter_analyzer/formatters/formatter_factory.py +15 -15
- tree_sitter_analyzer/formatters/java_formatter.py +291 -291
- tree_sitter_analyzer/formatters/python_formatter.py +259 -259
- tree_sitter_analyzer/interfaces/cli_adapter.py +32 -32
- tree_sitter_analyzer/interfaces/mcp_adapter.py +2 -2
- tree_sitter_analyzer/language_detector.py +398 -398
- tree_sitter_analyzer/language_loader.py +224 -224
- tree_sitter_analyzer/languages/java_plugin.py +1174 -1174
- tree_sitter_analyzer/languages/python_plugin.py +10 -2
- tree_sitter_analyzer/mcp/resources/project_stats_resource.py +555 -555
- tree_sitter_analyzer/models.py +470 -470
- tree_sitter_analyzer/output_manager.py +8 -10
- tree_sitter_analyzer/plugins/base.py +33 -0
- tree_sitter_analyzer/queries/java.py +78 -78
- tree_sitter_analyzer/queries/javascript.py +7 -7
- tree_sitter_analyzer/queries/python.py +18 -18
- tree_sitter_analyzer/queries/typescript.py +12 -12
- tree_sitter_analyzer/query_loader.py +13 -13
- tree_sitter_analyzer/table_formatter.py +20 -18
- tree_sitter_analyzer/utils.py +1 -1
- {tree_sitter_analyzer-0.4.0.dist-info → tree_sitter_analyzer-0.6.0.dist-info}/METADATA +11 -11
- {tree_sitter_analyzer-0.4.0.dist-info → tree_sitter_analyzer-0.6.0.dist-info}/RECORD +37 -38
- tree_sitter_analyzer/java_analyzer.py +0 -187
- {tree_sitter_analyzer-0.4.0.dist-info → tree_sitter_analyzer-0.6.0.dist-info}/WHEEL +0 -0
- {tree_sitter_analyzer-0.4.0.dist-info → tree_sitter_analyzer-0.6.0.dist-info}/entry_points.txt +0 -0
|
@@ -1,259 +1,259 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
"""
|
|
3
|
-
Python-specific table formatter.
|
|
4
|
-
"""
|
|
5
|
-
|
|
6
|
-
from typing import Any
|
|
7
|
-
|
|
8
|
-
from .base_formatter import BaseTableFormatter
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
class PythonTableFormatter(BaseTableFormatter):
|
|
12
|
-
"""Python言語専用のテーブルフォーマッター"""
|
|
13
|
-
|
|
14
|
-
def _format_full_table(self, data: dict[str, Any]) -> str:
|
|
15
|
-
"""Python用完全版テーブル形式"""
|
|
16
|
-
lines = []
|
|
17
|
-
|
|
18
|
-
# ヘッダー - Python用(複数クラス対応)
|
|
19
|
-
classes = data.get("classes", [])
|
|
20
|
-
if len(classes) > 1:
|
|
21
|
-
# 複数クラスがある場合はファイル名を使用
|
|
22
|
-
file_name = data.get("file_path", "Unknown").split("/")[-1].split("\\")[-1]
|
|
23
|
-
lines.append(f"# {file_name}")
|
|
24
|
-
else:
|
|
25
|
-
# 単一クラスの場合はクラス名を使用
|
|
26
|
-
class_name = classes[0].get("name", "Unknown") if classes else "Unknown"
|
|
27
|
-
lines.append(f"# {class_name}")
|
|
28
|
-
lines.append("")
|
|
29
|
-
|
|
30
|
-
# Imports
|
|
31
|
-
imports = data.get("imports", [])
|
|
32
|
-
if imports:
|
|
33
|
-
lines.append("## Imports")
|
|
34
|
-
lines.append("```python")
|
|
35
|
-
for imp in imports:
|
|
36
|
-
lines.append(str(imp.get("statement", "")))
|
|
37
|
-
lines.append("```")
|
|
38
|
-
lines.append("")
|
|
39
|
-
|
|
40
|
-
# Classes - Python用(複数クラス対応)
|
|
41
|
-
if len(classes) > 1:
|
|
42
|
-
lines.append("## Classes")
|
|
43
|
-
lines.append("| Class | Type | Visibility | Lines | Methods | Fields |")
|
|
44
|
-
lines.append("|-------|------|------------|-------|---------|--------|")
|
|
45
|
-
|
|
46
|
-
for class_info in classes:
|
|
47
|
-
name = str(class_info.get("name", "Unknown"))
|
|
48
|
-
class_type = str(class_info.get("type", "class"))
|
|
49
|
-
visibility = str(class_info.get("visibility", "public"))
|
|
50
|
-
line_range = class_info.get("line_range", {})
|
|
51
|
-
lines_str = f"{line_range.get('start', 0)}-{line_range.get('end', 0)}"
|
|
52
|
-
|
|
53
|
-
# このクラスのメソッド数とフィールド数を計算
|
|
54
|
-
class_methods = [
|
|
55
|
-
m
|
|
56
|
-
for m in data.get("methods", [])
|
|
57
|
-
if line_range.get("start", 0)
|
|
58
|
-
<= m.get("line_range", {}).get("start", 0)
|
|
59
|
-
<= line_range.get("end", 0)
|
|
60
|
-
]
|
|
61
|
-
class_fields = [
|
|
62
|
-
f
|
|
63
|
-
for f in data.get("fields", [])
|
|
64
|
-
if line_range.get("start", 0)
|
|
65
|
-
<= f.get("line_range", {}).get("start", 0)
|
|
66
|
-
<= line_range.get("end", 0)
|
|
67
|
-
]
|
|
68
|
-
|
|
69
|
-
lines.append(
|
|
70
|
-
f"| {name} | {class_type} | {visibility} | {lines_str} | {len(class_methods)} | {len(class_fields)} |"
|
|
71
|
-
)
|
|
72
|
-
else:
|
|
73
|
-
# 単一クラスの場合
|
|
74
|
-
lines.append("## Class Info")
|
|
75
|
-
lines.append("| Property | Value |")
|
|
76
|
-
lines.append("|----------|-------|")
|
|
77
|
-
|
|
78
|
-
class_info = data.get("classes", [{}])[0] if data.get("classes") else {}
|
|
79
|
-
stats = data.get("statistics") or {}
|
|
80
|
-
|
|
81
|
-
lines.append("| Package | (default) |")
|
|
82
|
-
lines.append(f"| Type | {str(class_info.get('type', 'class'))} |")
|
|
83
|
-
lines.append(
|
|
84
|
-
f"| Visibility | {str(class_info.get('visibility', 'public'))} |"
|
|
85
|
-
)
|
|
86
|
-
lines.append(
|
|
87
|
-
f"| Lines | {class_info.get('line_range', {}).get('start', 0)}-{class_info.get('line_range', {}).get('end', 0)} |"
|
|
88
|
-
)
|
|
89
|
-
lines.append(f"| Total Methods | {stats.get('method_count', 0)} |")
|
|
90
|
-
lines.append(f"| Total Fields | {stats.get('field_count', 0)} |")
|
|
91
|
-
|
|
92
|
-
lines.append("")
|
|
93
|
-
|
|
94
|
-
# Fields
|
|
95
|
-
fields = data.get("fields", [])
|
|
96
|
-
if fields:
|
|
97
|
-
lines.append("## Fields")
|
|
98
|
-
lines.append("| Name | Type | Vis | Modifiers | Line | Doc |")
|
|
99
|
-
lines.append("|------|------|-----|-----------|------|-----|")
|
|
100
|
-
|
|
101
|
-
for field in fields:
|
|
102
|
-
name = str(field.get("name", ""))
|
|
103
|
-
field_type = str(field.get("type", ""))
|
|
104
|
-
visibility = self._convert_visibility(str(field.get("visibility", "")))
|
|
105
|
-
modifiers = ",".join([str(m) for m in field.get("modifiers", [])])
|
|
106
|
-
line = field.get("line_range", {}).get("start", 0)
|
|
107
|
-
doc = str(field.get("javadoc", "")) or "-"
|
|
108
|
-
doc = doc.replace("\n", " ").replace("|", "\\|")[:50]
|
|
109
|
-
|
|
110
|
-
lines.append(
|
|
111
|
-
f"| {name} | {field_type} | {visibility} | {modifiers} | {line} | {doc} |"
|
|
112
|
-
)
|
|
113
|
-
lines.append("")
|
|
114
|
-
|
|
115
|
-
# Methods - Python用(コンストラクタ分離なし)
|
|
116
|
-
methods = data.get("methods", [])
|
|
117
|
-
if methods:
|
|
118
|
-
lines.append("## Methods")
|
|
119
|
-
lines.append("| Method | Signature | Vis | Lines | Cols | Cx | Doc |")
|
|
120
|
-
lines.append("|--------|-----------|-----|-------|------|----|----|")
|
|
121
|
-
|
|
122
|
-
for method in methods:
|
|
123
|
-
lines.append(self._format_method_row(method))
|
|
124
|
-
lines.append("")
|
|
125
|
-
|
|
126
|
-
# 末尾の空行を削除
|
|
127
|
-
while lines and lines[-1] == "":
|
|
128
|
-
lines.pop()
|
|
129
|
-
|
|
130
|
-
return "\n".join(lines)
|
|
131
|
-
|
|
132
|
-
def _format_compact_table(self, data: dict[str, Any]) -> str:
|
|
133
|
-
"""Python用コンパクト版テーブル形式"""
|
|
134
|
-
lines = []
|
|
135
|
-
|
|
136
|
-
# ヘッダー
|
|
137
|
-
classes = data.get("classes", [])
|
|
138
|
-
if len(classes) > 1:
|
|
139
|
-
file_name = data.get("file_path", "Unknown").split("/")[-1].split("\\")[-1]
|
|
140
|
-
lines.append(f"# {file_name}")
|
|
141
|
-
else:
|
|
142
|
-
class_name = classes[0].get("name", "Unknown") if classes else "Unknown"
|
|
143
|
-
lines.append(f"# {class_name}")
|
|
144
|
-
lines.append("")
|
|
145
|
-
|
|
146
|
-
# 基本情報
|
|
147
|
-
stats = data.get("statistics") or {}
|
|
148
|
-
lines.append("## Info")
|
|
149
|
-
lines.append("| Property | Value |")
|
|
150
|
-
lines.append("|----------|-------|")
|
|
151
|
-
lines.append(f"| Classes | {len(classes)} |")
|
|
152
|
-
lines.append(f"| Methods | {stats.get('method_count', 0)} |")
|
|
153
|
-
lines.append(f"| Fields | {stats.get('field_count', 0)} |")
|
|
154
|
-
lines.append("")
|
|
155
|
-
|
|
156
|
-
# メソッド(簡略版)
|
|
157
|
-
methods = data.get("methods", [])
|
|
158
|
-
if methods:
|
|
159
|
-
lines.append("## Methods")
|
|
160
|
-
lines.append("| Method | Sig | V | L | Cx | Doc |")
|
|
161
|
-
lines.append("|--------|-----|---|---|----|----|")
|
|
162
|
-
|
|
163
|
-
for method in methods:
|
|
164
|
-
name = str(method.get("name", ""))
|
|
165
|
-
signature = self._create_compact_signature(method)
|
|
166
|
-
visibility = self._convert_visibility(str(method.get("visibility", "")))
|
|
167
|
-
line_range = method.get("line_range", {})
|
|
168
|
-
lines_str = f"{line_range.get('start', 0)}-{line_range.get('end', 0)}"
|
|
169
|
-
complexity = method.get("complexity_score", 0)
|
|
170
|
-
doc = self._clean_csv_text(
|
|
171
|
-
self._extract_doc_summary(str(method.get("javadoc", "")))
|
|
172
|
-
)
|
|
173
|
-
|
|
174
|
-
lines.append(
|
|
175
|
-
f"| {name} | {signature} | {visibility} | {lines_str} | {complexity} | {doc} |"
|
|
176
|
-
)
|
|
177
|
-
lines.append("")
|
|
178
|
-
|
|
179
|
-
# 末尾の空行を削除
|
|
180
|
-
while lines and lines[-1] == "":
|
|
181
|
-
lines.pop()
|
|
182
|
-
|
|
183
|
-
return "\n".join(lines)
|
|
184
|
-
|
|
185
|
-
def _format_method_row(self, method: dict[str, Any]) -> str:
|
|
186
|
-
"""Python用メソッド行のフォーマット"""
|
|
187
|
-
name = str(method.get("name", ""))
|
|
188
|
-
signature = self._create_full_signature(method)
|
|
189
|
-
visibility = self._convert_visibility(str(method.get("visibility", "")))
|
|
190
|
-
line_range = method.get("line_range", {})
|
|
191
|
-
lines_str = f"{line_range.get('start', 0)}-{line_range.get('end', 0)}"
|
|
192
|
-
cols_str = "5-6" # デフォルト値
|
|
193
|
-
complexity = method.get("complexity_score", 0)
|
|
194
|
-
doc = self._clean_csv_text(
|
|
195
|
-
self._extract_doc_summary(str(method.get("javadoc", "")))
|
|
196
|
-
)
|
|
197
|
-
|
|
198
|
-
return f"| {name} | {signature} | {visibility} | {lines_str} | {cols_str} | {complexity} | {doc} |"
|
|
199
|
-
|
|
200
|
-
def _create_compact_signature(self, method: dict[str, Any]) -> str:
|
|
201
|
-
"""Python用コンパクトなメソッドシグネチャを作成"""
|
|
202
|
-
params = method.get("parameters", [])
|
|
203
|
-
param_types = []
|
|
204
|
-
|
|
205
|
-
for p in params:
|
|
206
|
-
if isinstance(p, dict):
|
|
207
|
-
param_types.append(self._shorten_type(p.get("type", "Any")))
|
|
208
|
-
else:
|
|
209
|
-
param_types.append("Any")
|
|
210
|
-
|
|
211
|
-
params_str = ",".join(param_types)
|
|
212
|
-
return_type = self._shorten_type(method.get("return_type", "Any"))
|
|
213
|
-
|
|
214
|
-
return f"({params_str}):{return_type}"
|
|
215
|
-
|
|
216
|
-
def _shorten_type(self, type_name: Any) -> str:
|
|
217
|
-
"""Python用型名を短縮"""
|
|
218
|
-
if type_name is None:
|
|
219
|
-
return "Any"
|
|
220
|
-
|
|
221
|
-
if not isinstance(type_name, str):
|
|
222
|
-
type_name = str(type_name)
|
|
223
|
-
|
|
224
|
-
type_mapping = {
|
|
225
|
-
"str": "s",
|
|
226
|
-
"int": "i",
|
|
227
|
-
"float": "f",
|
|
228
|
-
"bool": "b",
|
|
229
|
-
"None": "N",
|
|
230
|
-
"Any": "A",
|
|
231
|
-
"List": "L",
|
|
232
|
-
"Dict": "D",
|
|
233
|
-
"Optional": "O",
|
|
234
|
-
"Union": "U",
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
# List[str] -> L[s]
|
|
238
|
-
if "List[" in type_name:
|
|
239
|
-
result = (
|
|
240
|
-
type_name.replace("List[", "L[").replace("str", "s").replace("int", "i")
|
|
241
|
-
)
|
|
242
|
-
return str(result)
|
|
243
|
-
|
|
244
|
-
# Dict[str, int] -> D[s,i]
|
|
245
|
-
if "Dict[" in type_name:
|
|
246
|
-
result = (
|
|
247
|
-
type_name.replace("Dict[", "D[").replace("str", "s").replace("int", "i")
|
|
248
|
-
)
|
|
249
|
-
return str(result)
|
|
250
|
-
|
|
251
|
-
# Optional[str] -> O[s]
|
|
252
|
-
if "Optional[" in type_name:
|
|
253
|
-
result = type_name.replace("Optional[", "O[").replace("str", "s")
|
|
254
|
-
return str(result)
|
|
255
|
-
|
|
256
|
-
result = type_mapping.get(
|
|
257
|
-
type_name, type_name[:3] if len(type_name) > 3 else type_name
|
|
258
|
-
)
|
|
259
|
-
return str(result)
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Python-specific table formatter.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
from .base_formatter import BaseTableFormatter
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class PythonTableFormatter(BaseTableFormatter):
|
|
12
|
+
"""Python言語専用のテーブルフォーマッター"""
|
|
13
|
+
|
|
14
|
+
def _format_full_table(self, data: dict[str, Any]) -> str:
|
|
15
|
+
"""Python用完全版テーブル形式"""
|
|
16
|
+
lines = []
|
|
17
|
+
|
|
18
|
+
# ヘッダー - Python用(複数クラス対応)
|
|
19
|
+
classes = data.get("classes", [])
|
|
20
|
+
if len(classes) > 1:
|
|
21
|
+
# 複数クラスがある場合はファイル名を使用
|
|
22
|
+
file_name = data.get("file_path", "Unknown").split("/")[-1].split("\\")[-1]
|
|
23
|
+
lines.append(f"# {file_name}")
|
|
24
|
+
else:
|
|
25
|
+
# 単一クラスの場合はクラス名を使用
|
|
26
|
+
class_name = classes[0].get("name", "Unknown") if classes else "Unknown"
|
|
27
|
+
lines.append(f"# {class_name}")
|
|
28
|
+
lines.append("")
|
|
29
|
+
|
|
30
|
+
# Imports
|
|
31
|
+
imports = data.get("imports", [])
|
|
32
|
+
if imports:
|
|
33
|
+
lines.append("## Imports")
|
|
34
|
+
lines.append("```python")
|
|
35
|
+
for imp in imports:
|
|
36
|
+
lines.append(str(imp.get("statement", "")))
|
|
37
|
+
lines.append("```")
|
|
38
|
+
lines.append("")
|
|
39
|
+
|
|
40
|
+
# Classes - Python用(複数クラス対応)
|
|
41
|
+
if len(classes) > 1:
|
|
42
|
+
lines.append("## Classes")
|
|
43
|
+
lines.append("| Class | Type | Visibility | Lines | Methods | Fields |")
|
|
44
|
+
lines.append("|-------|------|------------|-------|---------|--------|")
|
|
45
|
+
|
|
46
|
+
for class_info in classes:
|
|
47
|
+
name = str(class_info.get("name", "Unknown"))
|
|
48
|
+
class_type = str(class_info.get("type", "class"))
|
|
49
|
+
visibility = str(class_info.get("visibility", "public"))
|
|
50
|
+
line_range = class_info.get("line_range", {})
|
|
51
|
+
lines_str = f"{line_range.get('start', 0)}-{line_range.get('end', 0)}"
|
|
52
|
+
|
|
53
|
+
# このクラスのメソッド数とフィールド数を計算
|
|
54
|
+
class_methods = [
|
|
55
|
+
m
|
|
56
|
+
for m in data.get("methods", [])
|
|
57
|
+
if line_range.get("start", 0)
|
|
58
|
+
<= m.get("line_range", {}).get("start", 0)
|
|
59
|
+
<= line_range.get("end", 0)
|
|
60
|
+
]
|
|
61
|
+
class_fields = [
|
|
62
|
+
f
|
|
63
|
+
for f in data.get("fields", [])
|
|
64
|
+
if line_range.get("start", 0)
|
|
65
|
+
<= f.get("line_range", {}).get("start", 0)
|
|
66
|
+
<= line_range.get("end", 0)
|
|
67
|
+
]
|
|
68
|
+
|
|
69
|
+
lines.append(
|
|
70
|
+
f"| {name} | {class_type} | {visibility} | {lines_str} | {len(class_methods)} | {len(class_fields)} |"
|
|
71
|
+
)
|
|
72
|
+
else:
|
|
73
|
+
# 単一クラスの場合
|
|
74
|
+
lines.append("## Class Info")
|
|
75
|
+
lines.append("| Property | Value |")
|
|
76
|
+
lines.append("|----------|-------|")
|
|
77
|
+
|
|
78
|
+
class_info = data.get("classes", [{}])[0] if data.get("classes") else {}
|
|
79
|
+
stats = data.get("statistics") or {}
|
|
80
|
+
|
|
81
|
+
lines.append("| Package | (default) |")
|
|
82
|
+
lines.append(f"| Type | {str(class_info.get('type', 'class'))} |")
|
|
83
|
+
lines.append(
|
|
84
|
+
f"| Visibility | {str(class_info.get('visibility', 'public'))} |"
|
|
85
|
+
)
|
|
86
|
+
lines.append(
|
|
87
|
+
f"| Lines | {class_info.get('line_range', {}).get('start', 0)}-{class_info.get('line_range', {}).get('end', 0)} |"
|
|
88
|
+
)
|
|
89
|
+
lines.append(f"| Total Methods | {stats.get('method_count', 0)} |")
|
|
90
|
+
lines.append(f"| Total Fields | {stats.get('field_count', 0)} |")
|
|
91
|
+
|
|
92
|
+
lines.append("")
|
|
93
|
+
|
|
94
|
+
# Fields
|
|
95
|
+
fields = data.get("fields", [])
|
|
96
|
+
if fields:
|
|
97
|
+
lines.append("## Fields")
|
|
98
|
+
lines.append("| Name | Type | Vis | Modifiers | Line | Doc |")
|
|
99
|
+
lines.append("|------|------|-----|-----------|------|-----|")
|
|
100
|
+
|
|
101
|
+
for field in fields:
|
|
102
|
+
name = str(field.get("name", ""))
|
|
103
|
+
field_type = str(field.get("type", ""))
|
|
104
|
+
visibility = self._convert_visibility(str(field.get("visibility", "")))
|
|
105
|
+
modifiers = ",".join([str(m) for m in field.get("modifiers", [])])
|
|
106
|
+
line = field.get("line_range", {}).get("start", 0)
|
|
107
|
+
doc = str(field.get("javadoc", "")) or "-"
|
|
108
|
+
doc = doc.replace("\n", " ").replace("|", "\\|")[:50]
|
|
109
|
+
|
|
110
|
+
lines.append(
|
|
111
|
+
f"| {name} | {field_type} | {visibility} | {modifiers} | {line} | {doc} |"
|
|
112
|
+
)
|
|
113
|
+
lines.append("")
|
|
114
|
+
|
|
115
|
+
# Methods - Python用(コンストラクタ分離なし)
|
|
116
|
+
methods = data.get("methods", [])
|
|
117
|
+
if methods:
|
|
118
|
+
lines.append("## Methods")
|
|
119
|
+
lines.append("| Method | Signature | Vis | Lines | Cols | Cx | Doc |")
|
|
120
|
+
lines.append("|--------|-----------|-----|-------|------|----|----|")
|
|
121
|
+
|
|
122
|
+
for method in methods:
|
|
123
|
+
lines.append(self._format_method_row(method))
|
|
124
|
+
lines.append("")
|
|
125
|
+
|
|
126
|
+
# 末尾の空行を削除
|
|
127
|
+
while lines and lines[-1] == "":
|
|
128
|
+
lines.pop()
|
|
129
|
+
|
|
130
|
+
return "\n".join(lines)
|
|
131
|
+
|
|
132
|
+
def _format_compact_table(self, data: dict[str, Any]) -> str:
|
|
133
|
+
"""Python用コンパクト版テーブル形式"""
|
|
134
|
+
lines = []
|
|
135
|
+
|
|
136
|
+
# ヘッダー
|
|
137
|
+
classes = data.get("classes", [])
|
|
138
|
+
if len(classes) > 1:
|
|
139
|
+
file_name = data.get("file_path", "Unknown").split("/")[-1].split("\\")[-1]
|
|
140
|
+
lines.append(f"# {file_name}")
|
|
141
|
+
else:
|
|
142
|
+
class_name = classes[0].get("name", "Unknown") if classes else "Unknown"
|
|
143
|
+
lines.append(f"# {class_name}")
|
|
144
|
+
lines.append("")
|
|
145
|
+
|
|
146
|
+
# 基本情報
|
|
147
|
+
stats = data.get("statistics") or {}
|
|
148
|
+
lines.append("## Info")
|
|
149
|
+
lines.append("| Property | Value |")
|
|
150
|
+
lines.append("|----------|-------|")
|
|
151
|
+
lines.append(f"| Classes | {len(classes)} |")
|
|
152
|
+
lines.append(f"| Methods | {stats.get('method_count', 0)} |")
|
|
153
|
+
lines.append(f"| Fields | {stats.get('field_count', 0)} |")
|
|
154
|
+
lines.append("")
|
|
155
|
+
|
|
156
|
+
# メソッド(簡略版)
|
|
157
|
+
methods = data.get("methods", [])
|
|
158
|
+
if methods:
|
|
159
|
+
lines.append("## Methods")
|
|
160
|
+
lines.append("| Method | Sig | V | L | Cx | Doc |")
|
|
161
|
+
lines.append("|--------|-----|---|---|----|----|")
|
|
162
|
+
|
|
163
|
+
for method in methods:
|
|
164
|
+
name = str(method.get("name", ""))
|
|
165
|
+
signature = self._create_compact_signature(method)
|
|
166
|
+
visibility = self._convert_visibility(str(method.get("visibility", "")))
|
|
167
|
+
line_range = method.get("line_range", {})
|
|
168
|
+
lines_str = f"{line_range.get('start', 0)}-{line_range.get('end', 0)}"
|
|
169
|
+
complexity = method.get("complexity_score", 0)
|
|
170
|
+
doc = self._clean_csv_text(
|
|
171
|
+
self._extract_doc_summary(str(method.get("javadoc", "")))
|
|
172
|
+
)
|
|
173
|
+
|
|
174
|
+
lines.append(
|
|
175
|
+
f"| {name} | {signature} | {visibility} | {lines_str} | {complexity} | {doc} |"
|
|
176
|
+
)
|
|
177
|
+
lines.append("")
|
|
178
|
+
|
|
179
|
+
# 末尾の空行を削除
|
|
180
|
+
while lines and lines[-1] == "":
|
|
181
|
+
lines.pop()
|
|
182
|
+
|
|
183
|
+
return "\n".join(lines)
|
|
184
|
+
|
|
185
|
+
def _format_method_row(self, method: dict[str, Any]) -> str:
|
|
186
|
+
"""Python用メソッド行のフォーマット"""
|
|
187
|
+
name = str(method.get("name", ""))
|
|
188
|
+
signature = self._create_full_signature(method)
|
|
189
|
+
visibility = self._convert_visibility(str(method.get("visibility", "")))
|
|
190
|
+
line_range = method.get("line_range", {})
|
|
191
|
+
lines_str = f"{line_range.get('start', 0)}-{line_range.get('end', 0)}"
|
|
192
|
+
cols_str = "5-6" # デフォルト値
|
|
193
|
+
complexity = method.get("complexity_score", 0)
|
|
194
|
+
doc = self._clean_csv_text(
|
|
195
|
+
self._extract_doc_summary(str(method.get("javadoc", "")))
|
|
196
|
+
)
|
|
197
|
+
|
|
198
|
+
return f"| {name} | {signature} | {visibility} | {lines_str} | {cols_str} | {complexity} | {doc} |"
|
|
199
|
+
|
|
200
|
+
def _create_compact_signature(self, method: dict[str, Any]) -> str:
|
|
201
|
+
"""Python用コンパクトなメソッドシグネチャを作成"""
|
|
202
|
+
params = method.get("parameters", [])
|
|
203
|
+
param_types = []
|
|
204
|
+
|
|
205
|
+
for p in params:
|
|
206
|
+
if isinstance(p, dict):
|
|
207
|
+
param_types.append(self._shorten_type(p.get("type", "Any")))
|
|
208
|
+
else:
|
|
209
|
+
param_types.append("Any")
|
|
210
|
+
|
|
211
|
+
params_str = ",".join(param_types)
|
|
212
|
+
return_type = self._shorten_type(method.get("return_type", "Any"))
|
|
213
|
+
|
|
214
|
+
return f"({params_str}):{return_type}"
|
|
215
|
+
|
|
216
|
+
def _shorten_type(self, type_name: Any) -> str:
|
|
217
|
+
"""Python用型名を短縮"""
|
|
218
|
+
if type_name is None:
|
|
219
|
+
return "Any"
|
|
220
|
+
|
|
221
|
+
if not isinstance(type_name, str):
|
|
222
|
+
type_name = str(type_name)
|
|
223
|
+
|
|
224
|
+
type_mapping = {
|
|
225
|
+
"str": "s",
|
|
226
|
+
"int": "i",
|
|
227
|
+
"float": "f",
|
|
228
|
+
"bool": "b",
|
|
229
|
+
"None": "N",
|
|
230
|
+
"Any": "A",
|
|
231
|
+
"List": "L",
|
|
232
|
+
"Dict": "D",
|
|
233
|
+
"Optional": "O",
|
|
234
|
+
"Union": "U",
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
# List[str] -> L[s]
|
|
238
|
+
if "List[" in type_name:
|
|
239
|
+
result = (
|
|
240
|
+
type_name.replace("List[", "L[").replace("str", "s").replace("int", "i")
|
|
241
|
+
)
|
|
242
|
+
return str(result)
|
|
243
|
+
|
|
244
|
+
# Dict[str, int] -> D[s,i]
|
|
245
|
+
if "Dict[" in type_name:
|
|
246
|
+
result = (
|
|
247
|
+
type_name.replace("Dict[", "D[").replace("str", "s").replace("int", "i")
|
|
248
|
+
)
|
|
249
|
+
return str(result)
|
|
250
|
+
|
|
251
|
+
# Optional[str] -> O[s]
|
|
252
|
+
if "Optional[" in type_name:
|
|
253
|
+
result = type_name.replace("Optional[", "O[").replace("str", "s")
|
|
254
|
+
return str(result)
|
|
255
|
+
|
|
256
|
+
result = type_mapping.get(
|
|
257
|
+
type_name, type_name[:3] if len(type_name) > 3 else type_name
|
|
258
|
+
)
|
|
259
|
+
return str(result)
|
|
@@ -2,14 +2,14 @@
|
|
|
2
2
|
"""
|
|
3
3
|
CLI Adapter for tree-sitter-analyzer
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
Adapter that uses the new unified analysis engine while maintaining compatibility with existing CLI API.
|
|
6
6
|
|
|
7
|
-
Roo Code
|
|
8
|
-
-
|
|
9
|
-
- MCP
|
|
7
|
+
Roo Code compliance:
|
|
8
|
+
- Type hints: Required for all functions
|
|
9
|
+
- MCP logging: Log output at each step
|
|
10
10
|
- docstring: Google Style docstring
|
|
11
|
-
-
|
|
12
|
-
-
|
|
11
|
+
- Error handling: Proper exception handling
|
|
12
|
+
- Performance: Optimization through unified engine
|
|
13
13
|
"""
|
|
14
14
|
|
|
15
15
|
import asyncio
|
|
@@ -26,17 +26,17 @@ logger = logging.getLogger(__name__)
|
|
|
26
26
|
|
|
27
27
|
class CLIAdapter:
|
|
28
28
|
"""
|
|
29
|
-
CLI
|
|
29
|
+
CLI Adapter
|
|
30
30
|
|
|
31
|
-
|
|
32
|
-
|
|
31
|
+
Uses the new unified analysis engine while maintaining compatibility with existing CLI API.
|
|
32
|
+
Provides synchronous API and internally calls asynchronous engine.
|
|
33
33
|
|
|
34
34
|
Features:
|
|
35
|
-
-
|
|
36
|
-
-
|
|
37
|
-
-
|
|
38
|
-
-
|
|
39
|
-
-
|
|
35
|
+
- Maintaining existing API compatibility
|
|
36
|
+
- Utilizing unified analysis engine
|
|
37
|
+
- Sync/async conversion
|
|
38
|
+
- Performance monitoring
|
|
39
|
+
- Error handling
|
|
40
40
|
|
|
41
41
|
Example:
|
|
42
42
|
>>> adapter = CLIAdapter()
|
|
@@ -46,10 +46,10 @@ class CLIAdapter:
|
|
|
46
46
|
|
|
47
47
|
def __init__(self) -> None:
|
|
48
48
|
"""
|
|
49
|
-
CLI
|
|
49
|
+
Initialize CLI adapter
|
|
50
50
|
|
|
51
51
|
Raises:
|
|
52
|
-
Exception:
|
|
52
|
+
Exception: If unified analysis engine initialization fails
|
|
53
53
|
"""
|
|
54
54
|
try:
|
|
55
55
|
self._engine = UnifiedAnalysisEngine()
|
|
@@ -60,26 +60,26 @@ class CLIAdapter:
|
|
|
60
60
|
|
|
61
61
|
def analyze_file(self, file_path: str, **kwargs: Any) -> AnalysisResult:
|
|
62
62
|
"""
|
|
63
|
-
|
|
63
|
+
Analyze file (synchronous version)
|
|
64
64
|
|
|
65
|
-
|
|
66
|
-
|
|
65
|
+
Provides synchronous interface to maintain compatibility with existing CLI API.
|
|
66
|
+
Internally calls asynchronous methods of unified analysis engine.
|
|
67
67
|
|
|
68
68
|
Args:
|
|
69
|
-
file_path:
|
|
70
|
-
**kwargs:
|
|
71
|
-
- language:
|
|
72
|
-
- include_complexity:
|
|
73
|
-
- include_details:
|
|
74
|
-
- format_type:
|
|
69
|
+
file_path: Path to file to analyze
|
|
70
|
+
**kwargs: Analysis options
|
|
71
|
+
- language: Language specification (auto-detection possible)
|
|
72
|
+
- include_complexity: Include complexity calculation
|
|
73
|
+
- include_details: Include detailed information
|
|
74
|
+
- format_type: Output format ("standard", "structure", "summary")
|
|
75
75
|
|
|
76
76
|
Returns:
|
|
77
|
-
AnalysisResult:
|
|
77
|
+
AnalysisResult: Analysis result
|
|
78
78
|
|
|
79
79
|
Raises:
|
|
80
|
-
ValueError:
|
|
81
|
-
FileNotFoundError:
|
|
82
|
-
UnsupportedLanguageError:
|
|
80
|
+
ValueError: For invalid file path
|
|
81
|
+
FileNotFoundError: If file does not exist
|
|
82
|
+
UnsupportedLanguageError: For unsupported language
|
|
83
83
|
|
|
84
84
|
Example:
|
|
85
85
|
>>> adapter = CLIAdapter()
|
|
@@ -88,16 +88,16 @@ class CLIAdapter:
|
|
|
88
88
|
"""
|
|
89
89
|
start_time = time.time()
|
|
90
90
|
|
|
91
|
-
#
|
|
91
|
+
# Input validation
|
|
92
92
|
if not file_path or not file_path.strip():
|
|
93
93
|
raise ValueError("File path cannot be empty")
|
|
94
94
|
|
|
95
|
-
#
|
|
95
|
+
# File existence check
|
|
96
96
|
if not Path(file_path).exists():
|
|
97
97
|
raise FileNotFoundError(f"File not found: {file_path}")
|
|
98
98
|
|
|
99
99
|
try:
|
|
100
|
-
# AnalysisRequest
|
|
100
|
+
# Create AnalysisRequest
|
|
101
101
|
request = AnalysisRequest(
|
|
102
102
|
file_path=file_path,
|
|
103
103
|
language=kwargs.get("language"),
|
|
@@ -182,12 +182,12 @@ class MCPAdapter:
|
|
|
182
182
|
|
|
183
183
|
async def analyze_with_mcp_request(
|
|
184
184
|
self, arguments: dict[str, Any]
|
|
185
|
-
) ->
|
|
185
|
+
) -> AnalysisResult:
|
|
186
186
|
"""Analyze with MCP request."""
|
|
187
187
|
if "file_path" not in arguments:
|
|
188
188
|
raise KeyError("file_path is required in MCP request")
|
|
189
189
|
result = await self.analyze_file_async(arguments["file_path"])
|
|
190
|
-
return
|
|
190
|
+
return result
|
|
191
191
|
|
|
192
192
|
|
|
193
193
|
class MCPServerAdapter:
|