tree-sitter-analyzer 1.9.17.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (149) hide show
  1. tree_sitter_analyzer/__init__.py +132 -0
  2. tree_sitter_analyzer/__main__.py +11 -0
  3. tree_sitter_analyzer/api.py +853 -0
  4. tree_sitter_analyzer/cli/__init__.py +39 -0
  5. tree_sitter_analyzer/cli/__main__.py +12 -0
  6. tree_sitter_analyzer/cli/argument_validator.py +89 -0
  7. tree_sitter_analyzer/cli/commands/__init__.py +26 -0
  8. tree_sitter_analyzer/cli/commands/advanced_command.py +226 -0
  9. tree_sitter_analyzer/cli/commands/base_command.py +181 -0
  10. tree_sitter_analyzer/cli/commands/default_command.py +18 -0
  11. tree_sitter_analyzer/cli/commands/find_and_grep_cli.py +188 -0
  12. tree_sitter_analyzer/cli/commands/list_files_cli.py +133 -0
  13. tree_sitter_analyzer/cli/commands/partial_read_command.py +139 -0
  14. tree_sitter_analyzer/cli/commands/query_command.py +109 -0
  15. tree_sitter_analyzer/cli/commands/search_content_cli.py +161 -0
  16. tree_sitter_analyzer/cli/commands/structure_command.py +156 -0
  17. tree_sitter_analyzer/cli/commands/summary_command.py +116 -0
  18. tree_sitter_analyzer/cli/commands/table_command.py +414 -0
  19. tree_sitter_analyzer/cli/info_commands.py +124 -0
  20. tree_sitter_analyzer/cli_main.py +472 -0
  21. tree_sitter_analyzer/constants.py +85 -0
  22. tree_sitter_analyzer/core/__init__.py +15 -0
  23. tree_sitter_analyzer/core/analysis_engine.py +580 -0
  24. tree_sitter_analyzer/core/cache_service.py +333 -0
  25. tree_sitter_analyzer/core/engine.py +585 -0
  26. tree_sitter_analyzer/core/parser.py +293 -0
  27. tree_sitter_analyzer/core/query.py +605 -0
  28. tree_sitter_analyzer/core/query_filter.py +200 -0
  29. tree_sitter_analyzer/core/query_service.py +340 -0
  30. tree_sitter_analyzer/encoding_utils.py +530 -0
  31. tree_sitter_analyzer/exceptions.py +747 -0
  32. tree_sitter_analyzer/file_handler.py +246 -0
  33. tree_sitter_analyzer/formatters/__init__.py +1 -0
  34. tree_sitter_analyzer/formatters/base_formatter.py +201 -0
  35. tree_sitter_analyzer/formatters/csharp_formatter.py +367 -0
  36. tree_sitter_analyzer/formatters/formatter_config.py +197 -0
  37. tree_sitter_analyzer/formatters/formatter_factory.py +84 -0
  38. tree_sitter_analyzer/formatters/formatter_registry.py +377 -0
  39. tree_sitter_analyzer/formatters/formatter_selector.py +96 -0
  40. tree_sitter_analyzer/formatters/go_formatter.py +368 -0
  41. tree_sitter_analyzer/formatters/html_formatter.py +498 -0
  42. tree_sitter_analyzer/formatters/java_formatter.py +423 -0
  43. tree_sitter_analyzer/formatters/javascript_formatter.py +611 -0
  44. tree_sitter_analyzer/formatters/kotlin_formatter.py +268 -0
  45. tree_sitter_analyzer/formatters/language_formatter_factory.py +123 -0
  46. tree_sitter_analyzer/formatters/legacy_formatter_adapters.py +228 -0
  47. tree_sitter_analyzer/formatters/markdown_formatter.py +725 -0
  48. tree_sitter_analyzer/formatters/php_formatter.py +301 -0
  49. tree_sitter_analyzer/formatters/python_formatter.py +830 -0
  50. tree_sitter_analyzer/formatters/ruby_formatter.py +278 -0
  51. tree_sitter_analyzer/formatters/rust_formatter.py +233 -0
  52. tree_sitter_analyzer/formatters/sql_formatter_wrapper.py +689 -0
  53. tree_sitter_analyzer/formatters/sql_formatters.py +536 -0
  54. tree_sitter_analyzer/formatters/typescript_formatter.py +543 -0
  55. tree_sitter_analyzer/formatters/yaml_formatter.py +462 -0
  56. tree_sitter_analyzer/interfaces/__init__.py +9 -0
  57. tree_sitter_analyzer/interfaces/cli.py +535 -0
  58. tree_sitter_analyzer/interfaces/cli_adapter.py +359 -0
  59. tree_sitter_analyzer/interfaces/mcp_adapter.py +224 -0
  60. tree_sitter_analyzer/interfaces/mcp_server.py +428 -0
  61. tree_sitter_analyzer/language_detector.py +553 -0
  62. tree_sitter_analyzer/language_loader.py +271 -0
  63. tree_sitter_analyzer/languages/__init__.py +10 -0
  64. tree_sitter_analyzer/languages/csharp_plugin.py +1076 -0
  65. tree_sitter_analyzer/languages/css_plugin.py +449 -0
  66. tree_sitter_analyzer/languages/go_plugin.py +836 -0
  67. tree_sitter_analyzer/languages/html_plugin.py +496 -0
  68. tree_sitter_analyzer/languages/java_plugin.py +1299 -0
  69. tree_sitter_analyzer/languages/javascript_plugin.py +1622 -0
  70. tree_sitter_analyzer/languages/kotlin_plugin.py +656 -0
  71. tree_sitter_analyzer/languages/markdown_plugin.py +1928 -0
  72. tree_sitter_analyzer/languages/php_plugin.py +862 -0
  73. tree_sitter_analyzer/languages/python_plugin.py +1636 -0
  74. tree_sitter_analyzer/languages/ruby_plugin.py +757 -0
  75. tree_sitter_analyzer/languages/rust_plugin.py +673 -0
  76. tree_sitter_analyzer/languages/sql_plugin.py +2444 -0
  77. tree_sitter_analyzer/languages/typescript_plugin.py +1892 -0
  78. tree_sitter_analyzer/languages/yaml_plugin.py +695 -0
  79. tree_sitter_analyzer/legacy_table_formatter.py +860 -0
  80. tree_sitter_analyzer/mcp/__init__.py +34 -0
  81. tree_sitter_analyzer/mcp/resources/__init__.py +43 -0
  82. tree_sitter_analyzer/mcp/resources/code_file_resource.py +208 -0
  83. tree_sitter_analyzer/mcp/resources/project_stats_resource.py +586 -0
  84. tree_sitter_analyzer/mcp/server.py +869 -0
  85. tree_sitter_analyzer/mcp/tools/__init__.py +28 -0
  86. tree_sitter_analyzer/mcp/tools/analyze_scale_tool.py +779 -0
  87. tree_sitter_analyzer/mcp/tools/analyze_scale_tool_cli_compatible.py +291 -0
  88. tree_sitter_analyzer/mcp/tools/base_tool.py +139 -0
  89. tree_sitter_analyzer/mcp/tools/fd_rg_utils.py +816 -0
  90. tree_sitter_analyzer/mcp/tools/find_and_grep_tool.py +686 -0
  91. tree_sitter_analyzer/mcp/tools/list_files_tool.py +413 -0
  92. tree_sitter_analyzer/mcp/tools/output_format_validator.py +148 -0
  93. tree_sitter_analyzer/mcp/tools/query_tool.py +443 -0
  94. tree_sitter_analyzer/mcp/tools/read_partial_tool.py +464 -0
  95. tree_sitter_analyzer/mcp/tools/search_content_tool.py +836 -0
  96. tree_sitter_analyzer/mcp/tools/table_format_tool.py +572 -0
  97. tree_sitter_analyzer/mcp/tools/universal_analyze_tool.py +653 -0
  98. tree_sitter_analyzer/mcp/utils/__init__.py +113 -0
  99. tree_sitter_analyzer/mcp/utils/error_handler.py +569 -0
  100. tree_sitter_analyzer/mcp/utils/file_output_factory.py +217 -0
  101. tree_sitter_analyzer/mcp/utils/file_output_manager.py +322 -0
  102. tree_sitter_analyzer/mcp/utils/gitignore_detector.py +358 -0
  103. tree_sitter_analyzer/mcp/utils/path_resolver.py +414 -0
  104. tree_sitter_analyzer/mcp/utils/search_cache.py +343 -0
  105. tree_sitter_analyzer/models.py +840 -0
  106. tree_sitter_analyzer/mypy_current_errors.txt +2 -0
  107. tree_sitter_analyzer/output_manager.py +255 -0
  108. tree_sitter_analyzer/platform_compat/__init__.py +3 -0
  109. tree_sitter_analyzer/platform_compat/adapter.py +324 -0
  110. tree_sitter_analyzer/platform_compat/compare.py +224 -0
  111. tree_sitter_analyzer/platform_compat/detector.py +67 -0
  112. tree_sitter_analyzer/platform_compat/fixtures.py +228 -0
  113. tree_sitter_analyzer/platform_compat/profiles.py +217 -0
  114. tree_sitter_analyzer/platform_compat/record.py +55 -0
  115. tree_sitter_analyzer/platform_compat/recorder.py +155 -0
  116. tree_sitter_analyzer/platform_compat/report.py +92 -0
  117. tree_sitter_analyzer/plugins/__init__.py +280 -0
  118. tree_sitter_analyzer/plugins/base.py +647 -0
  119. tree_sitter_analyzer/plugins/manager.py +384 -0
  120. tree_sitter_analyzer/project_detector.py +328 -0
  121. tree_sitter_analyzer/queries/__init__.py +27 -0
  122. tree_sitter_analyzer/queries/csharp.py +216 -0
  123. tree_sitter_analyzer/queries/css.py +615 -0
  124. tree_sitter_analyzer/queries/go.py +275 -0
  125. tree_sitter_analyzer/queries/html.py +543 -0
  126. tree_sitter_analyzer/queries/java.py +402 -0
  127. tree_sitter_analyzer/queries/javascript.py +724 -0
  128. tree_sitter_analyzer/queries/kotlin.py +192 -0
  129. tree_sitter_analyzer/queries/markdown.py +258 -0
  130. tree_sitter_analyzer/queries/php.py +95 -0
  131. tree_sitter_analyzer/queries/python.py +859 -0
  132. tree_sitter_analyzer/queries/ruby.py +92 -0
  133. tree_sitter_analyzer/queries/rust.py +223 -0
  134. tree_sitter_analyzer/queries/sql.py +555 -0
  135. tree_sitter_analyzer/queries/typescript.py +871 -0
  136. tree_sitter_analyzer/queries/yaml.py +236 -0
  137. tree_sitter_analyzer/query_loader.py +272 -0
  138. tree_sitter_analyzer/security/__init__.py +22 -0
  139. tree_sitter_analyzer/security/boundary_manager.py +277 -0
  140. tree_sitter_analyzer/security/regex_checker.py +297 -0
  141. tree_sitter_analyzer/security/validator.py +599 -0
  142. tree_sitter_analyzer/table_formatter.py +782 -0
  143. tree_sitter_analyzer/utils/__init__.py +53 -0
  144. tree_sitter_analyzer/utils/logging.py +433 -0
  145. tree_sitter_analyzer/utils/tree_sitter_compat.py +289 -0
  146. tree_sitter_analyzer-1.9.17.1.dist-info/METADATA +485 -0
  147. tree_sitter_analyzer-1.9.17.1.dist-info/RECORD +149 -0
  148. tree_sitter_analyzer-1.9.17.1.dist-info/WHEEL +4 -0
  149. tree_sitter_analyzer-1.9.17.1.dist-info/entry_points.txt +25 -0
@@ -0,0 +1,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"