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,443 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Query Tool for MCP
|
|
4
|
+
|
|
5
|
+
MCP tool providing tree-sitter query functionality using unified QueryService.
|
|
6
|
+
Supports both predefined query keys and custom query strings.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import logging
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
from typing import Any
|
|
12
|
+
|
|
13
|
+
from ...core.query_service import QueryService
|
|
14
|
+
from ...language_detector import detect_language_from_file
|
|
15
|
+
from ..utils.file_output_manager import FileOutputManager
|
|
16
|
+
from .base_tool import BaseMCPTool
|
|
17
|
+
|
|
18
|
+
logger = logging.getLogger(__name__)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class QueryTool(BaseMCPTool):
|
|
22
|
+
"""MCP query tool providing tree-sitter query functionality"""
|
|
23
|
+
|
|
24
|
+
def __init__(self, project_root: str | None = None) -> None:
|
|
25
|
+
"""Initialize query tool"""
|
|
26
|
+
super().__init__(project_root)
|
|
27
|
+
self.query_service = QueryService(project_root)
|
|
28
|
+
self.file_output_manager = FileOutputManager.get_managed_instance(project_root)
|
|
29
|
+
|
|
30
|
+
def set_project_path(self, project_path: str) -> None:
|
|
31
|
+
"""
|
|
32
|
+
Update the project path for all components.
|
|
33
|
+
|
|
34
|
+
Args:
|
|
35
|
+
project_path: New project root directory
|
|
36
|
+
"""
|
|
37
|
+
super().set_project_path(project_path)
|
|
38
|
+
self.query_service = QueryService(project_path)
|
|
39
|
+
self.file_output_manager = FileOutputManager.get_managed_instance(project_path)
|
|
40
|
+
logger.info(f"QueryTool project path updated to: {project_path}")
|
|
41
|
+
|
|
42
|
+
def get_tool_definition(self) -> dict[str, Any]:
|
|
43
|
+
"""
|
|
44
|
+
Get MCP tool definition
|
|
45
|
+
|
|
46
|
+
Returns:
|
|
47
|
+
Tool definition dictionary
|
|
48
|
+
"""
|
|
49
|
+
return {
|
|
50
|
+
"name": "query_code",
|
|
51
|
+
"description": "Execute tree-sitter queries on code files to extract specific code elements with filtering, multiple output formats, and optional file saving with token optimization",
|
|
52
|
+
"inputSchema": {
|
|
53
|
+
"type": "object",
|
|
54
|
+
"properties": {
|
|
55
|
+
"file_path": {
|
|
56
|
+
"type": "string",
|
|
57
|
+
"description": "Path to the code file to query (relative to project root or absolute path)",
|
|
58
|
+
},
|
|
59
|
+
"language": {
|
|
60
|
+
"type": "string",
|
|
61
|
+
"description": "Programming language (optional, auto-detected if not provided)",
|
|
62
|
+
},
|
|
63
|
+
"query_key": {
|
|
64
|
+
"type": "string",
|
|
65
|
+
"description": "Predefined query key (e.g., 'methods', 'class', 'functions')",
|
|
66
|
+
},
|
|
67
|
+
"query_string": {
|
|
68
|
+
"type": "string",
|
|
69
|
+
"description": "Custom tree-sitter query string (e.g., '(method_declaration) @method')",
|
|
70
|
+
},
|
|
71
|
+
"filter": {
|
|
72
|
+
"type": "string",
|
|
73
|
+
"description": "Filter expression to refine results (e.g., 'name=main', 'name=~get*,public=true')",
|
|
74
|
+
},
|
|
75
|
+
"output_format": {
|
|
76
|
+
"type": "string",
|
|
77
|
+
"enum": ["json", "summary"],
|
|
78
|
+
"default": "json",
|
|
79
|
+
"description": "Output format",
|
|
80
|
+
},
|
|
81
|
+
"output_file": {
|
|
82
|
+
"type": "string",
|
|
83
|
+
"description": "Optional filename to save output to file (extension auto-detected based on content)",
|
|
84
|
+
},
|
|
85
|
+
"suppress_output": {
|
|
86
|
+
"type": "boolean",
|
|
87
|
+
"description": "When true and output_file is specified, suppress detailed output in response to save tokens",
|
|
88
|
+
"default": False,
|
|
89
|
+
},
|
|
90
|
+
},
|
|
91
|
+
"required": ["file_path"],
|
|
92
|
+
},
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
async def execute(self, arguments: dict[str, Any]) -> dict[str, Any]:
|
|
96
|
+
"""
|
|
97
|
+
Execute query tool
|
|
98
|
+
|
|
99
|
+
Args:
|
|
100
|
+
arguments: Tool arguments
|
|
101
|
+
|
|
102
|
+
Returns:
|
|
103
|
+
Query results
|
|
104
|
+
"""
|
|
105
|
+
try:
|
|
106
|
+
# Validate input parameters - check for empty arguments first
|
|
107
|
+
if not arguments:
|
|
108
|
+
from ..utils.error_handler import AnalysisError
|
|
109
|
+
|
|
110
|
+
raise AnalysisError("file_path is required", operation="query_code")
|
|
111
|
+
|
|
112
|
+
file_path = arguments.get("file_path")
|
|
113
|
+
if not file_path:
|
|
114
|
+
from ..utils.error_handler import AnalysisError
|
|
115
|
+
|
|
116
|
+
raise AnalysisError("file_path is required", operation="query_code")
|
|
117
|
+
|
|
118
|
+
# Check that either query_key or query_string is provided early
|
|
119
|
+
query_key = arguments.get("query_key")
|
|
120
|
+
query_string = arguments.get("query_string")
|
|
121
|
+
|
|
122
|
+
if not query_key and not query_string:
|
|
123
|
+
from ..utils.error_handler import AnalysisError
|
|
124
|
+
|
|
125
|
+
raise AnalysisError(
|
|
126
|
+
"Either query_key or query_string must be provided",
|
|
127
|
+
operation="query_code",
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
# Security validation BEFORE path resolution to catch symlinks
|
|
131
|
+
is_valid, error_msg = self.security_validator.validate_file_path(file_path)
|
|
132
|
+
if not is_valid:
|
|
133
|
+
return {
|
|
134
|
+
"success": False,
|
|
135
|
+
"error": f"Invalid or unsafe file path: {error_msg or file_path}",
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
# Resolve file path to absolute path
|
|
139
|
+
resolved_file_path = self.path_resolver.resolve(file_path)
|
|
140
|
+
logger.info(
|
|
141
|
+
f"Querying file: {file_path} (resolved to: {resolved_file_path})"
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
# Additional security validation on resolved path
|
|
145
|
+
is_valid, error_msg = self.security_validator.validate_file_path(
|
|
146
|
+
resolved_file_path
|
|
147
|
+
)
|
|
148
|
+
if not is_valid:
|
|
149
|
+
return {
|
|
150
|
+
"success": False,
|
|
151
|
+
"error": f"Invalid or unsafe resolved path: {error_msg or resolved_file_path}",
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
# Get query parameters (already validated above)
|
|
155
|
+
filter_expression = arguments.get("filter")
|
|
156
|
+
output_format = arguments.get("output_format", "json")
|
|
157
|
+
output_file = arguments.get("output_file")
|
|
158
|
+
suppress_output = arguments.get("suppress_output", False)
|
|
159
|
+
|
|
160
|
+
if query_key and query_string:
|
|
161
|
+
return {
|
|
162
|
+
"success": False,
|
|
163
|
+
"error": "Cannot provide both query_key and query_string",
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
# Detect language
|
|
167
|
+
language = arguments.get("language")
|
|
168
|
+
if not language:
|
|
169
|
+
language = detect_language_from_file(resolved_file_path)
|
|
170
|
+
if not language:
|
|
171
|
+
return {
|
|
172
|
+
"success": False,
|
|
173
|
+
"error": f"Could not detect language for file: {file_path}",
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
# Execute query
|
|
177
|
+
results = await self.query_service.execute_query(
|
|
178
|
+
resolved_file_path, language, query_key, query_string, filter_expression
|
|
179
|
+
)
|
|
180
|
+
|
|
181
|
+
if not results:
|
|
182
|
+
return {
|
|
183
|
+
"success": True,
|
|
184
|
+
"message": "No results found matching the query",
|
|
185
|
+
"results": [],
|
|
186
|
+
"count": 0,
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
# Format output
|
|
190
|
+
if output_format == "summary":
|
|
191
|
+
formatted_result = self._format_summary(
|
|
192
|
+
results, query_key or "custom", language
|
|
193
|
+
)
|
|
194
|
+
else:
|
|
195
|
+
formatted_result = {
|
|
196
|
+
"success": True,
|
|
197
|
+
"results": results,
|
|
198
|
+
"count": len(results),
|
|
199
|
+
"file_path": file_path,
|
|
200
|
+
"language": language,
|
|
201
|
+
"query": query_key or query_string,
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
# Handle file output if requested
|
|
205
|
+
if output_file:
|
|
206
|
+
try:
|
|
207
|
+
import json
|
|
208
|
+
|
|
209
|
+
# Generate base name from original file path if not provided
|
|
210
|
+
if not output_file or output_file.strip() == "":
|
|
211
|
+
base_name = (
|
|
212
|
+
f"{Path(file_path).stem}_query_{query_key or 'custom'}"
|
|
213
|
+
)
|
|
214
|
+
else:
|
|
215
|
+
base_name = output_file
|
|
216
|
+
|
|
217
|
+
# Convert result to JSON string for file output
|
|
218
|
+
json_content = json.dumps(
|
|
219
|
+
formatted_result, indent=2, ensure_ascii=False
|
|
220
|
+
)
|
|
221
|
+
|
|
222
|
+
# Save to file with automatic extension detection
|
|
223
|
+
saved_file_path = self.file_output_manager.save_to_file(
|
|
224
|
+
content=json_content, base_name=base_name
|
|
225
|
+
)
|
|
226
|
+
|
|
227
|
+
# Add file output info to result
|
|
228
|
+
formatted_result["output_file_path"] = saved_file_path
|
|
229
|
+
formatted_result["file_saved"] = True
|
|
230
|
+
|
|
231
|
+
logger.info(f"Query output saved to: {saved_file_path}")
|
|
232
|
+
|
|
233
|
+
except Exception as e:
|
|
234
|
+
logger.error(f"Failed to save output to file: {e}")
|
|
235
|
+
formatted_result["file_save_error"] = str(e)
|
|
236
|
+
formatted_result["file_saved"] = False
|
|
237
|
+
|
|
238
|
+
# Apply suppress_output logic
|
|
239
|
+
if suppress_output and output_file:
|
|
240
|
+
# Create minimal result when output is suppressed
|
|
241
|
+
minimal_result = {
|
|
242
|
+
"success": formatted_result.get("success", True),
|
|
243
|
+
"count": formatted_result.get("count", len(results)),
|
|
244
|
+
"file_path": file_path,
|
|
245
|
+
"language": language,
|
|
246
|
+
"query": query_key or query_string,
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
# Include file output info if present
|
|
250
|
+
if "output_file_path" in formatted_result:
|
|
251
|
+
minimal_result["output_file_path"] = formatted_result[
|
|
252
|
+
"output_file_path"
|
|
253
|
+
]
|
|
254
|
+
minimal_result["file_saved"] = formatted_result["file_saved"]
|
|
255
|
+
if "file_save_error" in formatted_result:
|
|
256
|
+
minimal_result["file_save_error"] = formatted_result[
|
|
257
|
+
"file_save_error"
|
|
258
|
+
]
|
|
259
|
+
minimal_result["file_saved"] = formatted_result["file_saved"]
|
|
260
|
+
|
|
261
|
+
return minimal_result
|
|
262
|
+
else:
|
|
263
|
+
return formatted_result
|
|
264
|
+
|
|
265
|
+
except Exception as e:
|
|
266
|
+
from ..utils.error_handler import AnalysisError
|
|
267
|
+
|
|
268
|
+
# Re-raise AnalysisError to maintain proper error handling
|
|
269
|
+
if isinstance(e, AnalysisError):
|
|
270
|
+
raise
|
|
271
|
+
|
|
272
|
+
logger.error(f"Query execution failed: {e}")
|
|
273
|
+
return {
|
|
274
|
+
"success": False,
|
|
275
|
+
"error": str(e),
|
|
276
|
+
"file_path": arguments.get("file_path", "unknown"),
|
|
277
|
+
"language": arguments.get("language", "unknown"),
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
def _format_summary(
|
|
281
|
+
self, results: list[dict[str, Any]], query_type: str, language: str
|
|
282
|
+
) -> dict[str, Any]:
|
|
283
|
+
"""
|
|
284
|
+
Format summary output
|
|
285
|
+
|
|
286
|
+
Args:
|
|
287
|
+
results: Query results
|
|
288
|
+
query_type: Query type
|
|
289
|
+
language: Programming language
|
|
290
|
+
|
|
291
|
+
Returns:
|
|
292
|
+
Summary formatted results
|
|
293
|
+
"""
|
|
294
|
+
# Group by capture name
|
|
295
|
+
by_capture: dict[str, list[dict[str, Any]]] = {}
|
|
296
|
+
for result in results:
|
|
297
|
+
capture_name = result["capture_name"]
|
|
298
|
+
if capture_name not in by_capture:
|
|
299
|
+
by_capture[capture_name] = []
|
|
300
|
+
by_capture[capture_name].append(result)
|
|
301
|
+
|
|
302
|
+
# Create summary
|
|
303
|
+
summary: dict[str, Any] = {
|
|
304
|
+
"success": True,
|
|
305
|
+
"query_type": query_type,
|
|
306
|
+
"language": language,
|
|
307
|
+
"total_count": len(results),
|
|
308
|
+
"captures": {},
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
for capture_name, items in by_capture.items():
|
|
312
|
+
summary["captures"][capture_name] = {
|
|
313
|
+
"count": len(items),
|
|
314
|
+
"items": [
|
|
315
|
+
{
|
|
316
|
+
"name": self._extract_name_from_content(item["content"]),
|
|
317
|
+
"line_range": f"{item['start_line']}-{item['end_line']}",
|
|
318
|
+
"node_type": item["node_type"],
|
|
319
|
+
}
|
|
320
|
+
for item in items
|
|
321
|
+
],
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
return summary
|
|
325
|
+
|
|
326
|
+
def _extract_name_from_content(self, content: str) -> str:
|
|
327
|
+
"""
|
|
328
|
+
Extract name from content (simple heuristic method)
|
|
329
|
+
|
|
330
|
+
Args:
|
|
331
|
+
content: Code content
|
|
332
|
+
|
|
333
|
+
Returns:
|
|
334
|
+
Extracted name
|
|
335
|
+
"""
|
|
336
|
+
# Simple name extraction logic, can be improved as needed
|
|
337
|
+
lines = content.strip().split("\n")
|
|
338
|
+
if lines:
|
|
339
|
+
first_line = lines[0].strip()
|
|
340
|
+
# Extract method names, class names, etc.
|
|
341
|
+
import re
|
|
342
|
+
|
|
343
|
+
# Match common declaration patterns
|
|
344
|
+
patterns = [
|
|
345
|
+
# Markdown headers
|
|
346
|
+
r"^#{1,6}\s+(.+)$", # Markdown headers (# Title, ## Subtitle, etc.)
|
|
347
|
+
# Programming language patterns
|
|
348
|
+
r"(?:public|private|protected)?\s*(?:static)?\s*(?:class|interface)\s+(\w+)", # class/interface
|
|
349
|
+
r"(?:public|private|protected)?\s*(?:static)?\s*\w+\s+(\w+)\s*\(", # method
|
|
350
|
+
r"(\w+)\s*\(", # simple function call
|
|
351
|
+
]
|
|
352
|
+
|
|
353
|
+
for pattern in patterns:
|
|
354
|
+
match = re.search(pattern, first_line)
|
|
355
|
+
if match:
|
|
356
|
+
return match.group(1).strip()
|
|
357
|
+
|
|
358
|
+
return "unnamed"
|
|
359
|
+
|
|
360
|
+
def get_available_queries(self, language: str) -> list[str]:
|
|
361
|
+
"""
|
|
362
|
+
Get available query keys
|
|
363
|
+
|
|
364
|
+
Args:
|
|
365
|
+
language: Programming language
|
|
366
|
+
|
|
367
|
+
Returns:
|
|
368
|
+
List of available query keys
|
|
369
|
+
"""
|
|
370
|
+
return self.query_service.get_available_queries(language)
|
|
371
|
+
|
|
372
|
+
def validate_arguments(self, arguments: dict[str, Any]) -> bool:
|
|
373
|
+
"""
|
|
374
|
+
Validate tool arguments.
|
|
375
|
+
|
|
376
|
+
Args:
|
|
377
|
+
arguments: Arguments to validate
|
|
378
|
+
|
|
379
|
+
Returns:
|
|
380
|
+
True if arguments are valid
|
|
381
|
+
|
|
382
|
+
Raises:
|
|
383
|
+
ValueError: If arguments are invalid
|
|
384
|
+
"""
|
|
385
|
+
# Check required fields
|
|
386
|
+
if "file_path" not in arguments:
|
|
387
|
+
raise ValueError("file_path is required")
|
|
388
|
+
|
|
389
|
+
# Validate file_path
|
|
390
|
+
file_path = arguments["file_path"]
|
|
391
|
+
if not isinstance(file_path, str):
|
|
392
|
+
raise ValueError("file_path must be a string")
|
|
393
|
+
if not file_path.strip():
|
|
394
|
+
raise ValueError("file_path cannot be empty")
|
|
395
|
+
|
|
396
|
+
# Check that either query_key or query_string is provided
|
|
397
|
+
query_key = arguments.get("query_key")
|
|
398
|
+
query_string = arguments.get("query_string")
|
|
399
|
+
|
|
400
|
+
if not query_key and not query_string:
|
|
401
|
+
raise ValueError("Either query_key or query_string must be provided")
|
|
402
|
+
|
|
403
|
+
# Validate query_key if provided
|
|
404
|
+
if query_key and not isinstance(query_key, str):
|
|
405
|
+
raise ValueError("query_key must be a string")
|
|
406
|
+
|
|
407
|
+
# Validate query_string if provided
|
|
408
|
+
if query_string and not isinstance(query_string, str):
|
|
409
|
+
raise ValueError("query_string must be a string")
|
|
410
|
+
|
|
411
|
+
# Validate optional fields
|
|
412
|
+
if "language" in arguments:
|
|
413
|
+
language = arguments["language"]
|
|
414
|
+
if not isinstance(language, str):
|
|
415
|
+
raise ValueError("language must be a string")
|
|
416
|
+
|
|
417
|
+
if "filter" in arguments:
|
|
418
|
+
filter_expr = arguments["filter"]
|
|
419
|
+
if not isinstance(filter_expr, str):
|
|
420
|
+
raise ValueError("filter must be a string")
|
|
421
|
+
|
|
422
|
+
if "output_format" in arguments:
|
|
423
|
+
output_format = arguments["output_format"]
|
|
424
|
+
if not isinstance(output_format, str):
|
|
425
|
+
raise ValueError("output_format must be a string")
|
|
426
|
+
if output_format not in ["json", "summary"]:
|
|
427
|
+
raise ValueError("output_format must be one of: json, summary")
|
|
428
|
+
|
|
429
|
+
# Validate output_file if provided
|
|
430
|
+
if "output_file" in arguments:
|
|
431
|
+
output_file = arguments["output_file"]
|
|
432
|
+
if not isinstance(output_file, str):
|
|
433
|
+
raise ValueError("output_file must be a string")
|
|
434
|
+
if not output_file.strip():
|
|
435
|
+
raise ValueError("output_file cannot be empty")
|
|
436
|
+
|
|
437
|
+
# Validate suppress_output if provided
|
|
438
|
+
if "suppress_output" in arguments:
|
|
439
|
+
suppress_output = arguments["suppress_output"]
|
|
440
|
+
if not isinstance(suppress_output, bool):
|
|
441
|
+
raise ValueError("suppress_output must be a boolean")
|
|
442
|
+
|
|
443
|
+
return True
|