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,860 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Legacy Table Formatter for Tree-sitter Analyzer
4
+
5
+ This module provides the restored v1.6.1.4 TableFormatter implementation
6
+ to ensure backward compatibility for analyze_code_structure tool.
7
+ """
8
+
9
+ import csv
10
+ import io
11
+ from typing import Any
12
+
13
+
14
+ class LegacyTableFormatter:
15
+ """
16
+ Legacy table formatter for code analysis results.
17
+
18
+ This class restores the exact v1.6.1.4 behavior for the analyze_code_structure
19
+ tool, ensuring backward compatibility and specification compliance.
20
+ """
21
+
22
+ def __init__(
23
+ self,
24
+ format_type: str = "full",
25
+ language: str = "java",
26
+ include_javadoc: bool = False,
27
+ ):
28
+ """
29
+ Initialize the legacy table formatter.
30
+
31
+ Args:
32
+ format_type: Format type (full, compact, csv)
33
+ language: Programming language for syntax highlighting
34
+ include_javadoc: Whether to include JavaDoc/documentation
35
+ """
36
+ self.format_type = format_type
37
+ self.language = language
38
+ self.include_javadoc = include_javadoc
39
+
40
+ def _get_platform_newline(self) -> str:
41
+ """Get platform-specific newline character"""
42
+ import os
43
+
44
+ return "\r\n" if os.name == "nt" else "\n" # Windows uses \r\n, others use \n
45
+
46
+ def _convert_to_platform_newlines(self, text: str) -> str:
47
+ """Convert standard \\n to platform-specific newline characters"""
48
+ platform_newline = self._get_platform_newline()
49
+ if platform_newline != "\n":
50
+ return text.replace("\n", platform_newline)
51
+ return text
52
+
53
+ def format_structure(self, structure_data: dict[str, Any]) -> str:
54
+ """
55
+ Format structure data as table.
56
+
57
+ Args:
58
+ structure_data: Dictionary containing analysis results
59
+
60
+ Returns:
61
+ Formatted string in the specified format
62
+
63
+ Raises:
64
+ ValueError: If format_type is not supported
65
+ """
66
+ if self.format_type == "full":
67
+ result = self._format_full_table(structure_data)
68
+ elif self.format_type == "compact":
69
+ result = self._format_compact_table(structure_data)
70
+ elif self.format_type == "csv":
71
+ result = self._format_csv(structure_data)
72
+ else:
73
+ raise ValueError(f"Unsupported format type: {self.format_type}")
74
+
75
+ # Convert to platform-specific newline characters
76
+ # Skip newline conversion for CSV format
77
+ if self.format_type in ["csv"]:
78
+ return result
79
+
80
+ return self._convert_to_platform_newlines(result)
81
+
82
+ def _format_full_table(self, data: dict[str, Any]) -> str:
83
+ """Full table format - compliant with format specification"""
84
+ lines = []
85
+
86
+ # Header - use package.class format for single class
87
+ classes = data.get("classes", [])
88
+ if classes is None:
89
+ classes = []
90
+
91
+ # Determine header format
92
+ package_name = (data.get("package") or {}).get("name", "")
93
+ if len(classes) == 1:
94
+ # Single class: use package.ClassName format
95
+ class_name = classes[0].get("name", "Unknown")
96
+ if package_name:
97
+ header = f"{package_name}.{class_name}"
98
+ else:
99
+ header = class_name
100
+ else:
101
+ # Multiple classes or no classes: use filename or default
102
+ file_path = data.get("file_path", "")
103
+ if file_path and file_path != "Unknown":
104
+ file_name = file_path.split("/")[-1].split("\\")[-1]
105
+ if file_name.endswith(".java"):
106
+ file_name = file_name[:-5] # Remove .java extension
107
+ elif file_name.endswith(".py"):
108
+ file_name = file_name[:-3] # Remove .py extension
109
+ elif file_name.endswith(".js"):
110
+ file_name = file_name[:-3] # Remove .js extension
111
+
112
+ if package_name and len(classes) == 0:
113
+ # No classes but has package: use package.filename
114
+ header = f"{package_name}.{file_name}"
115
+ else:
116
+ header = file_name
117
+ else:
118
+ # No file path: use default format
119
+ if package_name:
120
+ header = f"{package_name}.Unknown"
121
+ else:
122
+ header = "unknown.Unknown"
123
+
124
+ lines.append(f"# {header}")
125
+ lines.append("")
126
+
127
+ # Get package name once for use throughout
128
+ package_name = (data.get("package") or {}).get("name", "")
129
+
130
+ # Package section (if package exists)
131
+ if package_name and package_name != "unknown":
132
+ lines.append("## Package")
133
+ lines.append(f"`{package_name}`")
134
+ lines.append("")
135
+
136
+ # Imports section (should appear before class info)
137
+ imports = data.get("imports", [])
138
+ if imports:
139
+ lines.append("## Imports")
140
+ lines.append(f"```{self.language}")
141
+ for imp in imports:
142
+ statement = str(imp.get("statement", ""))
143
+ lines.append(statement)
144
+ lines.append("```")
145
+ lines.append("")
146
+
147
+ # Class Info section (required by specification)
148
+ lines.append("## Class Info")
149
+ lines.append("| Property | Value |")
150
+ lines.append("|----------|-------|")
151
+
152
+ # Use package_name or default to "unknown" for display
153
+ display_package = package_name if package_name else "unknown"
154
+
155
+ if len(classes) >= 1:
156
+ class_info = classes[0]
157
+ class_name = str(class_info.get("name", "Unknown"))
158
+ lines.append(f"| Name | {class_name} |")
159
+ lines.append(f"| Package | {display_package} |")
160
+ lines.append(f"| Type | {str(class_info.get('type', 'class'))} |")
161
+ lines.append(f"| Access | {str(class_info.get('visibility', 'public'))} |")
162
+
163
+ # Lines
164
+ line_range = class_info.get("line_range", {})
165
+ lines_str = f"{line_range.get('start', 1)}-{line_range.get('end', 50)}"
166
+
167
+ # Add optional fields
168
+ extends = class_info.get("extends")
169
+ if extends:
170
+ lines.append(f"| Extends | {extends} |")
171
+
172
+ implements = class_info.get("implements", [])
173
+ if implements:
174
+ lines.append(f"| Implements | {', '.join(implements)} |")
175
+ else:
176
+ # Empty data case
177
+ lines.append("| Name | Unknown |")
178
+ lines.append(f"| Package | {display_package} |")
179
+ lines.append("| Type | class |")
180
+ lines.append("| Access | public |")
181
+
182
+ lines.append("")
183
+
184
+ # Check if we have multiple classes to organize by class
185
+ all_methods = data.get("methods", []) or []
186
+ all_fields = data.get("fields", []) or []
187
+
188
+ if len(classes) > 1:
189
+ # Multiple classes: add Classes Overview section
190
+ lines.append("## Classes Overview")
191
+ lines.append("| Class | Type | Visibility | Lines | Methods | Fields |")
192
+ lines.append("|-------|------|------------|-------|---------|--------|")
193
+
194
+ for class_info in classes:
195
+ class_name = str(class_info.get("name", "Unknown"))
196
+ class_type = str(class_info.get("type", "class"))
197
+ visibility = str(class_info.get("visibility", "public"))
198
+ line_range = class_info.get("line_range", {})
199
+ lines_str = f"{line_range.get('start', 0)}-{line_range.get('end', 0)}"
200
+
201
+ # Count methods and fields for this class
202
+ class_methods = self._get_class_methods(data, line_range)
203
+ class_fields = self._get_class_fields(data, line_range)
204
+
205
+ lines.append(
206
+ f"| {class_name} | {class_type} | {visibility} | {lines_str} | {len(class_methods)} | {len(class_fields)} |"
207
+ )
208
+ lines.append("")
209
+
210
+ # Multiple classes: organize methods and fields by class
211
+ for class_info in classes:
212
+ class_name = str(class_info.get("name", "Unknown"))
213
+ line_range = class_info.get("line_range", {})
214
+ lines_str = f"{line_range.get('start', 0)}-{line_range.get('end', 0)}"
215
+
216
+ lines.append(f"## {class_name} ({lines_str})")
217
+
218
+ # Get methods for this class
219
+ class_methods = self._get_class_methods(data, line_range)
220
+
221
+ if class_methods:
222
+ lines.append("### Methods")
223
+ lines.append("| Name | Return Type | Parameters | Access | Line |")
224
+ lines.append("|------|-------------|------------|--------|------|")
225
+
226
+ for method in class_methods:
227
+ name = str(method.get("name", ""))
228
+ # Constructors don't have return types
229
+ is_constructor = method.get("is_constructor", False)
230
+ return_type = (
231
+ "-"
232
+ if is_constructor
233
+ else str(method.get("return_type", "void"))
234
+ )
235
+
236
+ # Format parameters as "type1 param1, type2 param2"
237
+ params = method.get("parameters", [])
238
+ param_strs = []
239
+ for param in params:
240
+ if isinstance(param, dict):
241
+ param_type = str(param.get("type", "Object"))
242
+ param_name = str(param.get("name", "param"))
243
+ param_strs.append(f"{param_type} {param_name}")
244
+ elif isinstance(param, str):
245
+ param_strs.append(param)
246
+ else:
247
+ param_strs.append(str(param))
248
+ params_str = ", ".join(param_strs)
249
+
250
+ access = str(method.get("visibility", "public"))
251
+ line_num = method.get("line_range", {}).get("start", 0)
252
+
253
+ lines.append(
254
+ f"| {name} | {return_type} | {params_str} | {access} | {line_num} |"
255
+ )
256
+ lines.append("")
257
+
258
+ # Get fields for this class
259
+ class_fields = self._get_class_fields(data, line_range)
260
+
261
+ if class_fields:
262
+ lines.append("### Fields")
263
+ lines.append("| Name | Type | Access | Static | Final | Line |")
264
+ lines.append("|------|------|--------|--------|-------|------|")
265
+
266
+ for field in class_fields:
267
+ name = str(field.get("name", ""))
268
+ field_type = str(field.get("type", "Object"))
269
+ access = str(field.get("visibility", "private"))
270
+
271
+ # Check modifiers for static and final
272
+ modifiers = field.get("modifiers", [])
273
+ is_static = "static" in modifiers or field.get(
274
+ "is_static", False
275
+ )
276
+ is_final = "final" in modifiers or field.get("is_final", False)
277
+
278
+ static_str = "true" if is_static else "false"
279
+ final_str = "true" if is_final else "false"
280
+
281
+ line_num = field.get("line_range", {}).get("start", 0)
282
+
283
+ lines.append(
284
+ f"| {name} | {field_type} | {access} | {static_str} | {final_str} | {line_num} |"
285
+ )
286
+ lines.append("")
287
+ else:
288
+ # Single class or no classes: use original format
289
+ # Methods section (required by specification)
290
+ lines.append("## Methods")
291
+ if all_methods:
292
+ lines.append("| Name | Return Type | Parameters | Access | Line |")
293
+ lines.append("|------|-------------|------------|--------|------|")
294
+
295
+ for method in all_methods:
296
+ name = str(method.get("name", ""))
297
+ # Constructors don't have return types
298
+ is_constructor = method.get("is_constructor", False)
299
+ return_type = (
300
+ "-"
301
+ if is_constructor
302
+ else str(method.get("return_type", "void"))
303
+ )
304
+
305
+ # Format parameters as "type1 param1, type2 param2"
306
+ params = method.get("parameters", [])
307
+ param_strs = []
308
+ for param in params:
309
+ if isinstance(param, dict):
310
+ param_type = str(param.get("type", "Object"))
311
+ param_name = str(param.get("name", "param"))
312
+ param_strs.append(f"{param_type} {param_name}")
313
+ elif isinstance(param, str):
314
+ param_strs.append(param)
315
+ else:
316
+ param_strs.append(str(param))
317
+ params_str = ", ".join(param_strs)
318
+
319
+ access = str(method.get("visibility", "public"))
320
+ line_num = method.get("line_range", {}).get("start", 0)
321
+
322
+ lines.append(
323
+ f"| {name} | {return_type} | {params_str} | {access} | {line_num} |"
324
+ )
325
+ else:
326
+ lines.append("| Name | Return Type | Parameters | Access | Line |")
327
+ lines.append("|------|-------------|------------|--------|------|")
328
+ lines.append("")
329
+
330
+ # Fields section (required by specification)
331
+ lines.append("## Fields")
332
+ if all_fields:
333
+ lines.append("| Name | Type | Access | Static | Final | Line |")
334
+ lines.append("|------|------|--------|--------|-------|------|")
335
+
336
+ for field in all_fields:
337
+ name = str(field.get("name", ""))
338
+ field_type = str(field.get("type", "Object"))
339
+ access = str(field.get("visibility", "private"))
340
+
341
+ # Check modifiers for static and final
342
+ modifiers = field.get("modifiers", [])
343
+ is_static = "static" in modifiers or field.get("is_static", False)
344
+ is_final = "final" in modifiers or field.get("is_final", False)
345
+
346
+ static_str = "true" if is_static else "false"
347
+ final_str = "true" if is_final else "false"
348
+
349
+ line_num = field.get("line_range", {}).get("start", 0)
350
+
351
+ lines.append(
352
+ f"| {name} | {field_type} | {access} | {static_str} | {final_str} | {line_num} |"
353
+ )
354
+ else:
355
+ lines.append("| Name | Type | Access | Static | Final | Line |")
356
+ lines.append("|------|------|--------|--------|-------|------|")
357
+ lines.append("")
358
+
359
+ # Remove trailing empty lines
360
+ while lines and lines[-1] == "":
361
+ lines.pop()
362
+
363
+ return "\n".join(lines)
364
+
365
+ def _get_class_methods(
366
+ self, data: dict[str, Any], class_line_range: dict[str, int]
367
+ ) -> list[dict[str, Any]]:
368
+ """Get methods that belong to a specific class based on line range, excluding nested classes."""
369
+ methods = data.get("methods", [])
370
+ classes = data.get("classes", [])
371
+ class_methods = []
372
+
373
+ # Get nested class ranges to exclude their methods
374
+ nested_class_ranges = []
375
+ for cls in classes:
376
+ cls_range = cls.get("line_range", {})
377
+ cls_start = cls_range.get("start", 0)
378
+ cls_end = cls_range.get("end", 0)
379
+
380
+ # If this class is nested within the current class range
381
+ if class_line_range.get(
382
+ "start", 0
383
+ ) < cls_start and cls_end < class_line_range.get("end", 0):
384
+ nested_class_ranges.append((cls_start, cls_end))
385
+
386
+ for method in methods:
387
+ method_line = method.get("line_range", {}).get("start", 0)
388
+
389
+ # Check if method is within the class range
390
+ if (
391
+ class_line_range.get("start", 0)
392
+ <= method_line
393
+ <= class_line_range.get("end", 0)
394
+ ):
395
+ # Check if method is NOT within any nested class
396
+ in_nested_class = False
397
+ for nested_start, nested_end in nested_class_ranges:
398
+ if nested_start <= method_line <= nested_end:
399
+ in_nested_class = True
400
+ break
401
+
402
+ if not in_nested_class:
403
+ class_methods.append(method)
404
+
405
+ return class_methods
406
+
407
+ def _get_class_fields(
408
+ self, data: dict[str, Any], class_line_range: dict[str, int]
409
+ ) -> list[dict[str, Any]]:
410
+ """Get fields that belong to a specific class based on line range, excluding nested classes."""
411
+ fields = data.get("fields", [])
412
+ classes = data.get("classes", [])
413
+ class_fields = []
414
+
415
+ # Get nested class ranges to exclude their fields
416
+ nested_class_ranges = []
417
+ for cls in classes:
418
+ cls_range = cls.get("line_range", {})
419
+ cls_start = cls_range.get("start", 0)
420
+ cls_end = cls_range.get("end", 0)
421
+
422
+ # If this class is nested within the current class range
423
+ if class_line_range.get(
424
+ "start", 0
425
+ ) < cls_start and cls_end < class_line_range.get("end", 0):
426
+ nested_class_ranges.append((cls_start, cls_end))
427
+
428
+ for field in fields:
429
+ field_line = field.get("line_range", {}).get("start", 0)
430
+
431
+ # Check if field is within the class range
432
+ if (
433
+ class_line_range.get("start", 0)
434
+ <= field_line
435
+ <= class_line_range.get("end", 0)
436
+ ):
437
+ # Check if field is NOT within any nested class
438
+ in_nested_class = False
439
+ for nested_start, nested_end in nested_class_ranges:
440
+ if nested_start <= field_line <= nested_end:
441
+ in_nested_class = True
442
+ break
443
+
444
+ if not in_nested_class:
445
+ class_fields.append(field)
446
+
447
+ return class_fields
448
+
449
+ def _format_class_details(
450
+ self, class_info: dict[str, Any], data: dict[str, Any]
451
+ ) -> list[str]:
452
+ """Format detailed information for a single class."""
453
+ lines = []
454
+
455
+ name = str(class_info.get("name", "Unknown"))
456
+ line_range = class_info.get("line_range", {})
457
+ lines_str = f"{line_range.get('start', 0)}-{line_range.get('end', 0)}"
458
+
459
+ # Class header
460
+ lines.append(f"## {name} ({lines_str})")
461
+
462
+ # Get class-specific methods and fields
463
+ class_methods = self._get_class_methods(data, line_range)
464
+ class_fields = self._get_class_fields(data, line_range)
465
+
466
+ # Fields section
467
+ if class_fields:
468
+ lines.append("### Fields")
469
+ lines.append("| Name | Type | Vis | Modifiers | Line | Doc |")
470
+ lines.append("|------|------|-----|-----------|------|-----|")
471
+
472
+ for field in class_fields:
473
+ name_field = str(field.get("name", ""))
474
+ type_field = str(field.get("type", ""))
475
+ visibility = self._convert_visibility(str(field.get("visibility", "")))
476
+ modifiers = ",".join(field.get("modifiers", []))
477
+ line_num = field.get("line_range", {}).get("start", 0)
478
+ doc = (
479
+ self._extract_doc_summary(str(field.get("javadoc", "")))
480
+ if self.include_javadoc
481
+ else "-"
482
+ )
483
+
484
+ lines.append(
485
+ f"| {name_field} | {type_field} | {visibility} | {modifiers} | {line_num} | {doc} |"
486
+ )
487
+ lines.append("")
488
+
489
+ # Methods section - separate by type
490
+ constructors = [m for m in class_methods if m.get("is_constructor", False)]
491
+ regular_methods = [
492
+ m for m in class_methods if not m.get("is_constructor", False)
493
+ ]
494
+
495
+ # Constructors
496
+ if constructors:
497
+ lines.append("### Constructors")
498
+ lines.append("| Constructor | Signature | Vis | Lines | Cx | Doc |")
499
+ lines.append("|-------------|-----------|-----|-------|----|----|")
500
+
501
+ for method in constructors:
502
+ lines.append(self._format_method_row_detailed(method))
503
+ lines.append("")
504
+
505
+ # Methods grouped by visibility
506
+ public_methods = [
507
+ m for m in regular_methods if m.get("visibility", "") == "public"
508
+ ]
509
+ protected_methods = [
510
+ m for m in regular_methods if m.get("visibility", "") == "protected"
511
+ ]
512
+ package_methods = [
513
+ m for m in regular_methods if m.get("visibility", "") == "package"
514
+ ]
515
+ private_methods = [
516
+ m for m in regular_methods if m.get("visibility", "") == "private"
517
+ ]
518
+
519
+ for method_group, title in [
520
+ (public_methods, "Public Methods"),
521
+ (protected_methods, "Protected Methods"),
522
+ (package_methods, "Package Methods"),
523
+ (private_methods, "Private Methods"),
524
+ ]:
525
+ if method_group:
526
+ lines.append(f"### {title}")
527
+ lines.append("| Method | Signature | Vis | Lines | Cx | Doc |")
528
+ lines.append("|--------|-----------|-----|-------|----|----|")
529
+
530
+ for method in method_group:
531
+ lines.append(self._format_method_row_detailed(method))
532
+ lines.append("")
533
+
534
+ return lines
535
+
536
+ def _format_method_row_detailed(self, method: dict[str, Any]) -> str:
537
+ """Format method row for detailed class view."""
538
+ name = str(method.get("name", ""))
539
+ signature = self._create_full_signature(method)
540
+ visibility = self._convert_visibility(str(method.get("visibility", "")))
541
+ line_range = method.get("line_range", {})
542
+ lines_str = f"{line_range.get('start', 0)}-{line_range.get('end', 0)}"
543
+ complexity = method.get("complexity_score", 0)
544
+ doc = (
545
+ self._extract_doc_summary(str(method.get("javadoc", "")))
546
+ if self.include_javadoc
547
+ else "-"
548
+ )
549
+
550
+ return f"| {name} | {signature} | {visibility} | {lines_str} | {complexity} | {doc} |"
551
+
552
+ def _format_compact_table(self, data: dict[str, Any]) -> str:
553
+ """Compact table format - compliant with format specification"""
554
+ lines = []
555
+
556
+ # Header - just class name (no package in compact format)
557
+ classes = data.get("classes", [])
558
+ if classes is None:
559
+ classes = []
560
+ class_name = classes[0].get("name", "Unknown") if classes else "Unknown"
561
+ lines.append(f"# {class_name}")
562
+ lines.append("")
563
+
564
+ # Info section (required by specification)
565
+ classes = data.get("classes", [])
566
+ class_type = classes[0].get("type", "class") if classes else "class"
567
+ methods = data.get("methods", []) or []
568
+ fields = data.get("fields", []) or []
569
+
570
+ lines.append("## Info")
571
+ lines.append("| Property | Value |")
572
+ lines.append("|----------|-------|")
573
+ lines.append(f"| Type | {class_type} |")
574
+ lines.append(f"| Methods | {len(methods)} |")
575
+ lines.append(f"| Fields | {len(fields)} |")
576
+ lines.append("")
577
+
578
+ # Methods section (simplified, required by specification)
579
+ lines.append("## Methods")
580
+ if methods:
581
+ lines.append("| Name | Return Type | Access | Line |")
582
+ lines.append("|------|-------------|--------|------|")
583
+
584
+ for method in methods:
585
+ name = str(method.get("name", ""))
586
+ return_type = str(method.get("return_type", "void"))
587
+ access = str(method.get("visibility", "public"))
588
+ line_num = method.get("line_range", {}).get("start", 0)
589
+
590
+ lines.append(f"| {name} | {return_type} | {access} | {line_num} |")
591
+ else:
592
+ lines.append("| Name | Return Type | Access | Line |")
593
+ lines.append("|------|-------------|--------|------|")
594
+ lines.append("")
595
+
596
+ # Fields section (simplified, required by specification)
597
+ lines.append("## Fields")
598
+ if fields:
599
+ lines.append("| Name | Type | Access | Line |")
600
+ lines.append("|------|------|--------|------|")
601
+
602
+ for field in fields:
603
+ name = str(field.get("name", ""))
604
+ field_type = str(field.get("type", "Object"))
605
+ access = str(field.get("visibility", "private"))
606
+ line_num = field.get("line_range", {}).get("start", 0)
607
+
608
+ lines.append(f"| {name} | {field_type} | {access} | {line_num} |")
609
+ else:
610
+ lines.append("| Name | Type | Access | Line |")
611
+ lines.append("|------|------|--------|------|")
612
+ lines.append("")
613
+
614
+ # Remove trailing empty lines
615
+ while lines and lines[-1] == "":
616
+ lines.pop()
617
+
618
+ return "\n".join(lines)
619
+
620
+ def _format_csv(self, data: dict[str, Any]) -> str:
621
+ """CSV format - compliant with format specification"""
622
+ output = io.StringIO()
623
+ writer = csv.writer(
624
+ output, lineterminator="\n"
625
+ ) # Explicitly specify newline character
626
+
627
+ # Header - specification compliant
628
+ writer.writerow(
629
+ [
630
+ "Type",
631
+ "Name",
632
+ "ReturnType",
633
+ "Parameters",
634
+ "Access",
635
+ "Static",
636
+ "Final",
637
+ "Line",
638
+ ]
639
+ )
640
+
641
+ # Class row
642
+ classes = data.get("classes", [])
643
+ if classes:
644
+ for cls in classes:
645
+ writer.writerow(
646
+ [
647
+ str(cls.get("type", "class")),
648
+ str(cls.get("name", "Unknown")),
649
+ "", # No return type for class
650
+ "", # No parameters for class
651
+ str(cls.get("visibility", "public")),
652
+ "false", # Classes are not static
653
+ "true" if "final" in cls.get("modifiers", []) else "false",
654
+ cls.get("line_range", {}).get("start", 0),
655
+ ]
656
+ )
657
+
658
+ # Method rows
659
+ for method in data.get("methods", []):
660
+ # Format parameters as "param1:type1;param2:type2"
661
+ params = method.get("parameters", [])
662
+ param_strs = []
663
+ for param in params:
664
+ if isinstance(param, dict):
665
+ param_type = str(param.get("type", "Object"))
666
+ param_name = str(param.get("name", "param"))
667
+ param_strs.append(f"{param_name}:{param_type}")
668
+ elif isinstance(param, str):
669
+ # Handle "type param" format - convert to "param:type"
670
+ parts = param.strip().split()
671
+ if len(parts) >= 2:
672
+ # Everything except last part is type, last part is name
673
+ param_type = " ".join(parts[:-1])
674
+ param_name = parts[-1]
675
+ param_strs.append(f"{param_name}:{param_type}")
676
+ else:
677
+ # Fallback for single-part parameters
678
+ param_strs.append(param)
679
+ else:
680
+ param_strs.append(str(param))
681
+ params_str = ";".join(param_strs)
682
+
683
+ # Check modifiers for static and final
684
+ modifiers = method.get("modifiers", [])
685
+ is_static = "static" in modifiers or method.get("is_static", False)
686
+ is_final = "final" in modifiers or method.get("is_final", False)
687
+
688
+ writer.writerow(
689
+ [
690
+ "constructor" if method.get("is_constructor", False) else "method",
691
+ str(method.get("name", "")),
692
+ str(method.get("return_type", "void")),
693
+ params_str,
694
+ str(method.get("visibility", "public")),
695
+ "true" if is_static else "false",
696
+ "true" if is_final else "false",
697
+ method.get("line_range", {}).get("start", 0),
698
+ ]
699
+ )
700
+
701
+ # Field rows
702
+ for field in data.get("fields", []):
703
+ # Check modifiers for static and final
704
+ modifiers = field.get("modifiers", [])
705
+ is_static = "static" in modifiers or field.get("is_static", False)
706
+ is_final = "final" in modifiers or field.get("is_final", False)
707
+
708
+ writer.writerow(
709
+ [
710
+ "field",
711
+ str(field.get("name", "")),
712
+ str(field.get("type", "Object")),
713
+ "", # No parameters for fields
714
+ str(field.get("visibility", "private")),
715
+ "true" if is_static else "false",
716
+ "true" if is_final else "false",
717
+ field.get("line_range", {}).get("start", 0),
718
+ ]
719
+ )
720
+
721
+ # Control CSV output newlines
722
+ csv_content = output.getvalue()
723
+ # Unify all newline patterns and remove trailing newlines
724
+ csv_content = csv_content.replace("\r\n", "\n").replace("\r", "\n")
725
+ csv_content = csv_content.rstrip("\n")
726
+ output.close()
727
+
728
+ return csv_content
729
+
730
+ def _create_full_signature(self, method: dict[str, Any]) -> str:
731
+ """Create complete method signature"""
732
+ params = method.get("parameters", [])
733
+ param_strs = []
734
+ for param in params:
735
+ # Handle both dict and string parameters
736
+ if isinstance(param, dict):
737
+ param_type = str(param.get("type", "Object"))
738
+ param_name = str(param.get("name", "param"))
739
+ param_strs.append(f"{param_name}:{param_type}")
740
+ elif isinstance(param, str):
741
+ # If parameter is already a string, use it directly
742
+ param_strs.append(param)
743
+ else:
744
+ # Fallback for other types
745
+ param_strs.append(str(param))
746
+
747
+ params_str = ",".join(param_strs) # Remove space after comma
748
+ return_type = str(method.get("return_type", "void"))
749
+
750
+ modifiers = []
751
+ if method.get("is_static", False):
752
+ modifiers.append("[static]")
753
+
754
+ modifier_str = " ".join(modifiers)
755
+ signature = f"({params_str}):{return_type}"
756
+
757
+ if modifier_str:
758
+ signature += f" {modifier_str}"
759
+
760
+ return signature
761
+
762
+ def _create_compact_signature(self, method: dict[str, Any]) -> str:
763
+ """Create compact method signature"""
764
+ params = method.get("parameters", [])
765
+ param_types = []
766
+ for p in params:
767
+ if isinstance(p, dict):
768
+ param_types.append(self._shorten_type(p.get("type", "O")))
769
+ elif isinstance(p, str):
770
+ # If parameter is already a string, shorten it directly
771
+ param_types.append(self._shorten_type(p))
772
+ else:
773
+ # Fallback for other types
774
+ param_types.append(self._shorten_type(str(p)))
775
+
776
+ params_str = ",".join(param_types)
777
+ return_type = self._shorten_type(method.get("return_type", "void"))
778
+
779
+ return f"({params_str}):{return_type}"
780
+
781
+ def _shorten_type(self, type_name: Any) -> str:
782
+ """Shorten type name"""
783
+ if type_name is None:
784
+ return "O"
785
+
786
+ # Convert non-string types to string
787
+ if not isinstance(type_name, str):
788
+ type_name = str(type_name)
789
+
790
+ type_mapping = {
791
+ "String": "S",
792
+ "int": "i",
793
+ "long": "l",
794
+ "double": "d",
795
+ "boolean": "b",
796
+ "void": "void",
797
+ "Object": "O",
798
+ "Exception": "E",
799
+ "SQLException": "SE",
800
+ "IllegalArgumentException": "IAE",
801
+ "RuntimeException": "RE",
802
+ }
803
+
804
+ # Map<String,Object> -> M<S,O>
805
+ if "Map<" in type_name:
806
+ return str(
807
+ type_name.replace("Map<", "M<")
808
+ .replace("String", "S")
809
+ .replace("Object", "O")
810
+ )
811
+
812
+ # List<String> -> L<S>
813
+ if "List<" in type_name:
814
+ return str(type_name.replace("List<", "L<").replace("String", "S"))
815
+
816
+ # String[] -> S[]
817
+ if "[]" in type_name:
818
+ base_type = type_name.replace("[]", "")
819
+ if base_type:
820
+ return str(type_mapping.get(base_type, base_type[0].upper())) + "[]"
821
+ else:
822
+ return "O[]"
823
+
824
+ return str(type_mapping.get(type_name, type_name))
825
+
826
+ def _convert_visibility(self, visibility: str) -> str:
827
+ """Convert visibility to symbol"""
828
+ mapping = {"public": "+", "private": "-", "protected": "#", "package": "~"}
829
+ return mapping.get(visibility, visibility)
830
+
831
+ def _extract_doc_summary(self, javadoc: str) -> str:
832
+ """Extract summary from JavaDoc"""
833
+ if not javadoc:
834
+ return "-"
835
+
836
+ # Remove comment symbols
837
+ clean_doc = (
838
+ javadoc.replace("/**", "").replace("*/", "").replace("*", "").strip()
839
+ )
840
+
841
+ # Get first sentence
842
+ if clean_doc:
843
+ sentences = clean_doc.split(".")
844
+ if sentences:
845
+ return sentences[0].strip()
846
+
847
+ return "-"
848
+
849
+ def _clean_csv_text(self, text: str) -> str:
850
+ """Clean text for CSV output"""
851
+ if not text or text == "-":
852
+ return "-"
853
+
854
+ # Remove newlines and extra whitespace
855
+ cleaned = " ".join(text.split())
856
+
857
+ # Escape quotes for CSV
858
+ cleaned = cleaned.replace('"', '""')
859
+
860
+ return cleaned