tree-sitter-analyzer 1.7.7__py3-none-any.whl → 1.8.3__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.
Potentially problematic release.
This version of tree-sitter-analyzer might be problematic. Click here for more details.
- tree_sitter_analyzer/__init__.py +1 -1
- tree_sitter_analyzer/api.py +23 -30
- tree_sitter_analyzer/cli/argument_validator.py +77 -0
- tree_sitter_analyzer/cli/commands/table_command.py +7 -2
- tree_sitter_analyzer/cli_main.py +17 -3
- tree_sitter_analyzer/core/cache_service.py +15 -5
- tree_sitter_analyzer/core/query.py +33 -22
- tree_sitter_analyzer/core/query_service.py +179 -154
- tree_sitter_analyzer/formatters/formatter_registry.py +355 -0
- tree_sitter_analyzer/formatters/html_formatter.py +462 -0
- tree_sitter_analyzer/formatters/language_formatter_factory.py +3 -0
- tree_sitter_analyzer/formatters/markdown_formatter.py +1 -1
- tree_sitter_analyzer/language_detector.py +80 -7
- tree_sitter_analyzer/languages/css_plugin.py +390 -0
- tree_sitter_analyzer/languages/html_plugin.py +395 -0
- tree_sitter_analyzer/languages/java_plugin.py +116 -0
- tree_sitter_analyzer/languages/javascript_plugin.py +113 -0
- tree_sitter_analyzer/languages/markdown_plugin.py +266 -46
- tree_sitter_analyzer/languages/python_plugin.py +176 -33
- tree_sitter_analyzer/languages/typescript_plugin.py +130 -1
- tree_sitter_analyzer/mcp/tools/find_and_grep_tool.py +12 -1
- tree_sitter_analyzer/mcp/tools/query_tool.py +101 -60
- tree_sitter_analyzer/mcp/tools/search_content_tool.py +12 -1
- tree_sitter_analyzer/mcp/tools/table_format_tool.py +26 -12
- tree_sitter_analyzer/mcp/utils/file_output_factory.py +204 -0
- tree_sitter_analyzer/mcp/utils/file_output_manager.py +52 -2
- tree_sitter_analyzer/models.py +53 -0
- tree_sitter_analyzer/output_manager.py +1 -1
- tree_sitter_analyzer/plugins/base.py +50 -0
- tree_sitter_analyzer/plugins/manager.py +5 -1
- tree_sitter_analyzer/queries/css.py +634 -0
- tree_sitter_analyzer/queries/html.py +556 -0
- tree_sitter_analyzer/queries/markdown.py +54 -164
- tree_sitter_analyzer/query_loader.py +16 -3
- tree_sitter_analyzer/security/validator.py +182 -44
- tree_sitter_analyzer/utils/__init__.py +113 -0
- tree_sitter_analyzer/utils/tree_sitter_compat.py +282 -0
- tree_sitter_analyzer/utils.py +62 -24
- {tree_sitter_analyzer-1.7.7.dist-info → tree_sitter_analyzer-1.8.3.dist-info}/METADATA +135 -31
- {tree_sitter_analyzer-1.7.7.dist-info → tree_sitter_analyzer-1.8.3.dist-info}/RECORD +42 -32
- {tree_sitter_analyzer-1.7.7.dist-info → tree_sitter_analyzer-1.8.3.dist-info}/entry_points.txt +2 -0
- {tree_sitter_analyzer-1.7.7.dist-info → tree_sitter_analyzer-1.8.3.dist-info}/WHEEL +0 -0
|
@@ -6,11 +6,14 @@ Unified query service for both CLI and MCP interfaces to avoid code duplication.
|
|
|
6
6
|
Provides core tree-sitter query functionality including predefined and custom queries.
|
|
7
7
|
"""
|
|
8
8
|
|
|
9
|
+
import asyncio
|
|
9
10
|
import logging
|
|
10
11
|
from typing import Any
|
|
11
12
|
|
|
12
13
|
from ..encoding_utils import read_file_safe
|
|
14
|
+
from ..plugins.manager import PluginManager
|
|
13
15
|
from ..query_loader import query_loader
|
|
16
|
+
from ..utils.tree_sitter_compat import TreeSitterQueryCompat, get_node_text_safe
|
|
14
17
|
from .parser import Parser
|
|
15
18
|
from .query_filter import QueryFilter
|
|
16
19
|
|
|
@@ -25,6 +28,8 @@ class QueryService:
|
|
|
25
28
|
self.project_root = project_root
|
|
26
29
|
self.parser = Parser()
|
|
27
30
|
self.filter = QueryFilter()
|
|
31
|
+
self.plugin_manager = PluginManager()
|
|
32
|
+
self.plugin_manager.load_plugins()
|
|
28
33
|
|
|
29
34
|
async def execute_query(
|
|
30
35
|
self,
|
|
@@ -60,7 +65,7 @@ class QueryService:
|
|
|
60
65
|
|
|
61
66
|
try:
|
|
62
67
|
# Read file content
|
|
63
|
-
content, encoding =
|
|
68
|
+
content, encoding = await self._read_file_async(file_path)
|
|
64
69
|
|
|
65
70
|
# Parse file
|
|
66
71
|
parse_result = self.parser.parse_code(content, language, file_path)
|
|
@@ -80,45 +85,43 @@ class QueryService:
|
|
|
80
85
|
f"Query '{query_key}' not found for language '{language}'"
|
|
81
86
|
)
|
|
82
87
|
|
|
83
|
-
# Execute tree-sitter query using
|
|
84
|
-
import tree_sitter
|
|
85
|
-
captures = []
|
|
86
|
-
|
|
87
|
-
# Try to create and execute the query
|
|
88
|
+
# Execute tree-sitter query using modern API
|
|
88
89
|
try:
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
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
|
+
)
|
|
101
108
|
|
|
102
109
|
# Process capture results
|
|
103
110
|
results = []
|
|
104
|
-
if isinstance(captures,
|
|
105
|
-
#
|
|
106
|
-
for capture_name, nodes in captures.items():
|
|
107
|
-
for node in nodes:
|
|
108
|
-
results.append(self._create_result_dict(node, capture_name))
|
|
109
|
-
elif isinstance(captures, list):
|
|
110
|
-
# Handle both old API (list of tuples) and manual execution (list of tuples)
|
|
111
|
+
if isinstance(captures, list):
|
|
112
|
+
# Handle list of tuples from modern API and plugin execution
|
|
111
113
|
for capture in captures:
|
|
112
114
|
if isinstance(capture, tuple) and len(capture) == 2:
|
|
113
115
|
node, name = capture
|
|
114
|
-
results.append(self._create_result_dict(node, name))
|
|
115
|
-
else
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
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))
|
|
122
125
|
|
|
123
126
|
# Apply filters
|
|
124
127
|
if filter_expression and results:
|
|
@@ -130,17 +133,23 @@ class QueryService:
|
|
|
130
133
|
logger.error(f"Query execution failed: {e}")
|
|
131
134
|
raise
|
|
132
135
|
|
|
133
|
-
def _create_result_dict(
|
|
136
|
+
def _create_result_dict(
|
|
137
|
+
self, node: Any, capture_name: str, source_code: str = ""
|
|
138
|
+
) -> dict[str, Any]:
|
|
134
139
|
"""
|
|
135
140
|
Create result dictionary from tree-sitter node
|
|
136
141
|
|
|
137
142
|
Args:
|
|
138
143
|
node: tree-sitter node
|
|
139
144
|
capture_name: capture name
|
|
145
|
+
source_code: source code content for text extraction
|
|
140
146
|
|
|
141
147
|
Returns:
|
|
142
148
|
Result dictionary
|
|
143
149
|
"""
|
|
150
|
+
# Use safe text extraction with source code
|
|
151
|
+
content = get_node_text_safe(node, source_code)
|
|
152
|
+
|
|
144
153
|
return {
|
|
145
154
|
"capture_name": capture_name,
|
|
146
155
|
"node_type": node.type if hasattr(node, "type") else "unknown",
|
|
@@ -148,11 +157,7 @@ class QueryService:
|
|
|
148
157
|
node.start_point[0] + 1 if hasattr(node, "start_point") else 0
|
|
149
158
|
),
|
|
150
159
|
"end_line": node.end_point[0] + 1 if hasattr(node, "end_point") else 0,
|
|
151
|
-
"content":
|
|
152
|
-
node.text.decode("utf-8", errors="replace")
|
|
153
|
-
if hasattr(node, "text") and node.text
|
|
154
|
-
else ""
|
|
155
|
-
),
|
|
160
|
+
"content": content,
|
|
156
161
|
}
|
|
157
162
|
|
|
158
163
|
def get_available_queries(self, language: str) -> list[str]:
|
|
@@ -183,130 +188,150 @@ class QueryService:
|
|
|
183
188
|
except Exception:
|
|
184
189
|
return None
|
|
185
190
|
|
|
186
|
-
def
|
|
191
|
+
def _execute_plugin_query(
|
|
192
|
+
self, root_node: Any, query_key: str | None, language: str, source_code: str
|
|
193
|
+
) -> list[tuple[Any, str]]:
|
|
187
194
|
"""
|
|
188
|
-
|
|
189
|
-
|
|
195
|
+
Execute query using plugin-based dynamic dispatch
|
|
196
|
+
|
|
190
197
|
Args:
|
|
191
198
|
root_node: Root node of the parsed tree
|
|
192
199
|
query_key: Query key to execute (can be None for custom queries)
|
|
193
200
|
language: Programming language
|
|
194
|
-
|
|
201
|
+
source_code: Source code content
|
|
202
|
+
|
|
195
203
|
Returns:
|
|
196
204
|
List of (node, capture_name) tuples
|
|
197
205
|
"""
|
|
198
206
|
captures = []
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
captures.append((node, "import"))
|
|
294
|
-
elif query_key in ["export", "exports"] and node.type == "export_statement":
|
|
295
|
-
captures.append((node, "export"))
|
|
296
|
-
|
|
297
|
-
# Java-specific queries
|
|
298
|
-
elif language == "java":
|
|
299
|
-
if query_key in ["method", "methods"] and node.type == "method_declaration":
|
|
300
|
-
# Always use "method" as capture name for consistency
|
|
301
|
-
captures.append((node, "method"))
|
|
302
|
-
elif query_key in ["class", "classes"] and node.type == "class_declaration":
|
|
303
|
-
captures.append((node, "class"))
|
|
304
|
-
elif query_key == "field" and node.type == "field_declaration":
|
|
305
|
-
captures.append((node, "field"))
|
|
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)
|
|
306
301
|
|
|
302
|
+
# Generic node type matching (support both singular and plural forms)
|
|
303
|
+
if query_key in ("function", "functions") and "function" in node_type:
|
|
304
|
+
captures.append((node, query_key))
|
|
305
|
+
elif query_key in ("class", "classes") and "class" in node_type:
|
|
306
|
+
captures.append((node, query_key))
|
|
307
|
+
elif query_key in ("method", "methods") and "method" in node_type:
|
|
308
|
+
captures.append((node, query_key))
|
|
309
|
+
elif query_key in ("variable", "variables") and "variable" in node_type:
|
|
310
|
+
captures.append((node, query_key))
|
|
311
|
+
elif query_key in ("import", "imports") and "import" in node_type:
|
|
312
|
+
captures.append((node, query_key))
|
|
313
|
+
elif query_key in ("header", "headers") and "heading" in node_type:
|
|
314
|
+
captures.append((node, query_key))
|
|
315
|
+
|
|
307
316
|
# Recursively process children
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
317
|
+
children = getattr(node, "children", [])
|
|
318
|
+
for child in children:
|
|
319
|
+
walk_tree_basic(child)
|
|
320
|
+
|
|
321
|
+
walk_tree_basic(root_node)
|
|
312
322
|
return captures
|
|
323
|
+
|
|
324
|
+
async def _read_file_async(self, file_path: str) -> tuple[str, str]:
|
|
325
|
+
"""
|
|
326
|
+
非同期ファイル読み込み
|
|
327
|
+
|
|
328
|
+
Args:
|
|
329
|
+
file_path: ファイルパス
|
|
330
|
+
|
|
331
|
+
Returns:
|
|
332
|
+
tuple[str, str]: (content, encoding)
|
|
333
|
+
"""
|
|
334
|
+
# CPU集約的でない単純なファイル読み込みなので、
|
|
335
|
+
# run_in_executorを使用して非同期化
|
|
336
|
+
loop = asyncio.get_event_loop()
|
|
337
|
+
return await loop.run_in_executor(None, read_file_safe, file_path)
|