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.

Files changed (78) hide show
  1. tree_sitter_analyzer/__init__.py +134 -121
  2. tree_sitter_analyzer/__main__.py +11 -12
  3. tree_sitter_analyzer/api.py +533 -539
  4. tree_sitter_analyzer/cli/__init__.py +39 -39
  5. tree_sitter_analyzer/cli/__main__.py +12 -13
  6. tree_sitter_analyzer/cli/commands/__init__.py +26 -27
  7. tree_sitter_analyzer/cli/commands/advanced_command.py +88 -88
  8. tree_sitter_analyzer/cli/commands/base_command.py +160 -155
  9. tree_sitter_analyzer/cli/commands/default_command.py +18 -19
  10. tree_sitter_analyzer/cli/commands/partial_read_command.py +141 -133
  11. tree_sitter_analyzer/cli/commands/query_command.py +81 -82
  12. tree_sitter_analyzer/cli/commands/structure_command.py +138 -121
  13. tree_sitter_analyzer/cli/commands/summary_command.py +101 -93
  14. tree_sitter_analyzer/cli/commands/table_command.py +235 -233
  15. tree_sitter_analyzer/cli/info_commands.py +120 -121
  16. tree_sitter_analyzer/cli_main.py +278 -276
  17. tree_sitter_analyzer/core/__init__.py +15 -20
  18. tree_sitter_analyzer/core/analysis_engine.py +555 -574
  19. tree_sitter_analyzer/core/cache_service.py +320 -330
  20. tree_sitter_analyzer/core/engine.py +559 -560
  21. tree_sitter_analyzer/core/parser.py +293 -288
  22. tree_sitter_analyzer/core/query.py +502 -502
  23. tree_sitter_analyzer/encoding_utils.py +456 -460
  24. tree_sitter_analyzer/exceptions.py +337 -340
  25. tree_sitter_analyzer/file_handler.py +210 -222
  26. tree_sitter_analyzer/formatters/__init__.py +1 -1
  27. tree_sitter_analyzer/formatters/base_formatter.py +167 -168
  28. tree_sitter_analyzer/formatters/formatter_factory.py +78 -74
  29. tree_sitter_analyzer/formatters/java_formatter.py +291 -270
  30. tree_sitter_analyzer/formatters/python_formatter.py +259 -235
  31. tree_sitter_analyzer/interfaces/__init__.py +9 -10
  32. tree_sitter_analyzer/interfaces/cli.py +528 -557
  33. tree_sitter_analyzer/interfaces/cli_adapter.py +343 -319
  34. tree_sitter_analyzer/interfaces/mcp_adapter.py +206 -170
  35. tree_sitter_analyzer/interfaces/mcp_server.py +405 -416
  36. tree_sitter_analyzer/java_analyzer.py +187 -219
  37. tree_sitter_analyzer/language_detector.py +398 -400
  38. tree_sitter_analyzer/language_loader.py +224 -228
  39. tree_sitter_analyzer/languages/__init__.py +10 -11
  40. tree_sitter_analyzer/languages/java_plugin.py +1174 -1113
  41. tree_sitter_analyzer/{plugins → languages}/javascript_plugin.py +446 -439
  42. tree_sitter_analyzer/languages/python_plugin.py +747 -712
  43. tree_sitter_analyzer/mcp/__init__.py +31 -32
  44. tree_sitter_analyzer/mcp/resources/__init__.py +44 -47
  45. tree_sitter_analyzer/mcp/resources/code_file_resource.py +209 -213
  46. tree_sitter_analyzer/mcp/resources/project_stats_resource.py +555 -550
  47. tree_sitter_analyzer/mcp/server.py +333 -345
  48. tree_sitter_analyzer/mcp/tools/__init__.py +30 -31
  49. tree_sitter_analyzer/mcp/tools/analyze_scale_tool.py +654 -557
  50. tree_sitter_analyzer/mcp/tools/analyze_scale_tool_cli_compatible.py +247 -245
  51. tree_sitter_analyzer/mcp/tools/base_tool.py +54 -55
  52. tree_sitter_analyzer/mcp/tools/read_partial_tool.py +300 -302
  53. tree_sitter_analyzer/mcp/tools/table_format_tool.py +362 -359
  54. tree_sitter_analyzer/mcp/tools/universal_analyze_tool.py +543 -476
  55. tree_sitter_analyzer/mcp/utils/__init__.py +107 -106
  56. tree_sitter_analyzer/mcp/utils/error_handler.py +549 -549
  57. tree_sitter_analyzer/models.py +470 -481
  58. tree_sitter_analyzer/output_manager.py +255 -264
  59. tree_sitter_analyzer/plugins/__init__.py +280 -334
  60. tree_sitter_analyzer/plugins/base.py +496 -446
  61. tree_sitter_analyzer/plugins/manager.py +379 -355
  62. tree_sitter_analyzer/queries/__init__.py +26 -27
  63. tree_sitter_analyzer/queries/java.py +391 -394
  64. tree_sitter_analyzer/queries/javascript.py +148 -149
  65. tree_sitter_analyzer/queries/python.py +285 -286
  66. tree_sitter_analyzer/queries/typescript.py +229 -230
  67. tree_sitter_analyzer/query_loader.py +257 -260
  68. tree_sitter_analyzer/table_formatter.py +471 -448
  69. tree_sitter_analyzer/utils.py +277 -277
  70. {tree_sitter_analyzer-0.2.0.dist-info → tree_sitter_analyzer-0.4.0.dist-info}/METADATA +23 -8
  71. tree_sitter_analyzer-0.4.0.dist-info/RECORD +73 -0
  72. {tree_sitter_analyzer-0.2.0.dist-info → tree_sitter_analyzer-0.4.0.dist-info}/entry_points.txt +2 -1
  73. tree_sitter_analyzer/plugins/java_plugin.py +0 -625
  74. tree_sitter_analyzer/plugins/plugin_loader.py +0 -83
  75. tree_sitter_analyzer/plugins/python_plugin.py +0 -598
  76. tree_sitter_analyzer/plugins/registry.py +0 -366
  77. tree_sitter_analyzer-0.2.0.dist-info/RECORD +0 -77
  78. {tree_sitter_analyzer-0.2.0.dist-info → tree_sitter_analyzer-0.4.0.dist-info}/WHEEL +0 -0
@@ -1,448 +1,471 @@
1
- #!/usr/bin/env python3
2
- # -*- coding: utf-8 -*-
3
- """
4
- Table Formatter for Tree-sitter Analyzer
5
-
6
- Provides table-formatted output for Java code analysis results.
7
- """
8
-
9
- import csv
10
- import io
11
- import os
12
- from typing import Any, Dict, List, Optional
13
-
14
- from .models import AnalysisResult
15
-
16
-
17
- class TableFormatter:
18
- """Table formatter for code analysis results"""
19
-
20
- def __init__(self, format_type: str = "full", language: str = "java", include_javadoc: bool = False):
21
- self.format_type = format_type
22
- self.language = language
23
- self.include_javadoc = include_javadoc
24
-
25
- def _get_platform_newline(self) -> str:
26
- """Get platform-specific newline character"""
27
- return os.linesep
28
-
29
- def _convert_to_platform_newlines(self, text: str) -> str:
30
- """Convert standard \\n to platform-specific newline characters"""
31
- if os.linesep != "\n":
32
- return text.replace("\n", os.linesep)
33
- return text
34
-
35
- def format_structure(self, structure_data: Dict[str, Any]) -> str:
36
- """Format structure data as table"""
37
- if self.format_type == "full":
38
- result = self._format_full_table(structure_data)
39
- elif self.format_type == "compact":
40
- result = self._format_compact_table(structure_data)
41
- elif self.format_type == "csv":
42
- result = self._format_csv(structure_data)
43
- else:
44
- raise ValueError(f"Unsupported format type: {self.format_type}")
45
-
46
- # Finally convert to platform-specific newline characters
47
- # Skip newline conversion for CSV format (newline control is handled within _format_csv)
48
- if self.format_type == "csv":
49
- return result
50
-
51
- return self._convert_to_platform_newlines(result)
52
-
53
- def _format_full_table(self, data: Dict[str, Any]) -> str:
54
- """Full table format"""
55
- lines = []
56
-
57
- # Header - use filename when multiple classes exist
58
- classes = data.get("classes", [])
59
- if classes is None:
60
- classes = []
61
- if len(classes) > 1:
62
- # 複数クラスがある場合はファイル名を使用
63
- file_name = data.get("file_path", "Unknown").split("/")[-1].split("\\")[-1]
64
- lines.append(f"# {file_name}")
65
- else:
66
- # 単一クラスの場合は従来通り
67
- class_name = classes[0].get("name", "Unknown") if classes else "Unknown"
68
- lines.append(
69
- f"# {(data.get('package') or {}).get('name', 'unknown')}.{class_name}"
70
- )
71
- lines.append("")
72
-
73
- # Imports
74
- imports = data.get("imports", [])
75
- if imports:
76
- lines.append("## Imports")
77
- lines.append(f"```{self.language}")
78
- for imp in imports:
79
- lines.append(str(imp.get("statement", "")))
80
- lines.append("```")
81
- lines.append("")
82
-
83
- # Class Info - 複数クラスに対応
84
- classes = data.get("classes", [])
85
- if classes is None:
86
- classes = []
87
- if len(classes) > 1:
88
- lines.append("## Classes")
89
- lines.append("| Class | Type | Visibility | Lines | Methods | Fields |")
90
- lines.append("|-------|------|------------|-------|---------|--------|")
91
-
92
- for class_info in classes:
93
- name = str(class_info.get("name", "Unknown"))
94
- class_type = str(class_info.get("type", "class"))
95
- visibility = str(class_info.get("visibility", "public"))
96
- line_range = class_info.get("line_range", {})
97
- lines_str = f"{line_range.get('start', 0)}-{line_range.get('end', 0)}"
98
-
99
- # このクラスのメソッド数とフィールド数を計算
100
- class_methods = [m for m in data.get("methods", [])
101
- if line_range.get('start', 0) <= m.get('line_range', {}).get('start', 0) <= line_range.get('end', 0)]
102
- class_fields = [f for f in data.get("fields", [])
103
- if line_range.get('start', 0) <= f.get('line_range', {}).get('start', 0) <= line_range.get('end', 0)]
104
-
105
- lines.append(f"| {name} | {class_type} | {visibility} | {lines_str} | {len(class_methods)} | {len(class_fields)} |")
106
- else:
107
- # 単一クラスの場合は従来通り
108
- lines.append("## Class Info")
109
- lines.append("| Property | Value |")
110
- lines.append("|----------|-------|")
111
-
112
- package_name = (data.get("package") or {}).get("name", "unknown")
113
- class_info = data.get("classes", [{}])[0] if data.get("classes") else {}
114
- stats = data.get("statistics") or {}
115
-
116
- lines.append(f"| Package | {package_name} |")
117
- lines.append(f"| Type | {str(class_info.get('type', 'class'))} |")
118
- lines.append(f"| Visibility | {str(class_info.get('visibility', 'public'))} |")
119
- lines.append(
120
- f"| Lines | {class_info.get('line_range', {}).get('start', 0)}-{class_info.get('line_range', {}).get('end', 0)} |"
121
- )
122
- lines.append(f"| Total Methods | {stats.get('method_count', 0)} |")
123
- lines.append(f"| Total Fields | {stats.get('field_count', 0)} |")
124
-
125
- lines.append("")
126
-
127
- # Fields
128
- fields = data.get("fields", [])
129
- if fields is None:
130
- fields = []
131
- if fields:
132
- lines.append("## Fields")
133
- lines.append("| Name | Type | Vis | Modifiers | Line | Doc |")
134
- lines.append("|------|------|-----|-----------|------|-----|")
135
-
136
- for field in fields:
137
- name = str(field.get("name", ""))
138
- field_type = str(field.get("type", ""))
139
- visibility = self._convert_visibility(str(field.get("visibility", "")))
140
- modifiers = ",".join([str(m) for m in field.get("modifiers", [])])
141
- line = field.get("line_range", {}).get("start", 0)
142
- if self.include_javadoc:
143
- doc = self._extract_doc_summary(str(field.get("javadoc", "")))
144
- else:
145
- doc = "-"
146
-
147
- lines.append(
148
- f"| {name} | {field_type} | {visibility} | {modifiers} | {line} | {doc} |"
149
- )
150
- lines.append("")
151
-
152
- # Constructor
153
- constructors = [
154
- m for m in (data.get("methods") or []) if m.get("is_constructor", False)
155
- ]
156
- if constructors:
157
- lines.append("## Constructor")
158
- lines.append("| Method | Signature | Vis | Lines | Cols | Cx | Doc |")
159
- lines.append("|--------|-----------|-----|-------|------|----|----|")
160
-
161
- for method in constructors:
162
- lines.append(self._format_method_row(method))
163
- lines.append("")
164
-
165
- # Public Methods
166
- public_methods = [
167
- m
168
- for m in (data.get("methods") or [])
169
- if not m.get("is_constructor", False)
170
- and str(m.get("visibility")) == "public"
171
- ]
172
- if public_methods:
173
- lines.append("## Public Methods")
174
- lines.append("| Method | Signature | Vis | Lines | Cols | Cx | Doc |")
175
- lines.append("|--------|-----------|-----|-------|------|----|----|")
176
-
177
- for method in public_methods:
178
- lines.append(self._format_method_row(method))
179
- lines.append("")
180
-
181
- # Private Methods
182
- private_methods = [
183
- m
184
- for m in (data.get("methods") or [])
185
- if not m.get("is_constructor", False)
186
- and str(m.get("visibility")) == "private"
187
- ]
188
- if private_methods:
189
- lines.append("## Private Methods")
190
- lines.append("| Method | Signature | Vis | Lines | Cols | Cx | Doc |")
191
- lines.append("|--------|-----------|-----|-------|------|----|----|")
192
-
193
- for method in private_methods:
194
- lines.append(self._format_method_row(method))
195
- lines.append("")
196
-
197
- # 末尾の空行を削除
198
- while lines and lines[-1] == "":
199
- lines.pop()
200
-
201
- return "\n".join(lines)
202
-
203
- def _format_compact_table(self, data: Dict[str, Any]) -> str:
204
- """Compact table format"""
205
- lines = []
206
-
207
- # Header
208
- package_name = (data.get("package") or {}).get("name", "unknown")
209
- classes = data.get("classes", [])
210
- if classes is None:
211
- classes = []
212
- class_name = classes[0].get("name", "Unknown") if classes else "Unknown"
213
- lines.append(f"# {package_name}.{class_name}")
214
- lines.append("")
215
-
216
- # 基本情報
217
- stats = data.get("statistics") or {}
218
- lines.append("## Info")
219
- lines.append("| Property | Value |")
220
- lines.append("|----------|-------|")
221
- lines.append(f"| Package | {package_name} |")
222
- lines.append(f"| Methods | {stats.get('method_count', 0)} |")
223
- lines.append(f"| Fields | {stats.get('field_count', 0)} |")
224
- lines.append("")
225
-
226
- # メソッド(簡略版)
227
- methods = data.get("methods", [])
228
- if methods is None:
229
- methods = []
230
- if methods:
231
- lines.append("## Methods")
232
- lines.append("| Method | Sig | V | L | Cx | Doc |")
233
- lines.append("|--------|-----|---|---|----|----|")
234
-
235
- for method in methods:
236
- name = str(method.get("name", ""))
237
- signature = self._create_compact_signature(method)
238
- visibility = self._convert_visibility(str(method.get("visibility", "")))
239
- line_range = method.get("line_range", {})
240
- lines_str = f"{line_range.get('start', 0)}-{line_range.get('end', 0)}"
241
- complexity = method.get("complexity_score", 0)
242
- doc = self._clean_csv_text(
243
- self._extract_doc_summary(str(method.get("javadoc", "")))
244
- )
245
-
246
- lines.append(
247
- f"| {name} | {signature} | {visibility} | {lines_str} | {complexity} | {doc} |"
248
- )
249
- lines.append("")
250
-
251
- # 末尾の空行を削除
252
- while lines and lines[-1] == "":
253
- lines.pop()
254
-
255
- return "\n".join(lines)
256
-
257
- def _format_csv(self, data: Dict[str, Any]) -> str:
258
- """CSV format"""
259
- output = io.StringIO()
260
- writer = csv.writer(output, lineterminator="\n") # Explicitly specify newline character
261
-
262
- # Header
263
- writer.writerow(
264
- ["Type", "Name", "Signature", "Visibility", "Lines", "Complexity", "Doc"]
265
- )
266
-
267
- # フィールド
268
- for field in data.get("fields", []):
269
- writer.writerow(
270
- [
271
- "Field",
272
- str(field.get("name", "")),
273
- f"{str(field.get('name', ''))}:{str(field.get('type', ''))}",
274
- str(field.get("visibility", "")),
275
- f"{field.get('line_range', {}).get('start', 0)}-{field.get('line_range', {}).get('end', 0)}",
276
- "",
277
- self._clean_csv_text(
278
- self._extract_doc_summary(str(field.get("javadoc", "")))
279
- ),
280
- ]
281
- )
282
-
283
- # メソッド
284
- for method in data.get("methods", []):
285
- writer.writerow(
286
- [
287
- "Constructor" if method.get("is_constructor", False) else "Method",
288
- str(method.get("name", "")),
289
- self._clean_csv_text(self._create_full_signature(method)),
290
- str(method.get("visibility", "")),
291
- f"{method.get('line_range', {}).get('start', 0)}-{method.get('line_range', {}).get('end', 0)}",
292
- method.get("complexity_score", 0),
293
- self._clean_csv_text(
294
- self._extract_doc_summary(str(method.get("javadoc", "")))
295
- ),
296
- ]
297
- )
298
-
299
- # Completely control CSV output newlines
300
- csv_content = output.getvalue()
301
- # Unify all newline patterns and remove trailing newlines
302
- csv_content = csv_content.replace("\r\n", "\n").replace("\r", "\n")
303
- csv_content = csv_content.rstrip("\n")
304
- output.close()
305
-
306
- return csv_content
307
-
308
- def _format_method_row(self, method: Dict[str, Any]) -> str:
309
- """メソッド行のフォーマット"""
310
- name = str(method.get("name", ""))
311
- signature = self._create_full_signature(method)
312
- visibility = self._convert_visibility(str(method.get("visibility", "")))
313
- line_range = method.get("line_range", {})
314
- lines_str = f"{line_range.get('start', 0)}-{line_range.get('end', 0)}"
315
- cols_str = "5-6" # デフォルト値(実際の実装では正確な値を取得)
316
- complexity = method.get("complexity_score", 0)
317
- if self.include_javadoc:
318
- doc = self._clean_csv_text(
319
- self._extract_doc_summary(str(method.get("javadoc", "")))
320
- )
321
- else:
322
- doc = "-"
323
-
324
- return f"| {name} | {signature} | {visibility} | {lines_str} | {cols_str} | {complexity} | {doc} |"
325
-
326
- def _create_full_signature(self, method: Dict[str, Any]) -> str:
327
- """完全なメソッドシグネチャを作成"""
328
- params = method.get("parameters", [])
329
- param_strs = []
330
- for param in params:
331
- param_type = str(param.get("type", "Object"))
332
- param_name = str(param.get("name", "param"))
333
- param_strs.append(f"{param_name}:{param_type}")
334
-
335
- params_str = ", ".join(param_strs)
336
- return_type = str(method.get("return_type", "void"))
337
-
338
- modifiers = []
339
- if method.get("is_static", False):
340
- modifiers.append("[static]")
341
-
342
- modifier_str = " ".join(modifiers)
343
- signature = f"({params_str}):{return_type}"
344
-
345
- if modifier_str:
346
- signature += f" {modifier_str}"
347
-
348
- return signature
349
-
350
- def _create_compact_signature(self, method: Dict[str, Any]) -> str:
351
- """コンパクトなメソッドシグネチャを作成"""
352
- params = method.get("parameters", [])
353
- param_types = [self._shorten_type(p.get("type", "O")) for p in params]
354
- params_str = ",".join(param_types)
355
- return_type = self._shorten_type(method.get("return_type", "void"))
356
-
357
- return f"({params_str}):{return_type}"
358
-
359
- def _shorten_type(self, type_name: Any) -> str:
360
- """型名を短縮"""
361
- if type_name is None:
362
- return "O"
363
-
364
- # Convert non-string types to string
365
- if not isinstance(type_name, str):
366
- type_name = str(type_name)
367
-
368
- type_mapping = {
369
- "String": "S",
370
- "int": "i",
371
- "long": "l",
372
- "double": "d",
373
- "boolean": "b",
374
- "void": "void",
375
- "Object": "O",
376
- "Exception": "E",
377
- "SQLException": "SE",
378
- "IllegalArgumentException": "IAE",
379
- "RuntimeException": "RE",
380
- }
381
-
382
- # Map<String,Object> -> M<S,O>
383
- if "Map<" in type_name:
384
- return (
385
- type_name.replace("Map<", "M<")
386
- .replace("String", "S")
387
- .replace("Object", "O")
388
- )
389
-
390
- # List<String> -> L<S>
391
- if "List<" in type_name:
392
- return type_name.replace("List<", "L<").replace("String", "S")
393
-
394
- # String[] -> S[]
395
- if "[]" in type_name:
396
- base_type = type_name.replace("[]", "")
397
- if base_type:
398
- return type_mapping.get(base_type, base_type[0].upper()) + "[]"
399
- else:
400
- return "O[]"
401
-
402
- return type_mapping.get(type_name, type_name)
403
-
404
- def _convert_visibility(self, visibility: str) -> str:
405
- """可視性を記号に変換"""
406
- mapping = {"public": "+", "private": "-", "protected": "#", "package": "~"}
407
- return mapping.get(visibility, visibility)
408
-
409
- def _extract_doc_summary(self, javadoc: str) -> str:
410
- """JavaDocから要約を抽出"""
411
- if not javadoc:
412
- return "-"
413
-
414
- # Remove comment symbols
415
- clean_doc = (
416
- javadoc.replace("/**", "").replace("*/", "").replace("*", "").strip()
417
- )
418
-
419
- # Get first line (use standard \\n only)
420
- lines = clean_doc.split("\n")
421
- first_line = lines[0].strip()
422
-
423
- # Truncate if too long
424
- if len(first_line) > 50:
425
- first_line = first_line[:47] + "..."
426
-
427
- # Escape characters that cause problems in Markdown tables (use standard \\n only)
428
- return first_line.replace("|", "\\|").replace("\n", " ")
429
-
430
- def _clean_csv_text(self, text: str) -> str:
431
- """Text cleaning for CSV format"""
432
- if not text:
433
- return ""
434
-
435
- # Replace all newline characters with spaces
436
- cleaned = text.replace("\r\n", " ").replace("\r", " ").replace("\n", " ")
437
- # Convert consecutive spaces to single space
438
- cleaned = " ".join(cleaned.split())
439
- # Escape characters that cause problems in CSV
440
- cleaned = cleaned.replace('"', '""') # Escape double quotes
441
-
442
- return cleaned
443
-
444
-
445
- def create_table_formatter(format_type: str, language: str = "java", include_javadoc: bool = False):
446
- """Create table formatter (using new factory)"""
447
- # Create TableFormatter directly (for JavaDoc support)
448
- return TableFormatter(format_type, language, include_javadoc)
1
+ #!/usr/bin/env python3
2
+ """
3
+ Table Formatter for Tree-sitter Analyzer
4
+
5
+ Provides table-formatted output for Java code analysis results.
6
+ """
7
+
8
+ import csv
9
+ import io
10
+ import os
11
+ from typing import Any
12
+
13
+
14
+ class TableFormatter:
15
+ """Table formatter for code analysis results"""
16
+
17
+ def __init__(
18
+ self,
19
+ format_type: str = "full",
20
+ language: str = "java",
21
+ include_javadoc: bool = False,
22
+ ):
23
+ self.format_type = format_type
24
+ self.language = language
25
+ self.include_javadoc = include_javadoc
26
+
27
+ def _get_platform_newline(self) -> str:
28
+ """Get platform-specific newline character"""
29
+ return os.linesep
30
+
31
+ def _convert_to_platform_newlines(self, text: str) -> str:
32
+ """Convert standard \\n to platform-specific newline characters"""
33
+ if os.linesep != "\n":
34
+ return text.replace("\n", os.linesep)
35
+ return text
36
+
37
+ def format_structure(self, structure_data: dict[str, Any]) -> str:
38
+ """Format structure data as table"""
39
+ if self.format_type == "full":
40
+ result = self._format_full_table(structure_data)
41
+ elif self.format_type == "compact":
42
+ result = self._format_compact_table(structure_data)
43
+ elif self.format_type == "csv":
44
+ result = self._format_csv(structure_data)
45
+ else:
46
+ raise ValueError(f"Unsupported format type: {self.format_type}")
47
+
48
+ # Finally convert to platform-specific newline characters
49
+ # Skip newline conversion for CSV format (newline control is handled within _format_csv)
50
+ if self.format_type == "csv":
51
+ return result
52
+
53
+ return self._convert_to_platform_newlines(result)
54
+
55
+ def _format_full_table(self, data: dict[str, Any]) -> str:
56
+ """Full table format"""
57
+ lines = []
58
+
59
+ # Header - use filename when multiple classes exist
60
+ classes = data.get("classes", [])
61
+ if classes is None:
62
+ classes = []
63
+ if len(classes) > 1:
64
+ # 複数クラスがある場合はファイル名を使用
65
+ file_name = data.get("file_path", "Unknown").split("/")[-1].split("\\")[-1]
66
+ lines.append(f"# {file_name}")
67
+ else:
68
+ # 単一クラスの場合は従来通り
69
+ class_name = classes[0].get("name", "Unknown") if classes else "Unknown"
70
+ lines.append(
71
+ f"# {(data.get('package') or {}).get('name', 'unknown')}.{class_name}"
72
+ )
73
+ lines.append("")
74
+
75
+ # Imports
76
+ imports = data.get("imports", [])
77
+ if imports:
78
+ lines.append("## Imports")
79
+ lines.append(f"```{self.language}")
80
+ for imp in imports:
81
+ lines.append(str(imp.get("statement", "")))
82
+ lines.append("```")
83
+ lines.append("")
84
+
85
+ # Class Info - 複数クラスに対応
86
+ classes = data.get("classes", [])
87
+ if classes is None:
88
+ classes = []
89
+ if len(classes) > 1:
90
+ lines.append("## Classes")
91
+ lines.append("| Class | Type | Visibility | Lines | Methods | Fields |")
92
+ lines.append("|-------|------|------------|-------|---------|--------|")
93
+
94
+ for class_info in classes:
95
+ name = str(class_info.get("name", "Unknown"))
96
+ class_type = str(class_info.get("type", "class"))
97
+ visibility = str(class_info.get("visibility", "public"))
98
+ line_range = class_info.get("line_range", {})
99
+ lines_str = f"{line_range.get('start', 0)}-{line_range.get('end', 0)}"
100
+
101
+ # このクラスのメソッド数とフィールド数を計算
102
+ class_methods = [
103
+ m
104
+ for m in data.get("methods", [])
105
+ if line_range.get("start", 0)
106
+ <= m.get("line_range", {}).get("start", 0)
107
+ <= line_range.get("end", 0)
108
+ ]
109
+ class_fields = [
110
+ f
111
+ for f in data.get("fields", [])
112
+ if line_range.get("start", 0)
113
+ <= f.get("line_range", {}).get("start", 0)
114
+ <= line_range.get("end", 0)
115
+ ]
116
+
117
+ lines.append(
118
+ f"| {name} | {class_type} | {visibility} | {lines_str} | {len(class_methods)} | {len(class_fields)} |"
119
+ )
120
+ else:
121
+ # 単一クラスの場合は従来通り
122
+ lines.append("## Class Info")
123
+ lines.append("| Property | Value |")
124
+ lines.append("|----------|-------|")
125
+
126
+ package_name = (data.get("package") or {}).get("name", "unknown")
127
+ class_info = data.get("classes", [{}])[0] if data.get("classes") else {}
128
+ stats = data.get("statistics") or {}
129
+
130
+ lines.append(f"| Package | {package_name} |")
131
+ lines.append(f"| Type | {str(class_info.get('type', 'class'))} |")
132
+ lines.append(
133
+ f"| Visibility | {str(class_info.get('visibility', 'public'))} |"
134
+ )
135
+ lines.append(
136
+ f"| Lines | {class_info.get('line_range', {}).get('start', 0)}-{class_info.get('line_range', {}).get('end', 0)} |"
137
+ )
138
+ lines.append(f"| Total Methods | {stats.get('method_count', 0)} |")
139
+ lines.append(f"| Total Fields | {stats.get('field_count', 0)} |")
140
+
141
+ lines.append("")
142
+
143
+ # Fields
144
+ fields = data.get("fields", [])
145
+ if fields is None:
146
+ fields = []
147
+ if fields:
148
+ lines.append("## Fields")
149
+ lines.append("| Name | Type | Vis | Modifiers | Line | Doc |")
150
+ lines.append("|------|------|-----|-----------|------|-----|")
151
+
152
+ for field in fields:
153
+ name = str(field.get("name", ""))
154
+ field_type = str(field.get("type", ""))
155
+ visibility = self._convert_visibility(str(field.get("visibility", "")))
156
+ modifiers = ",".join([str(m) for m in field.get("modifiers", [])])
157
+ line = field.get("line_range", {}).get("start", 0)
158
+ if self.include_javadoc:
159
+ doc = self._extract_doc_summary(str(field.get("javadoc", "")))
160
+ else:
161
+ doc = "-"
162
+
163
+ lines.append(
164
+ f"| {name} | {field_type} | {visibility} | {modifiers} | {line} | {doc} |"
165
+ )
166
+ lines.append("")
167
+
168
+ # Constructor
169
+ constructors = [
170
+ m for m in (data.get("methods") or []) if m.get("is_constructor", False)
171
+ ]
172
+ if constructors:
173
+ lines.append("## Constructor")
174
+ lines.append("| Method | Signature | Vis | Lines | Cols | Cx | Doc |")
175
+ lines.append("|--------|-----------|-----|-------|------|----|----|")
176
+
177
+ for method in constructors:
178
+ lines.append(self._format_method_row(method))
179
+ lines.append("")
180
+
181
+ # Public Methods
182
+ public_methods = [
183
+ m
184
+ for m in (data.get("methods") or [])
185
+ if not m.get("is_constructor", False)
186
+ and str(m.get("visibility")) == "public"
187
+ ]
188
+ if public_methods:
189
+ lines.append("## Public Methods")
190
+ lines.append("| Method | Signature | Vis | Lines | Cols | Cx | Doc |")
191
+ lines.append("|--------|-----------|-----|-------|------|----|----|")
192
+
193
+ for method in public_methods:
194
+ lines.append(self._format_method_row(method))
195
+ lines.append("")
196
+
197
+ # Private Methods
198
+ private_methods = [
199
+ m
200
+ for m in (data.get("methods") or [])
201
+ if not m.get("is_constructor", False)
202
+ and str(m.get("visibility")) == "private"
203
+ ]
204
+ if private_methods:
205
+ lines.append("## Private Methods")
206
+ lines.append("| Method | Signature | Vis | Lines | Cols | Cx | Doc |")
207
+ lines.append("|--------|-----------|-----|-------|------|----|----|")
208
+
209
+ for method in private_methods:
210
+ lines.append(self._format_method_row(method))
211
+ lines.append("")
212
+
213
+ # 末尾の空行を削除
214
+ while lines and lines[-1] == "":
215
+ lines.pop()
216
+
217
+ return "\n".join(lines)
218
+
219
+ def _format_compact_table(self, data: dict[str, Any]) -> str:
220
+ """Compact table format"""
221
+ lines = []
222
+
223
+ # Header
224
+ package_name = (data.get("package") or {}).get("name", "unknown")
225
+ classes = data.get("classes", [])
226
+ if classes is None:
227
+ classes = []
228
+ class_name = classes[0].get("name", "Unknown") if classes else "Unknown"
229
+ lines.append(f"# {package_name}.{class_name}")
230
+ lines.append("")
231
+
232
+ # 基本情報
233
+ stats = data.get("statistics") or {}
234
+ lines.append("## Info")
235
+ lines.append("| Property | Value |")
236
+ lines.append("|----------|-------|")
237
+ lines.append(f"| Package | {package_name} |")
238
+ lines.append(f"| Methods | {stats.get('method_count', 0)} |")
239
+ lines.append(f"| Fields | {stats.get('field_count', 0)} |")
240
+ lines.append("")
241
+
242
+ # メソッド(簡略版)
243
+ methods = data.get("methods", [])
244
+ if methods is None:
245
+ methods = []
246
+ if methods:
247
+ lines.append("## Methods")
248
+ lines.append("| Method | Sig | V | L | Cx | Doc |")
249
+ lines.append("|--------|-----|---|---|----|----|")
250
+
251
+ for method in methods:
252
+ name = str(method.get("name", ""))
253
+ signature = self._create_compact_signature(method)
254
+ visibility = self._convert_visibility(str(method.get("visibility", "")))
255
+ line_range = method.get("line_range", {})
256
+ lines_str = f"{line_range.get('start', 0)}-{line_range.get('end', 0)}"
257
+ complexity = method.get("complexity_score", 0)
258
+ doc = self._clean_csv_text(
259
+ self._extract_doc_summary(str(method.get("javadoc", "")))
260
+ )
261
+
262
+ lines.append(
263
+ f"| {name} | {signature} | {visibility} | {lines_str} | {complexity} | {doc} |"
264
+ )
265
+ lines.append("")
266
+
267
+ # 末尾の空行を削除
268
+ while lines and lines[-1] == "":
269
+ lines.pop()
270
+
271
+ return "\n".join(lines)
272
+
273
+ def _format_csv(self, data: dict[str, Any]) -> str:
274
+ """CSV format"""
275
+ output = io.StringIO()
276
+ writer = csv.writer(
277
+ output, lineterminator="\n"
278
+ ) # Explicitly specify newline character
279
+
280
+ # Header
281
+ writer.writerow(
282
+ ["Type", "Name", "Signature", "Visibility", "Lines", "Complexity", "Doc"]
283
+ )
284
+
285
+ # フィールド
286
+ for field in data.get("fields", []):
287
+ writer.writerow(
288
+ [
289
+ "Field",
290
+ str(field.get("name", "")),
291
+ f"{str(field.get('name', ''))}:{str(field.get('type', ''))}",
292
+ str(field.get("visibility", "")),
293
+ f"{field.get('line_range', {}).get('start', 0)}-{field.get('line_range', {}).get('end', 0)}",
294
+ "",
295
+ self._clean_csv_text(
296
+ self._extract_doc_summary(str(field.get("javadoc", "")))
297
+ ),
298
+ ]
299
+ )
300
+
301
+ # メソッド
302
+ for method in data.get("methods", []):
303
+ writer.writerow(
304
+ [
305
+ "Constructor" if method.get("is_constructor", False) else "Method",
306
+ str(method.get("name", "")),
307
+ self._clean_csv_text(self._create_full_signature(method)),
308
+ str(method.get("visibility", "")),
309
+ f"{method.get('line_range', {}).get('start', 0)}-{method.get('line_range', {}).get('end', 0)}",
310
+ method.get("complexity_score", 0),
311
+ self._clean_csv_text(
312
+ self._extract_doc_summary(str(method.get("javadoc", "")))
313
+ ),
314
+ ]
315
+ )
316
+
317
+ # Completely control CSV output newlines
318
+ csv_content = output.getvalue()
319
+ # Unify all newline patterns and remove trailing newlines
320
+ csv_content = csv_content.replace("\r\n", "\n").replace("\r", "\n")
321
+ csv_content = csv_content.rstrip("\n")
322
+ output.close()
323
+
324
+ return csv_content
325
+
326
+ def _format_method_row(self, method: dict[str, Any]) -> str:
327
+ """メソッド行のフォーマット"""
328
+ name = str(method.get("name", ""))
329
+ signature = self._create_full_signature(method)
330
+ visibility = self._convert_visibility(str(method.get("visibility", "")))
331
+ line_range = method.get("line_range", {})
332
+ lines_str = f"{line_range.get('start', 0)}-{line_range.get('end', 0)}"
333
+ cols_str = "5-6" # デフォルト値(実際の実装では正確な値を取得)
334
+ complexity = method.get("complexity_score", 0)
335
+ if self.include_javadoc:
336
+ doc = self._clean_csv_text(
337
+ self._extract_doc_summary(str(method.get("javadoc", "")))
338
+ )
339
+ else:
340
+ doc = "-"
341
+
342
+ return f"| {name} | {signature} | {visibility} | {lines_str} | {cols_str} | {complexity} | {doc} |"
343
+
344
+ def _create_full_signature(self, method: dict[str, Any]) -> str:
345
+ """完全なメソッドシグネチャを作成"""
346
+ params = method.get("parameters", [])
347
+ param_strs = []
348
+ for param in params:
349
+ param_type = str(param.get("type", "Object"))
350
+ param_name = str(param.get("name", "param"))
351
+ param_strs.append(f"{param_name}:{param_type}")
352
+
353
+ params_str = ", ".join(param_strs)
354
+ return_type = str(method.get("return_type", "void"))
355
+
356
+ modifiers = []
357
+ if method.get("is_static", False):
358
+ modifiers.append("[static]")
359
+
360
+ modifier_str = " ".join(modifiers)
361
+ signature = f"({params_str}):{return_type}"
362
+
363
+ if modifier_str:
364
+ signature += f" {modifier_str}"
365
+
366
+ return signature
367
+
368
+ def _create_compact_signature(self, method: dict[str, Any]) -> str:
369
+ """コンパクトなメソッドシグネチャを作成"""
370
+ params = method.get("parameters", [])
371
+ param_types = [self._shorten_type(p.get("type", "O")) for p in params]
372
+ params_str = ",".join(param_types)
373
+ return_type = self._shorten_type(method.get("return_type", "void"))
374
+
375
+ return f"({params_str}):{return_type}"
376
+
377
+ def _shorten_type(self, type_name: Any) -> str:
378
+ """型名を短縮"""
379
+ if type_name is None:
380
+ return "O"
381
+
382
+ # Convert non-string types to string
383
+ if not isinstance(type_name, str):
384
+ type_name = str(type_name)
385
+
386
+ # At this point, type_name is guaranteed to be a string
387
+ assert isinstance(type_name, str)
388
+
389
+ type_mapping = {
390
+ "String": "S",
391
+ "int": "i",
392
+ "long": "l",
393
+ "double": "d",
394
+ "boolean": "b",
395
+ "void": "void",
396
+ "Object": "O",
397
+ "Exception": "E",
398
+ "SQLException": "SE",
399
+ "IllegalArgumentException": "IAE",
400
+ "RuntimeException": "RE",
401
+ }
402
+
403
+ # Map<String,Object> -> M<S,O>
404
+ if "Map<" in type_name:
405
+ return (
406
+ type_name.replace("Map<", "M<")
407
+ .replace("String", "S")
408
+ .replace("Object", "O")
409
+ )
410
+
411
+ # List<String> -> L<S>
412
+ if "List<" in type_name:
413
+ return type_name.replace("List<", "L<").replace("String", "S")
414
+
415
+ # String[] -> S[]
416
+ if "[]" in type_name:
417
+ base_type = type_name.replace("[]", "")
418
+ if base_type:
419
+ return type_mapping.get(base_type, base_type[0].upper()) + "[]"
420
+ else:
421
+ return "O[]"
422
+
423
+ return type_mapping.get(type_name, type_name)
424
+
425
+ def _convert_visibility(self, visibility: str) -> str:
426
+ """可視性を記号に変換"""
427
+ mapping = {"public": "+", "private": "-", "protected": "#", "package": "~"}
428
+ return mapping.get(visibility, visibility)
429
+
430
+ def _extract_doc_summary(self, javadoc: str) -> str:
431
+ """JavaDocから要約を抽出"""
432
+ if not javadoc:
433
+ return "-"
434
+
435
+ # Remove comment symbols
436
+ clean_doc = (
437
+ javadoc.replace("/**", "").replace("*/", "").replace("*", "").strip()
438
+ )
439
+
440
+ # Get first line (use standard \\n only)
441
+ lines = clean_doc.split("\n")
442
+ first_line = lines[0].strip()
443
+
444
+ # Truncate if too long
445
+ if len(first_line) > 50:
446
+ first_line = first_line[:47] + "..."
447
+
448
+ # Escape characters that cause problems in Markdown tables (use standard \\n only)
449
+ return first_line.replace("|", "\\|").replace("\n", " ")
450
+
451
+ def _clean_csv_text(self, text: str) -> str:
452
+ """Text cleaning for CSV format"""
453
+ if not text:
454
+ return ""
455
+
456
+ # Replace all newline characters with spaces
457
+ cleaned = text.replace("\r\n", " ").replace("\r", " ").replace("\n", " ")
458
+ # Convert consecutive spaces to single space
459
+ cleaned = " ".join(cleaned.split())
460
+ # Escape characters that cause problems in CSV
461
+ cleaned = cleaned.replace('"', '""') # Escape double quotes
462
+
463
+ return cleaned
464
+
465
+
466
+ def create_table_formatter(
467
+ format_type: str, language: str = "java", include_javadoc: bool = False
468
+ ) -> "TableFormatter":
469
+ """Create table formatter (using new factory)"""
470
+ # Create TableFormatter directly (for JavaDoc support)
471
+ return TableFormatter(format_type, language, include_javadoc)