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,200 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Query Filter Service
|
|
4
|
+
|
|
5
|
+
Provides post-processing filtering for query results, supporting filtering by name, parameters, and other conditions.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import re
|
|
9
|
+
from typing import Any
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class QueryFilter:
|
|
13
|
+
"""Query result filter"""
|
|
14
|
+
|
|
15
|
+
def __init__(self) -> None:
|
|
16
|
+
pass
|
|
17
|
+
|
|
18
|
+
def filter_results(
|
|
19
|
+
self, results: list[dict[str, Any]], filter_expression: str
|
|
20
|
+
) -> list[dict[str, Any]]:
|
|
21
|
+
"""
|
|
22
|
+
Filter query results based on filter expression
|
|
23
|
+
|
|
24
|
+
Args:
|
|
25
|
+
results: Original query results
|
|
26
|
+
filter_expression: Filter expression supporting multiple formats:
|
|
27
|
+
- "name=main" - Exact name match
|
|
28
|
+
- "name~auth*" - Pattern name match
|
|
29
|
+
- "params=0" - Filter by parameter count
|
|
30
|
+
- "static=true" - Filter by modifier
|
|
31
|
+
|
|
32
|
+
Returns:
|
|
33
|
+
Filtered results list
|
|
34
|
+
"""
|
|
35
|
+
if not filter_expression:
|
|
36
|
+
return results
|
|
37
|
+
|
|
38
|
+
# Parse filter expression
|
|
39
|
+
filters = self._parse_filter_expression(filter_expression)
|
|
40
|
+
|
|
41
|
+
filtered_results = []
|
|
42
|
+
for result in results:
|
|
43
|
+
if self._matches_filters(result, filters):
|
|
44
|
+
filtered_results.append(result)
|
|
45
|
+
|
|
46
|
+
return filtered_results
|
|
47
|
+
|
|
48
|
+
def _parse_filter_expression(self, expression: str) -> dict[str, Any]:
|
|
49
|
+
"""Parse filter expression"""
|
|
50
|
+
filters = {}
|
|
51
|
+
|
|
52
|
+
# Support multiple conditions separated by commas
|
|
53
|
+
conditions = expression.split(",")
|
|
54
|
+
|
|
55
|
+
for condition in conditions:
|
|
56
|
+
condition = condition.strip()
|
|
57
|
+
|
|
58
|
+
if "=" in condition:
|
|
59
|
+
key, value = condition.split("=", 1)
|
|
60
|
+
key = key.strip()
|
|
61
|
+
value = value.strip()
|
|
62
|
+
|
|
63
|
+
# Handle pattern matching
|
|
64
|
+
if value.startswith("~"):
|
|
65
|
+
filters[key] = {"type": "pattern", "value": value[1:]}
|
|
66
|
+
else:
|
|
67
|
+
filters[key] = {"type": "exact", "value": value}
|
|
68
|
+
|
|
69
|
+
return filters
|
|
70
|
+
|
|
71
|
+
def _matches_filters(self, result: dict[str, Any], filters: dict[str, Any]) -> bool:
|
|
72
|
+
"""Check if result matches all filter conditions"""
|
|
73
|
+
for filter_key, filter_config in filters.items():
|
|
74
|
+
if not self._matches_single_filter(result, filter_key, filter_config):
|
|
75
|
+
return False
|
|
76
|
+
return True
|
|
77
|
+
|
|
78
|
+
def _matches_single_filter(
|
|
79
|
+
self, result: dict[str, Any], filter_key: str, filter_config: dict[str, Any]
|
|
80
|
+
) -> bool:
|
|
81
|
+
"""Check single filter condition"""
|
|
82
|
+
filter_type = filter_config["type"]
|
|
83
|
+
filter_value = filter_config["value"]
|
|
84
|
+
|
|
85
|
+
if filter_key == "name":
|
|
86
|
+
return self._match_name(result, filter_type, filter_value)
|
|
87
|
+
elif filter_key == "params":
|
|
88
|
+
return self._match_params(result, filter_type, filter_value)
|
|
89
|
+
elif filter_key == "static":
|
|
90
|
+
return self._match_modifier(result, "static", filter_value)
|
|
91
|
+
elif filter_key == "public":
|
|
92
|
+
return self._match_modifier(result, "public", filter_value)
|
|
93
|
+
elif filter_key == "private":
|
|
94
|
+
return self._match_modifier(result, "private", filter_value)
|
|
95
|
+
elif filter_key == "protected":
|
|
96
|
+
return self._match_modifier(result, "protected", filter_value)
|
|
97
|
+
|
|
98
|
+
return True
|
|
99
|
+
|
|
100
|
+
def _match_name(self, result: dict[str, Any], match_type: str, value: str) -> bool:
|
|
101
|
+
"""Match method name"""
|
|
102
|
+
content = result.get("content", "")
|
|
103
|
+
|
|
104
|
+
# Extract method name
|
|
105
|
+
method_name = self._extract_method_name(content)
|
|
106
|
+
|
|
107
|
+
if match_type == "exact":
|
|
108
|
+
return method_name == value
|
|
109
|
+
elif match_type == "pattern":
|
|
110
|
+
# Support wildcard patterns
|
|
111
|
+
pattern = value.replace("*", ".*")
|
|
112
|
+
return re.match(pattern, method_name, re.IGNORECASE) is not None
|
|
113
|
+
|
|
114
|
+
return False
|
|
115
|
+
|
|
116
|
+
def _match_params(
|
|
117
|
+
self, result: dict[str, Any], match_type: str, value: str
|
|
118
|
+
) -> bool:
|
|
119
|
+
"""Match parameter count"""
|
|
120
|
+
content = result.get("content", "")
|
|
121
|
+
param_count = self._count_parameters(content)
|
|
122
|
+
|
|
123
|
+
try:
|
|
124
|
+
target_count = int(value)
|
|
125
|
+
return param_count == target_count
|
|
126
|
+
except ValueError:
|
|
127
|
+
return False
|
|
128
|
+
|
|
129
|
+
def _match_modifier(
|
|
130
|
+
self, result: dict[str, Any], modifier: str, value: str
|
|
131
|
+
) -> bool:
|
|
132
|
+
"""Match modifier"""
|
|
133
|
+
content = result.get("content", "")
|
|
134
|
+
has_modifier = modifier in content
|
|
135
|
+
|
|
136
|
+
return (value.lower() == "true") == has_modifier
|
|
137
|
+
|
|
138
|
+
def _extract_method_name(self, content: str) -> str:
|
|
139
|
+
"""Extract method name from content"""
|
|
140
|
+
# Match method declaration patterns
|
|
141
|
+
patterns = [
|
|
142
|
+
r"(?:public|private|protected)?\s*(?:static)?\s*\w+\s+(\w+)\s*\(", # Java method
|
|
143
|
+
r"def\s+(\w+)\s*\(", # Python method
|
|
144
|
+
r"function\s+(\w+)\s*\(", # JavaScript function
|
|
145
|
+
]
|
|
146
|
+
|
|
147
|
+
for pattern in patterns:
|
|
148
|
+
match = re.search(pattern, content)
|
|
149
|
+
if match:
|
|
150
|
+
return match.group(1)
|
|
151
|
+
|
|
152
|
+
return "unknown"
|
|
153
|
+
|
|
154
|
+
def _count_parameters(self, content: str) -> int:
|
|
155
|
+
"""Count method parameters"""
|
|
156
|
+
# Find parameter list
|
|
157
|
+
match = re.search(r"\(([^)]*)\)", content)
|
|
158
|
+
if not match:
|
|
159
|
+
return 0
|
|
160
|
+
|
|
161
|
+
params_str = match.group(1).strip()
|
|
162
|
+
if not params_str:
|
|
163
|
+
return 0
|
|
164
|
+
|
|
165
|
+
# Simple parameter counting (by comma separation)
|
|
166
|
+
# Note: This is a simple implementation, doesn't handle generics etc.
|
|
167
|
+
params = [p.strip() for p in params_str.split(",") if p.strip()]
|
|
168
|
+
return len(params)
|
|
169
|
+
|
|
170
|
+
def get_filter_help(self) -> str:
|
|
171
|
+
"""Get filter help information"""
|
|
172
|
+
return """
|
|
173
|
+
Filter Syntax Help:
|
|
174
|
+
|
|
175
|
+
Basic Syntax:
|
|
176
|
+
--filter "key=value" # Exact match
|
|
177
|
+
--filter "key=~pattern" # Pattern match (supports wildcard *)
|
|
178
|
+
--filter "key1=value1,key2=value2" # Multiple conditions (AND logic)
|
|
179
|
+
|
|
180
|
+
Supported filter keys:
|
|
181
|
+
name - Method/function name
|
|
182
|
+
e.g.: name=main, name=~auth*, name=~get*
|
|
183
|
+
|
|
184
|
+
params - Number of parameters
|
|
185
|
+
e.g.: params=0, params=2
|
|
186
|
+
|
|
187
|
+
static - Whether it is a static method
|
|
188
|
+
e.g.: static=true, static=false
|
|
189
|
+
|
|
190
|
+
public - Whether it is a public method
|
|
191
|
+
e.g.: public=true, public=false
|
|
192
|
+
|
|
193
|
+
private - Whether it is a private method
|
|
194
|
+
e.g.: private=true, private=false
|
|
195
|
+
|
|
196
|
+
Examples:
|
|
197
|
+
--query-key methods --filter "name=main"
|
|
198
|
+
--query-key methods --filter "name=~get*,public=true"
|
|
199
|
+
--query-key methods --filter "params=0,static=true"
|
|
200
|
+
"""
|
|
@@ -0,0 +1,340 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Query Service
|
|
4
|
+
|
|
5
|
+
Unified query service for both CLI and MCP interfaces to avoid code duplication.
|
|
6
|
+
Provides core tree-sitter query functionality including predefined and custom queries.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import asyncio
|
|
10
|
+
import logging
|
|
11
|
+
from typing import Any
|
|
12
|
+
|
|
13
|
+
from ..encoding_utils import read_file_safe
|
|
14
|
+
from ..plugins.manager import PluginManager
|
|
15
|
+
from ..query_loader import query_loader
|
|
16
|
+
from ..utils.tree_sitter_compat import TreeSitterQueryCompat, get_node_text_safe
|
|
17
|
+
from .parser import Parser
|
|
18
|
+
from .query_filter import QueryFilter
|
|
19
|
+
|
|
20
|
+
logger = logging.getLogger(__name__)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class QueryService:
|
|
24
|
+
"""Unified query service providing tree-sitter query functionality"""
|
|
25
|
+
|
|
26
|
+
def __init__(self, project_root: str | None = None) -> None:
|
|
27
|
+
"""Initialize the query service"""
|
|
28
|
+
self.project_root = project_root
|
|
29
|
+
self.parser = Parser()
|
|
30
|
+
self.filter = QueryFilter()
|
|
31
|
+
self.plugin_manager = PluginManager()
|
|
32
|
+
self.plugin_manager.load_plugins()
|
|
33
|
+
|
|
34
|
+
async def execute_query(
|
|
35
|
+
self,
|
|
36
|
+
file_path: str,
|
|
37
|
+
language: str,
|
|
38
|
+
query_key: str | None = None,
|
|
39
|
+
query_string: str | None = None,
|
|
40
|
+
filter_expression: str | None = None,
|
|
41
|
+
) -> list[dict[str, Any]] | None:
|
|
42
|
+
"""
|
|
43
|
+
Execute a query
|
|
44
|
+
|
|
45
|
+
Args:
|
|
46
|
+
file_path: Path to the file to analyze
|
|
47
|
+
language: Programming language
|
|
48
|
+
query_key: Predefined query key (e.g., 'methods', 'class')
|
|
49
|
+
query_string: Custom query string (e.g., '(method_declaration) @method')
|
|
50
|
+
filter_expression: Filter expression (e.g., 'name=main', 'name=~get*,public=true')
|
|
51
|
+
|
|
52
|
+
Returns:
|
|
53
|
+
List of query results, each containing capture_name, node_type, start_line, end_line, content
|
|
54
|
+
|
|
55
|
+
Raises:
|
|
56
|
+
ValueError: If neither query_key nor query_string is provided
|
|
57
|
+
FileNotFoundError: If file doesn't exist
|
|
58
|
+
Exception: If query execution fails
|
|
59
|
+
"""
|
|
60
|
+
if not query_key and not query_string:
|
|
61
|
+
raise ValueError("Must provide either query_key or query_string")
|
|
62
|
+
|
|
63
|
+
if query_key and query_string:
|
|
64
|
+
raise ValueError("Cannot provide both query_key and query_string")
|
|
65
|
+
|
|
66
|
+
try:
|
|
67
|
+
# Read file content
|
|
68
|
+
content, encoding = await self._read_file_async(file_path)
|
|
69
|
+
|
|
70
|
+
# Parse file
|
|
71
|
+
parse_result = self.parser.parse_code(content, language, file_path)
|
|
72
|
+
if not parse_result or not parse_result.tree:
|
|
73
|
+
raise Exception("Failed to parse file")
|
|
74
|
+
|
|
75
|
+
tree = parse_result.tree
|
|
76
|
+
language_obj = tree.language if hasattr(tree, "language") else None
|
|
77
|
+
if not language_obj:
|
|
78
|
+
raise Exception(f"Language object not available for {language}")
|
|
79
|
+
|
|
80
|
+
# Get query string
|
|
81
|
+
if query_key:
|
|
82
|
+
query_string = query_loader.get_query(language, query_key)
|
|
83
|
+
if not query_string:
|
|
84
|
+
raise ValueError(
|
|
85
|
+
f"Query '{query_key}' not found for language '{language}'"
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
# Execute tree-sitter query using modern API
|
|
89
|
+
try:
|
|
90
|
+
captures = TreeSitterQueryCompat.safe_execute_query(
|
|
91
|
+
language_obj, query_string or "", tree.root_node, fallback_result=[]
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
# If captures is empty, use plugin fallback
|
|
95
|
+
if not captures:
|
|
96
|
+
captures = self._execute_plugin_query(
|
|
97
|
+
tree.root_node, query_key, language, content
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
except Exception as e:
|
|
101
|
+
logger.debug(
|
|
102
|
+
f"Tree-sitter query execution failed, using plugin fallback: {e}"
|
|
103
|
+
)
|
|
104
|
+
# If query creation or execution fails, use plugin fallback
|
|
105
|
+
captures = self._execute_plugin_query(
|
|
106
|
+
tree.root_node, query_key, language, content
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
# Process capture results
|
|
110
|
+
results = []
|
|
111
|
+
if isinstance(captures, list):
|
|
112
|
+
# Handle list of tuples from modern API and plugin execution
|
|
113
|
+
for capture in captures:
|
|
114
|
+
if isinstance(capture, tuple) and len(capture) == 2:
|
|
115
|
+
node, name = capture
|
|
116
|
+
results.append(self._create_result_dict(node, name, content))
|
|
117
|
+
# Note: This else block is unreachable due to the logic above, but kept for safety
|
|
118
|
+
# else:
|
|
119
|
+
# # If captures is not in expected format, use plugin fallback
|
|
120
|
+
# plugin_captures = self._execute_plugin_query(tree.root_node, query_key, language, content)
|
|
121
|
+
# for capture in plugin_captures:
|
|
122
|
+
# if isinstance(capture, tuple) and len(capture) == 2:
|
|
123
|
+
# node, name = capture
|
|
124
|
+
# results.append(self._create_result_dict(node, name, content))
|
|
125
|
+
|
|
126
|
+
# Apply filters
|
|
127
|
+
if filter_expression and results:
|
|
128
|
+
results = self.filter.filter_results(results, filter_expression)
|
|
129
|
+
|
|
130
|
+
return results
|
|
131
|
+
|
|
132
|
+
except Exception as e:
|
|
133
|
+
logger.error(f"Query execution failed: {e}")
|
|
134
|
+
raise
|
|
135
|
+
|
|
136
|
+
def _create_result_dict(
|
|
137
|
+
self, node: Any, capture_name: str, source_code: str = ""
|
|
138
|
+
) -> dict[str, Any]:
|
|
139
|
+
"""
|
|
140
|
+
Create result dictionary from tree-sitter node
|
|
141
|
+
|
|
142
|
+
Args:
|
|
143
|
+
node: tree-sitter node
|
|
144
|
+
capture_name: capture name
|
|
145
|
+
source_code: source code content for text extraction
|
|
146
|
+
|
|
147
|
+
Returns:
|
|
148
|
+
Result dictionary
|
|
149
|
+
"""
|
|
150
|
+
# Use safe text extraction with source code
|
|
151
|
+
content = get_node_text_safe(node, source_code)
|
|
152
|
+
|
|
153
|
+
return {
|
|
154
|
+
"capture_name": capture_name,
|
|
155
|
+
"node_type": node.type if hasattr(node, "type") else "unknown",
|
|
156
|
+
"start_line": (
|
|
157
|
+
node.start_point[0] + 1 if hasattr(node, "start_point") else 0
|
|
158
|
+
),
|
|
159
|
+
"end_line": node.end_point[0] + 1 if hasattr(node, "end_point") else 0,
|
|
160
|
+
"content": content,
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
def get_available_queries(self, language: str) -> list[str]:
|
|
164
|
+
"""
|
|
165
|
+
Get available query keys for specified language
|
|
166
|
+
|
|
167
|
+
Args:
|
|
168
|
+
language: Programming language
|
|
169
|
+
|
|
170
|
+
Returns:
|
|
171
|
+
List of available query keys
|
|
172
|
+
"""
|
|
173
|
+
return query_loader.list_queries(language)
|
|
174
|
+
|
|
175
|
+
def get_query_description(self, language: str, query_key: str) -> str | None:
|
|
176
|
+
"""
|
|
177
|
+
Get description for query key
|
|
178
|
+
|
|
179
|
+
Args:
|
|
180
|
+
language: Programming language
|
|
181
|
+
query_key: Query key
|
|
182
|
+
|
|
183
|
+
Returns:
|
|
184
|
+
Query description, or None if not found
|
|
185
|
+
"""
|
|
186
|
+
try:
|
|
187
|
+
return query_loader.get_query_description(language, query_key)
|
|
188
|
+
except Exception:
|
|
189
|
+
return None
|
|
190
|
+
|
|
191
|
+
def _execute_plugin_query(
|
|
192
|
+
self, root_node: Any, query_key: str | None, language: str, source_code: str
|
|
193
|
+
) -> list[tuple[Any, str]]:
|
|
194
|
+
"""
|
|
195
|
+
Execute query using plugin-based dynamic dispatch
|
|
196
|
+
|
|
197
|
+
Args:
|
|
198
|
+
root_node: Root node of the parsed tree
|
|
199
|
+
query_key: Query key to execute (can be None for custom queries)
|
|
200
|
+
language: Programming language
|
|
201
|
+
source_code: Source code content
|
|
202
|
+
|
|
203
|
+
Returns:
|
|
204
|
+
List of (node, capture_name) tuples
|
|
205
|
+
"""
|
|
206
|
+
captures = []
|
|
207
|
+
|
|
208
|
+
# Try to get plugin for the language
|
|
209
|
+
plugin = self.plugin_manager.get_plugin(language)
|
|
210
|
+
if not plugin:
|
|
211
|
+
logger.warning(f"No plugin found for language: {language}")
|
|
212
|
+
return self._fallback_query_execution(root_node, query_key)
|
|
213
|
+
|
|
214
|
+
# Use plugin's execute_query_strategy method
|
|
215
|
+
try:
|
|
216
|
+
# Create a mock tree object for plugin compatibility
|
|
217
|
+
class MockTree:
|
|
218
|
+
def __init__(self, root_node: Any) -> None:
|
|
219
|
+
self.root_node = root_node
|
|
220
|
+
|
|
221
|
+
# Execute plugin query strategy
|
|
222
|
+
elements = plugin.execute_query_strategy(
|
|
223
|
+
source_code, query_key or "function"
|
|
224
|
+
)
|
|
225
|
+
|
|
226
|
+
# Convert elements to captures format
|
|
227
|
+
if elements:
|
|
228
|
+
for element in elements:
|
|
229
|
+
if hasattr(element, "start_line") and hasattr(element, "end_line"):
|
|
230
|
+
# Create a mock node for compatibility
|
|
231
|
+
class MockNode:
|
|
232
|
+
def __init__(self, element: Any) -> None:
|
|
233
|
+
self.type = getattr(
|
|
234
|
+
element, "element_type", query_key or "unknown"
|
|
235
|
+
)
|
|
236
|
+
self.start_point = (
|
|
237
|
+
getattr(element, "start_line", 1) - 1,
|
|
238
|
+
0,
|
|
239
|
+
)
|
|
240
|
+
self.end_point = (
|
|
241
|
+
getattr(element, "end_line", 1) - 1,
|
|
242
|
+
0,
|
|
243
|
+
)
|
|
244
|
+
self.text = getattr(element, "raw_text", "").encode(
|
|
245
|
+
"utf-8"
|
|
246
|
+
)
|
|
247
|
+
|
|
248
|
+
mock_node = MockNode(element)
|
|
249
|
+
captures.append((mock_node, query_key or "element"))
|
|
250
|
+
|
|
251
|
+
return captures
|
|
252
|
+
|
|
253
|
+
except Exception as e:
|
|
254
|
+
logger.debug(f"Plugin query strategy failed: {e}")
|
|
255
|
+
|
|
256
|
+
# Fallback: Use plugin's element categories for tree traversal
|
|
257
|
+
try:
|
|
258
|
+
element_categories = plugin.get_element_categories()
|
|
259
|
+
if element_categories and query_key and query_key in element_categories:
|
|
260
|
+
node_types = element_categories[query_key]
|
|
261
|
+
|
|
262
|
+
def walk_tree(node: Any) -> None:
|
|
263
|
+
"""Walk the tree and find matching nodes using plugin categories"""
|
|
264
|
+
if node.type in node_types:
|
|
265
|
+
captures.append((node, query_key))
|
|
266
|
+
|
|
267
|
+
# Recursively process children
|
|
268
|
+
for child in node.children:
|
|
269
|
+
walk_tree(child)
|
|
270
|
+
|
|
271
|
+
walk_tree(root_node)
|
|
272
|
+
return captures
|
|
273
|
+
|
|
274
|
+
except Exception as e:
|
|
275
|
+
logger.debug(f"Plugin element categories failed: {e}")
|
|
276
|
+
|
|
277
|
+
# Final fallback
|
|
278
|
+
return self._fallback_query_execution(root_node, query_key)
|
|
279
|
+
|
|
280
|
+
def _fallback_query_execution(
|
|
281
|
+
self, root_node: Any, query_key: str | None
|
|
282
|
+
) -> list[tuple[Any, str]]:
|
|
283
|
+
"""
|
|
284
|
+
Basic fallback query execution for unsupported languages
|
|
285
|
+
|
|
286
|
+
Args:
|
|
287
|
+
root_node: Root node of the parsed tree
|
|
288
|
+
query_key: Query key to execute
|
|
289
|
+
|
|
290
|
+
Returns:
|
|
291
|
+
List of (node, capture_name) tuples
|
|
292
|
+
"""
|
|
293
|
+
captures = []
|
|
294
|
+
|
|
295
|
+
def walk_tree_basic(node: Any) -> None:
|
|
296
|
+
"""Basic tree walking for unsupported languages"""
|
|
297
|
+
# Get node type safely
|
|
298
|
+
node_type = getattr(node, "type", "")
|
|
299
|
+
if not isinstance(node_type, str):
|
|
300
|
+
node_type = str(node_type)
|
|
301
|
+
|
|
302
|
+
# Generic node type matching (support both singular and plural forms)
|
|
303
|
+
if (
|
|
304
|
+
query_key in ("function", "functions")
|
|
305
|
+
and "function" in node_type
|
|
306
|
+
or query_key in ("class", "classes")
|
|
307
|
+
and "class" in node_type
|
|
308
|
+
or query_key in ("method", "methods")
|
|
309
|
+
and "method" in node_type
|
|
310
|
+
or query_key in ("variable", "variables")
|
|
311
|
+
and "variable" in node_type
|
|
312
|
+
or query_key in ("import", "imports")
|
|
313
|
+
and "import" in node_type
|
|
314
|
+
or query_key in ("header", "headers")
|
|
315
|
+
and "heading" in node_type
|
|
316
|
+
):
|
|
317
|
+
captures.append((node, query_key))
|
|
318
|
+
|
|
319
|
+
# Recursively process children
|
|
320
|
+
children = getattr(node, "children", [])
|
|
321
|
+
for child in children:
|
|
322
|
+
walk_tree_basic(child)
|
|
323
|
+
|
|
324
|
+
walk_tree_basic(root_node)
|
|
325
|
+
return captures
|
|
326
|
+
|
|
327
|
+
async def _read_file_async(self, file_path: str) -> tuple[str, str]:
|
|
328
|
+
"""
|
|
329
|
+
非同期ファイル読み込み
|
|
330
|
+
|
|
331
|
+
Args:
|
|
332
|
+
file_path: ファイルパス
|
|
333
|
+
|
|
334
|
+
Returns:
|
|
335
|
+
tuple[str, str]: (content, encoding)
|
|
336
|
+
"""
|
|
337
|
+
# CPU集約的でない単純なファイル読み込みなので、
|
|
338
|
+
# run_in_executorを使用して非同期化
|
|
339
|
+
loop = asyncio.get_event_loop()
|
|
340
|
+
return await loop.run_in_executor(None, read_file_safe, file_path)
|