tree-sitter-analyzer 1.9.17.1__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.
Files changed (149) hide show
  1. tree_sitter_analyzer/__init__.py +132 -0
  2. tree_sitter_analyzer/__main__.py +11 -0
  3. tree_sitter_analyzer/api.py +853 -0
  4. tree_sitter_analyzer/cli/__init__.py +39 -0
  5. tree_sitter_analyzer/cli/__main__.py +12 -0
  6. tree_sitter_analyzer/cli/argument_validator.py +89 -0
  7. tree_sitter_analyzer/cli/commands/__init__.py +26 -0
  8. tree_sitter_analyzer/cli/commands/advanced_command.py +226 -0
  9. tree_sitter_analyzer/cli/commands/base_command.py +181 -0
  10. tree_sitter_analyzer/cli/commands/default_command.py +18 -0
  11. tree_sitter_analyzer/cli/commands/find_and_grep_cli.py +188 -0
  12. tree_sitter_analyzer/cli/commands/list_files_cli.py +133 -0
  13. tree_sitter_analyzer/cli/commands/partial_read_command.py +139 -0
  14. tree_sitter_analyzer/cli/commands/query_command.py +109 -0
  15. tree_sitter_analyzer/cli/commands/search_content_cli.py +161 -0
  16. tree_sitter_analyzer/cli/commands/structure_command.py +156 -0
  17. tree_sitter_analyzer/cli/commands/summary_command.py +116 -0
  18. tree_sitter_analyzer/cli/commands/table_command.py +414 -0
  19. tree_sitter_analyzer/cli/info_commands.py +124 -0
  20. tree_sitter_analyzer/cli_main.py +472 -0
  21. tree_sitter_analyzer/constants.py +85 -0
  22. tree_sitter_analyzer/core/__init__.py +15 -0
  23. tree_sitter_analyzer/core/analysis_engine.py +580 -0
  24. tree_sitter_analyzer/core/cache_service.py +333 -0
  25. tree_sitter_analyzer/core/engine.py +585 -0
  26. tree_sitter_analyzer/core/parser.py +293 -0
  27. tree_sitter_analyzer/core/query.py +605 -0
  28. tree_sitter_analyzer/core/query_filter.py +200 -0
  29. tree_sitter_analyzer/core/query_service.py +340 -0
  30. tree_sitter_analyzer/encoding_utils.py +530 -0
  31. tree_sitter_analyzer/exceptions.py +747 -0
  32. tree_sitter_analyzer/file_handler.py +246 -0
  33. tree_sitter_analyzer/formatters/__init__.py +1 -0
  34. tree_sitter_analyzer/formatters/base_formatter.py +201 -0
  35. tree_sitter_analyzer/formatters/csharp_formatter.py +367 -0
  36. tree_sitter_analyzer/formatters/formatter_config.py +197 -0
  37. tree_sitter_analyzer/formatters/formatter_factory.py +84 -0
  38. tree_sitter_analyzer/formatters/formatter_registry.py +377 -0
  39. tree_sitter_analyzer/formatters/formatter_selector.py +96 -0
  40. tree_sitter_analyzer/formatters/go_formatter.py +368 -0
  41. tree_sitter_analyzer/formatters/html_formatter.py +498 -0
  42. tree_sitter_analyzer/formatters/java_formatter.py +423 -0
  43. tree_sitter_analyzer/formatters/javascript_formatter.py +611 -0
  44. tree_sitter_analyzer/formatters/kotlin_formatter.py +268 -0
  45. tree_sitter_analyzer/formatters/language_formatter_factory.py +123 -0
  46. tree_sitter_analyzer/formatters/legacy_formatter_adapters.py +228 -0
  47. tree_sitter_analyzer/formatters/markdown_formatter.py +725 -0
  48. tree_sitter_analyzer/formatters/php_formatter.py +301 -0
  49. tree_sitter_analyzer/formatters/python_formatter.py +830 -0
  50. tree_sitter_analyzer/formatters/ruby_formatter.py +278 -0
  51. tree_sitter_analyzer/formatters/rust_formatter.py +233 -0
  52. tree_sitter_analyzer/formatters/sql_formatter_wrapper.py +689 -0
  53. tree_sitter_analyzer/formatters/sql_formatters.py +536 -0
  54. tree_sitter_analyzer/formatters/typescript_formatter.py +543 -0
  55. tree_sitter_analyzer/formatters/yaml_formatter.py +462 -0
  56. tree_sitter_analyzer/interfaces/__init__.py +9 -0
  57. tree_sitter_analyzer/interfaces/cli.py +535 -0
  58. tree_sitter_analyzer/interfaces/cli_adapter.py +359 -0
  59. tree_sitter_analyzer/interfaces/mcp_adapter.py +224 -0
  60. tree_sitter_analyzer/interfaces/mcp_server.py +428 -0
  61. tree_sitter_analyzer/language_detector.py +553 -0
  62. tree_sitter_analyzer/language_loader.py +271 -0
  63. tree_sitter_analyzer/languages/__init__.py +10 -0
  64. tree_sitter_analyzer/languages/csharp_plugin.py +1076 -0
  65. tree_sitter_analyzer/languages/css_plugin.py +449 -0
  66. tree_sitter_analyzer/languages/go_plugin.py +836 -0
  67. tree_sitter_analyzer/languages/html_plugin.py +496 -0
  68. tree_sitter_analyzer/languages/java_plugin.py +1299 -0
  69. tree_sitter_analyzer/languages/javascript_plugin.py +1622 -0
  70. tree_sitter_analyzer/languages/kotlin_plugin.py +656 -0
  71. tree_sitter_analyzer/languages/markdown_plugin.py +1928 -0
  72. tree_sitter_analyzer/languages/php_plugin.py +862 -0
  73. tree_sitter_analyzer/languages/python_plugin.py +1636 -0
  74. tree_sitter_analyzer/languages/ruby_plugin.py +757 -0
  75. tree_sitter_analyzer/languages/rust_plugin.py +673 -0
  76. tree_sitter_analyzer/languages/sql_plugin.py +2444 -0
  77. tree_sitter_analyzer/languages/typescript_plugin.py +1892 -0
  78. tree_sitter_analyzer/languages/yaml_plugin.py +695 -0
  79. tree_sitter_analyzer/legacy_table_formatter.py +860 -0
  80. tree_sitter_analyzer/mcp/__init__.py +34 -0
  81. tree_sitter_analyzer/mcp/resources/__init__.py +43 -0
  82. tree_sitter_analyzer/mcp/resources/code_file_resource.py +208 -0
  83. tree_sitter_analyzer/mcp/resources/project_stats_resource.py +586 -0
  84. tree_sitter_analyzer/mcp/server.py +869 -0
  85. tree_sitter_analyzer/mcp/tools/__init__.py +28 -0
  86. tree_sitter_analyzer/mcp/tools/analyze_scale_tool.py +779 -0
  87. tree_sitter_analyzer/mcp/tools/analyze_scale_tool_cli_compatible.py +291 -0
  88. tree_sitter_analyzer/mcp/tools/base_tool.py +139 -0
  89. tree_sitter_analyzer/mcp/tools/fd_rg_utils.py +816 -0
  90. tree_sitter_analyzer/mcp/tools/find_and_grep_tool.py +686 -0
  91. tree_sitter_analyzer/mcp/tools/list_files_tool.py +413 -0
  92. tree_sitter_analyzer/mcp/tools/output_format_validator.py +148 -0
  93. tree_sitter_analyzer/mcp/tools/query_tool.py +443 -0
  94. tree_sitter_analyzer/mcp/tools/read_partial_tool.py +464 -0
  95. tree_sitter_analyzer/mcp/tools/search_content_tool.py +836 -0
  96. tree_sitter_analyzer/mcp/tools/table_format_tool.py +572 -0
  97. tree_sitter_analyzer/mcp/tools/universal_analyze_tool.py +653 -0
  98. tree_sitter_analyzer/mcp/utils/__init__.py +113 -0
  99. tree_sitter_analyzer/mcp/utils/error_handler.py +569 -0
  100. tree_sitter_analyzer/mcp/utils/file_output_factory.py +217 -0
  101. tree_sitter_analyzer/mcp/utils/file_output_manager.py +322 -0
  102. tree_sitter_analyzer/mcp/utils/gitignore_detector.py +358 -0
  103. tree_sitter_analyzer/mcp/utils/path_resolver.py +414 -0
  104. tree_sitter_analyzer/mcp/utils/search_cache.py +343 -0
  105. tree_sitter_analyzer/models.py +840 -0
  106. tree_sitter_analyzer/mypy_current_errors.txt +2 -0
  107. tree_sitter_analyzer/output_manager.py +255 -0
  108. tree_sitter_analyzer/platform_compat/__init__.py +3 -0
  109. tree_sitter_analyzer/platform_compat/adapter.py +324 -0
  110. tree_sitter_analyzer/platform_compat/compare.py +224 -0
  111. tree_sitter_analyzer/platform_compat/detector.py +67 -0
  112. tree_sitter_analyzer/platform_compat/fixtures.py +228 -0
  113. tree_sitter_analyzer/platform_compat/profiles.py +217 -0
  114. tree_sitter_analyzer/platform_compat/record.py +55 -0
  115. tree_sitter_analyzer/platform_compat/recorder.py +155 -0
  116. tree_sitter_analyzer/platform_compat/report.py +92 -0
  117. tree_sitter_analyzer/plugins/__init__.py +280 -0
  118. tree_sitter_analyzer/plugins/base.py +647 -0
  119. tree_sitter_analyzer/plugins/manager.py +384 -0
  120. tree_sitter_analyzer/project_detector.py +328 -0
  121. tree_sitter_analyzer/queries/__init__.py +27 -0
  122. tree_sitter_analyzer/queries/csharp.py +216 -0
  123. tree_sitter_analyzer/queries/css.py +615 -0
  124. tree_sitter_analyzer/queries/go.py +275 -0
  125. tree_sitter_analyzer/queries/html.py +543 -0
  126. tree_sitter_analyzer/queries/java.py +402 -0
  127. tree_sitter_analyzer/queries/javascript.py +724 -0
  128. tree_sitter_analyzer/queries/kotlin.py +192 -0
  129. tree_sitter_analyzer/queries/markdown.py +258 -0
  130. tree_sitter_analyzer/queries/php.py +95 -0
  131. tree_sitter_analyzer/queries/python.py +859 -0
  132. tree_sitter_analyzer/queries/ruby.py +92 -0
  133. tree_sitter_analyzer/queries/rust.py +223 -0
  134. tree_sitter_analyzer/queries/sql.py +555 -0
  135. tree_sitter_analyzer/queries/typescript.py +871 -0
  136. tree_sitter_analyzer/queries/yaml.py +236 -0
  137. tree_sitter_analyzer/query_loader.py +272 -0
  138. tree_sitter_analyzer/security/__init__.py +22 -0
  139. tree_sitter_analyzer/security/boundary_manager.py +277 -0
  140. tree_sitter_analyzer/security/regex_checker.py +297 -0
  141. tree_sitter_analyzer/security/validator.py +599 -0
  142. tree_sitter_analyzer/table_formatter.py +782 -0
  143. tree_sitter_analyzer/utils/__init__.py +53 -0
  144. tree_sitter_analyzer/utils/logging.py +433 -0
  145. tree_sitter_analyzer/utils/tree_sitter_compat.py +289 -0
  146. tree_sitter_analyzer-1.9.17.1.dist-info/METADATA +485 -0
  147. tree_sitter_analyzer-1.9.17.1.dist-info/RECORD +149 -0
  148. tree_sitter_analyzer-1.9.17.1.dist-info/WHEEL +4 -0
  149. tree_sitter_analyzer-1.9.17.1.dist-info/entry_points.txt +25 -0
@@ -0,0 +1,782 @@
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 json
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
+ import os
30
+
31
+ return "\r\n" if os.name == "nt" else "\n" # Windows uses \r\n, others use \n
32
+
33
+ def _convert_to_platform_newlines(self, text: str) -> str:
34
+ """Convert standard \\n to platform-specific newline characters"""
35
+ platform_newline = self._get_platform_newline()
36
+ if platform_newline != "\n":
37
+ return text.replace("\n", platform_newline)
38
+ return text
39
+
40
+ def format_structure(self, structure_data: dict[str, Any]) -> str:
41
+ """Format structure data as table"""
42
+ if self.format_type == "full":
43
+ result = self._format_full_table(structure_data)
44
+ elif self.format_type == "compact":
45
+ result = self._format_compact_table(structure_data)
46
+ elif self.format_type == "csv":
47
+ result = self._format_csv(structure_data)
48
+ elif self.format_type == "json":
49
+ result = self._format_json(structure_data)
50
+ else:
51
+ raise ValueError(f"Unsupported format type: {self.format_type}")
52
+
53
+ # Finally convert to platform-specific newline characters
54
+ # Skip newline conversion for CSV and JSON formats
55
+ if self.format_type in ["csv", "json"]:
56
+ return result
57
+
58
+ return self._convert_to_platform_newlines(result)
59
+
60
+ def _format_full_table(self, data: dict[str, Any]) -> str:
61
+ """Full table format - organized by class"""
62
+ lines = []
63
+
64
+ # Header - use language-specific title generation
65
+ title = self._generate_title(data)
66
+ lines.append(f"# {title}")
67
+ lines.append("")
68
+
69
+ # Get classes for later use
70
+ classes = data.get("classes", [])
71
+ if classes is None:
72
+ classes = []
73
+
74
+ # Package info
75
+ package_name = (data.get("package") or {}).get("name", "unknown")
76
+ if package_name and package_name != "unknown":
77
+ lines.append("## Package")
78
+ lines.append(f"`{package_name}`")
79
+ lines.append("")
80
+
81
+ # Imports
82
+ imports = data.get("imports", [])
83
+ if imports:
84
+ lines.append("## Imports")
85
+ lines.append(f"```{self.language}")
86
+ for imp in imports:
87
+ lines.append(str(imp.get("statement", "")))
88
+ lines.append("```")
89
+ lines.append("")
90
+
91
+ # Class Info section (for single class files or empty data)
92
+ if len(classes) == 1 or len(classes) == 0:
93
+ lines.append("## Class Info")
94
+ lines.append("| Property | Value |")
95
+ lines.append("|----------|-------|")
96
+
97
+ # Re-use package_name from above instead of re-fetching
98
+
99
+ if len(classes) == 1:
100
+ class_info = classes[0]
101
+ lines.append(f"| Package | {package_name} |")
102
+ lines.append(f"| Type | {str(class_info.get('type', 'class'))} |")
103
+ lines.append(
104
+ f"| Visibility | {str(class_info.get('visibility', 'public'))} |"
105
+ )
106
+
107
+ # Lines
108
+ line_range = class_info.get("line_range", {})
109
+ lines_str = f"{line_range.get('start', 1)}-{line_range.get('end', 50)}"
110
+ lines.append(f"| Lines | {lines_str} |")
111
+ else:
112
+ # Empty data case
113
+ lines.append(f"| Package | {package_name} |")
114
+ lines.append("| Type | class |")
115
+ lines.append("| Visibility | public |")
116
+ lines.append("| Lines | 0-0 |")
117
+
118
+ # Count methods and fields
119
+ all_methods = data.get("methods", []) or []
120
+ all_fields = data.get("fields", []) or []
121
+ lines.append(f"| Total Methods | {len(all_methods)} |")
122
+ lines.append(f"| Total Fields | {len(all_fields)} |")
123
+ lines.append("")
124
+
125
+ # Classes Overview
126
+ if len(classes) > 1:
127
+ lines.append("## Classes Overview")
128
+ lines.append("| Class | Type | Visibility | Lines | Methods | Fields |")
129
+ lines.append("|-------|------|------------|-------|---------|--------|")
130
+
131
+ for class_info in classes:
132
+ name = str(class_info.get("name", "Unknown"))
133
+ class_type = str(class_info.get("type", "class"))
134
+ visibility = str(class_info.get("visibility", "public"))
135
+ line_range = class_info.get("line_range", {})
136
+ lines_str = f"{line_range.get('start', 0)}-{line_range.get('end', 0)}"
137
+
138
+ # Calculate method and field counts for this class
139
+ class_methods = self._get_class_methods(data, line_range)
140
+ class_fields = self._get_class_fields(data, line_range)
141
+
142
+ lines.append(
143
+ f"| {name} | {class_type} | {visibility} | {lines_str} | {len(class_methods)} | {len(class_fields)} |"
144
+ )
145
+ lines.append("")
146
+
147
+ # Detailed class information - organized by class
148
+ for class_info in classes:
149
+ lines.extend(self._format_class_details(class_info, data))
150
+
151
+ # Remove trailing empty lines
152
+ while lines and lines[-1] == "":
153
+ lines.pop()
154
+
155
+ return "\n".join(lines)
156
+
157
+ def _get_class_methods(
158
+ self, data: dict[str, Any], class_line_range: dict[str, int]
159
+ ) -> list[dict[str, Any]]:
160
+ """Get methods that belong to a specific class based on line range, excluding nested classes."""
161
+ methods = data.get("methods", [])
162
+ classes = data.get("classes", [])
163
+ class_methods = []
164
+
165
+ # Get nested class ranges to exclude their methods
166
+ nested_class_ranges = []
167
+ for cls in classes:
168
+ cls_range = cls.get("line_range", {})
169
+ cls_start = cls_range.get("start", 0)
170
+ cls_end = cls_range.get("end", 0)
171
+
172
+ # If this class is nested within the current class range
173
+ if class_line_range.get(
174
+ "start", 0
175
+ ) < cls_start and cls_end < class_line_range.get("end", 0):
176
+ nested_class_ranges.append((cls_start, cls_end))
177
+
178
+ for method in methods:
179
+ method_line = method.get("line_range", {}).get("start", 0)
180
+
181
+ # Check if method is within the class range
182
+ if (
183
+ class_line_range.get("start", 0)
184
+ <= method_line
185
+ <= class_line_range.get("end", 0)
186
+ ):
187
+ # Check if method is NOT within any nested class
188
+ in_nested_class = False
189
+ for nested_start, nested_end in nested_class_ranges:
190
+ if nested_start <= method_line <= nested_end:
191
+ in_nested_class = True
192
+ break
193
+
194
+ if not in_nested_class:
195
+ class_methods.append(method)
196
+
197
+ return class_methods
198
+
199
+ def _get_class_fields(
200
+ self, data: dict[str, Any], class_line_range: dict[str, int]
201
+ ) -> list[dict[str, Any]]:
202
+ """Get fields that belong to a specific class based on line range, excluding nested classes."""
203
+ fields = data.get("fields", [])
204
+ classes = data.get("classes", [])
205
+ class_fields = []
206
+
207
+ # Get nested class ranges to exclude their fields
208
+ nested_class_ranges = []
209
+ for cls in classes:
210
+ cls_range = cls.get("line_range", {})
211
+ cls_start = cls_range.get("start", 0)
212
+ cls_end = cls_range.get("end", 0)
213
+
214
+ # If this class is nested within the current class range
215
+ if class_line_range.get(
216
+ "start", 0
217
+ ) < cls_start and cls_end < class_line_range.get("end", 0):
218
+ nested_class_ranges.append((cls_start, cls_end))
219
+
220
+ for field in fields:
221
+ field_line = field.get("line_range", {}).get("start", 0)
222
+
223
+ # Check if field is within the class range
224
+ if (
225
+ class_line_range.get("start", 0)
226
+ <= field_line
227
+ <= class_line_range.get("end", 0)
228
+ ):
229
+ # Check if field is NOT within any nested class
230
+ in_nested_class = False
231
+ for nested_start, nested_end in nested_class_ranges:
232
+ if nested_start <= field_line <= nested_end:
233
+ in_nested_class = True
234
+ break
235
+
236
+ if not in_nested_class:
237
+ class_fields.append(field)
238
+
239
+ return class_fields
240
+
241
+ def _format_class_details(
242
+ self, class_info: dict[str, Any], data: dict[str, Any]
243
+ ) -> list[str]:
244
+ """Format detailed information for a single class."""
245
+ lines = []
246
+
247
+ name = str(class_info.get("name", "Unknown"))
248
+ line_range = class_info.get("line_range", {})
249
+ lines_str = f"{line_range.get('start', 0)}-{line_range.get('end', 0)}"
250
+
251
+ # Class header
252
+ lines.append(f"## {name} ({lines_str})")
253
+
254
+ # Get class-specific methods and fields
255
+ class_methods = self._get_class_methods(data, line_range)
256
+ class_fields = self._get_class_fields(data, line_range)
257
+
258
+ # Fields section
259
+ if class_fields:
260
+ lines.append("### Fields")
261
+ lines.append("| Name | Type | Vis | Modifiers | Line | Doc |")
262
+ lines.append("|------|------|-----|-----------|------|-----|")
263
+
264
+ for field in class_fields:
265
+ name_field = str(field.get("name", ""))
266
+ type_field = str(field.get("type", ""))
267
+ visibility = self._convert_visibility(str(field.get("visibility", "")))
268
+ modifiers = ",".join(field.get("modifiers", []))
269
+ line_num = field.get("line_range", {}).get("start", 0)
270
+ doc = (
271
+ self._extract_doc_summary(str(field.get("javadoc", "")))
272
+ if self.include_javadoc
273
+ else "-"
274
+ )
275
+
276
+ lines.append(
277
+ f"| {name_field} | {type_field} | {visibility} | {modifiers} | {line_num} | {doc} |"
278
+ )
279
+ lines.append("")
280
+
281
+ # Methods section - separate by type
282
+ constructors = [m for m in class_methods if m.get("is_constructor", False)]
283
+ regular_methods = [
284
+ m for m in class_methods if not m.get("is_constructor", False)
285
+ ]
286
+
287
+ # Constructors
288
+ if constructors:
289
+ lines.append("### Constructors")
290
+ lines.append("| Constructor | Signature | Vis | Lines | Cx | Doc |")
291
+ lines.append("|-------------|-----------|-----|-------|----|----|")
292
+
293
+ for method in constructors:
294
+ lines.append(self._format_method_row_detailed(method))
295
+ lines.append("")
296
+
297
+ # Methods grouped by visibility
298
+ public_methods = [
299
+ m for m in regular_methods if m.get("visibility", "") == "public"
300
+ ]
301
+ protected_methods = [
302
+ m for m in regular_methods if m.get("visibility", "") == "protected"
303
+ ]
304
+ package_methods = [
305
+ m for m in regular_methods if m.get("visibility", "") == "package"
306
+ ]
307
+ private_methods = [
308
+ m for m in regular_methods if m.get("visibility", "") == "private"
309
+ ]
310
+
311
+ for method_group, title in [
312
+ (public_methods, "Public Methods"),
313
+ (protected_methods, "Protected Methods"),
314
+ (package_methods, "Package Methods"),
315
+ (private_methods, "Private Methods"),
316
+ ]:
317
+ if method_group:
318
+ lines.append(f"### {title}")
319
+ lines.append("| Method | Signature | Vis | Lines | Cx | Doc |")
320
+ lines.append("|--------|-----------|-----|-------|----|----|")
321
+
322
+ for method in method_group:
323
+ lines.append(self._format_method_row_detailed(method))
324
+ lines.append("")
325
+
326
+ return lines
327
+
328
+ def _format_method_row_detailed(self, method: dict[str, Any]) -> str:
329
+ """Format method row for detailed class view."""
330
+ name = str(method.get("name", ""))
331
+ signature = self._create_full_signature(method)
332
+ visibility = self._convert_visibility(str(method.get("visibility", "")))
333
+ line_range = method.get("line_range", {})
334
+ lines_str = f"{line_range.get('start', 0)}-{line_range.get('end', 0)}"
335
+ complexity = method.get("complexity_score", 0)
336
+ doc = (
337
+ self._extract_doc_summary(str(method.get("javadoc", "")))
338
+ if self.include_javadoc
339
+ else "-"
340
+ )
341
+
342
+ return f"| {name} | {signature} | {visibility} | {lines_str} | {complexity} | {doc} |"
343
+
344
+ def _format_traditional_sections(self, data: dict[str, Any]) -> list[str]:
345
+ """Format traditional sections when no classes are found."""
346
+ lines = []
347
+
348
+ # Traditional class info
349
+ lines.append("## Class Info")
350
+ lines.append("| Property | Value |")
351
+ lines.append("|----------|-------|")
352
+
353
+ package_name = (data.get("package") or {}).get("name", "unknown")
354
+ class_info = data.get("classes", [{}])[0] if data.get("classes") else {}
355
+ stats = data.get("statistics") or {}
356
+
357
+ lines.append(f"| Package | {package_name} |")
358
+ lines.append(f"| Type | {str(class_info.get('type', 'class'))} |")
359
+ lines.append(f"| Visibility | {str(class_info.get('visibility', 'public'))} |")
360
+ lines.append(
361
+ f"| Lines | {class_info.get('line_range', {}).get('start', 0)}-{class_info.get('line_range', {}).get('end', 0)} |"
362
+ )
363
+ lines.append(f"| Total Methods | {stats.get('method_count', 0)} |")
364
+ lines.append(f"| Total Fields | {stats.get('field_count', 0)} |")
365
+ lines.append("")
366
+
367
+ # Fields
368
+ fields = data.get("fields", [])
369
+ if fields:
370
+ lines.append("## Fields")
371
+ lines.append("| Name | Type | Vis | Modifiers | Line | Doc |")
372
+ lines.append("|------|------|-----|-----------|------|-----|")
373
+
374
+ for field in fields:
375
+ name = str(field.get("name", ""))
376
+ field_type = str(field.get("type", ""))
377
+ visibility = self._convert_visibility(str(field.get("visibility", "")))
378
+ modifiers = ",".join([str(m) for m in field.get("modifiers", [])])
379
+ line = field.get("line_range", {}).get("start", 0)
380
+ doc = (
381
+ self._extract_doc_summary(str(field.get("javadoc", "")))
382
+ if self.include_javadoc
383
+ else "-"
384
+ )
385
+
386
+ lines.append(
387
+ f"| {name} | {field_type} | {visibility} | {modifiers} | {line} | {doc} |"
388
+ )
389
+ lines.append("")
390
+
391
+ # Methods by type
392
+ methods = data.get("methods", [])
393
+ constructors = [m for m in methods if m.get("is_constructor", False)]
394
+ regular_methods = [m for m in methods if not m.get("is_constructor", False)]
395
+
396
+ # Constructors
397
+ if constructors:
398
+ lines.append("## Constructor")
399
+ lines.append("| Method | Signature | Vis | Lines | Cols | Cx | Doc |")
400
+ lines.append("|--------|-----------|-----|-------|------|----|----|")
401
+ for method in constructors:
402
+ lines.append(self._format_method_row(method))
403
+ lines.append("")
404
+
405
+ # Methods by visibility
406
+ for visibility, title in [
407
+ ("public", "Public Methods"),
408
+ ("private", "Private Methods"),
409
+ ]:
410
+ visibility_methods = [
411
+ m for m in regular_methods if str(m.get("visibility")) == visibility
412
+ ]
413
+ if visibility_methods:
414
+ lines.append(f"## {title}")
415
+ lines.append("| Method | Signature | Vis | Lines | Cols | Cx | Doc |")
416
+ lines.append("|--------|-----------|-----|-------|------|----|----|")
417
+ for method in visibility_methods:
418
+ lines.append(self._format_method_row(method))
419
+ lines.append("")
420
+
421
+ return lines
422
+
423
+ def _generate_title(self, data: dict[str, Any]) -> str:
424
+ """
425
+ Generate document title based on language and structure.
426
+
427
+ Args:
428
+ data: Analysis result dictionary containing classes, package, file_path
429
+
430
+ Returns:
431
+ Formatted title string (without leading "# ")
432
+ """
433
+ language = self.language.lower()
434
+ package_name = (data.get("package") or {}).get("name", "")
435
+ classes = data.get("classes", []) or []
436
+ file_path = data.get("file_path", "")
437
+
438
+ # Extract filename without extension
439
+ filename = self._extract_filename(file_path)
440
+
441
+ if language == "java":
442
+ return self._generate_java_title(package_name, classes, filename)
443
+ elif language == "python":
444
+ return self._generate_python_title(filename)
445
+ elif language in ["javascript", "typescript", "js", "ts"]:
446
+ return self._generate_js_ts_title(classes, filename)
447
+ else:
448
+ # Default fallback
449
+ return self._generate_default_title(package_name, classes, filename)
450
+
451
+ def _generate_java_title(
452
+ self, package_name: str, classes: list, filename: str
453
+ ) -> str:
454
+ """Generate title for Java files."""
455
+ if len(classes) == 1:
456
+ # Single class: use package.ClassName format
457
+ class_name = classes[0].get("name", "Unknown")
458
+ if package_name and package_name != "unknown":
459
+ return f"{package_name}.{class_name}"
460
+ return class_name
461
+ else:
462
+ # Multiple classes or no classes: use package.filename format
463
+ if package_name and package_name != "unknown":
464
+ return f"{package_name}.{filename}"
465
+ return filename
466
+
467
+ def _generate_python_title(self, filename: str) -> str:
468
+ """Generate title for Python files."""
469
+ return f"Module: {filename}"
470
+
471
+ def _generate_js_ts_title(self, classes: list, filename: str) -> str:
472
+ """Generate title for JavaScript/TypeScript files."""
473
+ if classes:
474
+ # Use primary (first) class name
475
+ return classes[0].get("name", filename)
476
+ return filename
477
+
478
+ def _generate_default_title(
479
+ self, package_name: str, classes: list, filename: str
480
+ ) -> str:
481
+ """Generate default title for unsupported languages."""
482
+ if len(classes) == 1:
483
+ class_name = classes[0].get("name", "Unknown")
484
+ if package_name and package_name != "unknown":
485
+ return f"{package_name}.{class_name}"
486
+ return class_name
487
+ return filename
488
+
489
+ def _extract_filename(self, file_path: str) -> str:
490
+ """Extract filename without extension from file path."""
491
+ if not file_path or file_path == "Unknown":
492
+ return "unknown"
493
+
494
+ # Get basename
495
+ filename = file_path.split("/")[-1].split("\\")[-1]
496
+
497
+ # Remove common extensions
498
+ for ext in [".java", ".py", ".js", ".ts", ".tsx", ".jsx"]:
499
+ if filename.endswith(ext):
500
+ filename = filename[: -len(ext)]
501
+ break
502
+
503
+ return filename or "unknown"
504
+
505
+ def _format_compact_table(self, data: dict[str, Any]) -> str:
506
+ """Compact table format"""
507
+ lines = []
508
+
509
+ # Header - use language-specific title generation
510
+ title = self._generate_title(data)
511
+ lines.append(f"# {title}")
512
+ lines.append("")
513
+
514
+ # Basic information
515
+ stats = data.get("statistics") or {}
516
+ package_name = (data.get("package") or {}).get("name", "unknown")
517
+ lines.append("## Info")
518
+ lines.append("| Property | Value |")
519
+ lines.append("|----------|-------|")
520
+ lines.append(f"| Package | {package_name} |")
521
+ lines.append(f"| Methods | {stats.get('method_count', 0)} |")
522
+ lines.append(f"| Fields | {stats.get('field_count', 0)} |")
523
+ lines.append("")
524
+
525
+ # Methods (simplified version)
526
+ methods = data.get("methods", [])
527
+ if methods is None:
528
+ methods = []
529
+ if methods:
530
+ lines.append("## Methods")
531
+ lines.append("| Method | Sig | V | L | Cx | Doc |")
532
+ lines.append("|--------|-----|---|---|----|----|")
533
+
534
+ for method in methods:
535
+ name = str(method.get("name", ""))
536
+ signature = self._create_compact_signature(method)
537
+ visibility = self._convert_visibility(str(method.get("visibility", "")))
538
+ line_range = method.get("line_range", {})
539
+ lines_str = f"{line_range.get('start', 0)}-{line_range.get('end', 0)}"
540
+ complexity = method.get("complexity_score", 0)
541
+ doc = self._clean_csv_text(
542
+ self._extract_doc_summary(str(method.get("javadoc", "")))
543
+ )
544
+
545
+ lines.append(
546
+ f"| {name} | {signature} | {visibility} | {lines_str} | {complexity} | {doc} |"
547
+ )
548
+ lines.append("")
549
+
550
+ # Remove trailing empty lines
551
+ while lines and lines[-1] == "":
552
+ lines.pop()
553
+
554
+ return "\n".join(lines)
555
+
556
+ def _format_csv(self, data: dict[str, Any]) -> str:
557
+ """CSV format"""
558
+ output = io.StringIO()
559
+ writer = csv.writer(
560
+ output, lineterminator="\n"
561
+ ) # Explicitly specify newline character
562
+
563
+ # Header
564
+ writer.writerow(
565
+ ["Type", "Name", "Signature", "Visibility", "Lines", "Complexity", "Doc"]
566
+ )
567
+
568
+ # Fields
569
+ for field in data.get("fields", []):
570
+ writer.writerow(
571
+ [
572
+ "Field",
573
+ str(field.get("name", "")),
574
+ f"{str(field.get('name', ''))}:{str(field.get('type', ''))}",
575
+ str(field.get("visibility", "")),
576
+ f"{field.get('line_range', {}).get('start', 0)}-{field.get('line_range', {}).get('end', 0)}",
577
+ "",
578
+ self._clean_csv_text(
579
+ self._extract_doc_summary(str(field.get("javadoc", "")))
580
+ ),
581
+ ]
582
+ )
583
+
584
+ # Methods
585
+ for method in data.get("methods", []):
586
+ writer.writerow(
587
+ [
588
+ "Constructor" if method.get("is_constructor", False) else "Method",
589
+ str(method.get("name", "")),
590
+ self._clean_csv_text(self._create_full_signature(method)),
591
+ str(method.get("visibility", "")),
592
+ f"{method.get('line_range', {}).get('start', 0)}-{method.get('line_range', {}).get('end', 0)}",
593
+ method.get("complexity_score", 0),
594
+ self._clean_csv_text(
595
+ self._extract_doc_summary(str(method.get("javadoc", "")))
596
+ ),
597
+ ]
598
+ )
599
+
600
+ # Completely control CSV output newlines
601
+ csv_content = output.getvalue()
602
+ # Unify all newline patterns and remove trailing newlines
603
+ csv_content = csv_content.replace("\r\n", "\n").replace("\r", "\n")
604
+ csv_content = csv_content.rstrip("\n")
605
+ output.close()
606
+
607
+ return csv_content
608
+
609
+ def _format_json(self, data: dict[str, Any]) -> str:
610
+ """JSON format"""
611
+ # Create a clean JSON structure with all the analysis data
612
+ json_data = {
613
+ "file_info": {
614
+ "file_path": data.get("file_path", ""),
615
+ "language": data.get("language", ""),
616
+ "package": data.get("package"),
617
+ },
618
+ "statistics": data.get("statistics", {}),
619
+ "elements": {
620
+ "classes": data.get("classes", []),
621
+ "methods": data.get("methods", []),
622
+ "fields": data.get("fields", []),
623
+ "imports": data.get("imports", []),
624
+ },
625
+ }
626
+
627
+ # Return formatted JSON with proper indentation
628
+ return json.dumps(json_data, indent=2, ensure_ascii=False)
629
+
630
+ def _format_method_row(self, method: dict[str, Any]) -> str:
631
+ """Format method row"""
632
+ name = str(method.get("name", ""))
633
+ signature = self._create_full_signature(method)
634
+ visibility = self._convert_visibility(str(method.get("visibility", "")))
635
+ line_range = method.get("line_range", {})
636
+ lines_str = f"{line_range.get('start', 0)}-{line_range.get('end', 0)}"
637
+ cols_str = (
638
+ "5-6" # Default value (actual implementation should get accurate values)
639
+ )
640
+ complexity = method.get("complexity_score", 0)
641
+ if self.include_javadoc:
642
+ doc = self._clean_csv_text(
643
+ self._extract_doc_summary(str(method.get("javadoc", "")))
644
+ )
645
+ else:
646
+ doc = "-"
647
+
648
+ return f"| {name} | {signature} | {visibility} | {lines_str} | {cols_str} | {complexity} | {doc} |"
649
+
650
+ def _create_full_signature(self, method: dict[str, Any]) -> str:
651
+ """Create complete method signature"""
652
+ params = method.get("parameters", [])
653
+ param_strs = []
654
+ for param in params:
655
+ param_type = str(param.get("type", "Object"))
656
+ param_name = str(param.get("name", "param"))
657
+ param_strs.append(f"{param_name}:{param_type}")
658
+
659
+ params_str = ", ".join(param_strs)
660
+ return_type = str(method.get("return_type", "void"))
661
+
662
+ modifiers = []
663
+ if method.get("is_static", False):
664
+ modifiers.append("[static]")
665
+
666
+ modifier_str = " ".join(modifiers)
667
+ signature = f"({params_str}):{return_type}"
668
+
669
+ if modifier_str:
670
+ signature += f" {modifier_str}"
671
+
672
+ return signature
673
+
674
+ def _create_compact_signature(self, method: dict[str, Any]) -> str:
675
+ """Create compact method signature"""
676
+ params = method.get("parameters", [])
677
+ param_types = [self._shorten_type(p.get("type", "O")) for p in params]
678
+ params_str = ",".join(param_types)
679
+ return_type = self._shorten_type(method.get("return_type", "void"))
680
+
681
+ return f"({params_str}):{return_type}"
682
+
683
+ def _shorten_type(self, type_name: Any) -> str:
684
+ """Shorten type name"""
685
+ if type_name is None:
686
+ return "O"
687
+
688
+ # Convert non-string types to string
689
+ if not isinstance(type_name, str):
690
+ type_name = str(type_name)
691
+
692
+ # At this point, type_name is guaranteed to be a string
693
+ # Defensive check (avoid using assert for runtime safety and security checks)
694
+ if not isinstance(type_name, str):
695
+ try:
696
+ type_name = str(type_name)
697
+ except Exception:
698
+ type_name = "O"
699
+
700
+ type_mapping = {
701
+ "String": "S",
702
+ "int": "i",
703
+ "long": "l",
704
+ "double": "d",
705
+ "boolean": "b",
706
+ "void": "void",
707
+ "Object": "O",
708
+ "Exception": "E",
709
+ "SQLException": "SE",
710
+ "IllegalArgumentException": "IAE",
711
+ "RuntimeException": "RE",
712
+ }
713
+
714
+ # Map<String,Object> -> M<S,O>
715
+ if "Map<" in type_name:
716
+ return str(
717
+ type_name.replace("Map<", "M<")
718
+ .replace("String", "S")
719
+ .replace("Object", "O")
720
+ )
721
+
722
+ # List<String> -> L<S>
723
+ if "List<" in type_name:
724
+ return str(type_name.replace("List<", "L<").replace("String", "S"))
725
+
726
+ # String[] -> S[]
727
+ if "[]" in type_name:
728
+ base_type = type_name.replace("[]", "")
729
+ if base_type:
730
+ return str(type_mapping.get(base_type, base_type[0].upper())) + "[]"
731
+ else:
732
+ return "O[]"
733
+
734
+ return str(type_mapping.get(type_name, type_name))
735
+
736
+ def _convert_visibility(self, visibility: str) -> str:
737
+ """Convert visibility to symbol"""
738
+ mapping = {"public": "+", "private": "-", "protected": "#", "package": "~"}
739
+ return mapping.get(visibility, visibility)
740
+
741
+ def _extract_doc_summary(self, javadoc: str) -> str:
742
+ """Extract summary from JavaDoc"""
743
+ if not javadoc:
744
+ return "-"
745
+
746
+ # Remove comment symbols
747
+ clean_doc = (
748
+ javadoc.replace("/**", "").replace("*/", "").replace("*", "").strip()
749
+ )
750
+
751
+ # Get first line (use standard \\n only)
752
+ lines = clean_doc.split("\n")
753
+ first_line = lines[0].strip()
754
+
755
+ # Truncate if too long
756
+ if len(first_line) > 50:
757
+ first_line = first_line[:47] + "..."
758
+
759
+ # Escape characters that cause problems in Markdown tables (use standard \\n only)
760
+ return first_line.replace("|", "\\|").replace("\n", " ")
761
+
762
+ def _clean_csv_text(self, text: str) -> str:
763
+ """Text cleaning for CSV format"""
764
+ if not text:
765
+ return ""
766
+
767
+ # Replace all newline characters with spaces
768
+ cleaned = text.replace("\r\n", " ").replace("\r", " ").replace("\n", " ")
769
+ # Convert consecutive spaces to single space
770
+ cleaned = " ".join(cleaned.split())
771
+ # Escape characters that cause problems in CSV
772
+ cleaned = cleaned.replace('"', '""') # Escape double quotes
773
+
774
+ return cleaned
775
+
776
+
777
+ def create_table_formatter(
778
+ format_type: str, language: str = "java", include_javadoc: bool = False
779
+ ) -> "TableFormatter":
780
+ """Create table formatter (using new factory)"""
781
+ # Create TableFormatter directly (for JavaDoc support)
782
+ return TableFormatter(format_type, language, include_javadoc)