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,536 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
SQL-Specific Formatters
|
|
4
|
+
|
|
5
|
+
Provides SQL-specific output formatting to replace generic class-based format
|
|
6
|
+
with database-appropriate terminology and comprehensive element representation.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from typing import Any
|
|
10
|
+
|
|
11
|
+
from ..models import (
|
|
12
|
+
SQLElement,
|
|
13
|
+
SQLElementType,
|
|
14
|
+
SQLFunction,
|
|
15
|
+
SQLIndex,
|
|
16
|
+
SQLProcedure,
|
|
17
|
+
SQLTable,
|
|
18
|
+
SQLTrigger,
|
|
19
|
+
SQLView,
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class SQLFormatterBase:
|
|
24
|
+
"""Base class for SQL-specific formatters"""
|
|
25
|
+
|
|
26
|
+
def format_elements(self, elements: list[SQLElement], file_path: str = "") -> str:
|
|
27
|
+
"""Format SQL elements with appropriate terminology"""
|
|
28
|
+
if not elements:
|
|
29
|
+
return self._format_empty_file(file_path)
|
|
30
|
+
|
|
31
|
+
# Group elements by type
|
|
32
|
+
grouped_elements = self.group_elements_by_type(elements)
|
|
33
|
+
|
|
34
|
+
# Format based on specific formatter type
|
|
35
|
+
return self._format_grouped_elements(grouped_elements, file_path)
|
|
36
|
+
|
|
37
|
+
def group_elements_by_type(
|
|
38
|
+
self, elements: list[SQLElement]
|
|
39
|
+
) -> dict[SQLElementType, list[SQLElement]]:
|
|
40
|
+
"""Group elements by SQL type"""
|
|
41
|
+
grouped: dict[SQLElementType, list[SQLElement]] = {}
|
|
42
|
+
for element in elements:
|
|
43
|
+
element_type = element.sql_element_type
|
|
44
|
+
if element_type not in grouped:
|
|
45
|
+
grouped[element_type] = []
|
|
46
|
+
grouped[element_type].append(element)
|
|
47
|
+
return grouped
|
|
48
|
+
|
|
49
|
+
def _format_empty_file(self, file_path: str) -> str:
|
|
50
|
+
"""Format empty SQL file"""
|
|
51
|
+
filename = file_path.split("/")[-1] if file_path else "unknown.sql"
|
|
52
|
+
return f"# {filename}\n\nNo SQL elements found."
|
|
53
|
+
|
|
54
|
+
def _format_grouped_elements(
|
|
55
|
+
self, grouped_elements: dict[SQLElementType, list[SQLElement]], file_path: str
|
|
56
|
+
) -> str:
|
|
57
|
+
"""Format grouped elements - to be implemented by subclasses"""
|
|
58
|
+
raise NotImplementedError("Subclasses must implement _format_grouped_elements")
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
class SQLFullFormatter(SQLFormatterBase):
|
|
62
|
+
"""Comprehensive SQL format with detailed metadata"""
|
|
63
|
+
|
|
64
|
+
def format_analysis_result(
|
|
65
|
+
self, analysis_result: Any, table_type: str = "full"
|
|
66
|
+
) -> str:
|
|
67
|
+
"""Format AnalysisResult directly for SQL files."""
|
|
68
|
+
if not analysis_result or not analysis_result.elements:
|
|
69
|
+
return self._format_empty_file(
|
|
70
|
+
analysis_result.file_path if analysis_result else ""
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
# Filter only SQL elements
|
|
74
|
+
sql_elements = [
|
|
75
|
+
e for e in analysis_result.elements if hasattr(e, "sql_element_type")
|
|
76
|
+
]
|
|
77
|
+
|
|
78
|
+
if not sql_elements:
|
|
79
|
+
return self._format_empty_file(analysis_result.file_path)
|
|
80
|
+
|
|
81
|
+
return self.format_elements(sql_elements, analysis_result.file_path)
|
|
82
|
+
|
|
83
|
+
def _format_grouped_elements(
|
|
84
|
+
self, grouped_elements: dict[SQLElementType, list[SQLElement]], file_path: str
|
|
85
|
+
) -> str:
|
|
86
|
+
"""Format elements in full detail format"""
|
|
87
|
+
filename = file_path.split("/")[-1] if file_path else "unknown.sql"
|
|
88
|
+
output = [f"# {filename}", ""]
|
|
89
|
+
|
|
90
|
+
# Overview table
|
|
91
|
+
output.extend(self._format_overview_table(grouped_elements))
|
|
92
|
+
output.append("")
|
|
93
|
+
|
|
94
|
+
# Detailed sections for each element type
|
|
95
|
+
type_order = [
|
|
96
|
+
SQLElementType.TABLE,
|
|
97
|
+
SQLElementType.VIEW,
|
|
98
|
+
SQLElementType.PROCEDURE,
|
|
99
|
+
SQLElementType.FUNCTION,
|
|
100
|
+
SQLElementType.TRIGGER,
|
|
101
|
+
SQLElementType.INDEX,
|
|
102
|
+
]
|
|
103
|
+
|
|
104
|
+
for element_type in type_order:
|
|
105
|
+
if element_type in grouped_elements:
|
|
106
|
+
section = self._format_element_section(
|
|
107
|
+
element_type, grouped_elements[element_type]
|
|
108
|
+
)
|
|
109
|
+
if section:
|
|
110
|
+
output.extend(section)
|
|
111
|
+
output.append("")
|
|
112
|
+
|
|
113
|
+
return "\n".join(output).rstrip() + "\n"
|
|
114
|
+
|
|
115
|
+
def _format_overview_table(
|
|
116
|
+
self, grouped_elements: dict[SQLElementType, list[SQLElement]]
|
|
117
|
+
) -> list[str]:
|
|
118
|
+
"""Create overview table with SQL terminology"""
|
|
119
|
+
output = [
|
|
120
|
+
"## Database Schema Overview",
|
|
121
|
+
"| Element | Type | Lines | Columns/Parameters | Dependencies |",
|
|
122
|
+
"|---------|------|-------|-------------------|--------------|",
|
|
123
|
+
]
|
|
124
|
+
|
|
125
|
+
# Sort elements by line number for consistent output
|
|
126
|
+
all_elements = []
|
|
127
|
+
for elements in grouped_elements.values():
|
|
128
|
+
all_elements.extend(elements)
|
|
129
|
+
all_elements.sort(key=lambda x: x.start_line)
|
|
130
|
+
|
|
131
|
+
for element in all_elements:
|
|
132
|
+
line_range = f"{element.start_line}-{element.end_line}"
|
|
133
|
+
|
|
134
|
+
# Format columns/parameters info
|
|
135
|
+
if hasattr(element, "columns") and element.columns:
|
|
136
|
+
details = f"{len(element.columns)} columns"
|
|
137
|
+
elif hasattr(element, "parameters") and element.parameters:
|
|
138
|
+
# Clean parameter names for display
|
|
139
|
+
param_names = []
|
|
140
|
+
for param in element.parameters:
|
|
141
|
+
if hasattr(param, "name") and param.name:
|
|
142
|
+
# Only include valid parameter names, skip SQL keywords
|
|
143
|
+
if param.name.upper() not in (
|
|
144
|
+
"SELECT",
|
|
145
|
+
"FROM",
|
|
146
|
+
"WHERE",
|
|
147
|
+
"INTO",
|
|
148
|
+
"VALUES",
|
|
149
|
+
"SET",
|
|
150
|
+
"UPDATE",
|
|
151
|
+
"INSERT",
|
|
152
|
+
"DELETE",
|
|
153
|
+
"PENDING",
|
|
154
|
+
):
|
|
155
|
+
param_names.append(param.name)
|
|
156
|
+
if param_names:
|
|
157
|
+
details = f"({', '.join(param_names)})"
|
|
158
|
+
else:
|
|
159
|
+
details = f"{len(element.parameters)} parameters"
|
|
160
|
+
elif hasattr(element, "indexed_columns") and element.indexed_columns:
|
|
161
|
+
col_info = f"({', '.join(element.indexed_columns)})"
|
|
162
|
+
if hasattr(element, "table_name") and element.table_name:
|
|
163
|
+
col_info = f"{element.table_name}{col_info}"
|
|
164
|
+
details = col_info
|
|
165
|
+
elif hasattr(element, "source_tables") and element.source_tables:
|
|
166
|
+
details = f"from {', '.join(element.source_tables)}"
|
|
167
|
+
else:
|
|
168
|
+
details = "-"
|
|
169
|
+
|
|
170
|
+
# Format dependencies
|
|
171
|
+
deps = ", ".join(element.dependencies) if element.dependencies else "-"
|
|
172
|
+
|
|
173
|
+
output.append(
|
|
174
|
+
f"| {element.name} | {element.sql_element_type.value} | {line_range} | {details} | {deps} |"
|
|
175
|
+
)
|
|
176
|
+
|
|
177
|
+
return output
|
|
178
|
+
|
|
179
|
+
def _format_element_section(
|
|
180
|
+
self, element_type: SQLElementType, elements: list[SQLElement]
|
|
181
|
+
) -> list[str]:
|
|
182
|
+
"""Format detailed section for specific element type"""
|
|
183
|
+
if not elements:
|
|
184
|
+
return []
|
|
185
|
+
|
|
186
|
+
section_title = self._get_section_title(element_type)
|
|
187
|
+
output = [f"## {section_title}"]
|
|
188
|
+
|
|
189
|
+
for element in sorted(elements, key=lambda x: x.start_line):
|
|
190
|
+
output.extend(self._format_element_details(element))
|
|
191
|
+
output.append("")
|
|
192
|
+
|
|
193
|
+
return output[:-1] # Remove last empty line
|
|
194
|
+
|
|
195
|
+
def _get_section_title(self, element_type: SQLElementType) -> str:
|
|
196
|
+
"""Get section title for element type"""
|
|
197
|
+
titles = {
|
|
198
|
+
SQLElementType.TABLE: "Tables",
|
|
199
|
+
SQLElementType.VIEW: "Views",
|
|
200
|
+
SQLElementType.PROCEDURE: "Procedures",
|
|
201
|
+
SQLElementType.FUNCTION: "Functions",
|
|
202
|
+
SQLElementType.TRIGGER: "Triggers",
|
|
203
|
+
SQLElementType.INDEX: "Indexes",
|
|
204
|
+
}
|
|
205
|
+
return titles.get(element_type, element_type.value.title() + "s")
|
|
206
|
+
|
|
207
|
+
def _format_element_details(self, element: SQLElement) -> list[str]:
|
|
208
|
+
"""Format detailed information for a single element"""
|
|
209
|
+
output = [f"### {element.name} ({element.start_line}-{element.end_line})"]
|
|
210
|
+
|
|
211
|
+
if isinstance(element, SQLTable):
|
|
212
|
+
output.extend(self._format_table_details(element))
|
|
213
|
+
elif isinstance(element, SQLView):
|
|
214
|
+
output.extend(self._format_view_details(element))
|
|
215
|
+
elif isinstance(element, SQLProcedure):
|
|
216
|
+
output.extend(self._format_procedure_details(element))
|
|
217
|
+
elif isinstance(element, SQLFunction):
|
|
218
|
+
output.extend(self._format_function_details(element))
|
|
219
|
+
elif isinstance(element, SQLTrigger):
|
|
220
|
+
output.extend(self._format_trigger_details(element))
|
|
221
|
+
elif isinstance(element, SQLIndex):
|
|
222
|
+
output.extend(self._format_index_details(element))
|
|
223
|
+
|
|
224
|
+
return output
|
|
225
|
+
|
|
226
|
+
def _format_table_details(self, table: SQLTable) -> list[str]:
|
|
227
|
+
"""Format table-specific details"""
|
|
228
|
+
output = []
|
|
229
|
+
|
|
230
|
+
if table.columns:
|
|
231
|
+
column_names = [col.name for col in table.columns]
|
|
232
|
+
output.append(f"**Columns**: {', '.join(column_names)}")
|
|
233
|
+
|
|
234
|
+
# Primary keys
|
|
235
|
+
pk_columns = table.get_primary_key_columns()
|
|
236
|
+
if pk_columns:
|
|
237
|
+
output.append(f"**Primary Key**: {', '.join(pk_columns)}")
|
|
238
|
+
|
|
239
|
+
# Foreign keys
|
|
240
|
+
fk_columns = table.get_foreign_key_columns()
|
|
241
|
+
if fk_columns:
|
|
242
|
+
fk_details = []
|
|
243
|
+
for col in table.columns:
|
|
244
|
+
if col.is_foreign_key and col.foreign_key_reference:
|
|
245
|
+
fk_details.append(f"{col.name} → {col.foreign_key_reference}")
|
|
246
|
+
if fk_details:
|
|
247
|
+
output.append(f"**Foreign Keys**: {', '.join(fk_details)}")
|
|
248
|
+
|
|
249
|
+
if table.constraints:
|
|
250
|
+
constraint_types = [c.constraint_type for c in table.constraints]
|
|
251
|
+
output.append(f"**Constraints**: {', '.join(set(constraint_types))}")
|
|
252
|
+
|
|
253
|
+
return output
|
|
254
|
+
|
|
255
|
+
def _format_view_details(self, view: SQLView) -> list[str]:
|
|
256
|
+
"""Format view-specific details"""
|
|
257
|
+
output = []
|
|
258
|
+
|
|
259
|
+
if view.source_tables:
|
|
260
|
+
output.append(f"**Source Tables**: {', '.join(view.source_tables)}")
|
|
261
|
+
|
|
262
|
+
if view.columns:
|
|
263
|
+
column_names = [col.name for col in view.columns]
|
|
264
|
+
output.append(f"**Columns**: {', '.join(column_names)}")
|
|
265
|
+
|
|
266
|
+
return output
|
|
267
|
+
|
|
268
|
+
def _format_procedure_details(self, procedure: SQLProcedure) -> list[str]:
|
|
269
|
+
"""Format procedure-specific details"""
|
|
270
|
+
output = []
|
|
271
|
+
|
|
272
|
+
if procedure.parameters:
|
|
273
|
+
param_details = []
|
|
274
|
+
for param in procedure.parameters:
|
|
275
|
+
param_str = f"{param.name} {param.data_type}"
|
|
276
|
+
if param.direction != "IN":
|
|
277
|
+
param_str = f"{param.direction} {param_str}"
|
|
278
|
+
param_details.append(param_str)
|
|
279
|
+
output.append(f"**Parameters**: {', '.join(param_details)}")
|
|
280
|
+
|
|
281
|
+
if procedure.dependencies:
|
|
282
|
+
output.append(f"**Dependencies**: {', '.join(procedure.dependencies)}")
|
|
283
|
+
|
|
284
|
+
return output
|
|
285
|
+
|
|
286
|
+
def _format_function_details(self, function: SQLFunction) -> list[str]:
|
|
287
|
+
"""Format function-specific details"""
|
|
288
|
+
output = []
|
|
289
|
+
|
|
290
|
+
if function.parameters:
|
|
291
|
+
param_details = []
|
|
292
|
+
for param in function.parameters:
|
|
293
|
+
param_str = f"{param.name} {param.data_type}"
|
|
294
|
+
if param.direction != "IN":
|
|
295
|
+
param_str = f"{param.direction} {param_str}"
|
|
296
|
+
param_details.append(param_str)
|
|
297
|
+
output.append(f"**Parameters**: {', '.join(param_details)}")
|
|
298
|
+
|
|
299
|
+
if function.return_type:
|
|
300
|
+
output.append(f"**Returns**: {function.return_type}")
|
|
301
|
+
|
|
302
|
+
if function.dependencies:
|
|
303
|
+
output.append(f"**Dependencies**: {', '.join(function.dependencies)}")
|
|
304
|
+
|
|
305
|
+
return output
|
|
306
|
+
|
|
307
|
+
def _format_trigger_details(self, trigger: SQLTrigger) -> list[str]:
|
|
308
|
+
"""Format trigger-specific details"""
|
|
309
|
+
output = []
|
|
310
|
+
|
|
311
|
+
if trigger.trigger_timing and trigger.trigger_event:
|
|
312
|
+
output.append(
|
|
313
|
+
f"**Event**: {trigger.trigger_timing} {trigger.trigger_event}"
|
|
314
|
+
)
|
|
315
|
+
|
|
316
|
+
if trigger.table_name:
|
|
317
|
+
output.append(f"**Target Table**: {trigger.table_name}")
|
|
318
|
+
|
|
319
|
+
if trigger.dependencies:
|
|
320
|
+
output.append(f"**Dependencies**: {', '.join(trigger.dependencies)}")
|
|
321
|
+
|
|
322
|
+
return output
|
|
323
|
+
|
|
324
|
+
def _format_index_details(self, index: SQLIndex) -> list[str]:
|
|
325
|
+
"""Format index-specific details"""
|
|
326
|
+
output = []
|
|
327
|
+
|
|
328
|
+
if index.table_name:
|
|
329
|
+
output.append(f"**Table**: {index.table_name}")
|
|
330
|
+
|
|
331
|
+
if index.indexed_columns:
|
|
332
|
+
output.append(f"**Columns**: {', '.join(index.indexed_columns)}")
|
|
333
|
+
|
|
334
|
+
if index.is_unique:
|
|
335
|
+
output.append("**Type**: Unique index")
|
|
336
|
+
else:
|
|
337
|
+
output.append("**Type**: Standard index")
|
|
338
|
+
|
|
339
|
+
return output
|
|
340
|
+
|
|
341
|
+
|
|
342
|
+
class SQLCompactFormatter(SQLFormatterBase):
|
|
343
|
+
"""Compact SQL format for quick overview"""
|
|
344
|
+
|
|
345
|
+
def format_analysis_result(
|
|
346
|
+
self, analysis_result: Any, table_type: str = "compact"
|
|
347
|
+
) -> str:
|
|
348
|
+
"""Format AnalysisResult directly for SQL files."""
|
|
349
|
+
if not analysis_result or not analysis_result.elements:
|
|
350
|
+
return self._format_empty_file(
|
|
351
|
+
analysis_result.file_path if analysis_result else ""
|
|
352
|
+
)
|
|
353
|
+
|
|
354
|
+
# Filter only SQL elements
|
|
355
|
+
sql_elements = [
|
|
356
|
+
e for e in analysis_result.elements if hasattr(e, "sql_element_type")
|
|
357
|
+
]
|
|
358
|
+
|
|
359
|
+
if not sql_elements:
|
|
360
|
+
return self._format_empty_file(analysis_result.file_path)
|
|
361
|
+
|
|
362
|
+
return self.format_elements(sql_elements, analysis_result.file_path)
|
|
363
|
+
|
|
364
|
+
def _format_grouped_elements(
|
|
365
|
+
self, grouped_elements: dict[SQLElementType, list[SQLElement]], file_path: str
|
|
366
|
+
) -> str:
|
|
367
|
+
"""Format elements in compact table format"""
|
|
368
|
+
filename = file_path.split("/")[-1] if file_path else "unknown.sql"
|
|
369
|
+
output = [
|
|
370
|
+
f"# {filename}",
|
|
371
|
+
"",
|
|
372
|
+
"| Element | Type | Lines | Details |",
|
|
373
|
+
"|---------|------|-------|---------|",
|
|
374
|
+
]
|
|
375
|
+
|
|
376
|
+
# Sort all elements by line number
|
|
377
|
+
all_elements = []
|
|
378
|
+
for elements in grouped_elements.values():
|
|
379
|
+
all_elements.extend(elements)
|
|
380
|
+
all_elements.sort(key=lambda x: x.start_line)
|
|
381
|
+
|
|
382
|
+
for element in all_elements:
|
|
383
|
+
line_range = f"{element.start_line}-{element.end_line}"
|
|
384
|
+
details = self._format_compact_details(element)
|
|
385
|
+
output.append(
|
|
386
|
+
f"| {element.name} | {element.sql_element_type.value} | {line_range} | {details} |"
|
|
387
|
+
)
|
|
388
|
+
|
|
389
|
+
return "\n".join(output) + "\n"
|
|
390
|
+
|
|
391
|
+
def _format_compact_details(self, element: SQLElement) -> str:
|
|
392
|
+
"""Format compact details for an element"""
|
|
393
|
+
if isinstance(element, SQLTable):
|
|
394
|
+
details = []
|
|
395
|
+
if element.columns:
|
|
396
|
+
details.append(f"{len(element.columns)} cols")
|
|
397
|
+
pk_columns = element.get_primary_key_columns()
|
|
398
|
+
if pk_columns:
|
|
399
|
+
details.append(f"PK: {', '.join(pk_columns)}")
|
|
400
|
+
return ", ".join(details) if details else "-"
|
|
401
|
+
|
|
402
|
+
elif isinstance(element, SQLView):
|
|
403
|
+
if element.source_tables:
|
|
404
|
+
return f"from {', '.join(element.source_tables)}"
|
|
405
|
+
return "view"
|
|
406
|
+
|
|
407
|
+
elif isinstance(element, SQLProcedure):
|
|
408
|
+
if element.parameters:
|
|
409
|
+
return f"{len(element.parameters)} params"
|
|
410
|
+
return "procedure"
|
|
411
|
+
|
|
412
|
+
elif isinstance(element, SQLFunction):
|
|
413
|
+
details = []
|
|
414
|
+
if element.parameters:
|
|
415
|
+
details.append(f"{len(element.parameters)} params")
|
|
416
|
+
if element.return_type:
|
|
417
|
+
details.append(f"-> {element.return_type}")
|
|
418
|
+
return ", ".join(details) if details else "function"
|
|
419
|
+
|
|
420
|
+
elif isinstance(element, SQLTrigger):
|
|
421
|
+
details = []
|
|
422
|
+
if element.trigger_timing and element.trigger_event:
|
|
423
|
+
details.append(f"{element.trigger_timing} {element.trigger_event}")
|
|
424
|
+
if element.table_name:
|
|
425
|
+
details.append(f"on {element.table_name}")
|
|
426
|
+
return ", ".join(details) if details else "trigger"
|
|
427
|
+
|
|
428
|
+
elif isinstance(element, SQLIndex):
|
|
429
|
+
details = []
|
|
430
|
+
if element.table_name:
|
|
431
|
+
details.append(f"on {element.table_name}")
|
|
432
|
+
if element.indexed_columns:
|
|
433
|
+
details.append(f"({', '.join(element.indexed_columns)})")
|
|
434
|
+
if element.is_unique:
|
|
435
|
+
details.append("UNIQUE")
|
|
436
|
+
return ", ".join(details) if details else "index"
|
|
437
|
+
|
|
438
|
+
return "-"
|
|
439
|
+
|
|
440
|
+
|
|
441
|
+
class SQLCSVFormatter(SQLFormatterBase):
|
|
442
|
+
"""CSV format for data processing"""
|
|
443
|
+
|
|
444
|
+
def format_analysis_result(
|
|
445
|
+
self, analysis_result: Any, table_type: str = "csv"
|
|
446
|
+
) -> str:
|
|
447
|
+
"""Format AnalysisResult directly for SQL files."""
|
|
448
|
+
if not analysis_result or not analysis_result.elements:
|
|
449
|
+
return "Element,Type,Lines,Columns_Parameters,Dependencies\n"
|
|
450
|
+
|
|
451
|
+
# Filter only SQL elements
|
|
452
|
+
sql_elements = [
|
|
453
|
+
e for e in analysis_result.elements if hasattr(e, "sql_element_type")
|
|
454
|
+
]
|
|
455
|
+
|
|
456
|
+
if not sql_elements:
|
|
457
|
+
return "Element,Type,Lines,Columns_Parameters,Dependencies\n"
|
|
458
|
+
|
|
459
|
+
return self.format_elements(sql_elements, analysis_result.file_path)
|
|
460
|
+
|
|
461
|
+
def format_elements(self, elements: list[SQLElement], file_path: str = "") -> str:
|
|
462
|
+
"""Format SQL elements as CSV - override to always include header"""
|
|
463
|
+
if not elements:
|
|
464
|
+
# For CSV, always include header even with empty elements
|
|
465
|
+
return "Element,Type,Lines,Columns_Parameters,Dependencies\n"
|
|
466
|
+
|
|
467
|
+
# Group elements by type
|
|
468
|
+
grouped_elements = self.group_elements_by_type(elements)
|
|
469
|
+
|
|
470
|
+
# Format based on specific formatter type
|
|
471
|
+
return self._format_grouped_elements(grouped_elements, file_path)
|
|
472
|
+
|
|
473
|
+
def _format_grouped_elements(
|
|
474
|
+
self, grouped_elements: dict[SQLElementType, list[SQLElement]], file_path: str
|
|
475
|
+
) -> str:
|
|
476
|
+
"""Format elements as CSV"""
|
|
477
|
+
output = ["Element,Type,Lines,Columns_Parameters,Dependencies"]
|
|
478
|
+
|
|
479
|
+
# Sort all elements by line number
|
|
480
|
+
all_elements = []
|
|
481
|
+
for elements in grouped_elements.values():
|
|
482
|
+
all_elements.extend(elements)
|
|
483
|
+
all_elements.sort(key=lambda x: x.start_line)
|
|
484
|
+
|
|
485
|
+
for element in all_elements:
|
|
486
|
+
line_range = f"{element.start_line}-{element.end_line}"
|
|
487
|
+
|
|
488
|
+
# Format columns/parameters
|
|
489
|
+
if hasattr(element, "columns") and element.columns:
|
|
490
|
+
details = f"{len(element.columns)} columns"
|
|
491
|
+
elif hasattr(element, "parameters") and element.parameters:
|
|
492
|
+
# Clean parameter names for CSV display
|
|
493
|
+
param_names = []
|
|
494
|
+
for param in element.parameters:
|
|
495
|
+
if hasattr(param, "name") and param.name:
|
|
496
|
+
# Only include valid parameter names, skip SQL keywords
|
|
497
|
+
if param.name.upper() not in (
|
|
498
|
+
"SELECT",
|
|
499
|
+
"FROM",
|
|
500
|
+
"WHERE",
|
|
501
|
+
"INTO",
|
|
502
|
+
"VALUES",
|
|
503
|
+
"SET",
|
|
504
|
+
"UPDATE",
|
|
505
|
+
"INSERT",
|
|
506
|
+
"DELETE",
|
|
507
|
+
"PENDING",
|
|
508
|
+
):
|
|
509
|
+
param_names.append(param.name)
|
|
510
|
+
if param_names:
|
|
511
|
+
details = f"{len(param_names)} parameters"
|
|
512
|
+
else:
|
|
513
|
+
details = f"{len(element.parameters)} parameters"
|
|
514
|
+
elif hasattr(element, "indexed_columns") and element.indexed_columns:
|
|
515
|
+
details = f"{';'.join(element.indexed_columns)}"
|
|
516
|
+
else:
|
|
517
|
+
details = ""
|
|
518
|
+
|
|
519
|
+
# Format dependencies - ensure no line breaks in CSV
|
|
520
|
+
deps = ""
|
|
521
|
+
if element.dependencies:
|
|
522
|
+
# Clean dependencies and join with semicolon
|
|
523
|
+
clean_deps = []
|
|
524
|
+
for dep in element.dependencies:
|
|
525
|
+
if dep and isinstance(dep, str):
|
|
526
|
+
# Remove any line breaks or extra whitespace
|
|
527
|
+
clean_dep = dep.replace("\n", "").replace("\r", "").strip()
|
|
528
|
+
if clean_dep:
|
|
529
|
+
clean_deps.append(clean_dep)
|
|
530
|
+
deps = ";".join(clean_deps)
|
|
531
|
+
|
|
532
|
+
output.append(
|
|
533
|
+
f"{element.name},{element.sql_element_type.value},{line_range},{details},{deps}"
|
|
534
|
+
)
|
|
535
|
+
|
|
536
|
+
return "\n".join(output) + "\n"
|