tree-sitter-analyzer 1.9.17.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- tree_sitter_analyzer/__init__.py +132 -0
- tree_sitter_analyzer/__main__.py +11 -0
- tree_sitter_analyzer/api.py +853 -0
- tree_sitter_analyzer/cli/__init__.py +39 -0
- tree_sitter_analyzer/cli/__main__.py +12 -0
- tree_sitter_analyzer/cli/argument_validator.py +89 -0
- tree_sitter_analyzer/cli/commands/__init__.py +26 -0
- tree_sitter_analyzer/cli/commands/advanced_command.py +226 -0
- tree_sitter_analyzer/cli/commands/base_command.py +181 -0
- tree_sitter_analyzer/cli/commands/default_command.py +18 -0
- tree_sitter_analyzer/cli/commands/find_and_grep_cli.py +188 -0
- tree_sitter_analyzer/cli/commands/list_files_cli.py +133 -0
- tree_sitter_analyzer/cli/commands/partial_read_command.py +139 -0
- tree_sitter_analyzer/cli/commands/query_command.py +109 -0
- tree_sitter_analyzer/cli/commands/search_content_cli.py +161 -0
- tree_sitter_analyzer/cli/commands/structure_command.py +156 -0
- tree_sitter_analyzer/cli/commands/summary_command.py +116 -0
- tree_sitter_analyzer/cli/commands/table_command.py +414 -0
- tree_sitter_analyzer/cli/info_commands.py +124 -0
- tree_sitter_analyzer/cli_main.py +472 -0
- tree_sitter_analyzer/constants.py +85 -0
- tree_sitter_analyzer/core/__init__.py +15 -0
- tree_sitter_analyzer/core/analysis_engine.py +580 -0
- tree_sitter_analyzer/core/cache_service.py +333 -0
- tree_sitter_analyzer/core/engine.py +585 -0
- tree_sitter_analyzer/core/parser.py +293 -0
- tree_sitter_analyzer/core/query.py +605 -0
- tree_sitter_analyzer/core/query_filter.py +200 -0
- tree_sitter_analyzer/core/query_service.py +340 -0
- tree_sitter_analyzer/encoding_utils.py +530 -0
- tree_sitter_analyzer/exceptions.py +747 -0
- tree_sitter_analyzer/file_handler.py +246 -0
- tree_sitter_analyzer/formatters/__init__.py +1 -0
- tree_sitter_analyzer/formatters/base_formatter.py +201 -0
- tree_sitter_analyzer/formatters/csharp_formatter.py +367 -0
- tree_sitter_analyzer/formatters/formatter_config.py +197 -0
- tree_sitter_analyzer/formatters/formatter_factory.py +84 -0
- tree_sitter_analyzer/formatters/formatter_registry.py +377 -0
- tree_sitter_analyzer/formatters/formatter_selector.py +96 -0
- tree_sitter_analyzer/formatters/go_formatter.py +368 -0
- tree_sitter_analyzer/formatters/html_formatter.py +498 -0
- tree_sitter_analyzer/formatters/java_formatter.py +423 -0
- tree_sitter_analyzer/formatters/javascript_formatter.py +611 -0
- tree_sitter_analyzer/formatters/kotlin_formatter.py +268 -0
- tree_sitter_analyzer/formatters/language_formatter_factory.py +123 -0
- tree_sitter_analyzer/formatters/legacy_formatter_adapters.py +228 -0
- tree_sitter_analyzer/formatters/markdown_formatter.py +725 -0
- tree_sitter_analyzer/formatters/php_formatter.py +301 -0
- tree_sitter_analyzer/formatters/python_formatter.py +830 -0
- tree_sitter_analyzer/formatters/ruby_formatter.py +278 -0
- tree_sitter_analyzer/formatters/rust_formatter.py +233 -0
- tree_sitter_analyzer/formatters/sql_formatter_wrapper.py +689 -0
- tree_sitter_analyzer/formatters/sql_formatters.py +536 -0
- tree_sitter_analyzer/formatters/typescript_formatter.py +543 -0
- tree_sitter_analyzer/formatters/yaml_formatter.py +462 -0
- tree_sitter_analyzer/interfaces/__init__.py +9 -0
- tree_sitter_analyzer/interfaces/cli.py +535 -0
- tree_sitter_analyzer/interfaces/cli_adapter.py +359 -0
- tree_sitter_analyzer/interfaces/mcp_adapter.py +224 -0
- tree_sitter_analyzer/interfaces/mcp_server.py +428 -0
- tree_sitter_analyzer/language_detector.py +553 -0
- tree_sitter_analyzer/language_loader.py +271 -0
- tree_sitter_analyzer/languages/__init__.py +10 -0
- tree_sitter_analyzer/languages/csharp_plugin.py +1076 -0
- tree_sitter_analyzer/languages/css_plugin.py +449 -0
- tree_sitter_analyzer/languages/go_plugin.py +836 -0
- tree_sitter_analyzer/languages/html_plugin.py +496 -0
- tree_sitter_analyzer/languages/java_plugin.py +1299 -0
- tree_sitter_analyzer/languages/javascript_plugin.py +1622 -0
- tree_sitter_analyzer/languages/kotlin_plugin.py +656 -0
- tree_sitter_analyzer/languages/markdown_plugin.py +1928 -0
- tree_sitter_analyzer/languages/php_plugin.py +862 -0
- tree_sitter_analyzer/languages/python_plugin.py +1636 -0
- tree_sitter_analyzer/languages/ruby_plugin.py +757 -0
- tree_sitter_analyzer/languages/rust_plugin.py +673 -0
- tree_sitter_analyzer/languages/sql_plugin.py +2444 -0
- tree_sitter_analyzer/languages/typescript_plugin.py +1892 -0
- tree_sitter_analyzer/languages/yaml_plugin.py +695 -0
- tree_sitter_analyzer/legacy_table_formatter.py +860 -0
- tree_sitter_analyzer/mcp/__init__.py +34 -0
- tree_sitter_analyzer/mcp/resources/__init__.py +43 -0
- tree_sitter_analyzer/mcp/resources/code_file_resource.py +208 -0
- tree_sitter_analyzer/mcp/resources/project_stats_resource.py +586 -0
- tree_sitter_analyzer/mcp/server.py +869 -0
- tree_sitter_analyzer/mcp/tools/__init__.py +28 -0
- tree_sitter_analyzer/mcp/tools/analyze_scale_tool.py +779 -0
- tree_sitter_analyzer/mcp/tools/analyze_scale_tool_cli_compatible.py +291 -0
- tree_sitter_analyzer/mcp/tools/base_tool.py +139 -0
- tree_sitter_analyzer/mcp/tools/fd_rg_utils.py +816 -0
- tree_sitter_analyzer/mcp/tools/find_and_grep_tool.py +686 -0
- tree_sitter_analyzer/mcp/tools/list_files_tool.py +413 -0
- tree_sitter_analyzer/mcp/tools/output_format_validator.py +148 -0
- tree_sitter_analyzer/mcp/tools/query_tool.py +443 -0
- tree_sitter_analyzer/mcp/tools/read_partial_tool.py +464 -0
- tree_sitter_analyzer/mcp/tools/search_content_tool.py +836 -0
- tree_sitter_analyzer/mcp/tools/table_format_tool.py +572 -0
- tree_sitter_analyzer/mcp/tools/universal_analyze_tool.py +653 -0
- tree_sitter_analyzer/mcp/utils/__init__.py +113 -0
- tree_sitter_analyzer/mcp/utils/error_handler.py +569 -0
- tree_sitter_analyzer/mcp/utils/file_output_factory.py +217 -0
- tree_sitter_analyzer/mcp/utils/file_output_manager.py +322 -0
- tree_sitter_analyzer/mcp/utils/gitignore_detector.py +358 -0
- tree_sitter_analyzer/mcp/utils/path_resolver.py +414 -0
- tree_sitter_analyzer/mcp/utils/search_cache.py +343 -0
- tree_sitter_analyzer/models.py +840 -0
- tree_sitter_analyzer/mypy_current_errors.txt +2 -0
- tree_sitter_analyzer/output_manager.py +255 -0
- tree_sitter_analyzer/platform_compat/__init__.py +3 -0
- tree_sitter_analyzer/platform_compat/adapter.py +324 -0
- tree_sitter_analyzer/platform_compat/compare.py +224 -0
- tree_sitter_analyzer/platform_compat/detector.py +67 -0
- tree_sitter_analyzer/platform_compat/fixtures.py +228 -0
- tree_sitter_analyzer/platform_compat/profiles.py +217 -0
- tree_sitter_analyzer/platform_compat/record.py +55 -0
- tree_sitter_analyzer/platform_compat/recorder.py +155 -0
- tree_sitter_analyzer/platform_compat/report.py +92 -0
- tree_sitter_analyzer/plugins/__init__.py +280 -0
- tree_sitter_analyzer/plugins/base.py +647 -0
- tree_sitter_analyzer/plugins/manager.py +384 -0
- tree_sitter_analyzer/project_detector.py +328 -0
- tree_sitter_analyzer/queries/__init__.py +27 -0
- tree_sitter_analyzer/queries/csharp.py +216 -0
- tree_sitter_analyzer/queries/css.py +615 -0
- tree_sitter_analyzer/queries/go.py +275 -0
- tree_sitter_analyzer/queries/html.py +543 -0
- tree_sitter_analyzer/queries/java.py +402 -0
- tree_sitter_analyzer/queries/javascript.py +724 -0
- tree_sitter_analyzer/queries/kotlin.py +192 -0
- tree_sitter_analyzer/queries/markdown.py +258 -0
- tree_sitter_analyzer/queries/php.py +95 -0
- tree_sitter_analyzer/queries/python.py +859 -0
- tree_sitter_analyzer/queries/ruby.py +92 -0
- tree_sitter_analyzer/queries/rust.py +223 -0
- tree_sitter_analyzer/queries/sql.py +555 -0
- tree_sitter_analyzer/queries/typescript.py +871 -0
- tree_sitter_analyzer/queries/yaml.py +236 -0
- tree_sitter_analyzer/query_loader.py +272 -0
- tree_sitter_analyzer/security/__init__.py +22 -0
- tree_sitter_analyzer/security/boundary_manager.py +277 -0
- tree_sitter_analyzer/security/regex_checker.py +297 -0
- tree_sitter_analyzer/security/validator.py +599 -0
- tree_sitter_analyzer/table_formatter.py +782 -0
- tree_sitter_analyzer/utils/__init__.py +53 -0
- tree_sitter_analyzer/utils/logging.py +433 -0
- tree_sitter_analyzer/utils/tree_sitter_compat.py +289 -0
- tree_sitter_analyzer-1.9.17.1.dist-info/METADATA +485 -0
- tree_sitter_analyzer-1.9.17.1.dist-info/RECORD +149 -0
- tree_sitter_analyzer-1.9.17.1.dist-info/WHEEL +4 -0
- tree_sitter_analyzer-1.9.17.1.dist-info/entry_points.txt +25 -0
|
@@ -0,0 +1,830 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Python-specific table formatter.
|
|
4
|
+
|
|
5
|
+
Provides specialized formatting for Python code analysis results,
|
|
6
|
+
handling modern Python features like async/await, type hints, decorators,
|
|
7
|
+
context managers, and framework-specific patterns.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from typing import Any
|
|
11
|
+
|
|
12
|
+
from .base_formatter import BaseTableFormatter
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class PythonTableFormatter(BaseTableFormatter):
|
|
16
|
+
"""Table formatter specialized for Python"""
|
|
17
|
+
|
|
18
|
+
def format(self, data: dict[str, Any]) -> str:
|
|
19
|
+
"""Format data using the configured format type"""
|
|
20
|
+
# Handle None data - raise exception for edge case tests
|
|
21
|
+
if data is None:
|
|
22
|
+
raise TypeError("Cannot format None data")
|
|
23
|
+
|
|
24
|
+
# Ensure data is a dictionary - raise exception for edge case tests
|
|
25
|
+
if not isinstance(data, dict):
|
|
26
|
+
raise TypeError(f"Expected dict, got {type(data)}")
|
|
27
|
+
|
|
28
|
+
return self.format_structure(data)
|
|
29
|
+
|
|
30
|
+
def format_table(self, data: dict[str, Any], table_type: str = "full") -> str:
|
|
31
|
+
"""Format table output for Python files"""
|
|
32
|
+
# Set the format type and delegate to format_structure
|
|
33
|
+
original_format_type = self.format_type
|
|
34
|
+
self.format_type = table_type
|
|
35
|
+
try:
|
|
36
|
+
result = self.format_structure(data)
|
|
37
|
+
return result
|
|
38
|
+
finally:
|
|
39
|
+
self.format_type = original_format_type
|
|
40
|
+
|
|
41
|
+
def format_summary(self, analysis_result: dict[str, Any]) -> str:
|
|
42
|
+
"""Format summary output for Python"""
|
|
43
|
+
return self._format_compact_table(analysis_result)
|
|
44
|
+
|
|
45
|
+
def format_structure(self, analysis_result: dict[str, Any]) -> str:
|
|
46
|
+
"""Format structure analysis output for Python"""
|
|
47
|
+
return super().format_structure(analysis_result)
|
|
48
|
+
|
|
49
|
+
def format_advanced(
|
|
50
|
+
self, analysis_result: dict[str, Any], output_format: str = "json"
|
|
51
|
+
) -> str:
|
|
52
|
+
"""Format advanced analysis output for Python"""
|
|
53
|
+
if output_format == "json":
|
|
54
|
+
return self._format_json(analysis_result)
|
|
55
|
+
elif output_format == "csv":
|
|
56
|
+
return self._format_csv(analysis_result)
|
|
57
|
+
else:
|
|
58
|
+
return self._format_full_table(analysis_result)
|
|
59
|
+
|
|
60
|
+
def _format_json(self, data: dict[str, Any]) -> str:
|
|
61
|
+
"""Format data as JSON"""
|
|
62
|
+
import json
|
|
63
|
+
|
|
64
|
+
try:
|
|
65
|
+
return json.dumps(data, indent=2, ensure_ascii=False)
|
|
66
|
+
except (TypeError, ValueError) as e:
|
|
67
|
+
return f"# JSON serialization error: {e}\n"
|
|
68
|
+
|
|
69
|
+
def format_analysis_result(
|
|
70
|
+
self, analysis_result: Any, table_type: str = "full"
|
|
71
|
+
) -> str:
|
|
72
|
+
"""Format AnalysisResult directly for Python files - prevents degradation"""
|
|
73
|
+
# Convert AnalysisResult to the format expected by Python formatter
|
|
74
|
+
data = self._convert_analysis_result_to_python_format(analysis_result)
|
|
75
|
+
return self.format_table(data, table_type)
|
|
76
|
+
|
|
77
|
+
def _convert_analysis_result_to_python_format(
|
|
78
|
+
self, analysis_result: Any
|
|
79
|
+
) -> dict[str, Any]:
|
|
80
|
+
"""Convert AnalysisResult to Python formatter's expected format"""
|
|
81
|
+
from ..constants import (
|
|
82
|
+
ELEMENT_TYPE_CLASS,
|
|
83
|
+
ELEMENT_TYPE_FUNCTION,
|
|
84
|
+
ELEMENT_TYPE_IMPORT,
|
|
85
|
+
ELEMENT_TYPE_PACKAGE,
|
|
86
|
+
ELEMENT_TYPE_VARIABLE,
|
|
87
|
+
get_element_type,
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
classes = []
|
|
91
|
+
methods = []
|
|
92
|
+
fields = []
|
|
93
|
+
imports = []
|
|
94
|
+
package_name = "unknown"
|
|
95
|
+
|
|
96
|
+
# Process each element
|
|
97
|
+
for element in analysis_result.elements:
|
|
98
|
+
element_type = get_element_type(element)
|
|
99
|
+
element_name = getattr(element, "name", None)
|
|
100
|
+
|
|
101
|
+
if element_type == ELEMENT_TYPE_PACKAGE:
|
|
102
|
+
package_name = str(element_name)
|
|
103
|
+
elif element_type == ELEMENT_TYPE_CLASS:
|
|
104
|
+
classes.append(self._convert_class_element_for_python(element))
|
|
105
|
+
elif element_type == ELEMENT_TYPE_FUNCTION:
|
|
106
|
+
methods.append(self._convert_function_element_for_python(element))
|
|
107
|
+
elif element_type == ELEMENT_TYPE_VARIABLE:
|
|
108
|
+
fields.append(self._convert_variable_element_for_python(element))
|
|
109
|
+
elif element_type == ELEMENT_TYPE_IMPORT:
|
|
110
|
+
imports.append(self._convert_import_element_for_python(element))
|
|
111
|
+
|
|
112
|
+
return {
|
|
113
|
+
"file_path": analysis_result.file_path,
|
|
114
|
+
"language": analysis_result.language,
|
|
115
|
+
"line_count": analysis_result.line_count,
|
|
116
|
+
"package": {"name": package_name},
|
|
117
|
+
"classes": classes,
|
|
118
|
+
"methods": methods,
|
|
119
|
+
"fields": fields,
|
|
120
|
+
"imports": imports,
|
|
121
|
+
"statistics": {
|
|
122
|
+
"method_count": len(methods),
|
|
123
|
+
"field_count": len(fields),
|
|
124
|
+
"class_count": len(classes),
|
|
125
|
+
"import_count": len(imports),
|
|
126
|
+
},
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
def _convert_class_element_for_python(self, element: Any) -> dict[str, Any]:
|
|
130
|
+
"""Convert class element for Python formatter"""
|
|
131
|
+
element_name = getattr(element, "name", None)
|
|
132
|
+
final_name = element_name if element_name else "UnknownClass"
|
|
133
|
+
|
|
134
|
+
return {
|
|
135
|
+
"name": final_name,
|
|
136
|
+
"type": getattr(element, "class_type", "class"),
|
|
137
|
+
"visibility": getattr(element, "visibility", "public"),
|
|
138
|
+
"line_range": {
|
|
139
|
+
"start": getattr(element, "start_line", 0),
|
|
140
|
+
"end": getattr(element, "end_line", 0),
|
|
141
|
+
},
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
def _convert_function_element_for_python(self, element: Any) -> dict[str, Any]:
|
|
145
|
+
"""Convert function element for Python formatter"""
|
|
146
|
+
params = getattr(element, "parameters", [])
|
|
147
|
+
processed_params = self._process_python_parameters(params)
|
|
148
|
+
|
|
149
|
+
return {
|
|
150
|
+
"name": getattr(element, "name", str(element)),
|
|
151
|
+
"visibility": getattr(element, "visibility", "public"),
|
|
152
|
+
"return_type": getattr(element, "return_type", "Any"),
|
|
153
|
+
"parameters": processed_params,
|
|
154
|
+
"is_constructor": getattr(element, "is_constructor", False),
|
|
155
|
+
"is_static": getattr(element, "is_static", False),
|
|
156
|
+
"is_async": getattr(element, "is_async", False),
|
|
157
|
+
"complexity_score": getattr(element, "complexity_score", 1),
|
|
158
|
+
"line_range": {
|
|
159
|
+
"start": getattr(element, "start_line", 0),
|
|
160
|
+
"end": getattr(element, "end_line", 0),
|
|
161
|
+
},
|
|
162
|
+
"docstring": getattr(element, "docstring", "") or "",
|
|
163
|
+
"decorators": getattr(element, "decorators", []),
|
|
164
|
+
"modifiers": getattr(element, "modifiers", []),
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
def _convert_variable_element_for_python(self, element: Any) -> dict[str, Any]:
|
|
168
|
+
"""Convert variable element for Python formatter"""
|
|
169
|
+
return {
|
|
170
|
+
"name": getattr(element, "name", str(element)),
|
|
171
|
+
"type": getattr(element, "variable_type", "")
|
|
172
|
+
or getattr(element, "field_type", ""),
|
|
173
|
+
"visibility": getattr(element, "visibility", "public"),
|
|
174
|
+
"modifiers": getattr(element, "modifiers", []),
|
|
175
|
+
"line_range": {
|
|
176
|
+
"start": getattr(element, "start_line", 0),
|
|
177
|
+
"end": getattr(element, "end_line", 0),
|
|
178
|
+
},
|
|
179
|
+
"javadoc": getattr(element, "docstring", ""),
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
def _convert_import_element_for_python(self, element: Any) -> dict[str, Any]:
|
|
183
|
+
"""Convert import element for Python formatter"""
|
|
184
|
+
raw_text = getattr(element, "raw_text", "")
|
|
185
|
+
if raw_text:
|
|
186
|
+
statement = raw_text
|
|
187
|
+
else:
|
|
188
|
+
statement = f"import {getattr(element, 'name', str(element))}"
|
|
189
|
+
|
|
190
|
+
return {
|
|
191
|
+
"statement": statement,
|
|
192
|
+
"raw_text": statement,
|
|
193
|
+
"name": getattr(element, "name", str(element)),
|
|
194
|
+
"module_name": getattr(element, "module_name", ""),
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
def _process_python_parameters(self, params: Any) -> list[dict[str, str]]:
|
|
198
|
+
"""Process parameters for Python formatter"""
|
|
199
|
+
if isinstance(params, str):
|
|
200
|
+
param_list = []
|
|
201
|
+
if params.strip():
|
|
202
|
+
param_names = [p.strip() for p in params.split(",") if p.strip()]
|
|
203
|
+
param_list = [{"name": name, "type": "Any"} for name in param_names]
|
|
204
|
+
return param_list
|
|
205
|
+
elif isinstance(params, list):
|
|
206
|
+
param_list = []
|
|
207
|
+
for param in params:
|
|
208
|
+
if isinstance(param, str):
|
|
209
|
+
param = param.strip()
|
|
210
|
+
# Python format: "name: type"
|
|
211
|
+
if ":" in param:
|
|
212
|
+
parts = param.split(":", 1)
|
|
213
|
+
param_name = parts[0].strip()
|
|
214
|
+
param_type = parts[1].strip() if len(parts) > 1 else "Any"
|
|
215
|
+
param_list.append({"name": param_name, "type": param_type})
|
|
216
|
+
else:
|
|
217
|
+
param_list.append({"name": param, "type": "Any"})
|
|
218
|
+
elif isinstance(param, dict):
|
|
219
|
+
param_list.append(param)
|
|
220
|
+
else:
|
|
221
|
+
param_list.append({"name": str(param), "type": "Any"})
|
|
222
|
+
return param_list
|
|
223
|
+
else:
|
|
224
|
+
return []
|
|
225
|
+
|
|
226
|
+
def _format_full_table(self, data: dict[str, Any]) -> str:
|
|
227
|
+
"""Full table format for Python"""
|
|
228
|
+
if data is None:
|
|
229
|
+
raise TypeError("Cannot format None data")
|
|
230
|
+
|
|
231
|
+
if not isinstance(data, dict):
|
|
232
|
+
raise TypeError(f"Expected dict, got {type(data)}")
|
|
233
|
+
|
|
234
|
+
lines = []
|
|
235
|
+
|
|
236
|
+
# Header - Python (module/package based)
|
|
237
|
+
file_path = data.get("file_path", "Unknown")
|
|
238
|
+
if file_path is None:
|
|
239
|
+
file_path = "Unknown"
|
|
240
|
+
file_name = str(file_path).split("/")[-1].split("\\")[-1]
|
|
241
|
+
module_name = (
|
|
242
|
+
file_name.replace(".py", "").replace(".pyw", "").replace(".pyi", "")
|
|
243
|
+
)
|
|
244
|
+
|
|
245
|
+
# Check if this is a package module
|
|
246
|
+
classes = data.get("classes", [])
|
|
247
|
+
functions = data.get("functions", [])
|
|
248
|
+
imports = data.get("imports", [])
|
|
249
|
+
|
|
250
|
+
# Determine module type
|
|
251
|
+
is_package = "__init__.py" in file_name
|
|
252
|
+
is_script = any(
|
|
253
|
+
"if __name__ == '__main__'" in func.get("raw_text", "")
|
|
254
|
+
for func in functions
|
|
255
|
+
)
|
|
256
|
+
|
|
257
|
+
if is_package:
|
|
258
|
+
lines.append(f"# Package: {module_name}")
|
|
259
|
+
elif is_script:
|
|
260
|
+
lines.append(f"# Script: {module_name}")
|
|
261
|
+
else:
|
|
262
|
+
lines.append(f"# Module: {module_name}")
|
|
263
|
+
lines.append("")
|
|
264
|
+
|
|
265
|
+
# Module docstring
|
|
266
|
+
module_docstring = self._extract_module_docstring(data)
|
|
267
|
+
if module_docstring:
|
|
268
|
+
lines.append("## Description")
|
|
269
|
+
lines.append(f'"{module_docstring}"')
|
|
270
|
+
lines.append("")
|
|
271
|
+
|
|
272
|
+
# Package information
|
|
273
|
+
package_info = data.get("package") or {}
|
|
274
|
+
package_name = package_info.get("name", "unknown")
|
|
275
|
+
if package_name and package_name != "unknown":
|
|
276
|
+
lines.append("## Package")
|
|
277
|
+
lines.append(f"`{package_name}`")
|
|
278
|
+
lines.append("")
|
|
279
|
+
|
|
280
|
+
# Imports
|
|
281
|
+
if imports:
|
|
282
|
+
lines.append("## Imports")
|
|
283
|
+
lines.append("```python")
|
|
284
|
+
for imp in imports:
|
|
285
|
+
import_statement = imp.get("raw_text", "")
|
|
286
|
+
if not import_statement:
|
|
287
|
+
# Fallback construction
|
|
288
|
+
module_name = imp.get("module_name", "")
|
|
289
|
+
name = imp.get("name", "")
|
|
290
|
+
if module_name:
|
|
291
|
+
import_statement = f"from {module_name} import {name}"
|
|
292
|
+
else:
|
|
293
|
+
import_statement = f"import {name}"
|
|
294
|
+
lines.append(import_statement)
|
|
295
|
+
lines.append("```")
|
|
296
|
+
lines.append("")
|
|
297
|
+
|
|
298
|
+
# Classes Overview or Class Info
|
|
299
|
+
if classes:
|
|
300
|
+
if len(classes) == 1:
|
|
301
|
+
# Single class - use Class Info format
|
|
302
|
+
class_info = classes[0]
|
|
303
|
+
if class_info is not None:
|
|
304
|
+
lines.append("## Class Info")
|
|
305
|
+
lines.append("| Property | Value |")
|
|
306
|
+
lines.append("|----------|-------|")
|
|
307
|
+
|
|
308
|
+
name = str(class_info.get("name", "Unknown"))
|
|
309
|
+
class_type = str(class_info.get("type", "class"))
|
|
310
|
+
visibility = str(class_info.get("visibility", "public"))
|
|
311
|
+
line_range = class_info.get("line_range") or {}
|
|
312
|
+
lines_str = (
|
|
313
|
+
f"{line_range.get('start', 0)}-{line_range.get('end', 0)}"
|
|
314
|
+
)
|
|
315
|
+
|
|
316
|
+
# Get statistics
|
|
317
|
+
stats = data.get("statistics", {})
|
|
318
|
+
method_count = stats.get("method_count", 0)
|
|
319
|
+
field_count = stats.get("field_count", 0)
|
|
320
|
+
|
|
321
|
+
lines.append(f"| Type | {class_type} |")
|
|
322
|
+
lines.append(f"| Visibility | {visibility} |")
|
|
323
|
+
lines.append(f"| Lines | {lines_str} |")
|
|
324
|
+
lines.append(f"| Total Methods | {method_count} |")
|
|
325
|
+
lines.append(f"| Total Fields | {field_count} |")
|
|
326
|
+
lines.append("")
|
|
327
|
+
else:
|
|
328
|
+
# Multiple classes - use Classes Overview format
|
|
329
|
+
lines.append("## Classes Overview")
|
|
330
|
+
lines.append("| Class | Type | Visibility | Lines | Methods | Fields |")
|
|
331
|
+
lines.append("|-------|------|------------|-------|---------|--------|")
|
|
332
|
+
|
|
333
|
+
for class_info in classes:
|
|
334
|
+
# Handle None class_info
|
|
335
|
+
if class_info is None:
|
|
336
|
+
continue
|
|
337
|
+
|
|
338
|
+
name = str(class_info.get("name", "Unknown"))
|
|
339
|
+
class_type = str(class_info.get("type", "class"))
|
|
340
|
+
visibility = str(class_info.get("visibility", "public"))
|
|
341
|
+
line_range = class_info.get("line_range") or {}
|
|
342
|
+
lines_str = (
|
|
343
|
+
f"{line_range.get('start', 0)}-{line_range.get('end', 0)}"
|
|
344
|
+
)
|
|
345
|
+
|
|
346
|
+
# Count methods/fields within the class range
|
|
347
|
+
class_methods = [
|
|
348
|
+
m
|
|
349
|
+
for m in data.get("methods", [])
|
|
350
|
+
if line_range.get("start", 0)
|
|
351
|
+
<= (m.get("line_range") or {}).get("start", 0)
|
|
352
|
+
<= line_range.get("end", 0)
|
|
353
|
+
]
|
|
354
|
+
class_fields = [
|
|
355
|
+
f
|
|
356
|
+
for f in data.get("fields", [])
|
|
357
|
+
if line_range.get("start", 0)
|
|
358
|
+
<= (f.get("line_range") or {}).get("start", 0)
|
|
359
|
+
<= line_range.get("end", 0)
|
|
360
|
+
]
|
|
361
|
+
|
|
362
|
+
lines.append(
|
|
363
|
+
f"| {name} | {class_type} | {visibility} | {lines_str} | {len(class_methods)} | {len(class_fields)} |"
|
|
364
|
+
)
|
|
365
|
+
lines.append("")
|
|
366
|
+
|
|
367
|
+
# Class-specific method sections
|
|
368
|
+
methods = data.get("methods", []) or functions
|
|
369
|
+
for class_info in classes:
|
|
370
|
+
if class_info is None:
|
|
371
|
+
continue
|
|
372
|
+
|
|
373
|
+
class_name = str(class_info.get("name", "Unknown"))
|
|
374
|
+
line_range = class_info.get("line_range") or {}
|
|
375
|
+
lines_str = f"{line_range.get('start', 0)}-{line_range.get('end', 0)}"
|
|
376
|
+
|
|
377
|
+
# Get methods for this class
|
|
378
|
+
class_methods = [
|
|
379
|
+
m
|
|
380
|
+
for m in methods
|
|
381
|
+
if line_range.get("start", 0)
|
|
382
|
+
<= (m.get("line_range") or {}).get("start", 0)
|
|
383
|
+
<= line_range.get("end", 0)
|
|
384
|
+
]
|
|
385
|
+
|
|
386
|
+
if class_methods:
|
|
387
|
+
lines.append(f"## {class_name} ({lines_str})")
|
|
388
|
+
lines.append("### Public Methods")
|
|
389
|
+
lines.append("| Method | Signature | Vis | Lines | Cx | Doc |")
|
|
390
|
+
lines.append("|--------|-----------|-----|-------|----|----| ")
|
|
391
|
+
|
|
392
|
+
for method in class_methods:
|
|
393
|
+
lines.append(self._format_class_method_row(method))
|
|
394
|
+
lines.append("")
|
|
395
|
+
|
|
396
|
+
# Module-level functions (not in any class)
|
|
397
|
+
module_functions = []
|
|
398
|
+
if classes:
|
|
399
|
+
# Find functions that are not within any class range
|
|
400
|
+
for method in methods:
|
|
401
|
+
# Skip None methods
|
|
402
|
+
if method is None:
|
|
403
|
+
continue
|
|
404
|
+
method_start = (method.get("line_range") or {}).get("start", 0)
|
|
405
|
+
is_in_class = False
|
|
406
|
+
for class_info in classes:
|
|
407
|
+
if class_info is None:
|
|
408
|
+
continue
|
|
409
|
+
class_range = class_info.get("line_range") or {}
|
|
410
|
+
if (
|
|
411
|
+
class_range.get("start", 0)
|
|
412
|
+
<= method_start
|
|
413
|
+
<= class_range.get("end", 0)
|
|
414
|
+
):
|
|
415
|
+
is_in_class = True
|
|
416
|
+
break
|
|
417
|
+
if not is_in_class:
|
|
418
|
+
module_functions.append(method)
|
|
419
|
+
else:
|
|
420
|
+
# No classes, all methods are module-level (filter out None)
|
|
421
|
+
module_functions = [m for m in methods if m is not None]
|
|
422
|
+
|
|
423
|
+
if module_functions:
|
|
424
|
+
lines.append("## Module Functions")
|
|
425
|
+
lines.append("| Method | Signature | Vis | Lines | Cx | Doc |")
|
|
426
|
+
lines.append("|--------|-----------|-----|-------|----|----| ")
|
|
427
|
+
|
|
428
|
+
for method in module_functions:
|
|
429
|
+
lines.append(self._format_class_method_row(method))
|
|
430
|
+
lines.append("")
|
|
431
|
+
|
|
432
|
+
# Trim trailing blank lines
|
|
433
|
+
while lines and lines[-1] == "":
|
|
434
|
+
lines.pop()
|
|
435
|
+
|
|
436
|
+
return "\n".join(lines)
|
|
437
|
+
|
|
438
|
+
def _format_compact_table(self, data: dict[str, Any]) -> str:
|
|
439
|
+
"""Compact table format for Python"""
|
|
440
|
+
lines = []
|
|
441
|
+
|
|
442
|
+
# Header - extract module/file name
|
|
443
|
+
file_path = data.get("file_path", "Unknown")
|
|
444
|
+
file_name = str(file_path).split("/")[-1].split("\\")[-1]
|
|
445
|
+
module_name = (
|
|
446
|
+
file_name.replace(".py", "").replace(".pyw", "").replace(".pyi", "")
|
|
447
|
+
)
|
|
448
|
+
|
|
449
|
+
classes = data.get("classes", [])
|
|
450
|
+
|
|
451
|
+
# Title logic for Python modules:
|
|
452
|
+
# - Single class: use class name directly
|
|
453
|
+
# - Multiple classes or no classes: use "Module: filename"
|
|
454
|
+
if len(classes) == 1:
|
|
455
|
+
class_name = classes[0].get("name", module_name)
|
|
456
|
+
lines.append(f"# {class_name}")
|
|
457
|
+
else:
|
|
458
|
+
lines.append(f"# Module: {module_name}")
|
|
459
|
+
lines.append("")
|
|
460
|
+
|
|
461
|
+
# Info
|
|
462
|
+
stats = data.get("statistics") or {}
|
|
463
|
+
lines.append("## Info")
|
|
464
|
+
lines.append("| Property | Value |")
|
|
465
|
+
lines.append("|----------|-------|")
|
|
466
|
+
lines.append(f"| Classes | {len(classes)} |")
|
|
467
|
+
lines.append(f"| Methods | {stats.get('method_count', 0)} |")
|
|
468
|
+
lines.append(f"| Fields | {stats.get('field_count', 0)} |")
|
|
469
|
+
lines.append("")
|
|
470
|
+
|
|
471
|
+
# Classes section (add class names for better visibility)
|
|
472
|
+
if classes:
|
|
473
|
+
lines.append("## Classes")
|
|
474
|
+
lines.append("| Class | Type | Lines |")
|
|
475
|
+
lines.append("|-------|------|-------|")
|
|
476
|
+
for class_info in classes:
|
|
477
|
+
if class_info is None:
|
|
478
|
+
continue
|
|
479
|
+
name = str(class_info.get("name", "Unknown"))
|
|
480
|
+
class_type = str(class_info.get("type", "class"))
|
|
481
|
+
line_range = class_info.get("line_range") or {}
|
|
482
|
+
lines_str = f"{line_range.get('start', 0)}-{line_range.get('end', 0)}"
|
|
483
|
+
lines.append(f"| {name} | {class_type} | {lines_str} |")
|
|
484
|
+
lines.append("")
|
|
485
|
+
|
|
486
|
+
# Methods (compact)
|
|
487
|
+
methods = data.get("methods", [])
|
|
488
|
+
if methods:
|
|
489
|
+
lines.append("## Methods")
|
|
490
|
+
lines.append("| Method | Sig | V | L | Cx | Doc |")
|
|
491
|
+
lines.append("|--------|-----|---|---|----|----|")
|
|
492
|
+
|
|
493
|
+
for method in methods:
|
|
494
|
+
name = str(method.get("name", ""))
|
|
495
|
+
signature = self._create_compact_signature(method)
|
|
496
|
+
visibility = self._convert_visibility(str(method.get("visibility", "")))
|
|
497
|
+
line_range = method.get("line_range") or {}
|
|
498
|
+
lines_str = f"{line_range.get('start', 0)}-{line_range.get('end', 0)}"
|
|
499
|
+
complexity = method.get("complexity_score", 0)
|
|
500
|
+
doc = self._clean_csv_text(
|
|
501
|
+
self._extract_doc_summary(str(method.get("javadoc", "")))
|
|
502
|
+
)
|
|
503
|
+
|
|
504
|
+
lines.append(
|
|
505
|
+
f"| {name} | {signature} | {visibility} | {lines_str} | {complexity} | {doc} |"
|
|
506
|
+
)
|
|
507
|
+
lines.append("")
|
|
508
|
+
|
|
509
|
+
# Trim trailing blank lines
|
|
510
|
+
while lines and lines[-1] == "":
|
|
511
|
+
lines.pop()
|
|
512
|
+
|
|
513
|
+
return "\n".join(lines)
|
|
514
|
+
|
|
515
|
+
def _format_method_row(self, method: dict[str, Any]) -> str:
|
|
516
|
+
"""Format a method table row for Python"""
|
|
517
|
+
name = str(method.get("name", ""))
|
|
518
|
+
signature = self._format_python_signature(method)
|
|
519
|
+
|
|
520
|
+
# Python-specific visibility handling
|
|
521
|
+
visibility = method.get("visibility", "public")
|
|
522
|
+
if name.startswith("__") and name.endswith("__"):
|
|
523
|
+
visibility = "magic"
|
|
524
|
+
elif name.startswith("_"):
|
|
525
|
+
visibility = "private"
|
|
526
|
+
|
|
527
|
+
vis_symbol = self._get_python_visibility_symbol(visibility)
|
|
528
|
+
|
|
529
|
+
line_range = method.get("line_range") or {}
|
|
530
|
+
if not line_range or not isinstance(line_range, dict):
|
|
531
|
+
start_line = method.get("start_line", 0)
|
|
532
|
+
end_line = method.get("end_line", 0)
|
|
533
|
+
lines_str = f"{start_line}-{end_line}"
|
|
534
|
+
else:
|
|
535
|
+
lines_str = f"{line_range.get('start', 0)}-{line_range.get('end', 0)}"
|
|
536
|
+
|
|
537
|
+
cols_str = "5-6" # default placeholder
|
|
538
|
+
complexity = method.get("complexity_score", 0)
|
|
539
|
+
|
|
540
|
+
# Use docstring instead of javadoc
|
|
541
|
+
doc = self._clean_csv_text(
|
|
542
|
+
self._extract_doc_summary(str(method.get("docstring", "")))
|
|
543
|
+
)
|
|
544
|
+
|
|
545
|
+
# Add decorators info
|
|
546
|
+
decorators = method.get("modifiers", []) or method.get("decorators", [])
|
|
547
|
+
decorator_str = self._format_decorators(decorators)
|
|
548
|
+
|
|
549
|
+
# Add async indicator
|
|
550
|
+
async_indicator = "🔄" if method.get("is_async", False) else ""
|
|
551
|
+
|
|
552
|
+
return f"| {name}{async_indicator} | {signature} | {vis_symbol} | {lines_str} | {cols_str} | {complexity} | {decorator_str} | {doc} |"
|
|
553
|
+
|
|
554
|
+
def _create_compact_signature(self, method: dict[str, Any]) -> str:
|
|
555
|
+
"""Create compact method signature for Python"""
|
|
556
|
+
if method is None or not isinstance(method, dict):
|
|
557
|
+
raise TypeError(f"Expected dict, got {type(method)}")
|
|
558
|
+
|
|
559
|
+
params = method.get("parameters", [])
|
|
560
|
+
param_types = []
|
|
561
|
+
|
|
562
|
+
# Handle both list and string parameters
|
|
563
|
+
if isinstance(params, str):
|
|
564
|
+
# If parameters is a malformed string, skip it
|
|
565
|
+
pass
|
|
566
|
+
elif isinstance(params, list):
|
|
567
|
+
for p in params:
|
|
568
|
+
if isinstance(p, dict):
|
|
569
|
+
param_type = p.get("type", "Any")
|
|
570
|
+
if param_type == "Any" or param_type is None:
|
|
571
|
+
param_types.append(
|
|
572
|
+
"Any"
|
|
573
|
+
) # Keep "Any" as is for missing type info
|
|
574
|
+
else:
|
|
575
|
+
param_types.append(
|
|
576
|
+
param_type
|
|
577
|
+
) # Don't shorten types for consistency
|
|
578
|
+
else:
|
|
579
|
+
param_types.append("Any") # Use "Any" for missing type info
|
|
580
|
+
|
|
581
|
+
params_str = ",".join(param_types)
|
|
582
|
+
return_type = method.get("return_type", "Any")
|
|
583
|
+
|
|
584
|
+
# Handle dict return type
|
|
585
|
+
if isinstance(return_type, dict):
|
|
586
|
+
return_type = return_type.get("type", "Any") or str(return_type)
|
|
587
|
+
elif not isinstance(return_type, str):
|
|
588
|
+
return_type = str(return_type)
|
|
589
|
+
|
|
590
|
+
return f"({params_str}):{return_type}"
|
|
591
|
+
|
|
592
|
+
def _shorten_type(self, type_name: Any) -> str:
|
|
593
|
+
"""Shorten type name for Python tables"""
|
|
594
|
+
if type_name is None:
|
|
595
|
+
return "Any" # Return "Any" instead of "A" for None
|
|
596
|
+
|
|
597
|
+
if not isinstance(type_name, str):
|
|
598
|
+
type_name = str(type_name)
|
|
599
|
+
|
|
600
|
+
type_mapping = {
|
|
601
|
+
"str": "s",
|
|
602
|
+
"int": "i",
|
|
603
|
+
"float": "f",
|
|
604
|
+
"bool": "b",
|
|
605
|
+
"None": "N",
|
|
606
|
+
"Any": "A",
|
|
607
|
+
"List": "L",
|
|
608
|
+
"Dict": "D",
|
|
609
|
+
"Optional": "O",
|
|
610
|
+
"Union": "U", # Changed from "Uni" to "U"
|
|
611
|
+
"Calculator": "Calculator", # Keep full name for Calculator
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
# List[str] -> L[s]
|
|
615
|
+
if "List[" in type_name:
|
|
616
|
+
result = (
|
|
617
|
+
type_name.replace("List[", "L[").replace("str", "s").replace("int", "i")
|
|
618
|
+
)
|
|
619
|
+
return str(result)
|
|
620
|
+
|
|
621
|
+
# Dict[str, int] -> D[s,i] (no space after comma)
|
|
622
|
+
if "Dict[" in type_name:
|
|
623
|
+
result = (
|
|
624
|
+
type_name.replace("Dict[", "D[").replace("str", "s").replace("int", "i")
|
|
625
|
+
)
|
|
626
|
+
# Remove spaces after commas for compact format
|
|
627
|
+
result = result.replace(", ", ",")
|
|
628
|
+
return str(result)
|
|
629
|
+
|
|
630
|
+
# Optional[float] -> O[f], Optional[str] -> O[s]
|
|
631
|
+
if "Optional[" in type_name:
|
|
632
|
+
result = (
|
|
633
|
+
type_name.replace("Optional[", "O[")
|
|
634
|
+
.replace("str", "s")
|
|
635
|
+
.replace("float", "f")
|
|
636
|
+
)
|
|
637
|
+
return str(result)
|
|
638
|
+
|
|
639
|
+
result = type_mapping.get(
|
|
640
|
+
type_name, type_name[:3] if len(type_name) > 3 else type_name
|
|
641
|
+
)
|
|
642
|
+
return str(result)
|
|
643
|
+
|
|
644
|
+
def _extract_module_docstring(self, data: dict[str, Any]) -> str | None:
|
|
645
|
+
"""Extract module-level docstring"""
|
|
646
|
+
# Look for module docstring in the first few lines
|
|
647
|
+
source_code = data.get("source_code", "")
|
|
648
|
+
if not source_code:
|
|
649
|
+
return None
|
|
650
|
+
|
|
651
|
+
lines = source_code.split("\n")
|
|
652
|
+
for i, line in enumerate(lines[:10]): # Check first 10 lines
|
|
653
|
+
stripped = line.strip()
|
|
654
|
+
if stripped.startswith('"""') or stripped.startswith("'''"):
|
|
655
|
+
quote_type = '"""' if stripped.startswith('"""') else "'''"
|
|
656
|
+
|
|
657
|
+
# Single line docstring
|
|
658
|
+
if stripped.count(quote_type) >= 2:
|
|
659
|
+
return str(stripped.replace(quote_type, "").strip())
|
|
660
|
+
|
|
661
|
+
# Multi-line docstring
|
|
662
|
+
docstring_lines = [stripped.replace(quote_type, "")]
|
|
663
|
+
for j in range(i + 1, len(lines)):
|
|
664
|
+
next_line = lines[j]
|
|
665
|
+
if quote_type in next_line:
|
|
666
|
+
docstring_lines.append(next_line.replace(quote_type, ""))
|
|
667
|
+
break
|
|
668
|
+
docstring_lines.append(next_line)
|
|
669
|
+
|
|
670
|
+
return "\n".join(docstring_lines).strip()
|
|
671
|
+
|
|
672
|
+
return None
|
|
673
|
+
|
|
674
|
+
def _format_python_signature(self, method: dict[str, Any]) -> str:
|
|
675
|
+
"""Create Python method signature"""
|
|
676
|
+
params = method.get("parameters", [])
|
|
677
|
+
if params is None:
|
|
678
|
+
params = []
|
|
679
|
+
param_strs = []
|
|
680
|
+
|
|
681
|
+
for p in params:
|
|
682
|
+
if isinstance(p, dict):
|
|
683
|
+
param_name = p.get("name", "")
|
|
684
|
+
param_type = p.get("type", "")
|
|
685
|
+
if param_type:
|
|
686
|
+
param_strs.append(f"{param_name}: {param_type}")
|
|
687
|
+
else:
|
|
688
|
+
param_strs.append(param_name)
|
|
689
|
+
else:
|
|
690
|
+
param_strs.append(str(p))
|
|
691
|
+
|
|
692
|
+
params_str = ", ".join(param_strs)
|
|
693
|
+
return_type = method.get("return_type", "")
|
|
694
|
+
|
|
695
|
+
if return_type and return_type != "Any":
|
|
696
|
+
return f"({params_str}) -> {return_type}"
|
|
697
|
+
else:
|
|
698
|
+
return f"({params_str})"
|
|
699
|
+
|
|
700
|
+
def _get_python_visibility_symbol(self, visibility: str) -> str:
|
|
701
|
+
"""Get Python visibility symbol"""
|
|
702
|
+
visibility_map = {
|
|
703
|
+
"public": "🔓",
|
|
704
|
+
"private": "🔒",
|
|
705
|
+
"protected": "🔐",
|
|
706
|
+
"magic": "✨",
|
|
707
|
+
}
|
|
708
|
+
return visibility_map.get(visibility, "🔓")
|
|
709
|
+
|
|
710
|
+
def _format_decorators(self, decorators: list[str]) -> str:
|
|
711
|
+
"""Format Python decorators"""
|
|
712
|
+
if not decorators:
|
|
713
|
+
return "-"
|
|
714
|
+
|
|
715
|
+
# Show important decorators
|
|
716
|
+
important = [
|
|
717
|
+
"property",
|
|
718
|
+
"staticmethod",
|
|
719
|
+
"classmethod",
|
|
720
|
+
"dataclass",
|
|
721
|
+
"abstractmethod",
|
|
722
|
+
]
|
|
723
|
+
shown_decorators = []
|
|
724
|
+
|
|
725
|
+
for dec in decorators:
|
|
726
|
+
if any(imp in dec for imp in important):
|
|
727
|
+
shown_decorators.append(f"@{dec}")
|
|
728
|
+
|
|
729
|
+
if shown_decorators:
|
|
730
|
+
return ", ".join(shown_decorators)
|
|
731
|
+
elif len(decorators) == 1:
|
|
732
|
+
return f"@{decorators[0]}"
|
|
733
|
+
else:
|
|
734
|
+
return f"@{decorators[0]} (+{len(decorators) - 1})"
|
|
735
|
+
|
|
736
|
+
def _format_class_method_row(self, method: dict[str, Any]) -> str:
|
|
737
|
+
"""Format a method table row for class-specific sections"""
|
|
738
|
+
name = str(method.get("name", ""))
|
|
739
|
+
signature = self._format_python_signature_compact(method)
|
|
740
|
+
|
|
741
|
+
# Python-specific visibility handling
|
|
742
|
+
visibility = method.get("visibility", "public")
|
|
743
|
+
if name.startswith("__") and name.endswith("__"):
|
|
744
|
+
visibility = "magic"
|
|
745
|
+
elif name.startswith("_"):
|
|
746
|
+
visibility = "private"
|
|
747
|
+
|
|
748
|
+
# Use simple + symbol for visibility
|
|
749
|
+
vis_symbol = "+" if visibility == "public" or visibility == "magic" else "-"
|
|
750
|
+
|
|
751
|
+
line_range = method.get("line_range") or {}
|
|
752
|
+
# Handle malformed line_range (could be string)
|
|
753
|
+
if isinstance(line_range, dict):
|
|
754
|
+
lines_str = f"{line_range.get('start', 0)}-{line_range.get('end', 0)}"
|
|
755
|
+
else:
|
|
756
|
+
lines_str = "0-0" # Fallback for malformed data
|
|
757
|
+
|
|
758
|
+
complexity = method.get("complexity_score", 0)
|
|
759
|
+
|
|
760
|
+
# Use docstring for doc - ensure we get the correct docstring for this specific method
|
|
761
|
+
docstring = method.get("docstring", "")
|
|
762
|
+
method_name = method.get("name", "")
|
|
763
|
+
|
|
764
|
+
# Special handling for __init__ methods - they often get wrong docstrings from tree-sitter
|
|
765
|
+
if method_name == "__init__":
|
|
766
|
+
# For __init__ methods, be more strict about docstring validation
|
|
767
|
+
if (
|
|
768
|
+
docstring
|
|
769
|
+
and str(docstring).strip()
|
|
770
|
+
and str(docstring).strip() != "None"
|
|
771
|
+
):
|
|
772
|
+
# Check if the docstring seems to belong to this method
|
|
773
|
+
# If it contains class-specific terms that don't match __init__, it's likely wrong
|
|
774
|
+
docstring_text = str(docstring).strip()
|
|
775
|
+
if any(
|
|
776
|
+
word in docstring_text.lower()
|
|
777
|
+
for word in ["bark", "meow", "fetch", "purr"]
|
|
778
|
+
):
|
|
779
|
+
# This looks like it belongs to another method, not __init__
|
|
780
|
+
doc = "-"
|
|
781
|
+
else:
|
|
782
|
+
doc = self._extract_doc_summary(docstring_text)
|
|
783
|
+
else:
|
|
784
|
+
doc = "-"
|
|
785
|
+
else:
|
|
786
|
+
# For non-__init__ methods, use normal processing
|
|
787
|
+
if (
|
|
788
|
+
docstring
|
|
789
|
+
and str(docstring).strip()
|
|
790
|
+
and str(docstring).strip() != "None"
|
|
791
|
+
):
|
|
792
|
+
doc = self._extract_doc_summary(str(docstring))
|
|
793
|
+
else:
|
|
794
|
+
doc = "-"
|
|
795
|
+
|
|
796
|
+
# Add static modifier if applicable
|
|
797
|
+
modifiers = []
|
|
798
|
+
if method.get("is_static", False):
|
|
799
|
+
modifiers.append("static")
|
|
800
|
+
|
|
801
|
+
modifier_str = f" [{', '.join(modifiers)}]" if modifiers else ""
|
|
802
|
+
|
|
803
|
+
return f"| {name} | {signature}{modifier_str} | {vis_symbol} | {lines_str} | {complexity} | {doc} |"
|
|
804
|
+
|
|
805
|
+
def _format_python_signature_compact(self, method: dict[str, Any]) -> str:
|
|
806
|
+
"""Create compact Python method signature for class sections"""
|
|
807
|
+
params = method.get("parameters", [])
|
|
808
|
+
if params is None:
|
|
809
|
+
params = []
|
|
810
|
+
param_strs = []
|
|
811
|
+
|
|
812
|
+
for p in params:
|
|
813
|
+
if isinstance(p, dict):
|
|
814
|
+
param_name = p.get("name", "")
|
|
815
|
+
param_type = p.get("type", "")
|
|
816
|
+
if param_type and param_type != "Any":
|
|
817
|
+
param_strs.append(f"{param_name}:{param_type}")
|
|
818
|
+
else:
|
|
819
|
+
# Include type hint as "Any" for all parameters including self
|
|
820
|
+
param_strs.append(f"{param_name}:Any")
|
|
821
|
+
else:
|
|
822
|
+
param_strs.append(str(p))
|
|
823
|
+
|
|
824
|
+
params_str = ", ".join(param_strs)
|
|
825
|
+
return_type = method.get("return_type", "")
|
|
826
|
+
|
|
827
|
+
if return_type and return_type != "Any":
|
|
828
|
+
return f"({params_str}):{return_type}"
|
|
829
|
+
else:
|
|
830
|
+
return f"({params_str}):Any"
|