tree-sitter-analyzer 0.9.8__py3-none-any.whl → 1.0.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.

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