tree-sitter-analyzer 0.7.0__py3-none-any.whl → 0.8.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 (69) hide show
  1. tree_sitter_analyzer/__init__.py +132 -132
  2. tree_sitter_analyzer/__main__.py +11 -11
  3. tree_sitter_analyzer/api.py +533 -533
  4. tree_sitter_analyzer/cli/__init__.py +39 -39
  5. tree_sitter_analyzer/cli/__main__.py +12 -12
  6. tree_sitter_analyzer/cli/commands/__init__.py +26 -26
  7. tree_sitter_analyzer/cli/commands/advanced_command.py +88 -88
  8. tree_sitter_analyzer/cli/commands/base_command.py +160 -160
  9. tree_sitter_analyzer/cli/commands/default_command.py +18 -18
  10. tree_sitter_analyzer/cli/commands/partial_read_command.py +141 -141
  11. tree_sitter_analyzer/cli/commands/query_command.py +81 -81
  12. tree_sitter_analyzer/cli/commands/structure_command.py +138 -138
  13. tree_sitter_analyzer/cli/commands/summary_command.py +101 -101
  14. tree_sitter_analyzer/cli/commands/table_command.py +235 -235
  15. tree_sitter_analyzer/cli/info_commands.py +121 -121
  16. tree_sitter_analyzer/cli_main.py +297 -297
  17. tree_sitter_analyzer/core/__init__.py +15 -15
  18. tree_sitter_analyzer/core/analysis_engine.py +555 -555
  19. tree_sitter_analyzer/core/cache_service.py +320 -320
  20. tree_sitter_analyzer/core/engine.py +566 -566
  21. tree_sitter_analyzer/core/parser.py +293 -293
  22. tree_sitter_analyzer/encoding_utils.py +459 -459
  23. tree_sitter_analyzer/exceptions.py +406 -337
  24. tree_sitter_analyzer/file_handler.py +210 -210
  25. tree_sitter_analyzer/formatters/__init__.py +1 -1
  26. tree_sitter_analyzer/formatters/base_formatter.py +167 -167
  27. tree_sitter_analyzer/formatters/formatter_factory.py +78 -78
  28. tree_sitter_analyzer/interfaces/__init__.py +9 -9
  29. tree_sitter_analyzer/interfaces/cli.py +528 -528
  30. tree_sitter_analyzer/interfaces/cli_adapter.py +343 -343
  31. tree_sitter_analyzer/interfaces/mcp_adapter.py +206 -206
  32. tree_sitter_analyzer/interfaces/mcp_server.py +425 -405
  33. tree_sitter_analyzer/languages/__init__.py +10 -10
  34. tree_sitter_analyzer/languages/javascript_plugin.py +446 -446
  35. tree_sitter_analyzer/languages/python_plugin.py +755 -755
  36. tree_sitter_analyzer/mcp/__init__.py +31 -31
  37. tree_sitter_analyzer/mcp/resources/__init__.py +44 -44
  38. tree_sitter_analyzer/mcp/resources/code_file_resource.py +209 -209
  39. tree_sitter_analyzer/mcp/server.py +346 -333
  40. tree_sitter_analyzer/mcp/tools/__init__.py +30 -30
  41. tree_sitter_analyzer/mcp/tools/analyze_scale_tool.py +654 -654
  42. tree_sitter_analyzer/mcp/tools/analyze_scale_tool_cli_compatible.py +247 -247
  43. tree_sitter_analyzer/mcp/tools/base_tool.py +54 -54
  44. tree_sitter_analyzer/mcp/tools/read_partial_tool.py +300 -300
  45. tree_sitter_analyzer/mcp/tools/table_format_tool.py +362 -362
  46. tree_sitter_analyzer/mcp/tools/universal_analyze_tool.py +543 -543
  47. tree_sitter_analyzer/mcp/utils/__init__.py +107 -107
  48. tree_sitter_analyzer/mcp/utils/error_handler.py +549 -549
  49. tree_sitter_analyzer/output_manager.py +253 -253
  50. tree_sitter_analyzer/plugins/__init__.py +280 -280
  51. tree_sitter_analyzer/plugins/base.py +529 -529
  52. tree_sitter_analyzer/plugins/manager.py +379 -379
  53. tree_sitter_analyzer/queries/__init__.py +26 -26
  54. tree_sitter_analyzer/queries/java.py +391 -391
  55. tree_sitter_analyzer/queries/javascript.py +148 -148
  56. tree_sitter_analyzer/queries/python.py +285 -285
  57. tree_sitter_analyzer/queries/typescript.py +229 -229
  58. tree_sitter_analyzer/query_loader.py +257 -257
  59. tree_sitter_analyzer/security/__init__.py +22 -0
  60. tree_sitter_analyzer/security/boundary_manager.py +237 -0
  61. tree_sitter_analyzer/security/regex_checker.py +292 -0
  62. tree_sitter_analyzer/security/validator.py +224 -0
  63. tree_sitter_analyzer/table_formatter.py +652 -589
  64. tree_sitter_analyzer/utils.py +277 -277
  65. {tree_sitter_analyzer-0.7.0.dist-info → tree_sitter_analyzer-0.8.0.dist-info}/METADATA +4 -1
  66. tree_sitter_analyzer-0.8.0.dist-info/RECORD +76 -0
  67. tree_sitter_analyzer-0.7.0.dist-info/RECORD +0 -72
  68. {tree_sitter_analyzer-0.7.0.dist-info → tree_sitter_analyzer-0.8.0.dist-info}/WHEEL +0 -0
  69. {tree_sitter_analyzer-0.7.0.dist-info → tree_sitter_analyzer-0.8.0.dist-info}/entry_points.txt +0 -0
@@ -1,589 +1,652 @@
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 - organized by class"""
57
- lines = []
58
-
59
- # Header - use filename for multi-class files
60
- classes = data.get("classes", [])
61
- if classes is None:
62
- classes = []
63
-
64
- # Always use filename for header to be consistent
65
- file_name = data.get("file_path", "Unknown").split("/")[-1].split("\\")[-1]
66
- lines.append(f"# {file_name}")
67
- lines.append("")
68
-
69
- # Package info
70
- package_name = (data.get("package") or {}).get("name", "")
71
- if package_name:
72
- lines.append("## Package")
73
- lines.append(f"`{package_name}`")
74
- lines.append("")
75
-
76
- # Imports
77
- imports = data.get("imports", [])
78
- if imports:
79
- lines.append("## Imports")
80
- lines.append(f"```{self.language}")
81
- for imp in imports:
82
- lines.append(str(imp.get("statement", "")))
83
- lines.append("```")
84
- lines.append("")
85
-
86
- # Classes Overview
87
- if len(classes) > 1:
88
- lines.append("## Classes Overview")
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
- # Calculate method and field counts for this class
100
- class_methods = self._get_class_methods(data, line_range)
101
- class_fields = self._get_class_fields(data, line_range)
102
-
103
- lines.append(
104
- f"| {name} | {class_type} | {visibility} | {lines_str} | {len(class_methods)} | {len(class_fields)} |"
105
- )
106
- lines.append("")
107
-
108
- # Detailed class information - organized by class
109
- for class_info in classes:
110
- lines.extend(self._format_class_details(class_info, data))
111
-
112
- # Remove trailing empty lines
113
- while lines and lines[-1] == "":
114
- lines.pop()
115
-
116
- return "\n".join(lines)
117
-
118
- def _get_class_methods(self, data: dict[str, Any], class_line_range: dict[str, int]) -> list[dict[str, Any]]:
119
- """Get methods that belong to a specific class based on line range, excluding nested classes."""
120
- methods = data.get("methods", [])
121
- classes = data.get("classes", [])
122
- class_methods = []
123
-
124
- # Get nested class ranges to exclude their methods
125
- nested_class_ranges = []
126
- for cls in classes:
127
- cls_range = cls.get("line_range", {})
128
- cls_start = cls_range.get("start", 0)
129
- cls_end = cls_range.get("end", 0)
130
-
131
- # If this class is nested within the current class range
132
- if (class_line_range.get("start", 0) < cls_start and
133
- cls_end < class_line_range.get("end", 0)):
134
- nested_class_ranges.append((cls_start, cls_end))
135
-
136
- for method in methods:
137
- method_line = method.get("line_range", {}).get("start", 0)
138
-
139
- # Check if method is within the class range
140
- if (class_line_range.get("start", 0) <= method_line <= class_line_range.get("end", 0)):
141
- # Check if method is NOT within any nested class
142
- in_nested_class = False
143
- for nested_start, nested_end in nested_class_ranges:
144
- if nested_start <= method_line <= nested_end:
145
- in_nested_class = True
146
- break
147
-
148
- if not in_nested_class:
149
- class_methods.append(method)
150
-
151
- return class_methods
152
-
153
- def _get_class_fields(self, data: dict[str, Any], class_line_range: dict[str, int]) -> list[dict[str, Any]]:
154
- """Get fields that belong to a specific class based on line range, excluding nested classes."""
155
- fields = data.get("fields", [])
156
- classes = data.get("classes", [])
157
- class_fields = []
158
-
159
- # Get nested class ranges to exclude their fields
160
- nested_class_ranges = []
161
- for cls in classes:
162
- cls_range = cls.get("line_range", {})
163
- cls_start = cls_range.get("start", 0)
164
- cls_end = cls_range.get("end", 0)
165
-
166
- # If this class is nested within the current class range
167
- if (class_line_range.get("start", 0) < cls_start and
168
- cls_end < class_line_range.get("end", 0)):
169
- nested_class_ranges.append((cls_start, cls_end))
170
-
171
- for field in fields:
172
- field_line = field.get("line_range", {}).get("start", 0)
173
-
174
- # Check if field is within the class range
175
- if (class_line_range.get("start", 0) <= field_line <= class_line_range.get("end", 0)):
176
- # Check if field is NOT within any nested class
177
- in_nested_class = False
178
- for nested_start, nested_end in nested_class_ranges:
179
- if nested_start <= field_line <= nested_end:
180
- in_nested_class = True
181
- break
182
-
183
- if not in_nested_class:
184
- class_fields.append(field)
185
-
186
- return class_fields
187
-
188
- def _format_class_details(self, class_info: dict[str, Any], data: dict[str, Any]) -> list[str]:
189
- """Format detailed information for a single class."""
190
- lines = []
191
-
192
- name = str(class_info.get("name", "Unknown"))
193
- line_range = class_info.get("line_range", {})
194
- lines_str = f"{line_range.get('start', 0)}-{line_range.get('end', 0)}"
195
-
196
- # Class header
197
- lines.append(f"## {name} ({lines_str})")
198
-
199
- # Get class-specific methods and fields
200
- class_methods = self._get_class_methods(data, line_range)
201
- class_fields = self._get_class_fields(data, line_range)
202
-
203
- # Fields section
204
- if class_fields:
205
- lines.append("### Fields")
206
- lines.append("| Name | Type | Vis | Modifiers | Line | Doc |")
207
- lines.append("|------|------|-----|-----------|------|-----|")
208
-
209
- for field in class_fields:
210
- name_field = str(field.get("name", ""))
211
- type_field = str(field.get("type", ""))
212
- visibility = self._convert_visibility(str(field.get("visibility", "")))
213
- modifiers = ",".join(field.get("modifiers", []))
214
- line_num = field.get("line_range", {}).get("start", 0)
215
- doc = self._extract_doc_summary(str(field.get("javadoc", ""))) if self.include_javadoc else "-"
216
-
217
- lines.append(f"| {name_field} | {type_field} | {visibility} | {modifiers} | {line_num} | {doc} |")
218
- lines.append("")
219
-
220
- # Methods section - separate by type
221
- constructors = [m for m in class_methods if m.get("is_constructor", False)]
222
- regular_methods = [m for m in class_methods if not m.get("is_constructor", False)]
223
-
224
- # Constructors
225
- if constructors:
226
- lines.append("### Constructors")
227
- lines.append("| Constructor | Signature | Vis | Lines | Cx | Doc |")
228
- lines.append("|-------------|-----------|-----|-------|----|----|")
229
-
230
- for method in constructors:
231
- lines.append(self._format_method_row_detailed(method))
232
- lines.append("")
233
-
234
- # Methods grouped by visibility
235
- public_methods = [m for m in regular_methods if m.get("visibility", "") == "public"]
236
- protected_methods = [m for m in regular_methods if m.get("visibility", "") == "protected"]
237
- package_methods = [m for m in regular_methods if m.get("visibility", "") == "package"]
238
- private_methods = [m for m in regular_methods if m.get("visibility", "") == "private"]
239
-
240
- for method_group, title in [
241
- (public_methods, "Public Methods"),
242
- (protected_methods, "Protected Methods"),
243
- (package_methods, "Package Methods"),
244
- (private_methods, "Private Methods")
245
- ]:
246
- if method_group:
247
- lines.append(f"### {title}")
248
- lines.append("| Method | Signature | Vis | Lines | Cx | Doc |")
249
- lines.append("|--------|-----------|-----|-------|----|----|")
250
-
251
- for method in method_group:
252
- lines.append(self._format_method_row_detailed(method))
253
- lines.append("")
254
-
255
- return lines
256
-
257
- def _format_method_row_detailed(self, method: dict[str, Any]) -> str:
258
- """Format method row for detailed class view."""
259
- name = str(method.get("name", ""))
260
- signature = self._create_full_signature(method)
261
- visibility = self._convert_visibility(str(method.get("visibility", "")))
262
- line_range = method.get("line_range", {})
263
- lines_str = f"{line_range.get('start', 0)}-{line_range.get('end', 0)}"
264
- complexity = method.get("complexity_score", 0)
265
- doc = self._extract_doc_summary(str(method.get("javadoc", ""))) if self.include_javadoc else "-"
266
-
267
- return f"| {name} | {signature} | {visibility} | {lines_str} | {complexity} | {doc} |"
268
-
269
- def _format_traditional_sections(self, data: dict[str, Any]) -> list[str]:
270
- """Format traditional sections when no classes are found."""
271
- lines = []
272
-
273
- # Traditional class info
274
- lines.append("## Class Info")
275
- lines.append("| Property | Value |")
276
- lines.append("|----------|-------|")
277
-
278
- package_name = (data.get("package") or {}).get("name", "unknown")
279
- class_info = data.get("classes", [{}])[0] if data.get("classes") else {}
280
- stats = data.get("statistics") or {}
281
-
282
- lines.append(f"| Package | {package_name} |")
283
- lines.append(f"| Type | {str(class_info.get('type', 'class'))} |")
284
- lines.append(f"| Visibility | {str(class_info.get('visibility', 'public'))} |")
285
- lines.append(f"| Lines | {class_info.get('line_range', {}).get('start', 0)}-{class_info.get('line_range', {}).get('end', 0)} |")
286
- lines.append(f"| Total Methods | {stats.get('method_count', 0)} |")
287
- lines.append(f"| Total Fields | {stats.get('field_count', 0)} |")
288
- lines.append("")
289
-
290
- # Fields
291
- fields = data.get("fields", [])
292
- if fields:
293
- lines.append("## Fields")
294
- lines.append("| Name | Type | Vis | Modifiers | Line | Doc |")
295
- lines.append("|------|------|-----|-----------|------|-----|")
296
-
297
- for field in fields:
298
- name = str(field.get("name", ""))
299
- field_type = str(field.get("type", ""))
300
- visibility = self._convert_visibility(str(field.get("visibility", "")))
301
- modifiers = ",".join([str(m) for m in field.get("modifiers", [])])
302
- line = field.get("line_range", {}).get("start", 0)
303
- doc = self._extract_doc_summary(str(field.get("javadoc", ""))) if self.include_javadoc else "-"
304
-
305
- lines.append(f"| {name} | {field_type} | {visibility} | {modifiers} | {line} | {doc} |")
306
- lines.append("")
307
-
308
- # Methods by type
309
- methods = data.get("methods", [])
310
- constructors = [m for m in methods if m.get("is_constructor", False)]
311
- regular_methods = [m for m in methods if not m.get("is_constructor", False)]
312
-
313
- # Constructors
314
- if constructors:
315
- lines.append("## Constructor")
316
- lines.append("| Method | Signature | Vis | Lines | Cols | Cx | Doc |")
317
- lines.append("|--------|-----------|-----|-------|------|----|----|")
318
- for method in constructors:
319
- lines.append(self._format_method_row(method))
320
- lines.append("")
321
-
322
- # Methods by visibility
323
- for visibility, title in [("public", "Public Methods"), ("private", "Private Methods")]:
324
- visibility_methods = [m for m in regular_methods if str(m.get("visibility")) == visibility]
325
- if visibility_methods:
326
- lines.append(f"## {title}")
327
- lines.append("| Method | Signature | Vis | Lines | Cols | Cx | Doc |")
328
- lines.append("|--------|-----------|-----|-------|------|----|----|")
329
- for method in visibility_methods:
330
- lines.append(self._format_method_row(method))
331
- lines.append("")
332
-
333
- return lines
334
-
335
- def _format_compact_table(self, data: dict[str, Any]) -> str:
336
- """Compact table format"""
337
- lines = []
338
-
339
- # Header
340
- package_name = (data.get("package") or {}).get("name", "unknown")
341
- classes = data.get("classes", [])
342
- if classes is None:
343
- classes = []
344
- class_name = classes[0].get("name", "Unknown") if classes else "Unknown"
345
- lines.append(f"# {package_name}.{class_name}")
346
- lines.append("")
347
-
348
- # Basic information
349
- stats = data.get("statistics") or {}
350
- lines.append("## Info")
351
- lines.append("| Property | Value |")
352
- lines.append("|----------|-------|")
353
- lines.append(f"| Package | {package_name} |")
354
- lines.append(f"| Methods | {stats.get('method_count', 0)} |")
355
- lines.append(f"| Fields | {stats.get('field_count', 0)} |")
356
- lines.append("")
357
-
358
- # Methods (simplified version)
359
- methods = data.get("methods", [])
360
- if methods is None:
361
- methods = []
362
- if methods:
363
- lines.append("## Methods")
364
- lines.append("| Method | Sig | V | L | Cx | Doc |")
365
- lines.append("|--------|-----|---|---|----|----|")
366
-
367
- for method in methods:
368
- name = str(method.get("name", ""))
369
- signature = self._create_compact_signature(method)
370
- visibility = self._convert_visibility(str(method.get("visibility", "")))
371
- line_range = method.get("line_range", {})
372
- lines_str = f"{line_range.get('start', 0)}-{line_range.get('end', 0)}"
373
- complexity = method.get("complexity_score", 0)
374
- doc = self._clean_csv_text(
375
- self._extract_doc_summary(str(method.get("javadoc", "")))
376
- )
377
-
378
- lines.append(
379
- f"| {name} | {signature} | {visibility} | {lines_str} | {complexity} | {doc} |"
380
- )
381
- lines.append("")
382
-
383
- # Remove trailing empty lines
384
- while lines and lines[-1] == "":
385
- lines.pop()
386
-
387
- return "\n".join(lines)
388
-
389
- def _format_csv(self, data: dict[str, Any]) -> str:
390
- """CSV format"""
391
- output = io.StringIO()
392
- writer = csv.writer(
393
- output, lineterminator="\n"
394
- ) # Explicitly specify newline character
395
-
396
- # Header
397
- writer.writerow(
398
- ["Type", "Name", "Signature", "Visibility", "Lines", "Complexity", "Doc"]
399
- )
400
-
401
- # Fields
402
- for field in data.get("fields", []):
403
- writer.writerow(
404
- [
405
- "Field",
406
- str(field.get("name", "")),
407
- f"{str(field.get('name', ''))}:{str(field.get('type', ''))}",
408
- str(field.get("visibility", "")),
409
- f"{field.get('line_range', {}).get('start', 0)}-{field.get('line_range', {}).get('end', 0)}",
410
- "",
411
- self._clean_csv_text(
412
- self._extract_doc_summary(str(field.get("javadoc", "")))
413
- ),
414
- ]
415
- )
416
-
417
- # Methods
418
- for method in data.get("methods", []):
419
- writer.writerow(
420
- [
421
- "Constructor" if method.get("is_constructor", False) else "Method",
422
- str(method.get("name", "")),
423
- self._clean_csv_text(self._create_full_signature(method)),
424
- str(method.get("visibility", "")),
425
- f"{method.get('line_range', {}).get('start', 0)}-{method.get('line_range', {}).get('end', 0)}",
426
- method.get("complexity_score", 0),
427
- self._clean_csv_text(
428
- self._extract_doc_summary(str(method.get("javadoc", "")))
429
- ),
430
- ]
431
- )
432
-
433
- # Completely control CSV output newlines
434
- csv_content = output.getvalue()
435
- # Unify all newline patterns and remove trailing newlines
436
- csv_content = csv_content.replace("\r\n", "\n").replace("\r", "\n")
437
- csv_content = csv_content.rstrip("\n")
438
- output.close()
439
-
440
- return csv_content
441
-
442
- def _format_method_row(self, method: dict[str, Any]) -> str:
443
- """Format method row"""
444
- name = str(method.get("name", ""))
445
- signature = self._create_full_signature(method)
446
- visibility = self._convert_visibility(str(method.get("visibility", "")))
447
- line_range = method.get("line_range", {})
448
- lines_str = f"{line_range.get('start', 0)}-{line_range.get('end', 0)}"
449
- cols_str = (
450
- "5-6" # Default value (actual implementation should get accurate values)
451
- )
452
- complexity = method.get("complexity_score", 0)
453
- if self.include_javadoc:
454
- doc = self._clean_csv_text(
455
- self._extract_doc_summary(str(method.get("javadoc", "")))
456
- )
457
- else:
458
- doc = "-"
459
-
460
- return f"| {name} | {signature} | {visibility} | {lines_str} | {cols_str} | {complexity} | {doc} |"
461
-
462
- def _create_full_signature(self, method: dict[str, Any]) -> str:
463
- """Create complete method signature"""
464
- params = method.get("parameters", [])
465
- param_strs = []
466
- for param in params:
467
- param_type = str(param.get("type", "Object"))
468
- param_name = str(param.get("name", "param"))
469
- param_strs.append(f"{param_name}:{param_type}")
470
-
471
- params_str = ", ".join(param_strs)
472
- return_type = str(method.get("return_type", "void"))
473
-
474
- modifiers = []
475
- if method.get("is_static", False):
476
- modifiers.append("[static]")
477
-
478
- modifier_str = " ".join(modifiers)
479
- signature = f"({params_str}):{return_type}"
480
-
481
- if modifier_str:
482
- signature += f" {modifier_str}"
483
-
484
- return signature
485
-
486
- def _create_compact_signature(self, method: dict[str, Any]) -> str:
487
- """Create compact method signature"""
488
- params = method.get("parameters", [])
489
- param_types = [self._shorten_type(p.get("type", "O")) for p in params]
490
- params_str = ",".join(param_types)
491
- return_type = self._shorten_type(method.get("return_type", "void"))
492
-
493
- return f"({params_str}):{return_type}"
494
-
495
- def _shorten_type(self, type_name: Any) -> str:
496
- """Shorten type name"""
497
- if type_name is None:
498
- return "O"
499
-
500
- # Convert non-string types to string
501
- if not isinstance(type_name, str):
502
- type_name = str(type_name)
503
-
504
- # At this point, type_name is guaranteed to be a string
505
- assert isinstance(type_name, str)
506
-
507
- type_mapping = {
508
- "String": "S",
509
- "int": "i",
510
- "long": "l",
511
- "double": "d",
512
- "boolean": "b",
513
- "void": "void",
514
- "Object": "O",
515
- "Exception": "E",
516
- "SQLException": "SE",
517
- "IllegalArgumentException": "IAE",
518
- "RuntimeException": "RE",
519
- }
520
-
521
- # Map<String,Object> -> M<S,O>
522
- if "Map<" in type_name:
523
- return (
524
- type_name.replace("Map<", "M<")
525
- .replace("String", "S")
526
- .replace("Object", "O")
527
- )
528
-
529
- # List<String> -> L<S>
530
- if "List<" in type_name:
531
- return type_name.replace("List<", "L<").replace("String", "S")
532
-
533
- # String[] -> S[]
534
- if "[]" in type_name:
535
- base_type = type_name.replace("[]", "")
536
- if base_type:
537
- return type_mapping.get(base_type, base_type[0].upper()) + "[]"
538
- else:
539
- return "O[]"
540
-
541
- return type_mapping.get(type_name, type_name)
542
-
543
- def _convert_visibility(self, visibility: str) -> str:
544
- """Convert visibility to symbol"""
545
- mapping = {"public": "+", "private": "-", "protected": "#", "package": "~"}
546
- return mapping.get(visibility, visibility)
547
-
548
- def _extract_doc_summary(self, javadoc: str) -> str:
549
- """Extract summary from JavaDoc"""
550
- if not javadoc:
551
- return "-"
552
-
553
- # Remove comment symbols
554
- clean_doc = (
555
- javadoc.replace("/**", "").replace("*/", "").replace("*", "").strip()
556
- )
557
-
558
- # Get first line (use standard \\n only)
559
- lines = clean_doc.split("\n")
560
- first_line = lines[0].strip()
561
-
562
- # Truncate if too long
563
- if len(first_line) > 50:
564
- first_line = first_line[:47] + "..."
565
-
566
- # Escape characters that cause problems in Markdown tables (use standard \\n only)
567
- return first_line.replace("|", "\\|").replace("\n", " ")
568
-
569
- def _clean_csv_text(self, text: str) -> str:
570
- """Text cleaning for CSV format"""
571
- if not text:
572
- return ""
573
-
574
- # Replace all newline characters with spaces
575
- cleaned = text.replace("\r\n", " ").replace("\r", " ").replace("\n", " ")
576
- # Convert consecutive spaces to single space
577
- cleaned = " ".join(cleaned.split())
578
- # Escape characters that cause problems in CSV
579
- cleaned = cleaned.replace('"', '""') # Escape double quotes
580
-
581
- return cleaned
582
-
583
-
584
- def create_table_formatter(
585
- format_type: str, language: str = "java", include_javadoc: bool = False
586
- ) -> "TableFormatter":
587
- """Create table formatter (using new factory)"""
588
- # Create TableFormatter directly (for JavaDoc support)
589
- 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 - organized by class"""
57
+ lines = []
58
+
59
+ # Header - use package.class format for single class, filename for multi-class files
60
+ classes = data.get("classes", [])
61
+ if classes is None:
62
+ classes = []
63
+
64
+ # Determine header format
65
+ package_name = (data.get("package") or {}).get("name", "")
66
+ if len(classes) == 1:
67
+ # Single class: use package.ClassName format
68
+ class_name = classes[0].get("name", "Unknown")
69
+ if package_name:
70
+ header = f"{package_name}.{class_name}"
71
+ else:
72
+ header = class_name
73
+ else:
74
+ # Multiple classes or no classes: use filename or default
75
+ file_path = data.get("file_path", "")
76
+ if file_path and file_path != "Unknown":
77
+ file_name = file_path.split("/")[-1].split("\\")[-1]
78
+ if file_name.endswith(".java"):
79
+ file_name = file_name[:-5] # Remove .java extension
80
+ elif file_name.endswith(".py"):
81
+ file_name = file_name[:-3] # Remove .py extension
82
+ elif file_name.endswith(".js"):
83
+ file_name = file_name[:-3] # Remove .js extension
84
+
85
+ if package_name and len(classes) == 0:
86
+ # No classes but has package: use package.filename
87
+ header = f"{package_name}.{file_name}"
88
+ else:
89
+ header = file_name
90
+ else:
91
+ # No file path: use default format
92
+ if package_name:
93
+ header = f"{package_name}.Unknown"
94
+ else:
95
+ header = "unknown.Unknown"
96
+
97
+ lines.append(f"# {header}")
98
+ lines.append("")
99
+
100
+ # Package info
101
+ package_name = (data.get("package") or {}).get("name", "")
102
+ if package_name:
103
+ lines.append("## Package")
104
+ lines.append(f"`{package_name}`")
105
+ lines.append("")
106
+
107
+ # Imports
108
+ imports = data.get("imports", [])
109
+ if imports:
110
+ lines.append("## Imports")
111
+ lines.append(f"```{self.language}")
112
+ for imp in imports:
113
+ lines.append(str(imp.get("statement", "")))
114
+ lines.append("```")
115
+ lines.append("")
116
+
117
+ # Class Info section (for single class files or empty data)
118
+ if len(classes) == 1 or len(classes) == 0:
119
+ lines.append("## Class Info")
120
+ lines.append("| Property | Value |")
121
+ lines.append("|----------|-------|")
122
+
123
+ package_name = (data.get("package") or {}).get("name", "unknown")
124
+
125
+ if len(classes) == 1:
126
+ class_info = classes[0]
127
+ lines.append(f"| Package | {package_name} |")
128
+ lines.append(f"| Type | {str(class_info.get('type', 'class'))} |")
129
+ lines.append(f"| Visibility | {str(class_info.get('visibility', 'public'))} |")
130
+
131
+ # Lines
132
+ line_range = class_info.get("line_range", {})
133
+ lines_str = f"{line_range.get('start', 1)}-{line_range.get('end', 50)}"
134
+ lines.append(f"| Lines | {lines_str} |")
135
+ else:
136
+ # Empty data case
137
+ lines.append(f"| Package | {package_name} |")
138
+ lines.append("| Type | class |")
139
+ lines.append("| Visibility | public |")
140
+ lines.append("| Lines | 0-0 |")
141
+
142
+ # Count methods and fields
143
+ all_methods = data.get("methods", []) or []
144
+ all_fields = data.get("fields", []) or []
145
+ lines.append(f"| Total Methods | {len(all_methods)} |")
146
+ lines.append(f"| Total Fields | {len(all_fields)} |")
147
+ lines.append("")
148
+
149
+ # Classes Overview
150
+ if len(classes) > 1:
151
+ lines.append("## Classes Overview")
152
+ lines.append("| Class | Type | Visibility | Lines | Methods | Fields |")
153
+ lines.append("|-------|------|------------|-------|---------|--------|")
154
+
155
+ for class_info in classes:
156
+ name = str(class_info.get("name", "Unknown"))
157
+ class_type = str(class_info.get("type", "class"))
158
+ visibility = str(class_info.get("visibility", "public"))
159
+ line_range = class_info.get("line_range", {})
160
+ lines_str = f"{line_range.get('start', 0)}-{line_range.get('end', 0)}"
161
+
162
+ # Calculate method and field counts for this class
163
+ class_methods = self._get_class_methods(data, line_range)
164
+ class_fields = self._get_class_fields(data, line_range)
165
+
166
+ lines.append(
167
+ f"| {name} | {class_type} | {visibility} | {lines_str} | {len(class_methods)} | {len(class_fields)} |"
168
+ )
169
+ lines.append("")
170
+
171
+ # Detailed class information - organized by class
172
+ for class_info in classes:
173
+ lines.extend(self._format_class_details(class_info, data))
174
+
175
+ # Remove trailing empty lines
176
+ while lines and lines[-1] == "":
177
+ lines.pop()
178
+
179
+ return "\n".join(lines)
180
+
181
+ def _get_class_methods(self, data: dict[str, Any], class_line_range: dict[str, int]) -> list[dict[str, Any]]:
182
+ """Get methods that belong to a specific class based on line range, excluding nested classes."""
183
+ methods = data.get("methods", [])
184
+ classes = data.get("classes", [])
185
+ class_methods = []
186
+
187
+ # Get nested class ranges to exclude their methods
188
+ nested_class_ranges = []
189
+ for cls in classes:
190
+ cls_range = cls.get("line_range", {})
191
+ cls_start = cls_range.get("start", 0)
192
+ cls_end = cls_range.get("end", 0)
193
+
194
+ # If this class is nested within the current class range
195
+ if (class_line_range.get("start", 0) < cls_start and
196
+ cls_end < class_line_range.get("end", 0)):
197
+ nested_class_ranges.append((cls_start, cls_end))
198
+
199
+ for method in methods:
200
+ method_line = method.get("line_range", {}).get("start", 0)
201
+
202
+ # Check if method is within the class range
203
+ if (class_line_range.get("start", 0) <= method_line <= class_line_range.get("end", 0)):
204
+ # Check if method is NOT within any nested class
205
+ in_nested_class = False
206
+ for nested_start, nested_end in nested_class_ranges:
207
+ if nested_start <= method_line <= nested_end:
208
+ in_nested_class = True
209
+ break
210
+
211
+ if not in_nested_class:
212
+ class_methods.append(method)
213
+
214
+ return class_methods
215
+
216
+ def _get_class_fields(self, data: dict[str, Any], class_line_range: dict[str, int]) -> list[dict[str, Any]]:
217
+ """Get fields that belong to a specific class based on line range, excluding nested classes."""
218
+ fields = data.get("fields", [])
219
+ classes = data.get("classes", [])
220
+ class_fields = []
221
+
222
+ # Get nested class ranges to exclude their fields
223
+ nested_class_ranges = []
224
+ for cls in classes:
225
+ cls_range = cls.get("line_range", {})
226
+ cls_start = cls_range.get("start", 0)
227
+ cls_end = cls_range.get("end", 0)
228
+
229
+ # If this class is nested within the current class range
230
+ if (class_line_range.get("start", 0) < cls_start and
231
+ cls_end < class_line_range.get("end", 0)):
232
+ nested_class_ranges.append((cls_start, cls_end))
233
+
234
+ for field in fields:
235
+ field_line = field.get("line_range", {}).get("start", 0)
236
+
237
+ # Check if field is within the class range
238
+ if (class_line_range.get("start", 0) <= field_line <= class_line_range.get("end", 0)):
239
+ # Check if field is NOT within any nested class
240
+ in_nested_class = False
241
+ for nested_start, nested_end in nested_class_ranges:
242
+ if nested_start <= field_line <= nested_end:
243
+ in_nested_class = True
244
+ break
245
+
246
+ if not in_nested_class:
247
+ class_fields.append(field)
248
+
249
+ return class_fields
250
+
251
+ def _format_class_details(self, class_info: dict[str, Any], data: dict[str, Any]) -> list[str]:
252
+ """Format detailed information for a single class."""
253
+ lines = []
254
+
255
+ name = str(class_info.get("name", "Unknown"))
256
+ line_range = class_info.get("line_range", {})
257
+ lines_str = f"{line_range.get('start', 0)}-{line_range.get('end', 0)}"
258
+
259
+ # Class header
260
+ lines.append(f"## {name} ({lines_str})")
261
+
262
+ # Get class-specific methods and fields
263
+ class_methods = self._get_class_methods(data, line_range)
264
+ class_fields = self._get_class_fields(data, line_range)
265
+
266
+ # Fields section
267
+ if class_fields:
268
+ lines.append("### Fields")
269
+ lines.append("| Name | Type | Vis | Modifiers | Line | Doc |")
270
+ lines.append("|------|------|-----|-----------|------|-----|")
271
+
272
+ for field in class_fields:
273
+ name_field = str(field.get("name", ""))
274
+ type_field = str(field.get("type", ""))
275
+ visibility = self._convert_visibility(str(field.get("visibility", "")))
276
+ modifiers = ",".join(field.get("modifiers", []))
277
+ line_num = field.get("line_range", {}).get("start", 0)
278
+ doc = self._extract_doc_summary(str(field.get("javadoc", ""))) if self.include_javadoc else "-"
279
+
280
+ lines.append(f"| {name_field} | {type_field} | {visibility} | {modifiers} | {line_num} | {doc} |")
281
+ lines.append("")
282
+
283
+ # Methods section - separate by type
284
+ constructors = [m for m in class_methods if m.get("is_constructor", False)]
285
+ regular_methods = [m for m in class_methods if not m.get("is_constructor", False)]
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 = [m for m in regular_methods if m.get("visibility", "") == "public"]
299
+ protected_methods = [m for m in regular_methods if m.get("visibility", "") == "protected"]
300
+ package_methods = [m for m in regular_methods if m.get("visibility", "") == "package"]
301
+ private_methods = [m for m in regular_methods if m.get("visibility", "") == "private"]
302
+
303
+ for method_group, title in [
304
+ (public_methods, "Public Methods"),
305
+ (protected_methods, "Protected Methods"),
306
+ (package_methods, "Package Methods"),
307
+ (private_methods, "Private Methods")
308
+ ]:
309
+ if method_group:
310
+ lines.append(f"### {title}")
311
+ lines.append("| Method | Signature | Vis | Lines | Cx | Doc |")
312
+ lines.append("|--------|-----------|-----|-------|----|----|")
313
+
314
+ for method in method_group:
315
+ lines.append(self._format_method_row_detailed(method))
316
+ lines.append("")
317
+
318
+ return lines
319
+
320
+ def _format_method_row_detailed(self, method: dict[str, Any]) -> str:
321
+ """Format method row for detailed class view."""
322
+ name = str(method.get("name", ""))
323
+ signature = self._create_full_signature(method)
324
+ visibility = self._convert_visibility(str(method.get("visibility", "")))
325
+ line_range = method.get("line_range", {})
326
+ lines_str = f"{line_range.get('start', 0)}-{line_range.get('end', 0)}"
327
+ complexity = method.get("complexity_score", 0)
328
+ doc = self._extract_doc_summary(str(method.get("javadoc", ""))) if self.include_javadoc else "-"
329
+
330
+ return f"| {name} | {signature} | {visibility} | {lines_str} | {complexity} | {doc} |"
331
+
332
+ def _format_traditional_sections(self, data: dict[str, Any]) -> list[str]:
333
+ """Format traditional sections when no classes are found."""
334
+ lines = []
335
+
336
+ # Traditional class info
337
+ lines.append("## Class Info")
338
+ lines.append("| Property | Value |")
339
+ lines.append("|----------|-------|")
340
+
341
+ package_name = (data.get("package") or {}).get("name", "unknown")
342
+ class_info = data.get("classes", [{}])[0] if data.get("classes") else {}
343
+ stats = data.get("statistics") or {}
344
+
345
+ lines.append(f"| Package | {package_name} |")
346
+ lines.append(f"| Type | {str(class_info.get('type', 'class'))} |")
347
+ lines.append(f"| Visibility | {str(class_info.get('visibility', 'public'))} |")
348
+ lines.append(f"| Lines | {class_info.get('line_range', {}).get('start', 0)}-{class_info.get('line_range', {}).get('end', 0)} |")
349
+ lines.append(f"| Total Methods | {stats.get('method_count', 0)} |")
350
+ lines.append(f"| Total Fields | {stats.get('field_count', 0)} |")
351
+ lines.append("")
352
+
353
+ # Fields
354
+ fields = data.get("fields", [])
355
+ if fields:
356
+ lines.append("## Fields")
357
+ lines.append("| Name | Type | Vis | Modifiers | Line | Doc |")
358
+ lines.append("|------|------|-----|-----------|------|-----|")
359
+
360
+ for field in fields:
361
+ name = str(field.get("name", ""))
362
+ field_type = str(field.get("type", ""))
363
+ visibility = self._convert_visibility(str(field.get("visibility", "")))
364
+ modifiers = ",".join([str(m) for m in field.get("modifiers", [])])
365
+ line = field.get("line_range", {}).get("start", 0)
366
+ doc = self._extract_doc_summary(str(field.get("javadoc", ""))) if self.include_javadoc else "-"
367
+
368
+ lines.append(f"| {name} | {field_type} | {visibility} | {modifiers} | {line} | {doc} |")
369
+ lines.append("")
370
+
371
+ # Methods by type
372
+ methods = data.get("methods", [])
373
+ constructors = [m for m in methods if m.get("is_constructor", False)]
374
+ regular_methods = [m for m in methods if not m.get("is_constructor", False)]
375
+
376
+ # Constructors
377
+ if constructors:
378
+ lines.append("## Constructor")
379
+ lines.append("| Method | Signature | Vis | Lines | Cols | Cx | Doc |")
380
+ lines.append("|--------|-----------|-----|-------|------|----|----|")
381
+ for method in constructors:
382
+ lines.append(self._format_method_row(method))
383
+ lines.append("")
384
+
385
+ # Methods by visibility
386
+ for visibility, title in [("public", "Public Methods"), ("private", "Private Methods")]:
387
+ visibility_methods = [m for m in regular_methods if str(m.get("visibility")) == visibility]
388
+ if visibility_methods:
389
+ lines.append(f"## {title}")
390
+ lines.append("| Method | Signature | Vis | Lines | Cols | Cx | Doc |")
391
+ lines.append("|--------|-----------|-----|-------|------|----|----|")
392
+ for method in visibility_methods:
393
+ lines.append(self._format_method_row(method))
394
+ lines.append("")
395
+
396
+ return lines
397
+
398
+ def _format_compact_table(self, data: dict[str, Any]) -> str:
399
+ """Compact table format"""
400
+ lines = []
401
+
402
+ # Header
403
+ package_name = (data.get("package") or {}).get("name", "unknown")
404
+ classes = data.get("classes", [])
405
+ if classes is None:
406
+ classes = []
407
+ class_name = classes[0].get("name", "Unknown") if classes else "Unknown"
408
+ lines.append(f"# {package_name}.{class_name}")
409
+ lines.append("")
410
+
411
+ # Basic information
412
+ stats = data.get("statistics") or {}
413
+ lines.append("## Info")
414
+ lines.append("| Property | Value |")
415
+ lines.append("|----------|-------|")
416
+ lines.append(f"| Package | {package_name} |")
417
+ lines.append(f"| Methods | {stats.get('method_count', 0)} |")
418
+ lines.append(f"| Fields | {stats.get('field_count', 0)} |")
419
+ lines.append("")
420
+
421
+ # Methods (simplified version)
422
+ methods = data.get("methods", [])
423
+ if methods is None:
424
+ methods = []
425
+ if methods:
426
+ lines.append("## Methods")
427
+ lines.append("| Method | Sig | V | L | Cx | Doc |")
428
+ lines.append("|--------|-----|---|---|----|----|")
429
+
430
+ for method in methods:
431
+ name = str(method.get("name", ""))
432
+ signature = self._create_compact_signature(method)
433
+ visibility = self._convert_visibility(str(method.get("visibility", "")))
434
+ line_range = method.get("line_range", {})
435
+ lines_str = f"{line_range.get('start', 0)}-{line_range.get('end', 0)}"
436
+ complexity = method.get("complexity_score", 0)
437
+ doc = self._clean_csv_text(
438
+ self._extract_doc_summary(str(method.get("javadoc", "")))
439
+ )
440
+
441
+ lines.append(
442
+ f"| {name} | {signature} | {visibility} | {lines_str} | {complexity} | {doc} |"
443
+ )
444
+ lines.append("")
445
+
446
+ # Remove trailing empty lines
447
+ while lines and lines[-1] == "":
448
+ lines.pop()
449
+
450
+ return "\n".join(lines)
451
+
452
+ def _format_csv(self, data: dict[str, Any]) -> str:
453
+ """CSV format"""
454
+ output = io.StringIO()
455
+ writer = csv.writer(
456
+ output, lineterminator="\n"
457
+ ) # Explicitly specify newline character
458
+
459
+ # Header
460
+ writer.writerow(
461
+ ["Type", "Name", "Signature", "Visibility", "Lines", "Complexity", "Doc"]
462
+ )
463
+
464
+ # Fields
465
+ for field in data.get("fields", []):
466
+ writer.writerow(
467
+ [
468
+ "Field",
469
+ str(field.get("name", "")),
470
+ f"{str(field.get('name', ''))}:{str(field.get('type', ''))}",
471
+ str(field.get("visibility", "")),
472
+ f"{field.get('line_range', {}).get('start', 0)}-{field.get('line_range', {}).get('end', 0)}",
473
+ "",
474
+ self._clean_csv_text(
475
+ self._extract_doc_summary(str(field.get("javadoc", "")))
476
+ ),
477
+ ]
478
+ )
479
+
480
+ # Methods
481
+ for method in data.get("methods", []):
482
+ writer.writerow(
483
+ [
484
+ "Constructor" if method.get("is_constructor", False) else "Method",
485
+ str(method.get("name", "")),
486
+ self._clean_csv_text(self._create_full_signature(method)),
487
+ str(method.get("visibility", "")),
488
+ f"{method.get('line_range', {}).get('start', 0)}-{method.get('line_range', {}).get('end', 0)}",
489
+ method.get("complexity_score", 0),
490
+ self._clean_csv_text(
491
+ self._extract_doc_summary(str(method.get("javadoc", "")))
492
+ ),
493
+ ]
494
+ )
495
+
496
+ # Completely control CSV output newlines
497
+ csv_content = output.getvalue()
498
+ # Unify all newline patterns and remove trailing newlines
499
+ csv_content = csv_content.replace("\r\n", "\n").replace("\r", "\n")
500
+ csv_content = csv_content.rstrip("\n")
501
+ output.close()
502
+
503
+ return csv_content
504
+
505
+ def _format_method_row(self, method: dict[str, Any]) -> str:
506
+ """Format method row"""
507
+ name = str(method.get("name", ""))
508
+ signature = self._create_full_signature(method)
509
+ visibility = self._convert_visibility(str(method.get("visibility", "")))
510
+ line_range = method.get("line_range", {})
511
+ lines_str = f"{line_range.get('start', 0)}-{line_range.get('end', 0)}"
512
+ cols_str = (
513
+ "5-6" # Default value (actual implementation should get accurate values)
514
+ )
515
+ complexity = method.get("complexity_score", 0)
516
+ if self.include_javadoc:
517
+ doc = self._clean_csv_text(
518
+ self._extract_doc_summary(str(method.get("javadoc", "")))
519
+ )
520
+ else:
521
+ doc = "-"
522
+
523
+ return f"| {name} | {signature} | {visibility} | {lines_str} | {cols_str} | {complexity} | {doc} |"
524
+
525
+ def _create_full_signature(self, method: dict[str, Any]) -> str:
526
+ """Create complete method signature"""
527
+ params = method.get("parameters", [])
528
+ param_strs = []
529
+ for param in params:
530
+ param_type = str(param.get("type", "Object"))
531
+ param_name = str(param.get("name", "param"))
532
+ param_strs.append(f"{param_name}:{param_type}")
533
+
534
+ params_str = ", ".join(param_strs)
535
+ return_type = str(method.get("return_type", "void"))
536
+
537
+ modifiers = []
538
+ if method.get("is_static", False):
539
+ modifiers.append("[static]")
540
+
541
+ modifier_str = " ".join(modifiers)
542
+ signature = f"({params_str}):{return_type}"
543
+
544
+ if modifier_str:
545
+ signature += f" {modifier_str}"
546
+
547
+ return signature
548
+
549
+ def _create_compact_signature(self, method: dict[str, Any]) -> str:
550
+ """Create compact method signature"""
551
+ params = method.get("parameters", [])
552
+ param_types = [self._shorten_type(p.get("type", "O")) for p in params]
553
+ params_str = ",".join(param_types)
554
+ return_type = self._shorten_type(method.get("return_type", "void"))
555
+
556
+ return f"({params_str}):{return_type}"
557
+
558
+ def _shorten_type(self, type_name: Any) -> str:
559
+ """Shorten type name"""
560
+ if type_name is None:
561
+ return "O"
562
+
563
+ # Convert non-string types to string
564
+ if not isinstance(type_name, str):
565
+ type_name = str(type_name)
566
+
567
+ # At this point, type_name is guaranteed to be a string
568
+ assert isinstance(type_name, str)
569
+
570
+ type_mapping = {
571
+ "String": "S",
572
+ "int": "i",
573
+ "long": "l",
574
+ "double": "d",
575
+ "boolean": "b",
576
+ "void": "void",
577
+ "Object": "O",
578
+ "Exception": "E",
579
+ "SQLException": "SE",
580
+ "IllegalArgumentException": "IAE",
581
+ "RuntimeException": "RE",
582
+ }
583
+
584
+ # Map<String,Object> -> M<S,O>
585
+ if "Map<" in type_name:
586
+ return (
587
+ type_name.replace("Map<", "M<")
588
+ .replace("String", "S")
589
+ .replace("Object", "O")
590
+ )
591
+
592
+ # List<String> -> L<S>
593
+ if "List<" in type_name:
594
+ return type_name.replace("List<", "L<").replace("String", "S")
595
+
596
+ # String[] -> S[]
597
+ if "[]" in type_name:
598
+ base_type = type_name.replace("[]", "")
599
+ if base_type:
600
+ return type_mapping.get(base_type, base_type[0].upper()) + "[]"
601
+ else:
602
+ return "O[]"
603
+
604
+ return type_mapping.get(type_name, type_name)
605
+
606
+ def _convert_visibility(self, visibility: str) -> str:
607
+ """Convert visibility to symbol"""
608
+ mapping = {"public": "+", "private": "-", "protected": "#", "package": "~"}
609
+ return mapping.get(visibility, visibility)
610
+
611
+ def _extract_doc_summary(self, javadoc: str) -> str:
612
+ """Extract summary from JavaDoc"""
613
+ if not javadoc:
614
+ return "-"
615
+
616
+ # Remove comment symbols
617
+ clean_doc = (
618
+ javadoc.replace("/**", "").replace("*/", "").replace("*", "").strip()
619
+ )
620
+
621
+ # Get first line (use standard \\n only)
622
+ lines = clean_doc.split("\n")
623
+ first_line = lines[0].strip()
624
+
625
+ # Truncate if too long
626
+ if len(first_line) > 50:
627
+ first_line = first_line[:47] + "..."
628
+
629
+ # Escape characters that cause problems in Markdown tables (use standard \\n only)
630
+ return first_line.replace("|", "\\|").replace("\n", " ")
631
+
632
+ def _clean_csv_text(self, text: str) -> str:
633
+ """Text cleaning for CSV format"""
634
+ if not text:
635
+ return ""
636
+
637
+ # Replace all newline characters with spaces
638
+ cleaned = text.replace("\r\n", " ").replace("\r", " ").replace("\n", " ")
639
+ # Convert consecutive spaces to single space
640
+ cleaned = " ".join(cleaned.split())
641
+ # Escape characters that cause problems in CSV
642
+ cleaned = cleaned.replace('"', '""') # Escape double quotes
643
+
644
+ return cleaned
645
+
646
+
647
+ def create_table_formatter(
648
+ format_type: str, language: str = "java", include_javadoc: bool = False
649
+ ) -> "TableFormatter":
650
+ """Create table formatter (using new factory)"""
651
+ # Create TableFormatter directly (for JavaDoc support)
652
+ return TableFormatter(format_type, language, include_javadoc)